fluidcad 0.0.24 → 0.0.26

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 (52) hide show
  1. package/lib/dist/common/scene-object.d.ts +6 -2
  2. package/lib/dist/common/scene-object.js +28 -16
  3. package/lib/dist/common/transformable-primitive.d.ts +16 -0
  4. package/lib/dist/common/transformable-primitive.js +116 -0
  5. package/lib/dist/core/2d/tarc.d.ts +2 -2
  6. package/lib/dist/core/copy.js +5 -4
  7. package/lib/dist/core/cylinder.d.ts +2 -2
  8. package/lib/dist/core/index.d.ts +2 -1
  9. package/lib/dist/core/index.js +1 -0
  10. package/lib/dist/core/interfaces.d.ts +80 -21
  11. package/lib/dist/core/local.d.ts +12 -0
  12. package/lib/dist/core/local.js +18 -0
  13. package/lib/dist/core/mirror.d.ts +2 -2
  14. package/lib/dist/core/mirror.js +42 -60
  15. package/lib/dist/core/plane.d.ts +5 -7
  16. package/lib/dist/core/sphere.d.ts +3 -3
  17. package/lib/dist/features/2d/circle.js +2 -1
  18. package/lib/dist/features/2d/tarc-with-tangent.js +4 -2
  19. package/lib/dist/features/2d/tarc.js +4 -2
  20. package/lib/dist/features/axis-from-edge.js +2 -2
  21. package/lib/dist/features/axis-from-sketch.d.ts +18 -0
  22. package/lib/dist/features/axis-from-sketch.js +58 -0
  23. package/lib/dist/features/copy-linear2d.d.ts +4 -2
  24. package/lib/dist/features/copy-linear2d.js +23 -9
  25. package/lib/dist/features/cylinder.d.ts +2 -2
  26. package/lib/dist/features/cylinder.js +2 -2
  27. package/lib/dist/features/mirror-shape.js +10 -0
  28. package/lib/dist/features/plane-from-object.d.ts +1 -1
  29. package/lib/dist/features/plane-from-object.js +11 -6
  30. package/lib/dist/features/plane-mid.d.ts +1 -1
  31. package/lib/dist/features/plane.d.ts +1 -1
  32. package/lib/dist/features/select.js +0 -1
  33. package/lib/dist/features/sphere.d.ts +2 -2
  34. package/lib/dist/features/sphere.js +2 -2
  35. package/lib/dist/math/axis.d.ts +1 -0
  36. package/lib/dist/math/axis.js +4 -1
  37. package/lib/dist/math/index.d.ts +2 -2
  38. package/lib/dist/math/index.js +1 -1
  39. package/lib/dist/math/plane.d.ts +1 -2
  40. package/lib/dist/math/plane.js +20 -19
  41. package/lib/dist/rendering/render.js +8 -0
  42. package/lib/dist/tests/features/cut-two-distances.test.js +1 -1
  43. package/lib/dist/tests/features/cut.test.js +1 -1
  44. package/lib/dist/tests/features/mirror2d.test.js +34 -0
  45. package/lib/dist/tests/features/primitive-chain.test.d.ts +1 -0
  46. package/lib/dist/tests/features/primitive-chain.test.js +45 -0
  47. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/server/dist/code-editor.d.ts +3 -2
  50. package/server/dist/code-editor.js +244 -121
  51. package/ui/dist/assets/{index-CqP_mgZk.js → index-BeLxRMCv.js} +4 -4
  52. package/ui/dist/index.html +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluidcad",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Parametric CAD modeling library using javascript",
5
5
  "author": "Marwan Aouida <contact@marwan.dev>",
6
6
  "homepage": "https://fluidcad.io",
@@ -12,8 +12,9 @@ export declare function clearBreakpoints(code: string): Promise<CodeEditResult>;
12
12
  export declare function insertPoint(code: string, sourceLine: number, point: [number, number]): Promise<CodeEditResult>;
13
13
  export declare function addPick(code: string, sourceLine: number): Promise<CodeEditResult>;
14
14
  /**
15
- * Remove an empty `.pick()` call from the line. Calls with points are left
16
- * untouched so concurrent/stale edits cannot discard user data.
15
+ * Remove an empty `.pick()` call from the chain on the resolved row.
16
+ * Calls with points are left untouched so concurrent/stale edits cannot
17
+ * discard user data.
17
18
  */
18
19
  export declare function removePick(code: string, sourceLine: number): Promise<CodeEditResult>;
19
20
  export declare function removePoint(code: string, sourceLine: number, point: [number, number]): Promise<CodeEditResult>;
@@ -22,7 +22,6 @@ async function getParser() {
22
22
  parser.setLanguage(lang);
23
23
  return parser;
24
24
  }
25
- const POINT_LITERAL = /\[([^\]]+)\]/g;
26
25
  function splitLines(code) {
27
26
  return code.split('\n');
28
27
  }
@@ -46,6 +45,114 @@ function* walkTree(node) {
46
45
  yield* walkTree(child);
47
46
  }
48
47
  }
