/
counterbugtech
/
KontrBugCAD
Обзор
Документация
Войти
/
counterbugtech
/
KontrBugCAD
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/core/BooleanOperations2.js
1 строка
11 KB
counterbugtech
first commit
04 мар 2026, 11:25
Верифицирован
04 мар 2026, 11:25
b2afbd6
Код
Авторство
О чём код?
import*as THREE from"three";import{GeometryRepair}from"../utils/geometry-repair.js";export class BooleanOperations{constructor(e){this.editor=e,this.geometryRepair=new GeometryRepair(this),this.manifold=null,this.isReady=!1,this.pendingQueue=[],this.OPS={ADDITION:"union",SUBTRACTION:"difference",INTERSECTION:"intersection"},this._initManifold()}async _initManifold(){let e;try{e=await import("/lib/manifold-3d.js")}catch(t){console.warn("⚠️ CDN недоступен, пробую локальную копию...",t);try{e=await import("https://cdn.jsdelivr.net/npm/manifold-3d@3.3.2/+esm")}catch(e){return console.error("❌ Локальная загрузка тоже не удалась:",e),void this.showError("Не удалось загрузить библиотеку Manifold (CDN и локально)")}}try{const t=await e.default();t.setup(),this.manifold=t,window.manifold=t,this.isReady=!0,console.log("✅ Manifold 3.3.2 загружен"),this._processPendingQueue()}catch(e){console.error("❌ Ошибка инициализации Manifold:",e),this.showError("Не удалось инициализировать библиотеку Manifold")}}_processPendingQueue(){for(;this.pendingQueue.length;){const{resolve:e,reject:t,operation:r,objects:o}=this.pendingQueue.shift();try{e(this._performOperationSync(o,r))}catch(e){t(e)}}}_repairGeometry(e){let t=e.clone();if(THREE.BufferGeometryUtils?.mergeVertices&&(t=THREE.BufferGeometryUtils.mergeVertices(t,.001),console.log(" → Сварка вершин выполнена")),t.index){const e=t.attributes.position,r=t.index.array,o=[];for(let t=0;t<r.length;t+=3){const n=(new THREE.Vector3).fromBufferAttribute(e,r[t]),i=(new THREE.Vector3).fromBufferAttribute(e,r[t+1]),s=(new THREE.Vector3).fromBufferAttribute(e,r[t+2]);.5*i.clone().sub(n).cross(s.clone().sub(n)).length()>1e-8&&o.push(r[t],r[t+1],r[t+2])}o.length<r.length&&(t.setIndex(o),console.log(" → Удалено вырожденных треугольников: "+(r.length-o.length)))}return t.computeVertexNormals(),t}_applyObjectTransform(e,t){const r=e.clone();return r.applyMatrix4(t.matrixWorld),r}_threeGeometryToManifold(e){let t=e;if(!e.userData?.isPrimitive)if(this.geometryRepair)try{t=this.geometryRepair.repair(e,{mergeDistance:1e-4,removeDegenerate:!0,removeDuplicates:!0,removeIsolated:!0,removeSmallComponents:!1,closeHoles:!1,fixNormals:!0,checkInfinity:!0,verbose:!0,maxIterations:2})}catch(r){console.warn("Ошибка GeometryRepair, используется fallback",r),t=this._repairGeometry(e)}else t=this._repairGeometry(e);const r=t.attributes.position.array;let o,n,i;if(t.index)o=t.index.array;else{o=new Uint32Array(r.length/3);for(let e=0;e<o.length;e++)o[e]=e}if(t.groups&&t.groups.length>0){const e=t.groups.map(e=>e.start),r=t.groups.map(e=>e.materialIndex||0),o=Array.from(e.keys());o.sort((t,r)=>e[t]-e[r]),n=new Uint32Array(o.map(t=>e[t])),i=new Uint32Array(o.map(e=>r[e]))}else n=new Uint32Array([0]),i=new Uint32Array([0]);const s=new this.manifold.Mesh({numProp:3,vertProperties:r,triVerts:o,runIndex:n,runOriginalID:i});if(s.merge(),0===s.numTri)throw new Error(`Mesh пустой: ${s.numVert} вершин, ${s.numTri} треугольников`);let a;try{a=this.manifold.Manifold(s)}catch(e){if("ManifoldError"!==e.name||"Not manifold"!==e.message)throw e;console.warn(" ⚠️ Геометрия не является манифолдной. Попытка дополнительной обработки...");try{a=this.manifold.Manifold(r,o)}catch(t){throw console.error(" ❌ Не удалось создать манифолд:",t),new Error(`Не удалось обработать геометрию: ${e.message}. Модель может содержать дыры, самопересечения или несвязанные грани.`)}}return a}_manifoldToThreeGeometry(e){const t=e.getMesh(),r=t.vertProperties,o=t.triVerts,n=new THREE.BufferGeometry;return n.setAttribute("position",new THREE.BufferAttribute(r instanceof Float32Array?r:new Float32Array(r),3)),n.setIndex(new THREE.BufferAttribute(o instanceof Uint32Array?o:new Uint32Array(o),1)),n.computeVertexNormals(),n}_validateGeometry(e,t){if(!e||!e.attributes?.position)throw new Error(`Объект ${t}: отсутствует геометрия`);const r=e.attributes.position.count;if(0===r)throw new Error(`Объект ${t}: 0 вершин`);if(r<3)throw new Error(`Объект ${t}: недостаточно вершин (${r})`)}_getManifoldStatus(e){try{if("function"==typeof e.status){const t=e.status();return"number"==typeof t?t:t&&"object"==typeof t?t.value||t.toString()||"unknown":t}return"N/A"}catch(e){return"unknown"}}_checkIntersection(e,t){const r=(new THREE.Box3).setFromObject(e),o=(new THREE.Box3).setFromObject(t);return r.intersectsBox(o)}_performOperationSync(e,t,r=!1,o=null){if(!this.manifold)throw new Error("Manifold не инициализирован");if(!e||e.length<2)throw new Error("Нужно минимум 2 объекта");console.log(`\n=== Manifold: ${this.getOperationName(t)} ===\n`);for(let t=0;t<e.length-1;t++)for(let r=t+1;r<e.length;r++)this._checkIntersection(e[t],e[r]);const n=e.map((e,t)=>{const r=e.userData?.name||e.name||`Объект ${t}`;if(console.log(`\n📦 Обработка: ${r}`),!e.geometry)throw new Error(`Объект ${r} не содержит geometry`);this._validateGeometry(e.geometry,r);const o=this._applyObjectTransform(e.geometry,e);let n=this._threeGeometryToManifold(o);if(0===n.numTri())throw new Error(`Объект ${r}: Manifold пустой (0 треугольников)`);"function"==typeof n.clean&&(n=n.clean());const i=this._getManifoldStatus(n);return console.log(` ✓ Готов: ${n.numTri()} треугольников, статус=${i}`),n});let i;if(t===this.OPS.ADDITION)i=this.manifold.Manifold.union(n);else if(t===this.OPS.SUBTRACTION){i=n[0];for(let e=1;e<n.length;e++)i=this.manifold.Manifold.difference(i,n[e])}else if(t===this.OPS.INTERSECTION){i=n[0];for(let e=1;e<n.length;e++)i=this.manifold.Manifold.intersection(i,n[e])}if("function"!=typeof i.numTri)throw new Error("Некорректный результат операции");const s=i.numTri();if(0===s)throw new Error("Результирующая геометрия пуста (0 треугольников). Проверьте, пересекаются ли объекты.");let a=this._manifoldToThreeGeometry(i),l=new THREE.Vector3;if(r){a.computeBoundingBox();const e=a.boundingBox;if(!e)throw new Error("Не удалось вычислить ограничивающий параллелепипед");e.getCenter(l);const t=a.attributes.position,r=t.array;for(let e=0;e<r.length;e+=3)r[e]-=l.x,r[e+1]-=l.y,r[e+2]-=l.z;t.needsUpdate=!0,a.computeBoundingBox(),a.computeVertexNormals()}let f=o;f?f=f.clone?f.clone():f:(f=e[0].material,e[0].userData&&e[0].userData.originalMaterial&&(f=e[0].userData.originalMaterial),f=f?f.clone():new THREE.MeshStandardMaterial({color:8421504,side:THREE.FrontSide,flatShading:!0})),f.flatShading=!0;const c=new THREE.Mesh(a,f);return r?c.position.copy(l):c.position.set(0,0,0),c.rotation.set(0,0,0),c.scale.set(1,1,1),c.castShadow=!0,c.receiveShadow=!0,c.userData={id:"manifold_"+Date.now(),name:this.getOperationName(t),type:"boolean_manifold",operation:t,originalMaterial:f,sourceObjects:e.map(e=>e.uuid),stats:{triangles:s,vertices:i.numVert?i.numVert():0,status:this._getManifoldStatus(i),volume:i.volume?i.volume():null}},c}simplify(e,t){if(!this.isReady)throw new Error("Manifold не готов");if(!e)throw new Error("Геометрия не предоставлена");if(console.log("\n=== Manifold: Упрощение геометрии ===\n"),!e.attributes?.position)throw new Error("Геометрия не содержит позиций");const r=this._getTriangleCount(e);if(r<4)return console.log("Слишком мало треугольников для упрощения"),e.clone();let o=Math.max(4,Math.floor(r*(1-t)));console.log(`Исходное количество треугольников: ${r}, целевое: ${o}`);let n=this._threeGeometryToManifold(e);if("function"!=typeof n.simplify)throw new Error("Manifold не поддерживает упрощение (simplify)");let i=null,s=0;let a=o;for(;s<10;){try{if(i=n.simplify(a),i&&i.numTri()>0){console.log(`✅ Упрощение успешно на попытке ${s+1}: ${i.numTri()} треугольников (цель ${a})`);break}console.warn(`⚠️ Попытка ${s+1}: результат пустой для цели ${a}`)}catch(e){console.warn(`⚠️ Попытка ${s+1}: ошибка упрощения для цели ${a}: ${e.message}`)}if(a=Math.min(r-1,Math.floor(.95*r+5*s)),a>=r)break;s++}if(!i||0===i.numTri())throw new Error("Упрощение не удалось: не удалось получить непустой результат ни при каких параметрах.");return console.log(`Упрощение выполнено: ${i.numTri()} треугольников`),this._manifoldToThreeGeometry(i)}_getTriangleCount(e){return e.index?e.index.count/3:e.attributes.position?e.attributes.position.count/3:0}unionMultiple(e,t=!0,r=null){if(!this.isReady)throw new Error("Manifold не готов");return this._performOperationSync(e,this.OPS.ADDITION,t,r)}subtract(e,t,r=!0,o=null){if(!this.isReady)throw new Error("Manifold не готов");return this._performOperationSync([e,t],this.OPS.SUBTRACTION,r,o)}intersect(e,t,r=!0,o=null){if(!this.isReady)throw new Error("Manifold не готов");return this._performOperationSync([e,t],this.OPS.INTERSECTION,r,o)}async performOperation(e,t){return this.isReady?this._performOperationSync(e,t):new Promise((r,o)=>{this.pendingQueue.push({resolve:r,reject:o,operation:t,objects:[...e]})})}canPerformOperation(e){if(!e||e.length<2)return{can:!1,reason:"Нужно минимум 2 объекта"};if(!this.isReady)return{can:!1,reason:"Manifold загружается"};for(const t of e){if(!t.geometry?.attributes?.position)return{can:!1,reason:`Объект ${t.name||t.uuid} не содержит геометрии`};if(t.geometry.attributes.position.count<3)return{can:!1,reason:`Объект ${t.name||t.uuid} имеет недостаточно вершин`}}return{can:!0,reason:""}}getOperationStats(e){return e?.userData?.stats||null}getOperationName(e){return{union:"Объединение",difference:"Вычитание",subtract:"Вычитание",intersection:"Пересечение"}[e]||e}showError(e){this.editor?.showStatus?this.editor.showStatus(e,"error"):console.error("Manifold Error:",e)}async waitForReady(){if(this.isReady&&this.manifold)try{const e=new THREE.BoxGeometry(1,1,1);new THREE.Mesh(e);if(this._threeGeometryToManifold(e).numTri()>0)return}catch(e){console.warn("Тестовая операция не удалась, продолжаем ожидание")}for(;!this.isReady||!this.manifold;)await new Promise(e=>setTimeout(e,20));await new Promise(e=>setTimeout(e,0))}}