fluidcad 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/lib/dist/common/scene-object.d.ts +3 -0
  2. package/lib/dist/common/scene-object.js +7 -0
  3. package/lib/dist/core/2d/arc.d.ts +14 -56
  4. package/lib/dist/core/2d/arc.js +8 -23
  5. package/lib/dist/core/2d/center.d.ts +9 -0
  6. package/lib/dist/core/2d/center.js +10 -0
  7. package/lib/dist/core/2d/index.d.ts +2 -0
  8. package/lib/dist/core/2d/index.js +2 -0
  9. package/lib/dist/core/2d/intersect.d.ts +17 -0
  10. package/lib/dist/core/2d/intersect.js +20 -0
  11. package/lib/dist/core/interfaces.d.ts +50 -2
  12. package/lib/dist/core/repeat.js +10 -3
  13. package/lib/dist/features/2d/arc.d.ts +27 -14
  14. package/lib/dist/features/2d/arc.js +269 -38
  15. package/lib/dist/features/2d/intersect.d.ts +15 -0
  16. package/lib/dist/features/2d/intersect.js +83 -0
  17. package/lib/dist/features/2d/plane-center.d.ts +13 -0
  18. package/lib/dist/features/2d/plane-center.js +40 -0
  19. package/lib/dist/features/2d/projection.js +3 -4
  20. package/lib/dist/features/2d/rect.d.ts +2 -2
  21. package/lib/dist/features/2d/rect.js +3 -3
  22. package/lib/dist/features/2d/sketch.d.ts +2 -2
  23. package/lib/dist/features/2d/sketch.js +13 -1
  24. package/lib/dist/features/2d/slot.d.ts +2 -2
  25. package/lib/dist/features/2d/slot.js +3 -3
  26. package/lib/dist/features/extrude-base.d.ts +4 -0
  27. package/lib/dist/features/extrude-base.js +27 -0
  28. package/lib/dist/features/extrude-to-face.d.ts +1 -0
  29. package/lib/dist/features/extrude-to-face.js +5 -1
  30. package/lib/dist/features/extrude-two-distances.d.ts +1 -0
  31. package/lib/dist/features/extrude-two-distances.js +5 -2
  32. package/lib/dist/features/extrude.d.ts +1 -0
  33. package/lib/dist/features/extrude.js +5 -1
  34. package/lib/dist/features/plane-renderable-base.d.ts +2 -1
  35. package/lib/dist/features/plane-renderable-base.js +1 -1
  36. package/lib/dist/features/plane.d.ts +1 -1
  37. package/lib/dist/filters/face/face-filter.d.ts +10 -0
  38. package/lib/dist/filters/face/face-filter.js +39 -0
  39. package/lib/dist/filters/face/intersects-with.d.ts +18 -0
  40. package/lib/dist/filters/face/intersects-with.js +41 -0
  41. package/lib/dist/helpers/clone-transform.js +1 -0
  42. package/lib/dist/oc/boolean-ops.js +0 -2
  43. package/lib/dist/oc/face-query.d.ts +1 -0
  44. package/lib/dist/oc/face-query.js +15 -0
  45. package/lib/dist/oc/index.d.ts +1 -0
  46. package/lib/dist/oc/index.js +1 -0
  47. package/lib/dist/oc/section-ops.d.ts +6 -0
  48. package/lib/dist/oc/section-ops.js +26 -0
  49. package/lib/dist/oc/thin-face-maker.d.ts +29 -0
  50. package/lib/dist/oc/thin-face-maker.js +163 -0
  51. package/lib/dist/rendering/render.js +16 -0
  52. package/lib/dist/tests/features/2d/arc.test.js +2 -2
  53. package/lib/dist/tests/features/2d/intersect.test.d.ts +1 -0
  54. package/lib/dist/tests/features/2d/intersect.test.js +22 -0
  55. package/lib/dist/tests/features/2d/rect.test.js +1 -1
  56. package/lib/dist/tests/features/loft.test.js +1 -1
  57. package/lib/dist/tests/features/select.test.js +49 -1
  58. package/lib/dist/tests/features/thin-extrude.test.d.ts +1 -0
  59. package/lib/dist/tests/features/thin-extrude.test.js +137 -0
  60. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +3 -3
  62. package/server/dist/index.js +23 -15
  63. package/ui/dist/assets/{index-mLcpjEcV.js → index-BJG141m7.js} +2 -2
  64. package/ui/dist/index.html +1 -1
  65. package/lib/dist/features/2d/arc-three-points.d.ts +0 -19
  66. package/lib/dist/features/2d/arc-three-points.js +0 -75
  67. package/lib/dist/features/2d/arc-to-point.d.ts +0 -17
  68. package/lib/dist/features/2d/arc-to-point.js +0 -95
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluidcad",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Parametric CAD modeling library using javascript",
5
5
  "author": "Marwan Aouida <contact@marwan.dev>",
6
6
  "homepage": "https://fluidcad.io",
@@ -58,8 +58,8 @@
58
58
  "chokidar": "^5.0.0",
59
59
  "color-name": "^2.1.0",
60
60
  "express": "^5.2.1",
61
- "occjs-fluidcad": "^1.0.0",
62
- "occjs-wrapper": "npm:occjs-fluidcad@^1.0.0",
61
+ "occjs-fluidcad": "^2.0.0",
62
+ "occjs-wrapper": "npm:occjs-fluidcad@2.0.0",
63
63
  "tsx": "^4.21.0",
64
64
  "vite": "^8.0.8",
65
65
  "ws": "^8.18.0"
@@ -174,22 +174,30 @@ async function handleExtensionMessage(msg) {
174
174
  broadcastToUI({ type: 'processing-file' });
175
175
  currentFile = msg.fileName;
176
176
  }