48
+ /**
49
+ * Resolve a 1-indexed `sourceLine` (captured from a V8 stack trace) to the
50
+ * outermost `call_expression` node whose invocation starts on that row.
51
+ *
52
+ * "Outermost" means: of all call_expression nodes starting on the resolved
53
+ * row, return the one with the largest endIndex. That picks the whole
54
+ * `.pick()` chain for `extrude(sk).pick()` and the only call on the row for
55
+ * the multi-line case
56
+ * trim(
57
+ * edge().circle()
58
+ * )
59
+ * — both match how the old line-based code (which found the last `)` on
60
+ * the line) behaved for the cases it handled.
61
+ *
62
+ * Returns `null` when no call starts on that row, preserving the existing
63
+ * silent-no-op contract of the edit functions.
64
+ */
65
+ function findEditableCallAt(tree, lines, sourceLine) {
66
+ const row = resolveSourceRow(lines, sourceLine);
67
+ if (row < 0) {
68
+ return null;
69
+ }
70
+ let best = null;
71
+ for (const node of walkTree(tree.rootNode)) {
72
+ if (node.type !== 'call_expression') {
73
+ continue;
74
+ }
75
+ if (node.startPosition.row !== row) {
76
+ continue;
77
+ }
78
+ if (!best || node.endIndex > best.endIndex) {
79
+ best = node;
80
+ }
81
+ }
82
+ return best;
83
+ }
84
+ function getArgumentsNode(call) {
85
+ return call.childForFieldName('arguments');
86
+ }
87
+ /**
88
+ * If `call` or any call in its `function` chain invokes `.pick(...)`, return
89
+ * the call_expression for that `.pick()` invocation. Centralises the
90
+ * "is this chain already picked?" check for addPick and removePick.
91
+ */
92
+ function findPickCallInChain(call) {
93
+ let current = call;
94
+ while (current && current.type === 'call_expression') {
95
+ const fn = current.childForFieldName('function');
96
+ if (fn && fn.type === 'member_expression') {
97
+ const prop = fn.childForFieldName('property');
98
+ if (prop && prop.text === 'pick') {
99
+ return current;
100
+ }
101
+ const object = fn.childForFieldName('object');
102
+ current = object;
103
+ continue;
104
+ }
105
+ break;
106
+ }
107
+ return null;
108
+ }
109
+ /**
110
+ * Extract `[x, y]` from an `array` node with exactly two numeric children.
111
+ * Handles unary minus (`-5`) because tree-sitter wraps it in a `unary_expression`.
112
+ */
113
+ function parsePointLiteral(node) {
114
+ if (node.type !== 'array' || node.namedChildren.length !== 2) {
115
+ return null;
116
+ }
117
+ const parts = [];
118
+ for (const child of node.namedChildren) {
119
+ const value = parseFloat(child.text);
120
+ if (Number.isNaN(value)) {
121
+ return null;
122
+ }
123
+ parts.push(value);
124
+ }
125
+ return [parts[0], parts[1]];
126
+ }
127
+ function spliceCode(code, startIndex, endIndex, replacement) {
128
+ return code.slice(0, startIndex) + replacement + code.slice(endIndex);
129
+ }
130
+ /**
131
+ * For point edits (insertPoint / removePoint / setPickPoints), the target is
132
+ * always the `.pick()` call if one exists in the chain — otherwise the
133
+ * outermost call itself. Without this, a chain like
134
+ * extrude(sk).pick([1, 2]).symmetric([3, 4], [5, 6])
135
+ * would drop new points into `.symmetric(...)` instead of `.pick(...)`,
136
+ * because `findEditableCallAt` picks the outermost (largest endIndex) call.
137
+ * The bezier draw-mode flow has no `.pick()` in its chain, so falling back
138
+ * to the outermost keeps bezier(...) point edits working.
139
+ */
140
+ function resolvePointEditTarget(call) {
141
+ return findPickCallInChain(call) ?? call;
142
+ }
143
+ /**
144
+ * Shared setup for the five AST-based edit functions: parse the code once,
145
+ * split it into lines for `resolveSourceRow`, run the caller's transform,
146
+ * and wrap the result. Returning `null` from `fn` means "no edit" and
147
+ * yields the original code verbatim.
148
+ */
149
+ async function withParsedCode(code, fn) {
150
+ const p = await getParser();
151
+ const tree = p.parse(code);
152
+ const lines = splitLines(code);
153
+ const next = fn(tree, lines);
154
+ return { newCode: next ?? code };
155
+ }
49
156
  /**
50
157
  * Recognise a `breakpoint();` statement: an expression_statement wrapping a
51
158
  * call_expression to the bare identifier `breakpoint` with zero arguments.
@@ -267,8 +374,9 @@ export async function clearBreakpoints(code) {
267
374
  return { newCode: joinLines(filtered) };
268
375
  }
269
376
  // ---------------------------------------------------------------------------
270
- // Point / pick edits — line-level transformations on the function call that
271
- // ends on the resolved source row.
377
+ // Point / pick edits — AST-driven transformations. `sourceLine` locates the
378
+ // outermost call_expression on that row; edits operate on the node's
379
+ // startIndex/endIndex so multi-line calls are handled the same as single-line.
272
380
  // ---------------------------------------------------------------------------
273
381
  /**
274
382
  * Resolve `sourceLine` (1-indexed) to a 0-indexed row containing code.
@@ -287,138 +395,153 @@ function resolveSourceRow(lines, sourceLine) {
287
395
  }
288
396
  return row;
289
397
  }
290
- export async function insertPoint(code, sourceLine, point) {
291
- const lines = splitLines(code);
292
- const row = resolveSourceRow(lines, sourceLine);
293
- if (row < 0) {
294
- return { newCode: code };
295
- }
296
- const lineText = lines[row];
297
- const closeParen = lineText.lastIndexOf(')');
298
- if (closeParen < 0) {
299
- return { newCode: code };
300
- }
301
- const openParen = lineText.lastIndexOf('(', closeParen);
302
- if (openParen < 0) {
303
- return { newCode: code };
398
+ /**
399
+ * Walk forward from `from` over whitespace; if a `,` follows, consume it
400
+ * and any trailing whitespace. Returns the index up to which to delete
401
+ * when stripping a non-last argument.
402
+ */
403
+ function consumeTrailingSeparator(code, from) {
404
+ let i = from;
405
+ while (i < code.length && /\s/.test(code[i])) {
406
+ i++;
407
+ }
408
+ if (i < code.length && code[i] === ',') {
409
+ i++;
410
+ while (i < code.length && /\s/.test(code[i])) {
411
+ i++;
412
+ }
413
+ return i;
304
414
  }
305
- const between = lineText.substring(openParen + 1, closeParen).trim();
306
- const prefix = between.length > 0 ? ', ' : '';
307
- const pointText = `[${point[0]}, ${point[1]}]`;
308
- lines[row] = lineText.slice(0, closeParen) + `${prefix}${pointText}` + lineText.slice(closeParen);
309
- return { newCode: joinLines(lines) };
415
+ return from;
310
416
  }
311
- export async function addPick(code, sourceLine) {
312
- const lines = splitLines(code);
313
- const row = resolveSourceRow(lines, sourceLine);
314
- if (row < 0) {
315
- return { newCode: code };
316
- }
317
- const lineText = lines[row];
318
- if (lineText.includes('.pick(')) {
319
- return { newCode: code };
320
- }
321
- const closeParen = lineText.lastIndexOf(')');
322
- if (closeParen < 0) {
323
- return { newCode: code };
417
+ /**
418
+ * Walk backward from `to` over whitespace; if a `,` precedes, consume it
419
+ * and any preceding whitespace. Returns the index from which to start
420
+ * deleting when stripping a non-first argument.
421
+ */
422
+ function consumeLeadingSeparator(code, to) {
423
+ let i = to;
424
+ while (i > 0 && /\s/.test(code[i - 1])) {
425
+ i--;
426
+ }
427
+ if (i > 0 && code[i - 1] === ',') {
428
+ i--;
429
+ while (i > 0 && /\s/.test(code[i - 1])) {
430
+ i--;
431
+ }
432
+ return i;
324
433
  }
325
- lines[row] = lineText.slice(0, closeParen + 1) + '.pick()' + lineText.slice(closeParen + 1);
326
- return { newCode: joinLines(lines) };
434
+ return to;
435
+ }
436
+ export function insertPoint(code, sourceLine, point) {
437
+ return withParsedCode(code, (tree, lines) => {
438
+ const call = findEditableCallAt(tree, lines, sourceLine);
439
+ if (!call) {
440
+ return null;
441
+ }
442
+ const target = resolvePointEditTarget(call);
443
+ const args = getArgumentsNode(target);
444
+ if (!args) {
445
+ return null;
446
+ }
447
+ const pointText = `[${point[0]}, ${point[1]}]`;
448
+ if (args.namedChildren.length === 0) {
449
+ return spliceCode(code, args.startIndex + 1, args.endIndex - 1, pointText);
450
+ }
451
+ return spliceCode(code, args.endIndex - 1, args.endIndex - 1, `, ${pointText}`);
452
+ });
453
+ }
454
+ export function addPick(code, sourceLine) {
455
+ return withParsedCode(code, (tree, lines) => {
456
+ const call = findEditableCallAt(tree, lines, sourceLine);
457
+ if (!call || findPickCallInChain(call)) {
458
+ return null;
459
+ }
460
+ return spliceCode(code, call.endIndex, call.endIndex, '.pick()');
461
+ });
327
462
  }
328
463
  /**
329
- * Remove an empty `.pick()` call from the line. Calls with points are left
330
- * untouched so concurrent/stale edits cannot discard user data.
464
+ * Remove an empty `.pick()` call from the chain on the resolved row.
465
+ * Calls with points are left untouched so concurrent/stale edits cannot
466
+ * discard user data.
331
467
  */
332
- export async function removePick(code, sourceLine) {
333
- const lines = splitLines(code);
334
- const row = resolveSourceRow(lines, sourceLine);
335
- if (row < 0) {
336
- return { newCode: code };
337
- }
338
- const lineText = lines[row];
339
- const match = lineText.match(/\.pick\(\s*\)/);
340
- if (!match) {
341
- return { newCode: code };
342
- }
343
- const idx = match.index;
344
- lines[row] = lineText.slice(0, idx) + lineText.slice(idx + match[0].length);
345
- return { newCode: joinLines(lines) };
468
+ export function removePick(code, sourceLine) {
469
+ return withParsedCode(code, (tree, lines) => {
470
+ const call = findEditableCallAt(tree, lines, sourceLine);
471
+ if (!call) {
472
+ return null;
473
+ }
474
+ const pickCall = findPickCallInChain(call);
475
+ if (!pickCall) {
476
+ return null;
477
+ }
478
+ const pickArgs = getArgumentsNode(pickCall);
479
+ if (!pickArgs || pickArgs.namedChildren.length !== 0) {
480
+ return null;
481
+ }
482
+ const member = pickCall.childForFieldName('function');
483
+ const object = member ? member.childForFieldName('object') : null;
484
+ if (!object) {
485
+ return null;
486
+ }
487
+ return spliceCode(code, object.endIndex, pickCall.endIndex, '');
488
+ });
346
489
  }
347
- export async function removePoint(code, sourceLine, point) {
348
- const lines = splitLines(code);
349
- const row = resolveSourceRow(lines, sourceLine);
350
- if (row < 0) {
351
- return { newCode: code };
352
- }
353
- const lineText = lines[row];
354
- const closeParen = lineText.lastIndexOf(')');
355
- if (closeParen < 0) {
356
- return { newCode: code };
357
- }
358
- const openParen = lineText.lastIndexOf('(', closeParen);
359
- if (openParen < 0) {
360
- return { newCode: code };
361
- }
362
- const argsStr = lineText.substring(openParen + 1, closeParen);
363
- const matches = [...argsStr.matchAll(POINT_LITERAL)];
364
- if (matches.length === 0) {
365
- return { newCode: code };
366
- }
367
- let bestIndex = 0;
368
- let bestDist = Infinity;
369
- for (let i = 0; i < matches.length; i++) {
370
- const parts = matches[i][1].split(',').map((s) => parseFloat(s.trim()));
371
- if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
372
- const dx = parts[0] - point[0];
373
- const dy = parts[1] - point[1];
490
+ export function removePoint(code, sourceLine, point) {
491
+ return withParsedCode(code, (tree, lines) => {
492
+ const call = findEditableCallAt(tree, lines, sourceLine);
493
+ if (!call) {
494
+ return null;
495
+ }
496
+ const target = resolvePointEditTarget(call);
497
+ const args = getArgumentsNode(target);
498
+ if (!args || args.namedChildren.length === 0) {
499
+ return null;
500
+ }
501
+ let bestIndex = -1;
502
+ let bestDist = Infinity;
503
+ for (let i = 0; i < args.namedChildren.length; i++) {
504
+ const parsed = parsePointLiteral(args.namedChildren[i]);
505
+ if (!parsed) {
506
+ continue;
507
+ }
508
+ const dx = parsed[0] - point[0];
509
+ const dy = parsed[1] - point[1];
374
510
  const dist = dx * dx + dy * dy;
375
511
  if (dist < bestDist) {
376
512
  bestDist = dist;
377
513
  bestIndex = i;
378
514
  }
379
515
  }
380
- }
381
- const match = matches[bestIndex];
382
- const matchStart = openParen + 1 + match.index;
383
- const matchEnd = matchStart + match[0].length;
384
- let deleteStart = matchStart;
385
- let deleteEnd = matchEnd;
386
- if (matches.length === 1) {
387
- // Only point — strip just the literal.
388
- }
389
- else if (bestIndex === 0) {
390
- const rest = lineText.substring(deleteEnd);
391
- const commaMatch = rest.match(/^,\s*/);
392
- if (commaMatch) {
393
- deleteEnd += commaMatch[0].length;
516
+ if (bestIndex < 0) {
517
+ return null;
394
518
  }
395
- }
396
- else {
397
- const before = lineText.substring(0, matchStart);
398
- const commaMatch = before.match(/,\s*$/);
399
- if (commaMatch) {
400
- deleteStart = matchStart - commaMatch[0].length;
519
+ const pointNode = args.namedChildren[bestIndex];
520
+ let deleteStart = pointNode.startIndex;
521
+ let deleteEnd = pointNode.endIndex;
522
+ if (args.namedChildren.length > 1) {
523
+ if (bestIndex === 0) {
524
+ deleteEnd = consumeTrailingSeparator(code, deleteEnd);
525
+ }
526
+ else {
527
+ deleteStart = consumeLeadingSeparator(code, deleteStart);
528
+ }
401
529
  }
402
- }
403
- lines[row] = lineText.slice(0, deleteStart) + lineText.slice(deleteEnd);
404
- return { newCode: joinLines(lines) };
530
+ return spliceCode(code, deleteStart, deleteEnd, '');
531
+ });
405
532
  }
406
- export async function setPickPoints(code, sourceLine, points) {
407
- const lines = splitLines(code);
408
- const row = resolveSourceRow(lines, sourceLine);
409
- if (row < 0) {
410
- return { newCode: code };
411
- }
412
- const lineText = lines[row];
413
- const closeParen = lineText.lastIndexOf(')');
414
- if (closeParen < 0) {
415
- return { newCode: code };
416
- }
417
- const openParen = lineText.lastIndexOf('(', closeParen);
418
- if (openParen < 0) {
419
- return { newCode: code };
420
- }
421
- const newArgs = points.map((p) => `[${p[0]}, ${p[1]}]`).join(', ');
422
- lines[row] = lineText.slice(0, openParen + 1) + newArgs + lineText.slice(closeParen);
423
- return { newCode: joinLines(lines) };
533
+ export function setPickPoints(code, sourceLine, points) {
534
+ return withParsedCode(code, (tree, lines) => {
535
+ const call = findEditableCallAt(tree, lines, sourceLine);
536
+ if (!call) {
537
+ return null;
538
+ }
539
+ const target = resolvePointEditTarget(call);
540
+ const args = getArgumentsNode(target);
541
+ if (!args) {
542
+ return null;
543
+ }
544
+ const newArgs = points.map((p) => `[${p[0]}, ${p[1]}]`).join(', ');
545
+ return spliceCode(code, args.startIndex + 1, args.endIndex - 1, newArgs);
546
+ });
424
547
  }
@@ -4272,7 +4272,7 @@ void main() {
4272
4272
 
4273
4273
  }
4274
4274
 
4275
- `,extensions:{clipCullDistance:!1,multiDraw:!1}});super(a,o),this.frustumCulled=!1}},Bd={cameraMode:`orthographic`,showGrid:!0,sectionView:!0},Vd=new class{current={...Bd};listeners=new Set;update(e){Object.assign(this.current,e);for(let e of this.listeners)e(this.current)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function Hd(e){Vd.update({showGrid:e.showGrid,cameraMode:e.cameraMode})}var Ud=new B(0,0,1),Wd=new B(50,-50,40),Gd=50;function Kd(e){return new B(e.x,e.y,e.z)}var qd=class{mode=`default`;cameraBackup=null;cameraBackupMode=null;enabled=!0;lastGridNormal=Ud.clone();lastGridPosition;_sectionPlane=null;constructor(e){this.ctx=e,this.setupDefaultAxes(),this.setupGrid(Ud),Vd.subscribe(()=>this.applyGridVisibility()),Pd(()=>this.rebuildGrid())}get currentMode(){return this.mode}get isSketchMode(){return this.mode===`sketch`}get sectionPlane(){return this._sectionPlane}set sketchEnabled(e){this.enabled=e}enterDefaultMode(){this.mode===`sketch`&&(this._sectionPlane=null,this.restoreCamera(),this.cameraBackupMode===`perspective`&&(this.ctx.switchCamera(`perspective`),Vd.update({cameraMode:`perspective`})),this.cameraBackupMode=null),this.mode=`default`,this.ctx.camera.up.copy(Un.DEFAULT_UP),this.ctx.cameraControls.updateCameraUp(),this.showDefaultAxes(),this.setupGrid(Ud)}enterSketchMode(e){if(!this.enabled)return;this.mode=`sketch`,Vd.current.cameraMode===`perspective`&&(this.cameraBackupMode=`perspective`,this.ctx.switchCamera(`orthographic`)),this.positionCameraForSketch(e),this.showSketchAxes(e);let t=Kd(e.normal),n=Kd(e.origin);this.setupGrid(t,n.add(t.clone().multiplyScalar(-.01))),this.createSectionPlane(e)}enforceSketchNormal(e){let t=this.ctx.cameraControls,n=Kd(e.normal),r=Kd(e.yDirection),i=new B;t.getTarget(i);let a=i.clone().add(n.clone().multiplyScalar(Gd));this.ctx.camera.up.copy(r),t.updateCameraUp(),t.normalizeRotations(),t.setLookAt(a.x,a.y,a.z,i.x,i.y,i.z,!1),t.getTarget(this.ctx.controls.target),this.ctx.gizmo.target=this.ctx.controls.target,this.createSectionPlane(e)}positionCameraForSketch(e){let t=this.ctx.cameraControls;if(!this.cameraBackup){let e=new B,n=new B;t.getPosition(e),t.getTarget(n),this.cameraBackup={position:e,target:n}}let n=Kd(e.center),r=Kd(e.normal),i=Kd(e.yDirection),a=n.clone().add(r.clone().multiplyScalar(Gd));this.ctx.camera.up.copy(i),t.updateCameraUp(),t.normalizeRotations(),t.setLookAt(a.x,a.y,a.z,n.x,n.y,n.z,!1),t.getTarget(this.ctx.controls.target),this.ctx.gizmo.target=this.ctx.controls.target}restoreCamera(){let e=this.ctx.cameraControls,t=this.cameraBackup,n=t?.position??Wd.clone(),r=t?.target??new B(0,0,0);this.ctx.camera.up.copy(Un.DEFAULT_UP),e.updateCameraUp(),e.normalizeRotations(),e.setLookAt(n.x,n.y,n.z,r.x,r.y,r.z,!1),e.getTarget(this.ctx.controls.target),this.ctx.gizmo.target=this.ctx.controls.target,this.cameraBackup=null}setupDefaultAxes(){let e=new mo(1e3);e.name=`defaultAxesHelper`,this.ctx.scene.add(e)}showDefaultAxes(){this.removeByName(`sketchAxesHelper`);let e=this.ctx.scene.getObjectByName(`defaultAxesHelper`);e&&(e.visible=!0)}showSketchAxes(e){let t=this.ctx.scene.getObjectByName(`defaultAxesHelper`);t&&(t.visible=!1),this.removeByName(`sketchAxesHelper`);let n=new mo(1e3);n.name=`sketchAxesHelper`;let r=Kd(e.origin),i=Kd(e.xDirection),a=Kd(e.yDirection),o=Kd(e.normal),s=new gn().makeBasis(i,a,o);s.setPosition(r),n.matrix.copy(s),n.matrixAutoUpdate=!1,this.ctx.scene.add(n)}rebuildGrid(){this.setupGrid(this.lastGridNormal,this.lastGridPosition),this.ctx.requestRender()}setupGrid(e,t){this.lastGridNormal=e.clone(),this.lastGridPosition=t?.clone(),this.removeByName(`grid`);let n=new zd(10,100,Nd.gridColor,1e5,e);n.name=`grid`,t&&n.position.copy(t),n.visible=this.mode===`sketch`||Vd.current.showGrid,this.ctx.scene.add(n)}applyGridVisibility(){let e=this.ctx.scene.getObjectByName(`grid`);e&&(e.visible=this.mode===`sketch`||Vd.current.showGrid,this.ctx.requestRender())}createSectionPlane(e){let t=Kd(e.normal).negate(),n=Kd(e.origin).add(Kd(e.normal).multiplyScalar(.1));this._sectionPlane||=new Ai,this._sectionPlane.setFromNormalAndCoplanarPoint(t,n)}removeByName(e){let t=this.ctx.scene.getObjectByName(e);t&&this.ctx.scene.remove(t)}},Jd={color:``,lineWidth:1,opacity:1,depthWrite:!0},Yd=class extends ii{constructor(e,t={}){super();let n={...Jd,...t};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 r=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new r(t.indices),1));let i=new qi(e,new Fi({color:n.color||Nd.edgeColor,linewidth:n.lineWidth,transparent:n.opacity<1,opacity:n.opacity,polygonOffset:!0,polygonOffsetFactor:2,polygonOffsetUnits:1,side:2,depthWrite:n.depthWrite,depthTest:n.depthWrite}));t.edgeIndex!==void 0&&(i.userData.edgeIndex=t.edgeIndex),this.add(i)}}},Xd={color:``,opacity:1},Zd=class extends ii{constructor(e,t={}){super();let n={...Xd,...t};for(let r of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(r.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(r.normals),3));let i=r.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new i(r.indices),1)),e.computeBoundingBox();let a=t?.color??r.color??`#`+Nd.faceColor.getHexString(),o=t?.color!==void 0||t?.opacity!==void 0,s=new G(e,new la({color:a,transparent:o||n.opacity<1,opacity:n.opacity,depthWrite:!o,polygonOffset:!0,polygonOffsetFactor:o?-1:1,polygonOffsetUnits:1,side:2}));r.faceMapping&&(s.userData.faceMapping=r.faceMapping),this.add(s)}}},Qd=class extends ii{constructor(e,t){super();for(let n of e.meshes)if(n.label===`solid-faces`){let e=new Zd({shapeType:`face`,meshes:[n]},t?.face);e.renderOrder=1,this.add(e)}else if(n.label===`solid-edges`){let e=new Yd({shapeType:`edge`,meshes:[n]},t?.edge);e.renderOrder=2,this.add(e)}}},$d=4,ef=1.5,tf=.6,nf=$d+ef+tf+ef,rf=`
4275
+ `,extensions:{clipCullDistance:!1,multiDraw:!1}});super(a,o),this.frustumCulled=!1}},Bd={cameraMode:`orthographic`,showGrid:!0,sectionView:!0},Vd=new class{current={...Bd};listeners=new Set;update(e){Object.assign(this.current,e);for(let e of this.listeners)e(this.current)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}};function Hd(e){Vd.update({showGrid:e.showGrid,cameraMode:e.cameraMode})}var Ud=new B(0,0,1),Wd=new B(50,-50,40),Gd=50;function Kd(e){return new B(e.x,e.y,e.z)}var qd=class{mode=`default`;cameraBackup=null;cameraBackupMode=null;enabled=!0;lastGridNormal=Ud.clone();lastGridPosition;_sectionPlane=null;constructor(e){this.ctx=e,this.setupDefaultAxes(),this.setupGrid(Ud),Vd.subscribe(()=>this.applyGridVisibility()),Pd(()=>this.rebuildGrid())}get currentMode(){return this.mode}get isSketchMode(){return this.mode===`sketch`}get sectionPlane(){return this._sectionPlane}set sketchEnabled(e){this.enabled=e}enterDefaultMode(){this.mode===`sketch`&&(this._sectionPlane=null,this.restoreCamera(),this.cameraBackupMode===`perspective`&&(this.ctx.switchCamera(`perspective`),Vd.update({cameraMode:`perspective`})),this.cameraBackupMode=null),this.mode=`default`,this.ctx.camera.up.copy(Un.DEFAULT_UP),this.ctx.cameraControls.updateCameraUp(),this.showDefaultAxes(),this.setupGrid(Ud)}enterSketchMode(e){if(!this.enabled)return;this.mode=`sketch`,Vd.current.cameraMode===`perspective`&&(this.cameraBackupMode=`perspective`,this.ctx.switchCamera(`orthographic`)),this.positionCameraForSketch(e),this.showSketchAxes(e);let t=Kd(e.normal),n=Kd(e.origin);this.setupGrid(t,n.add(t.clone().multiplyScalar(-.01))),this.createSectionPlane(e)}enforceSketchNormal(e){let t=this.ctx.cameraControls,n=Kd(e.normal),r=Kd(e.yDirection),i=new B;t.getTarget(i);let a=i.clone().add(n.clone().multiplyScalar(Gd));this.ctx.camera.up.copy(r),t.updateCameraUp(),t.normalizeRotations(),t.setLookAt(a.x,a.y,a.z,i.x,i.y,i.z,!1),t.getTarget(this.ctx.controls.target),this.ctx.gizmo.target=this.ctx.controls.target,this.showSketchAxes(e);let o=Kd(e.origin);this.setupGrid(n,o.add(n.clone().multiplyScalar(-.01))),this.createSectionPlane(e)}positionCameraForSketch(e){let t=this.ctx.cameraControls;if(!this.cameraBackup){let e=new B,n=new B;t.getPosition(e),t.getTarget(n),this.cameraBackup={position:e,target:n}}let n=Kd(e.center),r=Kd(e.normal),i=Kd(e.yDirection),a=n.clone().add(r.clone().multiplyScalar(Gd));this.ctx.camera.up.copy(i),t.updateCameraUp(),t.normalizeRotations(),t.setLookAt(a.x,a.y,a.z,n.x,n.y,n.z,!1),t.getTarget(this.ctx.controls.target),this.ctx.gizmo.target=this.ctx.controls.target}restoreCamera(){let e=this.ctx.cameraControls,t=this.cameraBackup,n=t?.position??Wd.clone(),r=t?.target??new B(0,0,0);this.ctx.camera.up.copy(Un.DEFAULT_UP),e.updateCameraUp(),e.normalizeRotations(),e.setLookAt(n.x,n.y,n.z,r.x,r.y,r.z,!1),e.getTarget(this.ctx.controls.target),this.ctx.gizmo.target=this.ctx.controls.target,this.cameraBackup=null}setupDefaultAxes(){let e=new mo(1e3);e.name=`defaultAxesHelper`,this.ctx.scene.add(e)}showDefaultAxes(){this.removeByName(`sketchAxesHelper`);let e=this.ctx.scene.getObjectByName(`defaultAxesHelper`);e&&(e.visible=!0)}showSketchAxes(e){let t=this.ctx.scene.getObjectByName(`defaultAxesHelper`);t&&(t.visible=!1),this.removeByName(`sketchAxesHelper`);let n=new mo(1e3);n.name=`sketchAxesHelper`;let r=Kd(e.origin),i=Kd(e.xDirection),a=Kd(e.yDirection),o=Kd(e.normal),s=new gn().makeBasis(i,a,o);s.setPosition(r),n.matrix.copy(s),n.matrixAutoUpdate=!1,this.ctx.scene.add(n)}rebuildGrid(){this.setupGrid(this.lastGridNormal,this.lastGridPosition),this.ctx.requestRender()}setupGrid(e,t){this.lastGridNormal=e.clone(),this.lastGridPosition=t?.clone(),this.removeByName(`grid`);let n=new zd(10,100,Nd.gridColor,1e5,e);n.name=`grid`,t&&n.position.copy(t),n.visible=this.mode===`sketch`||Vd.current.showGrid,this.ctx.scene.add(n)}applyGridVisibility(){let e=this.ctx.scene.getObjectByName(`grid`);e&&(e.visible=this.mode===`sketch`||Vd.current.showGrid,this.ctx.requestRender())}createSectionPlane(e){let t=Kd(e.normal).negate(),n=Kd(e.origin).add(Kd(e.normal).multiplyScalar(.1));this._sectionPlane||=new Ai,this._sectionPlane.setFromNormalAndCoplanarPoint(t,n)}removeByName(e){let t=this.ctx.scene.getObjectByName(e);t&&this.ctx.scene.remove(t)}},Jd={color:``,lineWidth:1,opacity:1,depthWrite:!0},Yd=class extends ii{constructor(e,t={}){super();let n={...Jd,...t};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 r=t.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new r(t.indices),1));let i=new qi(e,new Fi({color:n.color||Nd.edgeColor,linewidth:n.lineWidth,transparent:n.opacity<1,opacity:n.opacity,polygonOffset:!0,polygonOffsetFactor:2,polygonOffsetUnits:1,side:2,depthWrite:n.depthWrite,depthTest:n.depthWrite}));t.edgeIndex!==void 0&&(i.userData.edgeIndex=t.edgeIndex),this.add(i)}}},Xd={color:``,opacity:1},Zd=class extends ii{constructor(e,t={}){super();let n={...Xd,...t};for(let r of e.meshes){let e=new Tr;e.setAttribute(`position`,new W(new Float32Array(r.vertices),3)),e.setAttribute(`normal`,new W(new Float32Array(r.normals),3));let i=r.vertices.length/3>65535?Uint32Array:Uint16Array;e.setIndex(new W(new i(r.indices),1)),e.computeBoundingBox();let a=t?.color??r.color??`#`+Nd.faceColor.getHexString(),o=t?.color!==void 0||t?.opacity!==void 0,s=new G(e,new la({color:a,transparent:o||n.opacity<1,opacity:n.opacity,depthWrite:!o,polygonOffset:!0,polygonOffsetFactor:o?-1:1,polygonOffsetUnits:1,side:2}));r.faceMapping&&(s.userData.faceMapping=r.faceMapping),this.add(s)}}},Qd=class extends ii{constructor(e,t){super();for(let n of e.meshes)if(n.label===`solid-faces`){let e=new Zd({shapeType:`face`,meshes:[n]},t?.face);e.renderOrder=1,this.add(e)}else if(n.label===`solid-edges`){let e=new Yd({shapeType:`edge`,meshes:[n]},t?.edge);e.renderOrder=2,this.add(e)}}},$d=4,ef=1.5,tf=.6,nf=$d+ef+tf+ef,rf=`
4276
4276
  attribute float lineDistance;
4277
4277
  varying float vLineDistance;
4278
4278
 
@@ -4305,7 +4305,7 @@ void main() {
4305
4305
 
4306
4306
  gl_FragColor = vec4(color, 1.0);
4307
4307
  }
4308
- `,of=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:$d},gapLength:{value:ef},dotLength:{value:tf},patternLength:{value:nf}},vertexShader:rf,fragmentShader:af,side:2,transparent:!0,polygonOffset:!0,polygonOffsetFactor:2,polygonOffsetUnits:1}));a.computeLineDistances(),this.add(a)}}},sf=`#2297ff`,cf=2,lf=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:sf,linewidth:cf,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1,side:2,depthWrite:!0,depthTest:!0}));this.add(r)}}},uf=`#2297ff`,df=`#2297ff`,ff=.15,pf=.4,mf=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?df:uf,r=t?pf:ff;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)}}},hf=`#2297ff`,gf=2,_f=2,vf=16,yf=.003,bf=1.5,xf=1e-8;function Sf(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 Cf=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;let t=new Zi(_f,vf),n=new dr({color:hf,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:hf,linewidth:gf,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)<xf)||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(Sf(r,e,yf),bf)),i.updateMatrixWorld(!0)},this.add(i)}}},wf={color:`#2297ff`,lineWidth:2},Tf={trim:e=>new lf(e),"pick-edge":e=>new Cf(e)};function Ef(e){let t=e.metaType?Tf[e.metaType]:void 0;return t?t(e):new of(e)}var Df={"pick-region":e=>new mf(e,!1),"pick-region-selected":e=>new mf(e,!0)};function Of(e){let t=e.metaType?Df[e.metaType]:void 0;return t?t(e):new Zd(e)}var kf=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=Ef(a);break;case`face`:e=Of(a);break}else switch(a.shapeType){case`wire`:case`edge`:e=new Yd(a,n?.edge??(r?wf:void 0));break;case`face`:e=new Zd(a,n?.face);break;case`solid`:e=new Qd(a,n);break}e&&(a.shapeId&&(e.userData.shapeId=a.shapeId),this.add(e))}}};function Af(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 jf=`#2297ff`,Mf=2,Nf=16,Pf=.003,Ff=1.5,If=15954511,Lf=64,Rf=3,zf=15954511,Bf=.35,Vf=.6,Hf=18,Uf=5,Wf=2.5,Gf=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=Ef(e);e.shapeId&&(t.userData.shapeId=e.shapeId),this.add(t)}continue}let t=new Yd(e,{color:jf,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(Mf,Nf),s=new dr({color:jf,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(Af(n,e,Pf),Ff)),t.onBeforeRender=(t,n,r)=>{i.scale.setScalar(Math.min(Af(r,e,Pf),Ff)),i.updateMatrixWorld(!0)},this.add(i)}}buildCursor(e,t){let n=e.object?.currentPosition;if(!n)return;let r=new Zi(Rf,Lf),i=new dr({color:If,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(Af(t,o.position,.003)),a.onBeforeRender=(e,t,n)=>{o.scale.setScalar(Af(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:zf,transparent:!0,opacity:Bf,depthTest:!1}),s=new Qi(Vf,Vf,Hf,8);s.translate(0,Hf/2,0);let c=new G(s,o),l=new $i(Wf,Uf,8);l.translate(0,Hf+Uf/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(Af(t,d.position,.003)),c.onBeforeRender=(e,t,n)=>{d.scale.setScalar(Af(n,d.position,.003)),d.updateMatrixWorld(!0)},this.add(d)}};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 qf=`#ffc26c`,Jf=`#c88f40`,Yf=`#c88f40`,Xf=.1,Zf=20,Qf=3,$f=1.5,ep=.4,tp=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:qf,transparent:!0,opacity:Xf,side:2,polygonOffset:!0,polygonOffsetFactor:1,polygonOffsetUnits:1}));this.add(o);let s=new qi(new ia(a,18),new Fi({color:Jf,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:Yf}),d=Zf-Qf,f=new Qi(ep,ep,d,8);f.translate(0,d/2,0);let p=new G(f,u),m=new $i($f,Qf,8);m.translate(0,d+Qf/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(Kf(t,g.position,.006)),p.onBeforeRender=(e,t,n)=>{g.scale.setScalar(Kf(n,g.position,.006)),g.updateMatrixWorld(!0)},this.add(g),this.position.z=.01}},np=`#c88f40`,rp=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:np,dashSize:5,gapSize:5}));r.computeLineDistances(),this.add(r)}},ip={edge:{color:`#11a4ed`,lineWidth:3,depthWrite:!1},face:{color:`#5c9fcc`,opacity:1}},ap={edge:{opacity:.3},face:{opacity:.3}};function op(e,t,n,r){if(r)return r;if(e===`select`)return ip;if(t&&n!==`sketch`)return ap}function sp(e,t,n,r,i,a){switch(e.type){case`sketch`:return new Gf(e,t,n,r);case`plane`:return new tp(e,r);case`axis`:return new rp(e)}let o=e.uniqueType===`select`,s=op(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(sp(a,t,n,r,i,s));l=e}else l=new kf(e,i,s);return o&&l.traverse(e=>{e.renderOrder=999}),l}function cp(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(sp(a,e,t,n,r));let a=new Map,o=0;for(let t of e)if(t.sceneShapes)for(let e of t.sceneShapes)e.isMetaShape||(e.shapeId&&a.set(e.shapeId,o),o++);return i.traverse(e=>{let t=e.userData.shapeId;t&&a.has(t)&&(e.userData.shapeIndex=a.get(t))}),i}var lp=`/api/preferences`;async function up(){try{let e=await fetch(lp);return e.ok?await e.json():null}catch{return null}}function dp(e,t){fetch(lp,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({[e]:t})}).catch(()=>{})}var fp=`<svg
4308
+ `,of=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:$d},gapLength:{value:ef},dotLength:{value:tf},patternLength:{value:nf}},vertexShader:rf,fragmentShader:af,side:2,transparent:!0,polygonOffset:!0,polygonOffsetFactor:2,polygonOffsetUnits:1}));a.computeLineDistances(),this.add(a)}}},sf=`#2297ff`,cf=2,lf=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:sf,linewidth:cf,polygonOffset:!0,polygonOffsetFactor:-1,polygonOffsetUnits:-1,side:2,depthWrite:!0,depthTest:!0}));this.add(r)}}},uf=`#2297ff`,df=`#2297ff`,ff=.15,pf=.4,mf=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?df:uf,r=t?pf:ff;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)}}},hf=`#2297ff`,gf=2,_f=2,vf=16,yf=.003,bf=1.5,xf=1e-8;function Sf(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 Cf=class extends ii{constructor(e){super(),this.userData.isMetaShape=!0;let t=new Zi(_f,vf),n=new dr({color:hf,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:hf,linewidth:gf,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)<xf)||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(Sf(r,e,yf),bf)),i.updateMatrixWorld(!0)},this.add(i)}}},wf={color:`#2297ff`,lineWidth:2},Tf={trim:e=>new lf(e),"pick-edge":e=>new Cf(e)};function Ef(e){let t=e.metaType?Tf[e.metaType]:void 0;return t?t(e):new of(e)}var Df={"pick-region":e=>new mf(e,!1),"pick-region-selected":e=>new mf(e,!0)};function Of(e){let t=e.metaType?Df[e.metaType]:void 0;return t?t(e):new Zd(e)}var kf=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=Ef(a);break;case`face`:e=Of(a);break}else switch(a.shapeType){case`wire`:case`edge`:e=new Yd(a,n?.edge??(r?wf:void 0));break;case`face`:e=new Zd(a,n?.face);break;case`solid`:e=new Qd(a,n);break}e&&(a.shapeId&&(e.userData.shapeId=a.shapeId),this.add(e))}}};function Af(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 jf=`#2297ff`,Mf=2,Nf=16,Pf=.003,Ff=1.5,If=15954511,Lf=64,Rf=3,zf=15954511,Bf=.35,Vf=.6,Hf=18,Uf=5,Wf=2.5,Gf=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=Ef(e);e.shapeId&&(t.userData.shapeId=e.shapeId),this.add(t)}continue}let t=new Yd(e,{color:jf,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(Mf,Nf),s=new dr({color:jf,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(Af(n,e,Pf),Ff)),t.onBeforeRender=(t,n,r)=>{i.scale.setScalar(Math.min(Af(r,e,Pf),Ff)),i.updateMatrixWorld(!0)},this.add(i)}}buildCursor(e,t){let n=e.object?.currentPosition;if(!n)return;let r=new Zi(Rf,Lf),i=new dr({color:If,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(Af(t,o.position,.003)),a.onBeforeRender=(e,t,n)=>{o.scale.setScalar(Af(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:zf,transparent:!0,opacity:Bf,side:2,depthTest:!1,depthWrite:!1}),s=new Qi(Vf,Vf,Hf,16);s.translate(0,Hf/2,0);let c=new G(s,o),l=new $i(Wf,Uf,16);l.translate(0,Hf+Uf/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(Af(t,d.position,.003)),c.onBeforeRender=(e,t,n)=>{d.scale.setScalar(Af(n,d.position,.003)),d.updateMatrixWorld(!0)},this.add(d)}};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 qf=`#ffc26c`,Jf=`#c88f40`,Yf=`#c88f40`,Xf=.1,Zf=20,Qf=3,$f=1.5,ep=.4,tp=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:qf,transparent:!0,opacity:Xf,side:2,polygonOffset:!0,polygonOffsetFactor:1,polygonOffsetUnits:1}));this.add(o);let s=new qi(new ia(a,18),new Fi({color:Jf,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:Yf}),d=Zf-Qf,f=new Qi(ep,ep,d,8);f.translate(0,d/2,0);let p=new G(f,u),m=new $i($f,Qf,8);m.translate(0,d+Qf/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(Kf(t,g.position,.006)),p.onBeforeRender=(e,t,n)=>{g.scale.setScalar(Kf(n,g.position,.006)),g.updateMatrixWorld(!0)},this.add(g),this.position.z=.01}},np=`#c88f40`,rp=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:np,dashSize:5,gapSize:5}));r.computeLineDistances(),this.add(r)}},ip={edge:{color:`#11a4ed`,lineWidth:3,depthWrite:!1},face:{color:`#5c9fcc`,opacity:1}},ap={edge:{opacity:.3},face:{opacity:.3}};function op(e,t,n,r){if(r)return r;if(e===`select`)return ip;if(t&&n!==`sketch`)return ap}function sp(e,t,n,r,i,a){switch(e.type){case`sketch`:return new Gf(e,t,n,r);case`plane`:return new tp(e,r);case`axis`:return new rp(e)}let o=e.uniqueType===`select`,s=op(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(sp(a,t,n,r,i,s));l=e}else l=new kf(e,i,s);return o&&l.traverse(e=>{e.renderOrder=999}),l}function cp(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(sp(a,e,t,n,r));let a=new Map,o=0;for(let t of e)if(t.sceneShapes)for(let e of t.sceneShapes)e.isMetaShape||(e.shapeId&&a.set(e.shapeId,o),o++);return i.traverse(e=>{let t=e.userData.shapeId;t&&a.has(t)&&(e.userData.shapeIndex=a.get(t))}),i}var lp=`/api/preferences`;async function up(){try{let e=await fetch(lp);return e.ok?await e.json():null}catch{return null}}function dp(e,t){fetch(lp,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({[e]:t})}).catch(()=>{})}var fp=`<svg
4309
4309
  xmlns="http://www.w3.org/2000/svg"
4310
4310
  width="24"
4311
4311
  height="24"
@@ -4871,11 +4871,11 @@ void main() {
4871
4871
  <div class="h-4 w-px bg-base-content/10"></div>
4872
4872
  <label class="flex items-center gap-1.5 cursor-pointer">
4873
4873
  <input type="checkbox" class="checkbox checkbox-xs checkbox-primary" data-snap="vertex" checked />
4874
- <span class="text-xs">Vertices</span>
4874
+ <span class="text-xs">Snap to vertices</span>
4875
4875
  </label>
4876
4876
  <label class="flex items-center gap-1.5 cursor-pointer">
4877
4877
  <input type="checkbox" class="checkbox checkbox-xs checkbox-primary" data-snap="grid" checked />
4878
- <span class="text-xs">Grid</span>
4878
+ <span class="text-xs">Snap to grid</span>
4879
4879
  </label>
4880
4880
  </div>
4881
4881
  `,yh.appendChild(lg),lg.querySelector(`[data-snap="vertex"]`).addEventListener(`change`,e=>{ug&&(ug.snapController.snapToVertices=e.target.checked)}),lg.querySelector(`[data-snap="grid"]`).addEventListener(`change`,e=>{ug&&(ug.snapController.snapToGrid=e.target.checked)});var ug=null,dg=null;function fg(e){let t=null;for(let n=e.length-1;n>=0;n--)if(bh(e[n],e)){t=e[n];break}if(!t||t.type!==`sketch`||!t.id)return!1;for(let n=e.length-1;n>=0;n--)if(e[n].parentId===t.id)return e[n].type===`bezier`;return!1}function pg(e,t){for(let n=e.length-1;n>=0;n--){let r=e[n];if(r.parentId===t&&r.type===`bezier`){let e=r.object?.startPoint,t=r.object?.resolvedPoints;return e?[e,...t||[]]:[]}}return[]}function mg(e){let t=null;for(let n=e.length-1;n>=0;n--)if(bh(e[n],e)){t=e[n];break}let n=t?.type===`sketch`?t:null;if(!n||!n.id||!n.object?.plane){hg();return}let r=null;for(let t=e.length-1;t>=0;t--)if(e[t].parentId===n.id){r=e[t];break}if(!r||r.type!==`bezier`||!r.sourceLocation){hg();return}let i=r.sourceLocation.line,a=n.object.plane,o=pg(e,n.id),s=_h.fromSceneObjects(e,n.id,a);if(ug&&dg===i){ug.updateExistingPoles(o),ug.snapController.updateSnapManager(s);return}hg();let c=r.sourceLocation,l=new vh(s,a),u=lg.querySelector(`[data-snap="vertex"]`),d=lg.querySelector(`[data-snap="grid"]`);u&&(l.snapToVertices=u.checked),d&&(l.snapToGrid=d.checked),ug=new dh($.sceneContext,a,l,o,e=>{fetch(`/api/insert-point`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({point:e,sourceLocation:c})})},e=>{fetch(`/api/set-pick-points`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({points:e,sourceLocation:c})})}),dg=i,ug.activate(),lg.classList.remove(`hidden`)}function hg(){ug&&(ug.deactivate(),ug=null,dg=null),lg.classList.add(`hidden`)}var gg=document.createElement(`div`);gg.className=`absolute bottom-6 left-6 z-[100]`,gg.innerHTML=`
@@ -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-CqP_mgZk.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BeLxRMCv.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-gPoNOiIs.css">
9
9
  </head>
10
10
  <body class="m-0 p-0 overflow-hidden w-full h-full bg-base-100 text-base-content">