177
- const data = await fluidCadServer.updateLiveCode(msg.fileName, msg.code);
178
- if (myVersion !== renderVersion) {
179
- return;
177
+ try {
178
+ const data = await fluidCadServer.updateLiveCode(msg.fileName, msg.code);
179
+ if (myVersion !== renderVersion) {
180
+ return;
181
+ }
182
+ if (data) {
183
+ sendToExtension({
184
+ type: 'scene-rendered',
185
+ absPath: data.absPath,
186
+ result: data.result,
187
+ rollbackStop: data.rollbackStop,
188
+ });
189
+ broadcastToUI({
190
+ type: 'scene-rendered',
191
+ result: data.result,
192
+ absPath: data.absPath,
193
+ });
194
+ }
180
195
  }
181
- if (data) {
182
- sendToExtension({
183
- type: 'scene-rendered',
184
- absPath: data.absPath,
185
- result: data.result,
186
- rollbackStop: data.rollbackStop,
187
- });
188
- broadcastToUI({
189
- type: 'scene-rendered',
190
- result: data.result,
191
- absPath: data.absPath,
192
- });
196
+ catch {
197
+ // Silently ignore errors during live-update (syntax errors, incomplete
198
+ // expressions, etc. while the user is typing). The last successful
199
+ // render remains visible. Model-building errors are delivered separately
200
+ // via scene-rendered object properties and are not affected.
193
201
  }
194
202
  break;
195
203
  }
@@ -4305,7 +4305,7 @@ void main() {
4305
4305
 
4306
4306
  gl_FragColor = vec4(color, 1.0);
4307
4307
  }
4308
- `,af=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;for(let t of e.meshes){let e=t.vertices,n=t.indices,r=[];for(let t=0;t<n.length;t+=2){let i=n[t]*3;r.push(e[i],e[i+1],e[i+2])}if(n.length>=2){let t=n[n.length-1]*3;r.push(e[t],e[t+1],e[t+2])}let i=new Tr;i.setAttribute(`position`,new W(new Float32Array(r),3));let a=new Ui(i,new qr({uniforms:{color:{value:Nd.metaEdgeColor},dashLength:{value:Qd},gapLength:{value:$d},dotLength:{value:ef},patternLength:{value:tf}},vertexShader:nf,fragmentShader:rf,side:2,transparent:!0,polygonOffset:!0,polygonOffsetFactor:2,polygonOffsetUnits:1}));a.computeLineDistances(),this.add(a)}}},of=`#2297ff`,sf=2,cf=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;for(let t of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(t.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(t.normals),3));let n=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new n(t.indices),1));let r=new qi(e,new Fi({color:of,linewidth:sf,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1,side:2,depthWrite:!0,depthTest:!0}));this.add(r)}}},lf=`#2297ff`,uf=`#2297ff`,df=.15,ff=.4,pf=class extends ii{constructor(e,t){super(),this.userData.isMetaShape=!0,this.userData.isPickRegion=!0,this.userData.isPickRegionSelected=t,e.metaData&&(this.userData.metaData=e.metaData);let n=t?uf:lf,r=t?ff:df;for(let t of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(t.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(t.normals),3));let i=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new i(t.indices),1)),e.computeBoundingBox();let a=new G(e,new dr({color:n,transparent:!0,opacity:r,side:2,depthWrite:!1,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1}));this.add(a)}}},mf=`#2297ff`,hf=2,gf=2,_f=16,vf=.003,yf=1.5,bf=1e-8;function xf(e,t,n){if(e instanceof Pa)return(e.top-e.bottom)/e.zoom*n;if(e instanceof Qr){let r=e.position.distanceTo(t),i=e.fov*Math.PI/180;return 2*r*Math.tan(i/2)*n}return 1}var Sf=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;let t=new Zi(gf,_f),n=new dr({color:mf,side:2,depthTest:!0}),r=[];for(let t of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(t.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(t.normals),3));let n=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new n(t.indices),1));let i=new Fi({color:mf,linewidth:hf,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1,side:2,depthWrite:!0,depthTest:!0});this.add(new qi(e,i));let a=t.vertices,o=t.indices,s=new Map;for(let e of o)s.set(e,(s.get(e)||0)+1);for(let[e,t]of s)if(t===1){let t=new B(a[e*3],a[e*3+1],a[e*3+2]);r.some(e=>e.distanceToSquared(t)<bf)||r.push(t)}}for(let e of r){let r=new G(t,n);r.renderOrder=2;let i=new ii;i.renderOrder=2,i.userData.isVertexDot=!0,i.add(r),i.position.copy(e),r.onBeforeRender=(t,n,r)=>{i.scale.setScalar(Math.min(xf(r,e,vf),yf)),i.updateMatrixWorld(!0)},this.add(i)}}},Cf={color:`#2297ff`,lineWidth:2},wf={trim:e=>new cf(e),"pick-edge":e=>new Sf(e)};function Tf(e){let t=e.metaType?wf[e.metaType]:void 0;return t?t(e):new af(e)}var Ef={"pick-region":e=>new pf(e,!1),"pick-region-selected":e=>new pf(e,!0)};function Df(e){let t=e.metaType?Ef[e.metaType]:void 0;return t?t(e):new Xd(e)}var Of=class extends ii{constructor(e,t,n){if(super(),!e.sceneShapes)return;let r=e.sceneShapes.every(e=>e.isMetaShape||e.shapeType===`wire`||e.shapeType===`edge`),i=[`pick-region`,`pick-region-selected`,`pick-edge`];for(let a of e.sceneShapes){if(!t&&a.isMetaShape&&a.metaType&&i.includes(a.metaType))continue;let e;if(a.isMetaShape)switch(a.shapeType){case`wire`:case`edge`:e=Tf(a);break;case`face`:e=Df(a);break}else switch(a.shapeType){case`wire`:case`edge`:e=new Jd(a,n?.edge??(r?Cf:void 0));break;case`face`:e=new Xd(a,n?.face);break;case`solid`:e=new Zd(a,n);break}e&&(a.shapeId&&(e.userData.shapeId=a.shapeId),this.add(e))}}};function kf(e,t,n){if(e instanceof Pa)return(e.top-e.bottom)/e.zoom*n;if(e instanceof Qr){let r=e.position.distanceTo(t),i=e.fov*Math.PI/180;return 2*r*Math.tan(i/2)*n}return 1}var Af=`#2297ff`,jf=2,Mf=16,Nf=.003,Pf=1.5,Ff=15954511,If=64,Lf=3,Rf=15954511,zf=.35,Bf=.6,Vf=18,Hf=5,Uf=2.5,Wf=class extends ii{constructor(e,t,n,r){super(),this.buildEdges(e,t),this.buildVertices(e,t,r),n&&(this.buildCursor(e,r),this.buildTangentArrow(e,r))}buildEdges(e,t){for(let n of t)if(!(n.parentId!==e.id||!n.sceneShapes.length))for(let e of n.sceneShapes){if(e.isMetaShape||e.isGuide){if(e.shapeType===`wire`||e.shapeType===`edge`){let t=Tf(e);e.shapeId&&(t.userData.shapeId=e.shapeId),this.add(t)}continue}let t=new Jd(e,{color:Af,lineWidth:2});e.shapeId&&(t.userData.shapeId=e.shapeId),this.add(t)}}buildVertices(e,t,n){let r=e.object?.plane?.normal,i=[];for(let n of t)if(!(n.parentId!==e.id||!n.sceneShapes.length)){for(let e of n.sceneShapes)if(!(e.isMetaShape||e.isGuide))for(let t of e.meshes){if(!t.indices.length)continue;let e=new Map;for(let n of t.indices)e.set(n,(e.get(n)||0)+1);for(let[n,r]of e)r===1&&i.push(new B(t.vertices[n*3],t.vertices[n*3+1],t.vertices[n*3+2]))}}let a=[];for(let e of i)a.some(t=>t.distanceToSquared(e)<1e-12)||a.push(e);let o=new Zi(jf,Mf),s=new dr({color:Af,side:2,depthTest:!1});for(let e of a){let t=new G(o,s);t.renderOrder=2;let i=new ii;i.renderOrder=2,i.userData.isVertexDot=!0,i.add(t),i.position.copy(e),r&&i.lookAt(new B(e.x+r.x,e.y+r.y,e.z+r.z)),i.scale.setScalar(Math.min(kf(n,e,Nf),Pf)),t.onBeforeRender=(t,n,r)=>{i.scale.setScalar(Math.min(kf(r,e,Nf),Pf)),i.updateMatrixWorld(!0)},this.add(i)}}buildCursor(e,t){let n=e.object?.currentPosition;if(!n)return;let r=new Zi(Lf,If),i=new dr({color:Ff,side:2,depthTest:!1});i.transparent=!0,i.opacity=.8;let a=new G(r,i);a.renderOrder=1;let o=new ii;o.renderOrder=1,o.add(a),o.position.set(n.x,n.y,n.z);let s=e.object?.plane?.normal;if(s){let e=new B(n.x+s.x,n.y+s.y,n.z+s.z);o.lookAt(e)}o.scale.setScalar(kf(t,o.position,.003)),a.onBeforeRender=(e,t,n)=>{o.scale.setScalar(kf(n,o.position,.003)),o.updateMatrixWorld(!0)},this.add(o)}buildTangentArrow(e,t){let n=e.object?.currentPosition,r=e.object?.currentTangent,i=e.object?.plane?.origin;if(!n||!r||!i)return;let a=new B(r.x-i.x,r.y-i.y,r.z-i.z).normalize(),o=new dr({color:Rf,transparent:!0,opacity:zf,depthTest:!1}),s=new Qi(Bf,Bf,Vf,8);s.translate(0,Vf/2,0);let c=new G(s,o),l=new $i(Uf,Hf,8);l.translate(0,Vf+Hf/2,0);let u=new G(l,o),d=new ii;d.renderOrder=1,d.add(c),d.add(u);let f=new B(0,1,0),p=new mt().setFromUnitVectors(f,a);d.quaternion.copy(p),d.position.set(n.x,n.y,n.z),d.scale.setScalar(kf(t,d.position,.003)),c.onBeforeRender=(e,t,n)=>{d.scale.setScalar(kf(n,d.position,.003)),d.updateMatrixWorld(!0)},this.add(d)}};function Gf(e,t,n){if(e instanceof Pa)return(e.top-e.bottom)/e.zoom*n;if(e instanceof Qr){let r=e.position.distanceTo(t),i=e.fov*Math.PI/180;return 2*r*Math.tan(i/2)*n}return 1}var Kf=`#ffc26c`,qf=`#c88f40`,Jf=`#c88f40`,Yf=.1,Xf=20,Zf=3,Qf=1.5,$f=.4,ep=class extends ii{constructor(e,t){super();let n=e.sceneShapes[0]?.meshes[0];if(!n)return;this.userData.isMetaShape=!0,this.userData.isConstructionPlane=!0;let r=e.object.normal,i=e.object.center,a=new Tr;a.setAttribute(`position`,new W(new Float32Array(n.vertices),3)),a.setAttribute(`normal`,new W(new Float32Array(n.normals),3)),a.setIndex(new W(new Uint16Array(n.indices),1)),a.computeBoundingBox();let o=new G(a,new dr({color:Kf,transparent:!0,opacity:Yf,side:2,polygonOffset:!0,polygonOffsetFactor:1,polygonOffsetUnits:1}));this.add(o);let s=new qi(new ia(a,18),new Fi({color:qf,linewidth:1}));this.add(s);let c=new B(r.x,r.y,r.z).normalize(),l=new B(i.x,i.y,i.z),u=new dr({color:Jf}),d=Xf-Zf,f=new Qi($f,$f,d,8);f.translate(0,d/2,0);let p=new G(f,u),m=new $i(Qf,Zf,8);m.translate(0,d+Zf/2,0);let h=new G(m,u),g=new ii;g.add(p),g.add(h);let _=new B(0,1,0),v=new mt().setFromUnitVectors(_,c);g.quaternion.copy(v),g.position.copy(l),g.scale.setScalar(Gf(t,g.position,.006)),p.onBeforeRender=(e,t,n)=>{g.scale.setScalar(Gf(n,g.position,.006)),g.updateMatrixWorld(!0)},this.add(g),this.position.z=.01}},tp=`#c88f40`,np=class extends ii{constructor(e){super();let t=e.sceneShapes[0]?.meshes[0];if(!t)return;let n=new Tr;n.setAttribute(`position`,new W(new Float32Array(t.vertices),3));let r=new qi(n,new fa({color:tp,dashSize:5,gapSize:5}));r.computeLineDistances(),this.add(r)}},rp={edge:{color:`#11a4ed`,lineWidth:3,depthWrite:!1},face:{color:`#5c9fcc`,opacity:1}},ip={edge:{opacity:.3},face:{opacity:.3}};function ap(e,t,n,r){if(r)return r;if(e===`select`)return rp;if(t&&n!==`sketch`)return ip}function op(e,t,n,r,i,a){switch(e.type){case`sketch`:return new Wf(e,t,n,r);case`plane`:return new ep(e,r);case`axis`:return new np(e)}let o=e.uniqueType===`select`,s=ap(e.uniqueType,n,e.type,a),c=t.filter(t=>t.parentId===e.id),l;if(c.length>0){let e=new ii;for(let a of c)e.add(op(a,t,n,r,i,s));l=e}else l=new Of(e,i,s);return o&&l.traverse(e=>{e.renderOrder=999}),l}function sp(e,t,n,r=!1){let i=new ii;i.name=`compiledMesh`;for(let a of e)a.parentId||!a.visible&&!(t&&a.type===`sketch`)||i.add(op(a,e,t,n,r));return i}var cp=`<svg
4308
+ `,af=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;for(let t of e.meshes){let e=t.vertices,n=t.indices,r=[];for(let t=0;t<n.length;t+=2){let i=n[t]*3;r.push(e[i],e[i+1],e[i+2])}if(n.length>=2){let t=n[n.length-1]*3;r.push(e[t],e[t+1],e[t+2])}let i=new Tr;i.setAttribute(`position`,new W(new Float32Array(r),3));let a=new Ui(i,new qr({uniforms:{color:{value:Nd.metaEdgeColor},dashLength:{value:Qd},gapLength:{value:$d},dotLength:{value:ef},patternLength:{value:tf}},vertexShader:nf,fragmentShader:rf,side:2,transparent:!0,polygonOffset:!0,polygonOffsetFactor:2,polygonOffsetUnits:1}));a.computeLineDistances(),this.add(a)}}},of=`#2297ff`,sf=2,cf=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;for(let t of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(t.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(t.normals),3));let n=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new n(t.indices),1));let r=new qi(e,new Fi({color:of,linewidth:sf,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1,side:2,depthWrite:!0,depthTest:!0}));this.add(r)}}},lf=`#2297ff`,uf=`#2297ff`,df=.15,ff=.4,pf=class extends ii{constructor(e,t){super(),this.userData.isMetaShape=!0,this.userData.isPickRegion=!0,this.userData.isPickRegionSelected=t,e.metaData&&(this.userData.metaData=e.metaData);let n=t?uf:lf,r=t?ff:df;for(let t of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(t.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(t.normals),3));let i=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new i(t.indices),1)),e.computeBoundingBox();let a=new G(e,new dr({color:n,transparent:!0,opacity:r,side:2,depthWrite:!1,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1}));this.add(a)}}},mf=`#2297ff`,hf=2,gf=2,_f=16,vf=.003,yf=1.5,bf=1e-8;function xf(e,t,n){if(e instanceof Pa)return(e.top-e.bottom)/e.zoom*n;if(e instanceof Qr){let r=e.position.distanceTo(t),i=e.fov*Math.PI/180;return 2*r*Math.tan(i/2)*n}return 1}var Sf=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;let t=new Zi(gf,_f),n=new dr({color:mf,side:2,depthTest:!0}),r=[];for(let t of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(t.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(t.normals),3));let n=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new n(t.indices),1));let i=new Fi({color:mf,linewidth:hf,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1,side:2,depthWrite:!0,depthTest:!0});this.add(new qi(e,i));let a=t.vertices,o=t.indices,s=new Map;for(let e of o)s.set(e,(s.get(e)||0)+1);for(let[e,t]of s)if(t===1){let t=new B(a[e*3],a[e*3+1],a[e*3+2]);r.some(e=>e.distanceToSquared(t)<bf)||r.push(t)}}for(let e of r){let r=new G(t,n);r.renderOrder=2;let i=new ii;i.renderOrder=2,i.userData.isVertexDot=!0,i.add(r),i.position.copy(e),r.onBeforeRender=(t,n,r)=>{i.scale.setScalar(Math.min(xf(r,e,vf),yf)),i.updateMatrixWorld(!0)},this.add(i)}}},Cf={color:`#2297ff`,lineWidth:2},wf={trim:e=>new cf(e),"pick-edge":e=>new Sf(e)};function Tf(e){let t=e.metaType?wf[e.metaType]:void 0;return t?t(e):new af(e)}var Ef={"pick-region":e=>new pf(e,!1),"pick-region-selected":e=>new pf(e,!0)};function Df(e){let t=e.metaType?Ef[e.metaType]:void 0;return t?t(e):new Xd(e)}var Of=class extends ii{constructor(e,t,n){if(super(),!e.sceneShapes)return;let r=e.sceneShapes.every(e=>e.isMetaShape||e.shapeType===`wire`||e.shapeType===`edge`),i=[`pick-region`,`pick-region-selected`,`pick-edge`];for(let a of e.sceneShapes){if(!t&&a.isMetaShape&&a.metaType&&i.includes(a.metaType))continue;let e;if(a.isMetaShape)switch(a.shapeType){case`wire`:case`edge`:e=Tf(a);break;case`face`:e=Df(a);break}else switch(a.shapeType){case`wire`:case`edge`:e=new Jd(a,n?.edge??(r?Cf:void 0));break;case`face`:e=new Xd(a,n?.face);break;case`solid`:e=new Zd(a,n);break}e&&(a.shapeId&&(e.userData.shapeId=a.shapeId),this.add(e))}}};function kf(e,t,n){if(e instanceof Pa)return(e.top-e.bottom)/e.zoom*n;if(e instanceof Qr){let r=e.position.distanceTo(t),i=e.fov*Math.PI/180;return 2*r*Math.tan(i/2)*n}return 1}var Af=`#2297ff`,jf=2,Mf=16,Nf=.003,Pf=1.5,Ff=15954511,If=64,Lf=3,Rf=15954511,zf=.35,Bf=.6,Vf=18,Hf=5,Uf=2.5,Wf=class extends ii{constructor(e,t,n,r){super(),this.buildEdges(e,t),this.buildVertices(e,t,r),n&&e.id===n&&(this.buildCursor(e,r),this.buildTangentArrow(e,r))}buildEdges(e,t){for(let n of t)if(!(n.parentId!==e.id||!n.sceneShapes.length))for(let e of n.sceneShapes){if(e.isMetaShape||e.isGuide){if(e.shapeType===`wire`||e.shapeType===`edge`){let t=Tf(e);e.shapeId&&(t.userData.shapeId=e.shapeId),this.add(t)}continue}let t=new Jd(e,{color:Af,lineWidth:2});e.shapeId&&(t.userData.shapeId=e.shapeId),this.add(t)}}buildVertices(e,t,n){let r=e.object?.plane?.normal,i=[];for(let n of t)if(!(n.parentId!==e.id||!n.sceneShapes.length)){for(let e of n.sceneShapes)if(!(e.isMetaShape||e.isGuide))for(let t of e.meshes){if(!t.indices.length)continue;let e=new Map;for(let n of t.indices)e.set(n,(e.get(n)||0)+1);for(let[n,r]of e)r===1&&i.push(new B(t.vertices[n*3],t.vertices[n*3+1],t.vertices[n*3+2]))}}let a=[];for(let e of i)a.some(t=>t.distanceToSquared(e)<1e-12)||a.push(e);let o=new Zi(jf,Mf),s=new dr({color:Af,side:2,depthTest:!1});for(let e of a){let t=new G(o,s);t.renderOrder=2;let i=new ii;i.renderOrder=2,i.userData.isVertexDot=!0,i.add(t),i.position.copy(e),r&&i.lookAt(new B(e.x+r.x,e.y+r.y,e.z+r.z)),i.scale.setScalar(Math.min(kf(n,e,Nf),Pf)),t.onBeforeRender=(t,n,r)=>{i.scale.setScalar(Math.min(kf(r,e,Nf),Pf)),i.updateMatrixWorld(!0)},this.add(i)}}buildCursor(e,t){let n=e.object?.currentPosition;if(!n)return;let r=new Zi(Lf,If),i=new dr({color:Ff,side:2,depthTest:!1});i.transparent=!0,i.opacity=.8;let a=new G(r,i);a.renderOrder=1;let o=new ii;o.renderOrder=1,o.add(a),o.position.set(n.x,n.y,n.z);let s=e.object?.plane?.normal;if(s){let e=new B(n.x+s.x,n.y+s.y,n.z+s.z);o.lookAt(e)}o.scale.setScalar(kf(t,o.position,.003)),a.onBeforeRender=(e,t,n)=>{o.scale.setScalar(kf(n,o.position,.003)),o.updateMatrixWorld(!0)},this.add(o)}buildTangentArrow(e,t){let n=e.object?.currentPosition,r=e.object?.currentTangent,i=e.object?.plane?.origin;if(!n||!r||!i)return;let a=new B(r.x-i.x,r.y-i.y,r.z-i.z).normalize(),o=new dr({color:Rf,transparent:!0,opacity:zf,depthTest:!1}),s=new Qi(Bf,Bf,Vf,8);s.translate(0,Vf/2,0);let c=new G(s,o),l=new $i(Uf,Hf,8);l.translate(0,Vf+Hf/2,0);let u=new G(l,o),d=new ii;d.renderOrder=1,d.add(c),d.add(u);let f=new B(0,1,0),p=new mt().setFromUnitVectors(f,a);d.quaternion.copy(p),d.position.set(n.x,n.y,n.z),d.scale.setScalar(kf(t,d.position,.003)),c.onBeforeRender=(e,t,n)=>{d.scale.setScalar(kf(n,d.position,.003)),d.updateMatrixWorld(!0)},this.add(d)}};function Gf(e,t,n){if(e instanceof Pa)return(e.top-e.bottom)/e.zoom*n;if(e instanceof Qr){let r=e.position.distanceTo(t),i=e.fov*Math.PI/180;return 2*r*Math.tan(i/2)*n}return 1}var Kf=`#ffc26c`,qf=`#c88f40`,Jf=`#c88f40`,Yf=.1,Xf=20,Zf=3,Qf=1.5,$f=.4,ep=class extends ii{constructor(e,t){super();let n=e.sceneShapes[0]?.meshes[0];if(!n)return;this.userData.isMetaShape=!0,this.userData.isConstructionPlane=!0;let r=e.object.normal,i=e.object.center,a=new Tr;a.setAttribute(`position`,new W(new Float32Array(n.vertices),3)),a.setAttribute(`normal`,new W(new Float32Array(n.normals),3)),a.setIndex(new W(new Uint16Array(n.indices),1)),a.computeBoundingBox();let o=new G(a,new dr({color:Kf,transparent:!0,opacity:Yf,side:2,polygonOffset:!0,polygonOffsetFactor:1,polygonOffsetUnits:1}));this.add(o);let s=new qi(new ia(a,18),new Fi({color:qf,linewidth:1}));this.add(s);let c=new B(r.x,r.y,r.z).normalize(),l=new B(i.x,i.y,i.z),u=new dr({color:Jf}),d=Xf-Zf,f=new Qi($f,$f,d,8);f.translate(0,d/2,0);let p=new G(f,u),m=new $i(Qf,Zf,8);m.translate(0,d+Zf/2,0);let h=new G(m,u),g=new ii;g.add(p),g.add(h);let _=new B(0,1,0),v=new mt().setFromUnitVectors(_,c);g.quaternion.copy(v),g.position.copy(l),g.scale.setScalar(Gf(t,g.position,.006)),p.onBeforeRender=(e,t,n)=>{g.scale.setScalar(Gf(n,g.position,.006)),g.updateMatrixWorld(!0)},this.add(g),this.position.z=.01}},tp=`#c88f40`,np=class extends ii{constructor(e){super();let t=e.sceneShapes[0]?.meshes[0];if(!t)return;let n=new Tr;n.setAttribute(`position`,new W(new Float32Array(t.vertices),3));let r=new qi(n,new fa({color:tp,dashSize:5,gapSize:5}));r.computeLineDistances(),this.add(r)}},rp={edge:{color:`#11a4ed`,lineWidth:3,depthWrite:!1},face:{color:`#5c9fcc`,opacity:1}},ip={edge:{opacity:.3},face:{opacity:.3}};function ap(e,t,n,r){if(r)return r;if(e===`select`)return rp;if(t&&n!==`sketch`)return ip}function op(e,t,n,r,i,a){switch(e.type){case`sketch`:return new Wf(e,t,n,r);case`plane`:return new ep(e,r);case`axis`:return new np(e)}let o=e.uniqueType===`select`,s=ap(e.uniqueType,n,e.type,a),c=t.filter(t=>t.parentId===e.id),l;if(c.length>0){let e=new ii;for(let a of c)e.add(op(a,t,n,r,i,s));l=e}else l=new Of(e,i,s);return o&&l.traverse(e=>{e.renderOrder=999}),l}function sp(e,t,n,r=!1){let i=new ii;i.name=`compiledMesh`;for(let a of e)a.parentId||!a.visible&&!(t&&a.type===`sketch`)||i.add(op(a,e,t,n,r));return i}var cp=`<svg
4309
4309
  xmlns="http://www.w3.org/2000/svg"
4310
4310
  width="24"
4311
4311
  height="24"
@@ -4521,7 +4521,7 @@ void main() {
4521
4521
  <button class="${Pp} ${e.showGrid?Fp:``}" data-action="grid" title="Toggle grid">${wp}</button>
4522
4522
  <div class="h-px bg-base-content/[0.08] my-0.5"></div>
4523
4523
  <button class="${Pp}" data-action="theme" title="${n}">${t}</button>
4524
- `}bindEvents(){this.el.querySelector(`[data-action="fit"]`)?.addEventListener(`click`,()=>{this.onFitView?.()}),this.el.querySelectorAll(`[data-mode]`).forEach(e=>{e.addEventListener(`click`,()=>{let t=e.dataset.mode;Vd.update({cameraMode:t}),this.onCameraSwitch(t)})}),this.el.querySelector(`[data-action="grid"]`)?.addEventListener(`click`,()=>{Vd.update({showGrid:!Vd.current.showGrid})}),this.el.querySelector(`[data-action="theme"]`)?.addEventListener(`click`,()=>{let e=Lp()?`fluidcad-light`:`fluidcad-dark`;document.documentElement.setAttribute(`data-theme`,e),this.syncThemeButton(),fetch(`/api/preferences`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({theme:e})})})}setFitHandler(e){this.onFitView=e}setFitButtonVisible(e){let t=this.el.querySelector(`[data-action="fit"]`);t&&(t.style.display=e?``:`none`);let n=this.el.querySelector(`.h-px`);n&&(n.style.display=e?``:`none`)}setProjectionLocked(e){this.el.querySelectorAll(`[data-mode]`).forEach(t=>{t.disabled=e})}syncThemeButton(){let e=this.el.querySelector(`[data-action="theme"]`);e&&(e.innerHTML=Lp()?Mp:Np,e.title=Lp()?`Switch to light theme`:`Switch to dark theme`)}sync(){let e=Vd.current;this.el.querySelectorAll(`[data-mode]`).forEach(t=>{t.className=t.dataset.mode===e.cameraMode?Fp:Pp});let t=this.el.querySelector(`[data-action="grid"]`);t&&(t.className=e.showGrid?Fp:Pp)}},zp=class{mesh=null;show(e,t,n){this.clear(e);let r=new G(new oa(n,16,16),new dr({color:16729088,depthTest:!1}));r.position.set(t.x,t.y,t.z),r.renderOrder=999,r.userData.isMetaShape=!0,e.add(r),this.mesh=r}clear(e){this.mesh&&=(this.mesh.geometry.dispose(),this.mesh.material.dispose(),e.remove(this.mesh),null)}};function Bp(e,t){if(t.userData.isMetaShape)return;let n=t;(n.isMesh||n.isLine||n.isPoints)&&n.geometry&&(n.geometry.computeBoundingBox(),n.geometry.boundingBox&&e.union(n.geometry.boundingBox.clone().applyMatrix4(n.matrixWorld)));for(let n of t.children)Bp(e,n)}var Vp=`#ffc578`,Hp=`#ffc578`,Up=`#64B5F6`,Wp=`#64B5F6`,Gp=.3,Kp=class{ctx;modeManager;settingsPanel;sceneObjects=[];highlightedShapeId=null;faceHighlightMeshes=[];hasRendered=!1;lastFitBox=null;isTrimming=!1;isRegionPicking=!1;isBezierDrawing=!1;selectionHandler=null;centroidIndicator=new zp;hoverState=null;hoverFaceOverlayMeshes=[];hoverRafId=null;isMouseDown=!1;highlightedSub=null;constructor(e){let t=document.getElementById(e);this.ctx=new Rd(t),this.modeManager=new Kd(this.ctx),this.settingsPanel=new Rp(t,e=>this.ctx.switchCamera(e)),this.settingsPanel.setFitHandler(()=>this.fitViewToScene()),this.initClickDetection(),this.initHoverDetection()}get sceneContext(){return this.ctx}get currentSceneObjects(){return this.sceneObjects}setSelectionHandler(e){this.selectionHandler=e}initClickDetection(){let e=this.ctx.renderer.domElement,t=0,n=0;e.addEventListener(`mousedown`,e=>{t=e.clientX,n=e.clientY}),e.addEventListener(`mouseup`,e=>{if(!this.selectionHandler||this.isTrimming||this.isRegionPicking||this.isBezierDrawing)return;let r=e.clientX-t,i=e.clientY-n;if(r*r+i*i>64)return;this.clearHover();let a=this.pickAt(e.clientX,e.clientY);a?this.selectionHandler(a.shapeId,a.sub):this.selectionHandler(null,null)})}pickAt(e,t){let n=this.ctx.camera,r=this.ctx.renderer.domElement.getBoundingClientRect(),i=(e-r.left)/r.width*2-1,a=-((t-r.top)/r.height)*2+1,o=new to;o.setFromCamera(new z(i,a),n),o.params.Line={threshold:this.computeEdgePickThreshold()};let s=[],c=[];this.ctx.scene.traverse(e=>{e.userData.isMetaShape||(e.isMesh&&e.userData.faceMapping?s.push(e):e.isLine&&e.userData.edgeIndex!==void 0&&c.push(e))});let l=s.length>0?o.intersectObjects(s,!1):[],u=c.length>0?o.intersectObjects(c,!1):[];if(l.length===0&&u.length===0)return null;let d=new B;n.getWorldDirection(d);let f;for(let e of l){if(!e.face){f=e;break}if(e.face.normal.clone().transformDirection(e.object.matrixWorld).dot(d)<0){f=e;break}}let p=f==null?1/0:f.distance,m=o.ray.origin,h=o.ray.direction,g=new B,_=new B;for(let e of u){let t=e.object.geometry,n=t.getAttribute(`position`),r=t.getIndex();if(r!==null&&e.faceIndex!=null){let t=r.getX(e.faceIndex*2),i=r.getX(e.faceIndex*2+1),a=new B().fromBufferAttribute(n,t).applyMatrix4(e.object.matrixWorld),s=new B().fromBufferAttribute(n,i).applyMatrix4(e.object.matrixWorld);o.ray.distanceSqToSegment(a,s,void 0,g)}else g.copy(e.point);if(h.dot(_.copy(g).sub(m))<=p+.001){let t=e.object.userData.edgeIndex,n=this.findShapeIdForObject(e.object);if(n)return{shapeId:n,sub:{type:`edge`,index:t}}}}if(f){let e=f.object.userData.faceMapping;if(!e||f.faceIndex==null)return null;let t=e[f.faceIndex];if(t==null)return null;let n=this.findShapeIdForObject(f.object);if(n)return{shapeId:n,sub:{type:`face`,index:t}}}return null}findShapeIdForObject(e){let t=e;for(;t;){if(t.userData.shapeId&&!t.userData.isMetaShape)return t.userData.shapeId;t=t.parent}return null}toggleSketchMode(e){this.modeManager.sketchEnabled=e}setFileName(e){}updateView(e,t=!1){if(this.sceneObjects=e,this.highlightedShapeId=null,this.highlightedSub=null,this.hoverState=null,this.hoverFaceOverlayMeshes=[],this.ctx.renderer.domElement.style.cursor=``,this.removeCompiledMesh(),!t){let t=this.findActiveObject(e);t?.type===`sketch`&&t.object?.plane?(this.modeManager.isSketchMode?this.modeManager.enforceSketchNormal(t.object.plane):this.modeManager.enterSketchMode(t.object.plane),this.settingsPanel.setProjectionLocked(!0),this.settingsPanel.setFitButtonVisible(!1)):(this.modeManager.enterDefaultMode(),this.settingsPanel.setProjectionLocked(!1),this.settingsPanel.setFitButtonVisible(!0),this.lastFitBox=null)}let n=sp(e,this.modeManager.isSketchMode,this.ctx.camera,this.isRegionPicking);if(this.ctx.scene.add(n),!this.hasRendered||this.modeManager.isSketchMode&&!t&&!this.isTrimming&&!this.isRegionPicking&&!this.isBezierDrawing){let e=new Ht;Bp(e,n),!e.isEmpty()&&!this.isBoxContained(e)&&(this.ctx.fitToBox(e,!0),this.lastFitBox=e.clone(),this.hasRendered=!0)}this.ctx.requestRender()}highlightShape(e){this.clearHighlight();let t=this.findShapeById(e);if(!t)return;let n=this.findMeshByShapeId(e);if(!n)return;let r=t.shapeType===`solid`||t.shapeType===`face`;n.traverse(e=>{e.material&&(r&&e instanceof G?(e.userData.originalColor=e.material.color.getHex(),e.material.color.set(Vp)):!r&&e instanceof qi&&(e.userData.originalColor=e.material.color.getHex(),e.material.color.set(Hp),e.material.opacity<1&&(e.userData.originalOpacity=e.material.opacity,e.material.opacity=1)))}),this.highlightedShapeId=e,this.highlightedSub=null,this.ctx.render()}clearHighlight(){if(!(!this.highlightedShapeId&&this.faceHighlightMeshes.length===0)){this.ctx.scene.traverse(e=>{e.userData.originalColor!==void 0&&(e.material.color.setHex(e.userData.originalColor),delete e.userData.originalColor),e.userData.originalOpacity!==void 0&&(e.material.opacity=e.userData.originalOpacity,delete e.userData.originalOpacity)});for(let e of this.faceHighlightMeshes)e.parent?.remove(e),e.geometry.dispose(),e.material.dispose();this.faceHighlightMeshes=[],this.highlightedShapeId=null,this.highlightedSub=null,this.ctx.render()}}highlightFace(e,t){this.clearHighlight(),this.ctx.scene.traverse(n=>{if(!n.isMesh)return;let r=n.userData.faceMapping;if(!r)return;let i=!1,a=n;for(;a;){if(a.userData.shapeId===e&&!a.userData.isMetaShape){i=!0;break}a=a.parent}if(!i)return;let o=n,s=o.geometry,c=s.index;if(!c)return;let l=c.array,u=s.getAttribute(`position`).array,d=[];for(let e=0;e<r.length;e++)if(r[e]===t){let t=l[e*3]*3,n=l[e*3+1]*3,r=l[e*3+2]*3;d.push(u[t],u[t+1],u[t+2]),d.push(u[n],u[n+1],u[n+2]),d.push(u[r],u[r+1],u[r+2])}if(d.length===0)return;let f=new Tr;f.setAttribute(`position`,new W(new Float32Array(d),3));let p=new G(f,new la({color:Vp,polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-1}));(o.parent??this.ctx.scene).add(p),this.faceHighlightMeshes.push(p)}),this.highlightedShapeId=e,this.highlightedSub={type:`face`,index:t},this.ctx.render()}highlightEdge(e,t){this.clearHighlight(),this.ctx.scene.traverse(n=>{if(!n.isLine||n.userData.edgeIndex!==t)return;let r=!1,i=n;for(;i;){if(i.userData.shapeId===e&&!i.userData.isMetaShape){r=!0;break}i=i.parent}r&&(n.userData.originalColor=n.material.color.getHex(),n.material.color.set(Hp))}),this.highlightedShapeId=e,this.highlightedSub={type:`edge`,index:t},this.ctx.render()}initHoverDetection(){let e=this.ctx.renderer.domElement;e.addEventListener(`mousedown`,()=>{this.isMouseDown=!0,this.clearHover()}),e.addEventListener(`mouseup`,()=>{this.isMouseDown=!1}),e.addEventListener(`mousemove`,e=>{this.isMouseDown||this.isTrimming||this.isRegionPicking||this.isBezierDrawing||this.hoverRafId===null&&(this.hoverRafId=requestAnimationFrame(()=>{this.hoverRafId=null,this.updateHover(e.clientX,e.clientY)}))}),e.addEventListener(`mouseleave`,()=>{this.clearHover()})}updateHover(e,t){if(!this.selectionHandler)return;let n=this.pickAt(e,t);if(!(this.hoverState&&n&&this.hoverState.shapeId===n.shapeId&&this.hoverState.sub?.type===n.sub?.type&&this.hoverState.sub?.index===n.sub?.index)){if(!n){this.hoverState&&this.clearHover();return}if(this.highlightedShapeId===n.shapeId&&this.highlightedSub?.type===n.sub?.type&&this.highlightedSub?.index===n.sub?.index){this.hoverState&&this.clearHover();return}this.clearHover(),this.hoverState=n,this.ctx.renderer.domElement.style.cursor=`pointer`,n.sub?.type===`face`?this.applyHoverFace(n.shapeId,n.sub.index):n.sub?.type===`edge`&&this.applyHoverEdge(n.shapeId,n.sub.index)}}clearHover(){for(let e of this.hoverFaceOverlayMeshes)e.parent?.remove(e),e.geometry.dispose(),e.material.dispose();this.hoverFaceOverlayMeshes=[],this.ctx.scene.traverse(e=>{e.userData.hoverOriginalColor!==void 0&&(e.material.color.setHex(e.userData.hoverOriginalColor),delete e.userData.hoverOriginalColor)}),this.hoverState=null,this.ctx.renderer.domElement.style.cursor=``,this.ctx.requestRender()}applyHoverFace(e,t){this.ctx.scene.traverse(n=>{if(!n.isMesh)return;let r=n.userData.faceMapping;if(!r)return;let i=!1,a=n;for(;a;){if(a.userData.shapeId===e&&!a.userData.isMetaShape){i=!0;break}a=a.parent}if(!i)return;let o=n,s=o.geometry,c=s.index;if(!c)return;let l=c.array,u=s.getAttribute(`position`).array,d=[];for(let e=0;e<r.length;e++)if(r[e]===t){let t=l[e*3]*3,n=l[e*3+1]*3,r=l[e*3+2]*3;d.push(u[t],u[t+1],u[t+2]),d.push(u[n],u[n+1],u[n+2]),d.push(u[r],u[r+1],u[r+2])}if(d.length===0)return;let f=new Tr;f.setAttribute(`position`,new W(new Float32Array(d),3));let p=new G(f,new la({color:Up,transparent:!0,opacity:Gp,depthWrite:!1,polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-1}));(o.parent??this.ctx.scene).add(p),this.hoverFaceOverlayMeshes.push(p)}),this.ctx.requestRender()}applyHoverEdge(e,t){this.ctx.scene.traverse(n=>{if(!n.isLine||n.userData.edgeIndex!==t)return;let r=!1,i=n;for(;i;){if(i.userData.shapeId===e&&!i.userData.isMetaShape){r=!0;break}i=i.parent}r&&(n.userData.hoverOriginalColor=n.material.color.getHex(),n.material.color.set(Wp))}),this.ctx.requestRender()}showCentroid(e){let t=this.computeCentroidRadius();this.centroidIndicator.show(this.ctx.scene,e,t),this.ctx.requestRender()}clearCentroid(){this.centroidIndicator.clear(this.ctx.scene),this.ctx.requestRender()}dispose(){this.ctx.dispose()}computeEdgePickThreshold(){let e=this.ctx.camera,t=this.ctx.renderer.domElement.getBoundingClientRect().height||1,n,r=e;if(r.isOrthographicCamera)n=(r.top-r.bottom)/(r.zoom||1);else{let t=new B;this.ctx.cameraControls.getTarget(t);let i=e.position.distanceTo(t),a=r.fov*Math.PI/180;n=2*i*Math.tan(a/2)}return n/t*8}computeCentroidRadius(){let e=this.ctx.scene.getObjectByName(`compiledMesh`);if(e){let t=new Ht;if(Bp(t,e),!t.isEmpty())return t.getSize(new B).length()*.015}return 2}fitViewToScene(){let e=this.ctx.scene.getObjectByName(`compiledMesh`);if(!e)return;let t=new Ht;Bp(t,e),t.isEmpty()||this.ctx.fitToBox(t,!0)}findShapeById(e){for(let t of this.sceneObjects)for(let n of t.sceneShapes)if(n.shapeId===e)return n}findMeshByShapeId(e){let t;return this.ctx.scene.traverse(n=>{n.userData.shapeId===e&&(t=n)}),t}findActiveObject(e){for(let t=e.length-1;t>=0;t--){let n=e[t];if(!n.parentId&&(n.visible||n.type===`sketch`)||n.parentId&&(n.visible||n.type===`sketch`)&&e.find(e=>e.id===n.parentId)?.type===`part`)return n}}isBoxContained(e){if(!this.lastFitBox)return!1;let t=this.lastFitBox.getCenter(new B),n=this.lastFitBox.getSize(new B).length()/2*Ld;if(n===0)return!1;let r=e.getCenter(new B),i=e.getSize(new B).length()/2;return t.distanceTo(r)+i<=n}rebuildSceneMesh(){if(!this.sceneObjects)return;this.removeCompiledMesh();let e=sp(this.sceneObjects,this.modeManager.isSketchMode,this.ctx.camera,this.isRegionPicking);this.ctx.scene.add(e),this.ctx.requestRender()}removeCompiledMesh(){let e=this.ctx.scene.getObjectByName(`compiledMesh`);e&&(e.traverse(e=>{e.geometry?.dispose(),Array.isArray(e.material)?e.material.forEach(e=>e.dispose()):e.material?.dispose()}),this.ctx.scene.remove(e))}};function qp(e){switch(e){case`inch`:return 16.387064;case`foot`:return 28316.846592;case`yard`:return 764554.857984;case`meter`:return 1e6;default:return .001}}function Jp(e){switch(e){case`inch`:return`in`;case`foot`:return`ft`;case`yard`:return`yd`;case`meter`:return`m`;default:return`mm`}}function Yp(e,t,n){let r=t===`kg`?1e3:t===`lbs`?453.592:1;return e*qp(n)/r}function Xp(e,t){switch(t){case`kg/m³`:return e*.001;case`g/mm³`:return e*1e3;case`lbs/in³`:return e*27.6799;default:return e}}function Zp(e,t){switch(t){case`kg/m³`:return e/.001;case`g/mm³`:return e/1e3;case`lbs/in³`:return e/27.6799;default:return e}}function Qp(e){return e===0?`0`:parseFloat(e.toPrecision(6)).toString()}var $p=class{btn;panel;placeholderEl;formEl;selectEl;densityEl;densityUnitSelectEl;lengthUnitEl;massUnitEl;calcBtn;resultsEl;errorEl;volVal;areaVal;massVal;centroidVal;selectedShapeId=null;rawProps=null;canonicalDensityGcm3=null;currentDensityUnit=`g/cm³`;centroidHandler=null;openHandler=null;constructor(e){this.btn=document.createElement(`button`),this.btn.className=`btn btn-ghost btn-square btn-sm rounded-md absolute bottom-6 right-8 z-[100] panel-bg border border-base-content/10 text-base-content/60`,this.btn.title=`Shape Properties`,this.btn.innerHTML=Tp,e.appendChild(this.btn),this.panel=document.createElement(`div`),this.panel.className=`absolute bottom-[68px] right-6 w-[300px] bg-base-100/95 backdrop-blur-xl border border-base-content/10 rounded-lg p-4 z-[200] shadow-[0_4px_24px_rgba(0,0,0,0.5)] text-base-content text-[13px] hidden`,this.panel.innerHTML=this.buildHTML(),e.appendChild(this.panel),this.bindRefs(),this.bindEvents(),this.loadMaterials()}buildHTML(){return`
4524
+ `}bindEvents(){this.el.querySelector(`[data-action="fit"]`)?.addEventListener(`click`,()=>{this.onFitView?.()}),this.el.querySelectorAll(`[data-mode]`).forEach(e=>{e.addEventListener(`click`,()=>{let t=e.dataset.mode;Vd.update({cameraMode:t}),this.onCameraSwitch(t)})}),this.el.querySelector(`[data-action="grid"]`)?.addEventListener(`click`,()=>{Vd.update({showGrid:!Vd.current.showGrid})}),this.el.querySelector(`[data-action="theme"]`)?.addEventListener(`click`,()=>{let e=Lp()?`fluidcad-light`:`fluidcad-dark`;document.documentElement.setAttribute(`data-theme`,e),this.syncThemeButton(),fetch(`/api/preferences`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({theme:e})})})}setFitHandler(e){this.onFitView=e}setFitButtonVisible(e){let t=this.el.querySelector(`[data-action="fit"]`);t&&(t.style.display=e?``:`none`);let n=this.el.querySelector(`.h-px`);n&&(n.style.display=e?``:`none`)}setProjectionLocked(e){this.el.querySelectorAll(`[data-mode]`).forEach(t=>{t.disabled=e})}syncThemeButton(){let e=this.el.querySelector(`[data-action="theme"]`);e&&(e.innerHTML=Lp()?Mp:Np,e.title=Lp()?`Switch to light theme`:`Switch to dark theme`)}sync(){let e=Vd.current;this.el.querySelectorAll(`[data-mode]`).forEach(t=>{t.className=t.dataset.mode===e.cameraMode?Fp:Pp});let t=this.el.querySelector(`[data-action="grid"]`);t&&(t.className=e.showGrid?Fp:Pp)}},zp=class{mesh=null;show(e,t,n){this.clear(e);let r=new G(new oa(n,16,16),new dr({color:16729088,depthTest:!1}));r.position.set(t.x,t.y,t.z),r.renderOrder=999,r.userData.isMetaShape=!0,e.add(r),this.mesh=r}clear(e){this.mesh&&=(this.mesh.geometry.dispose(),this.mesh.material.dispose(),e.remove(this.mesh),null)}};function Bp(e,t){if(t.userData.isMetaShape)return;let n=t;(n.isMesh||n.isLine||n.isPoints)&&n.geometry&&(n.geometry.computeBoundingBox(),n.geometry.boundingBox&&e.union(n.geometry.boundingBox.clone().applyMatrix4(n.matrixWorld)));for(let n of t.children)Bp(e,n)}var Vp=`#ffc578`,Hp=`#ffc578`,Up=`#64B5F6`,Wp=`#64B5F6`,Gp=.3,Kp=class{ctx;modeManager;settingsPanel;sceneObjects=[];highlightedShapeId=null;faceHighlightMeshes=[];hasRendered=!1;lastFitBox=null;isTrimming=!1;isRegionPicking=!1;isBezierDrawing=!1;selectionHandler=null;centroidIndicator=new zp;hoverState=null;hoverFaceOverlayMeshes=[];hoverRafId=null;isMouseDown=!1;highlightedSub=null;activeSketchId=null;constructor(e){let t=document.getElementById(e);this.ctx=new Rd(t),this.modeManager=new Kd(this.ctx),this.settingsPanel=new Rp(t,e=>this.ctx.switchCamera(e)),this.settingsPanel.setFitHandler(()=>this.fitViewToScene()),this.initClickDetection(),this.initHoverDetection()}get sceneContext(){return this.ctx}get currentSceneObjects(){return this.sceneObjects}setSelectionHandler(e){this.selectionHandler=e}initClickDetection(){let e=this.ctx.renderer.domElement,t=0,n=0;e.addEventListener(`mousedown`,e=>{t=e.clientX,n=e.clientY}),e.addEventListener(`mouseup`,e=>{if(!this.selectionHandler||this.isTrimming||this.isRegionPicking||this.isBezierDrawing)return;let r=e.clientX-t,i=e.clientY-n;if(r*r+i*i>64)return;this.clearHover();let a=this.pickAt(e.clientX,e.clientY);a?this.selectionHandler(a.shapeId,a.sub):this.selectionHandler(null,null)})}pickAt(e,t){let n=this.ctx.camera,r=this.ctx.renderer.domElement.getBoundingClientRect(),i=(e-r.left)/r.width*2-1,a=-((t-r.top)/r.height)*2+1,o=new to;o.setFromCamera(new z(i,a),n),o.params.Line={threshold:this.computeEdgePickThreshold()};let s=[],c=[];this.ctx.scene.traverse(e=>{e.userData.isMetaShape||(e.isMesh&&e.userData.faceMapping?s.push(e):e.isLine&&e.userData.edgeIndex!==void 0&&c.push(e))});let l=s.length>0?o.intersectObjects(s,!1):[],u=c.length>0?o.intersectObjects(c,!1):[];if(l.length===0&&u.length===0)return null;let d=new B;n.getWorldDirection(d);let f;for(let e of l){if(!e.face){f=e;break}if(e.face.normal.clone().transformDirection(e.object.matrixWorld).dot(d)<0){f=e;break}}let p=f==null?1/0:f.distance,m=o.ray.origin,h=o.ray.direction,g=new B,_=new B;for(let e of u){let t=e.object.geometry,n=t.getAttribute(`position`),r=t.getIndex();if(r!==null&&e.faceIndex!=null){let t=r.getX(e.faceIndex*2),i=r.getX(e.faceIndex*2+1),a=new B().fromBufferAttribute(n,t).applyMatrix4(e.object.matrixWorld),s=new B().fromBufferAttribute(n,i).applyMatrix4(e.object.matrixWorld);o.ray.distanceSqToSegment(a,s,void 0,g)}else g.copy(e.point);if(h.dot(_.copy(g).sub(m))<=p+.001){let t=e.object.userData.edgeIndex,n=this.findShapeIdForObject(e.object);if(n)return{shapeId:n,sub:{type:`edge`,index:t}}}}if(f){let e=f.object.userData.faceMapping;if(!e||f.faceIndex==null)return null;let t=e[f.faceIndex];if(t==null)return null;let n=this.findShapeIdForObject(f.object);if(n)return{shapeId:n,sub:{type:`face`,index:t}}}return null}findShapeIdForObject(e){let t=e;for(;t;){if(t.userData.shapeId&&!t.userData.isMetaShape)return t.userData.shapeId;t=t.parent}return null}toggleSketchMode(e){this.modeManager.sketchEnabled=e}setFileName(e){}updateView(e,t=!1){if(this.sceneObjects=e,this.highlightedShapeId=null,this.highlightedSub=null,this.hoverState=null,this.hoverFaceOverlayMeshes=[],this.ctx.renderer.domElement.style.cursor=``,this.removeCompiledMesh(),!t){let t=this.findActiveObject(e);t?.type===`sketch`&&t.object?.plane?(this.modeManager.isSketchMode?this.modeManager.enforceSketchNormal(t.object.plane):this.modeManager.enterSketchMode(t.object.plane),this.activeSketchId=t.id,this.settingsPanel.setProjectionLocked(!0),this.settingsPanel.setFitButtonVisible(!1)):(this.activeSketchId=null,this.modeManager.enterDefaultMode(),this.settingsPanel.setProjectionLocked(!1),this.settingsPanel.setFitButtonVisible(!0),this.lastFitBox=null)}let n=sp(e,this.activeSketchId,this.ctx.camera,this.isRegionPicking);if(this.ctx.scene.add(n),!this.hasRendered||this.modeManager.isSketchMode&&!t&&!this.isTrimming&&!this.isRegionPicking&&!this.isBezierDrawing){let e=new Ht;Bp(e,n),!e.isEmpty()&&!this.isBoxContained(e)&&(this.ctx.fitToBox(e,!0),this.lastFitBox=e.clone(),this.hasRendered=!0)}this.ctx.requestRender()}highlightShape(e){this.clearHighlight();let t=this.findShapeById(e);if(!t)return;let n=this.findMeshByShapeId(e);if(!n)return;let r=t.shapeType===`solid`||t.shapeType===`face`;n.traverse(e=>{e.material&&(r&&e instanceof G?(e.userData.originalColor=e.material.color.getHex(),e.material.color.set(Vp)):!r&&e instanceof qi&&(e.userData.originalColor=e.material.color.getHex(),e.material.color.set(Hp),e.material.opacity<1&&(e.userData.originalOpacity=e.material.opacity,e.material.opacity=1)))}),this.highlightedShapeId=e,this.highlightedSub=null,this.ctx.render()}clearHighlight(){if(!(!this.highlightedShapeId&&this.faceHighlightMeshes.length===0)){this.ctx.scene.traverse(e=>{e.userData.originalColor!==void 0&&(e.material.color.setHex(e.userData.originalColor),delete e.userData.originalColor),e.userData.originalOpacity!==void 0&&(e.material.opacity=e.userData.originalOpacity,delete e.userData.originalOpacity)});for(let e of this.faceHighlightMeshes)e.parent?.remove(e),e.geometry.dispose(),e.material.dispose();this.faceHighlightMeshes=[],this.highlightedShapeId=null,this.highlightedSub=null,this.ctx.render()}}highlightFace(e,t){this.clearHighlight(),this.ctx.scene.traverse(n=>{if(!n.isMesh)return;let r=n.userData.faceMapping;if(!r)return;let i=!1,a=n;for(;a;){if(a.userData.shapeId===e&&!a.userData.isMetaShape){i=!0;break}a=a.parent}if(!i)return;let o=n,s=o.geometry,c=s.index;if(!c)return;let l=c.array,u=s.getAttribute(`position`).array,d=[];for(let e=0;e<r.length;e++)if(r[e]===t){let t=l[e*3]*3,n=l[e*3+1]*3,r=l[e*3+2]*3;d.push(u[t],u[t+1],u[t+2]),d.push(u[n],u[n+1],u[n+2]),d.push(u[r],u[r+1],u[r+2])}if(d.length===0)return;let f=new Tr;f.setAttribute(`position`,new W(new Float32Array(d),3));let p=new G(f,new la({color:Vp,polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-1}));(o.parent??this.ctx.scene).add(p),this.faceHighlightMeshes.push(p)}),this.highlightedShapeId=e,this.highlightedSub={type:`face`,index:t},this.ctx.render()}highlightEdge(e,t){this.clearHighlight(),this.ctx.scene.traverse(n=>{if(!n.isLine||n.userData.edgeIndex!==t)return;let r=!1,i=n;for(;i;){if(i.userData.shapeId===e&&!i.userData.isMetaShape){r=!0;break}i=i.parent}r&&(n.userData.originalColor=n.material.color.getHex(),n.material.color.set(Hp))}),this.highlightedShapeId=e,this.highlightedSub={type:`edge`,index:t},this.ctx.render()}initHoverDetection(){let e=this.ctx.renderer.domElement;e.addEventListener(`mousedown`,()=>{this.isMouseDown=!0,this.clearHover()}),e.addEventListener(`mouseup`,()=>{this.isMouseDown=!1}),e.addEventListener(`mousemove`,e=>{this.isMouseDown||this.isTrimming||this.isRegionPicking||this.isBezierDrawing||this.hoverRafId===null&&(this.hoverRafId=requestAnimationFrame(()=>{this.hoverRafId=null,this.updateHover(e.clientX,e.clientY)}))}),e.addEventListener(`mouseleave`,()=>{this.clearHover()})}updateHover(e,t){if(!this.selectionHandler)return;let n=this.pickAt(e,t);if(!(this.hoverState&&n&&this.hoverState.shapeId===n.shapeId&&this.hoverState.sub?.type===n.sub?.type&&this.hoverState.sub?.index===n.sub?.index)){if(!n){this.hoverState&&this.clearHover();return}if(this.highlightedShapeId===n.shapeId&&this.highlightedSub?.type===n.sub?.type&&this.highlightedSub?.index===n.sub?.index){this.hoverState&&this.clearHover();return}this.clearHover(),this.hoverState=n,this.ctx.renderer.domElement.style.cursor=`pointer`,n.sub?.type===`face`?this.applyHoverFace(n.shapeId,n.sub.index):n.sub?.type===`edge`&&this.applyHoverEdge(n.shapeId,n.sub.index)}}clearHover(){for(let e of this.hoverFaceOverlayMeshes)e.parent?.remove(e),e.geometry.dispose(),e.material.dispose();this.hoverFaceOverlayMeshes=[],this.ctx.scene.traverse(e=>{e.userData.hoverOriginalColor!==void 0&&(e.material.color.setHex(e.userData.hoverOriginalColor),delete e.userData.hoverOriginalColor)}),this.hoverState=null,this.ctx.renderer.domElement.style.cursor=``,this.ctx.requestRender()}applyHoverFace(e,t){this.ctx.scene.traverse(n=>{if(!n.isMesh)return;let r=n.userData.faceMapping;if(!r)return;let i=!1,a=n;for(;a;){if(a.userData.shapeId===e&&!a.userData.isMetaShape){i=!0;break}a=a.parent}if(!i)return;let o=n,s=o.geometry,c=s.index;if(!c)return;let l=c.array,u=s.getAttribute(`position`).array,d=[];for(let e=0;e<r.length;e++)if(r[e]===t){let t=l[e*3]*3,n=l[e*3+1]*3,r=l[e*3+2]*3;d.push(u[t],u[t+1],u[t+2]),d.push(u[n],u[n+1],u[n+2]),d.push(u[r],u[r+1],u[r+2])}if(d.length===0)return;let f=new Tr;f.setAttribute(`position`,new W(new Float32Array(d),3));let p=new G(f,new la({color:Up,transparent:!0,opacity:Gp,depthWrite:!1,polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-1}));(o.parent??this.ctx.scene).add(p),this.hoverFaceOverlayMeshes.push(p)}),this.ctx.requestRender()}applyHoverEdge(e,t){this.ctx.scene.traverse(n=>{if(!n.isLine||n.userData.edgeIndex!==t)return;let r=!1,i=n;for(;i;){if(i.userData.shapeId===e&&!i.userData.isMetaShape){r=!0;break}i=i.parent}r&&(n.userData.hoverOriginalColor=n.material.color.getHex(),n.material.color.set(Wp))}),this.ctx.requestRender()}showCentroid(e){let t=this.computeCentroidRadius();this.centroidIndicator.show(this.ctx.scene,e,t),this.ctx.requestRender()}clearCentroid(){this.centroidIndicator.clear(this.ctx.scene),this.ctx.requestRender()}dispose(){this.ctx.dispose()}computeEdgePickThreshold(){let e=this.ctx.camera,t=this.ctx.renderer.domElement.getBoundingClientRect().height||1,n,r=e;if(r.isOrthographicCamera)n=(r.top-r.bottom)/(r.zoom||1);else{let t=new B;this.ctx.cameraControls.getTarget(t);let i=e.position.distanceTo(t),a=r.fov*Math.PI/180;n=2*i*Math.tan(a/2)}return n/t*8}computeCentroidRadius(){let e=this.ctx.scene.getObjectByName(`compiledMesh`);if(e){let t=new Ht;if(Bp(t,e),!t.isEmpty())return t.getSize(new B).length()*.015}return 2}fitViewToScene(){let e=this.ctx.scene.getObjectByName(`compiledMesh`);if(!e)return;let t=new Ht;Bp(t,e),t.isEmpty()||this.ctx.fitToBox(t,!0)}findShapeById(e){for(let t of this.sceneObjects)for(let n of t.sceneShapes)if(n.shapeId===e)return n}findMeshByShapeId(e){let t;return this.ctx.scene.traverse(n=>{n.userData.shapeId===e&&(t=n)}),t}findActiveObject(e){for(let t=e.length-1;t>=0;t--){let n=e[t];if(!n.parentId&&(n.visible||n.type===`sketch`)||n.parentId&&(n.visible||n.type===`sketch`)&&e.find(e=>e.id===n.parentId)?.type===`part`)return n}}isBoxContained(e){if(!this.lastFitBox)return!1;let t=this.lastFitBox.getCenter(new B),n=this.lastFitBox.getSize(new B).length()/2*Ld;if(n===0)return!1;let r=e.getCenter(new B),i=e.getSize(new B).length()/2;return t.distanceTo(r)+i<=n}rebuildSceneMesh(){if(!this.sceneObjects)return;this.removeCompiledMesh();let e=sp(this.sceneObjects,this.activeSketchId,this.ctx.camera,this.isRegionPicking);this.ctx.scene.add(e),this.ctx.requestRender()}removeCompiledMesh(){let e=this.ctx.scene.getObjectByName(`compiledMesh`);e&&(e.traverse(e=>{e.geometry?.dispose(),Array.isArray(e.material)?e.material.forEach(e=>e.dispose()):e.material?.dispose()}),this.ctx.scene.remove(e))}};function qp(e){switch(e){case`inch`:return 16.387064;case`foot`:return 28316.846592;case`yard`:return 764554.857984;case`meter`:return 1e6;default:return .001}}function Jp(e){switch(e){case`inch`:return`in`;case`foot`:return`ft`;case`yard`:return`yd`;case`meter`:return`m`;default:return`mm`}}function Yp(e,t,n){let r=t===`kg`?1e3:t===`lbs`?453.592:1;return e*qp(n)/r}function Xp(e,t){switch(t){case`kg/m³`:return e*.001;case`g/mm³`:return e*1e3;case`lbs/in³`:return e*27.6799;default:return e}}function Zp(e,t){switch(t){case`kg/m³`:return e/.001;case`g/mm³`:return e/1e3;case`lbs/in³`:return e/27.6799;default:return e}}function Qp(e){return e===0?`0`:parseFloat(e.toPrecision(6)).toString()}var $p=class{btn;panel;placeholderEl;formEl;selectEl;densityEl;densityUnitSelectEl;lengthUnitEl;massUnitEl;calcBtn;resultsEl;errorEl;volVal;areaVal;massVal;centroidVal;selectedShapeId=null;rawProps=null;canonicalDensityGcm3=null;currentDensityUnit=`g/cm³`;centroidHandler=null;openHandler=null;constructor(e){this.btn=document.createElement(`button`),this.btn.className=`btn btn-ghost btn-square btn-sm rounded-md absolute bottom-6 right-8 z-[100] panel-bg border border-base-content/10 text-base-content/60`,this.btn.title=`Shape Properties`,this.btn.innerHTML=Tp,e.appendChild(this.btn),this.panel=document.createElement(`div`),this.panel.className=`absolute bottom-[68px] right-6 w-[300px] bg-base-100/95 backdrop-blur-xl border border-base-content/10 rounded-lg p-4 z-[200] shadow-[0_4px_24px_rgba(0,0,0,0.5)] text-base-content text-[13px] hidden`,this.panel.innerHTML=this.buildHTML(),e.appendChild(this.panel),this.bindRefs(),this.bindEvents(),this.loadMaterials()}buildHTML(){return`
4525
4525
  <div class="flex items-center justify-between mb-3">
4526
4526
  <span class="text-xs font-semibold text-base-content/70 uppercase tracking-wider">Shape Properties</span>
4527
4527
  <button class="btn btn-ghost btn-xs btn-square" data-action="panel-close">×</button>
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>FluidCAD Viewer</title>
7
- <script type="module" crossorigin src="/assets/index-mLcpjEcV.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BJG141m7.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-BpvgjPLm.css">
9
9
  </head>
10
10
  <body class="m-0 p-0 overflow-hidden w-full h-full bg-base-100 text-base-content">
@@ -1,19 +0,0 @@
1
- import { GeometrySceneObject } from "./geometry.js";
2
- import { LazyVertex } from "../lazy-vertex.js";
3
- import { PlaneObjectBase } from "../plane-renderable-base.js";
4
- export declare class ArcThreePoints extends GeometrySceneObject {
5
- startPoint: LazyVertex;
6
- endPoint: LazyVertex;
7
- center: LazyVertex;
8
- private targetPlane;
9
- constructor(startPoint: LazyVertex, endPoint: LazyVertex, center: LazyVertex, targetPlane?: PlaneObjectBase);
10
- build(): void;
11
- compareTo(other: ArcThreePoints): boolean;
12
- getType(): string;
13
- getUniqueType(): string;
14
- serialize(): {
15
- startPoint: {};
16
- endPoint: {};
17
- center: {};
18
- };
19
- }
@@ -1,75 +0,0 @@
1
- import { Vertex } from "../../common/vertex.js";
2
- import { Geometry } from "../../oc/geometry.js";
3
- import { Point2D } from "../../math/point.js";
4
- import { GeometrySceneObject } from "./geometry.js";
5
- export class ArcThreePoints extends GeometrySceneObject {
6
- startPoint;
7
- endPoint;
8
- center;
9
- targetPlane;
10
- constructor(startPoint, endPoint, center, targetPlane = null) {
11
- super();
12
- this.startPoint = startPoint;
13
- this.endPoint = endPoint;
14
- this.center = center;
15
- this.targetPlane = targetPlane;
16
- }
17
- build() {
18
- const plane = this.targetPlane?.getPlane() || this.sketch.getPlane();
19
- const startPt = this.startPoint.asPoint2D();
20
- const endPt = this.endPoint.asPoint2D();
21
- const centerPt = this.center.asPoint2D();
22
- const dx = startPt.x - centerPt.x;
23
- const dy = startPt.y - centerPt.y;
24
- const radius = Math.sqrt(dx * dx + dy * dy);
25
- const endAngle = Math.atan2(endPt.y - centerPt.y, endPt.x - centerPt.x);
26
- const center = plane.localToWorld(centerPt);
27
- const start = plane.localToWorld(startPt);
28
- const end = plane.localToWorld(endPt);
29
- const arc = Geometry.makeArc(center, radius, plane.normal, start, end);
30
- const edge = Geometry.makeEdgeFromCurve(arc);
31
- // Tangent at end: perpendicular to radius direction at end point (CCW)
32
- const tx = -Math.sin(endAngle);
33
- const ty = Math.cos(endAngle);
34
- this.setTangent(new Point2D(tx, ty));
35
- this.setState('start', Vertex.fromPoint2D(startPt));
36
- this.setState('end', Vertex.fromPoint2D(endPt));
37
- this.addShape(edge);
38
- if (this.sketch) {
39
- this.setCurrentPosition(endPt);
40
- }
41
- if (this.targetPlane) {
42
- this.targetPlane.removeShapes(this);
43
- }
44
- }
45
- compareTo(other) {
46
- if (!(other instanceof ArcThreePoints)) {
47
- return false;
48
- }
49
- if (!super.compareTo(other)) {
50
- return false;
51
- }
52
- if (this.targetPlane?.constructor !== other.targetPlane?.constructor) {
53
- return false;
54
- }
55
- if (this.targetPlane && other.targetPlane && !this.targetPlane.compareTo(other.targetPlane)) {
56
- return false;
57
- }
58
- return this.startPoint.compareTo(other.startPoint) &&
59
- this.endPoint.compareTo(other.endPoint) &&
60
- this.center.compareTo(other.center);
61
- }
62
- getType() {
63
- return 'arc';
64
- }
65
- getUniqueType() {
66
- return 'arc-three-points';
67
- }
68
- serialize() {
69
- return {
70
- startPoint: this.startPoint.serialize(),
71
- endPoint: this.endPoint.serialize(),
72
- center: this.center.serialize()
73
- };
74
- }
75
- }
@@ -1,17 +0,0 @@
1
- import { GeometrySceneObject } from "./geometry.js";
2
- import { LazyVertex } from "../lazy-vertex.js";
3
- import { PlaneObjectBase } from "../plane-renderable-base.js";
4
- export declare class ArcToPoint extends GeometrySceneObject {
5
- endPoint: LazyVertex;
6
- radius: number;
7
- private targetPlane;
8
- constructor(endPoint: LazyVertex, radius?: number, targetPlane?: PlaneObjectBase);
9
- build(): void;
10
- compareTo(other: ArcToPoint): boolean;
11
- getType(): string;
12
- getUniqueType(): string;
13
- serialize(): {
14
- endPoint: {};
15
- radius: number;
16
- };
17
- }
@@ -1,95 +0,0 @@
1
- import { Vertex } from "../../common/vertex.js";
2
- import { Geometry } from "../../oc/geometry.js";
3
- import { Point2D } from "../../math/point.js";
4
- import { GeometrySceneObject } from "./geometry.js";
5
- export class ArcToPoint extends GeometrySceneObject {
6
- endPoint;
7
- radius;
8
- targetPlane;
9
- constructor(endPoint, radius = 0, targetPlane = null) {
10
- super();
11
- this.endPoint = endPoint;
12
- this.radius = radius;
13
- this.targetPlane = targetPlane;
14
- }
15
- build() {
16
- const plane = this.targetPlane?.getPlane() || this.sketch.getPlane();
17
- const targetPoint = this.endPoint.asPoint2D();
18
- const startPoint = this.targetPlane
19
- ? plane.worldToLocal(this.targetPlane.getPlaneCenter())
20
- : this.getCurrentPosition();
21
- const dx = targetPoint.x - startPoint.x;
22
- const dy = targetPoint.y - startPoint.y;
23
- const chordLen = Math.sqrt(dx * dx + dy * dy);
24
- // Default radius: semicircle (half the chord length)
25
- let r = this.radius || (chordLen / 2);
26
- const cw = r < 0;
27
- r = Math.abs(r);
28
- // Ensure radius is at least half chord length
29
- if (r < chordLen / 2) {
30
- r = chordLen / 2;
31
- }
32
- // Find center using perpendicular bisector + radius constraint
33
- const mx = (startPoint.x + targetPoint.x) / 2;
34
- const my = (startPoint.y + targetPoint.y) / 2;
35
- // Perpendicular direction to chord (normalized)
36
- const px = -dy / chordLen;
37
- const py = dx / chordLen;
38
- // Distance from midpoint to center along perpendicular
39
- const d = Math.sqrt(r * r - (chordLen / 2) * (chordLen / 2));
40
- // Choose side based on CW/CCW
41
- const sign = cw ? -1 : 1;
42
- const centerPoint = new Point2D(mx + sign * d * px, my + sign * d * py);
43
- const startAngle = Math.atan2(startPoint.y - centerPoint.y, startPoint.x - centerPoint.x);
44
- const endAngle = Math.atan2(targetPoint.y - centerPoint.y, targetPoint.x - centerPoint.x);
45
- const normal = cw ? plane.normal.negate() : plane.normal;
46
- const center = plane.localToWorld(centerPoint);
47
- const start = plane.localToWorld(startPoint);
48
- const end = plane.localToWorld(targetPoint);
49
- const arc = Geometry.makeArc(center, r, normal, start, end);
50
- const edge = Geometry.makeEdgeFromCurve(arc);
51
- // Tangent at end: perpendicular to radius direction at end point
52
- // CCW: (-sin θ, cos θ), CW: (sin θ, -cos θ)
53
- const signT = cw ? -1 : 1;
54
- const endTx = signT * (-Math.sin(endAngle));
55
- const endTy = signT * Math.cos(endAngle);
56
- this.setTangent(new Point2D(endTx, endTy));
57
- this.setState('start', Vertex.fromPoint2D(startPoint));
58
- this.setState('end', Vertex.fromPoint2D(targetPoint));
59
- this.addShape(edge);
60
- if (this.sketch) {
61
- this.setCurrentPosition(targetPoint);
62
- }
63
- if (this.targetPlane) {
64
- this.targetPlane.removeShapes(this);
65
- }
66
- }
67
- compareTo(other) {
68
- if (!(other instanceof ArcToPoint)) {
69
- return false;
70
- }
71
- if (!super.compareTo(other)) {
72
- return false;
73
- }
74
- if (this.targetPlane?.constructor !== other.targetPlane?.constructor) {
75
- return false;
76
- }
77
- if (this.targetPlane && other.targetPlane && !this.targetPlane.compareTo(other.targetPlane)) {
78
- return false;
79
- }
80
- return this.endPoint.compareTo(other.endPoint) &&
81
- this.radius === other.radius;
82
- }
83
- getType() {
84
- return 'arc';
85
- }
86
- getUniqueType() {
87
- return 'arc-to-point';
88
- }
89
- serialize() {
90
- return {
91
- endPoint: this.endPoint.serialize(),
92
- radius: this.radius
93
- };
94
- }
95
- }