cyclecad 2.1.0 → 3.1.0
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.
- package/BILLING-IMPLEMENTATION-SUMMARY.md +425 -0
- package/BILLING-INDEX.md +293 -0
- package/BILLING-INTEGRATION-GUIDE.md +414 -0
- package/COLLABORATION-INDEX.md +440 -0
- package/COLLABORATION-SYSTEM-SUMMARY.md +548 -0
- package/DELIVERABLES.txt +296 -445
- package/DOCKER-BUILD-MANIFEST.txt +483 -0
- package/DOCKER-FILES-REFERENCE.md +440 -0
- package/DOCKER-INFRASTRUCTURE.md +475 -0
- package/DOCKER-README.md +435 -0
- package/Dockerfile +33 -55
- package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
- package/ENHANCEMENT_SUMMARY.txt +308 -0
- package/FEATURE_INVENTORY.md +235 -0
- package/FUSION360_FEATURES_SUMMARY.md +452 -0
- package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
- package/FUSION360_PARITY_SUMMARY.md +520 -0
- package/FUSION360_QUICK_REFERENCE.md +351 -0
- package/MODULE_API_REFERENCE.md +712 -0
- package/MODULE_INVENTORY.txt +264 -0
- package/PWA-FILES-CREATED.txt +350 -0
- package/QUICK-START-TESTING.md +126 -0
- package/STEP-IMPORT-QUICKSTART.md +347 -0
- package/STEP-IMPORT-SYSTEM-SUMMARY.md +502 -0
- package/app/css/mobile.css +1074 -0
- package/app/icons/generate-icons.js +203 -0
- package/app/index.html +1342 -5031
- package/app/js/app.js +1312 -514
- package/app/js/billing-ui.js +990 -0
- package/app/js/brep-kernel.js +933 -981
- package/app/js/collab-client.js +750 -0
- package/app/js/mobile-nav.js +623 -0
- package/app/js/mobile-toolbar.js +476 -0
- package/app/js/modules/animation-module.js +497 -3
- package/app/js/modules/billing-module.js +724 -0
- package/app/js/modules/cam-module.js +507 -2
- package/app/js/modules/collaboration-module.js +513 -0
- package/app/js/modules/constraint-module.js +1266 -0
- package/app/js/modules/data-module.js +544 -1146
- package/app/js/modules/formats-module.js +438 -738
- package/app/js/modules/inspection-module.js +393 -0
- package/app/js/modules/mesh-module-enhanced.js +880 -0
- package/app/js/modules/plugin-module.js +597 -0
- package/app/js/modules/rendering-module.js +460 -0
- package/app/js/modules/scripting-module.js +593 -475
- package/app/js/modules/sketch-module.js +998 -2
- package/app/js/modules/step-module-enhanced.js +938 -0
- package/app/js/modules/surface-module.js +312 -0
- package/app/js/modules/version-module.js +420 -0
- package/app/js/offline-manager.js +705 -0
- package/app/js/responsive-init.js +360 -0
- package/app/js/touch-handler.js +429 -0
- package/app/manifest.json +211 -0
- package/app/offline.html +508 -0
- package/app/sw.js +571 -0
- package/app/tests/billing-tests.html +779 -0
- package/app/tests/brep-tests.html +980 -0
- package/app/tests/collab-tests.html +743 -0
- package/app/tests/mobile-tests.html +1299 -0
- package/app/tests/pwa-tests.html +1134 -0
- package/app/tests/step-tests.html +1042 -0
- package/app/tests/test-agent-v3.html +719 -0
- package/cycleCAD-Architecture-v2.pptx +0 -0
- package/docker-compose.yml +225 -0
- package/docs/BILLING-HELP.json +260 -0
- package/docs/BILLING-README.md +639 -0
- package/docs/BILLING-TUTORIAL.md +736 -0
- package/docs/BREP-HELP.json +326 -0
- package/docs/BREP-TUTORIAL.md +802 -0
- package/docs/COLLABORATION-HELP.json +228 -0
- package/docs/COLLABORATION-TUTORIAL.md +818 -0
- package/docs/DOCKER-HELP.json +224 -0
- package/docs/DOCKER-TUTORIAL.md +974 -0
- package/docs/MOBILE-HELP.json +243 -0
- package/docs/MOBILE-RESPONSIVE-README.md +378 -0
- package/docs/MOBILE-TUTORIAL.md +747 -0
- package/docs/PWA-HELP.json +228 -0
- package/docs/PWA-README.md +662 -0
- package/docs/PWA-TUTORIAL.md +757 -0
- package/docs/STEP-HELP.json +481 -0
- package/docs/STEP-IMPORT-TUTORIAL.md +824 -0
- package/docs/TESTING-GUIDE.md +528 -0
- package/docs/TESTING-HELP.json +182 -0
- package/fusion-vs-cyclecad.html +1771 -0
- package/nginx.conf +237 -0
- package/package.json +1 -1
- package/server/Dockerfile.converter +51 -0
- package/server/Dockerfile.signaling +28 -0
- package/server/billing-server.js +487 -0
- package/server/converter-enhanced.py +528 -0
- package/server/requirements-converter.txt +29 -0
- package/server/signaling-server.js +801 -0
- package/tests/docker-tests.sh +389 -0
- package/~$cycleCAD-Architecture-v2.pptx +0 -0
|
@@ -74,27 +74,62 @@ const SketchModule = {
|
|
|
74
74
|
getUI() {
|
|
75
75
|
return `
|
|
76
76
|
<div id="sketch-toolbar" style="display: none; background: #2a2a2a; padding: 8px; border-radius: 4px; flex-wrap: wrap; gap: 4px;">
|
|
77
|
+
<!-- BASIC TOOLS -->
|
|
77
78
|
<button data-tool="line" class="sketch-tool-btn" title="Line (L)">—</button>
|
|
78
79
|
<button data-tool="rectangle" class="sketch-tool-btn" title="Rectangle (R)">▭</button>
|
|
79
80
|
<button data-tool="circle" class="sketch-tool-btn" title="Circle (C)">●</button>
|
|
80
81
|
<button data-tool="arc" class="sketch-tool-btn" title="Arc (A)">⌒</button>
|
|
81
82
|
<button data-tool="ellipse" class="sketch-tool-btn" title="Ellipse (E)">⬭</button>
|
|
82
83
|
<button data-tool="spline" class="sketch-tool-btn" title="Spline (S)">✓</button>
|
|
84
|
+
<button data-tool="spline_fit" class="sketch-tool-btn" title="Fit Point Spline">↪</button>
|
|
83
85
|
<button data-tool="polygon" class="sketch-tool-btn" title="Polygon (P)">⬡</button>
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
<!-- SLOT TOOLS -->
|
|
88
|
+
<button data-tool="slot" class="sketch-tool-btn" title="Slot (Center-Point)">⊟</button>
|
|
89
|
+
<button data-tool="slot_3point" class="sketch-tool-btn" title="Slot (3-Point)">⊟*</button>
|
|
90
|
+
<button data-tool="slot_ctc" class="sketch-tool-btn" title="Slot (Center-to-Center)">⊟**</button>
|
|
91
|
+
|
|
92
|
+
<!-- CONIC & TEXT -->
|
|
93
|
+
<button data-tool="conic" class="sketch-tool-btn" title="Conic (Parabola/Hyperbola)">∿</button>
|
|
85
94
|
<button data-tool="text" class="sketch-tool-btn" title="Text (T)">T</button>
|
|
95
|
+
<button data-tool="text_path" class="sketch-tool-btn" title="Text Along Path">T↷</button>
|
|
96
|
+
|
|
97
|
+
<!-- REFERENCE -->
|
|
98
|
+
<button data-tool="point" class="sketch-tool-btn" title="Point (standalone)">•</button>
|
|
99
|
+
<button data-tool="midpoint" class="sketch-tool-btn" title="Midpoint">◈</button>
|
|
100
|
+
|
|
101
|
+
<!-- EDITING TOOLS -->
|
|
86
102
|
<button data-tool="trim" class="sketch-tool-btn" title="Trim">✂</button>
|
|
103
|
+
<button data-tool="power_trim" class="sketch-tool-btn" title="Power Trim (drag)">✂✂</button>
|
|
104
|
+
<button data-tool="break" class="sketch-tool-btn" title="Break at Point">⊥</button>
|
|
87
105
|
<button data-tool="extend" class="sketch-tool-btn" title="Extend">→</button>
|
|
88
106
|
<button data-tool="offset" class="sketch-tool-btn" title="Offset">⟿</button>
|
|
89
107
|
<button data-tool="mirror" class="sketch-tool-btn" title="Mirror">⇄</button>
|
|
90
108
|
<button data-tool="fillet" class="sketch-tool-btn" title="Fillet">⌢</button>
|
|
91
109
|
<button data-tool="chamfer" class="sketch-tool-btn" title="Chamfer">/</button>
|
|
110
|
+
|
|
111
|
+
<!-- PATTERN TOOLS -->
|
|
112
|
+
<button data-tool="rect_pattern" class="sketch-tool-btn" title="Rectangular Pattern">▦</button>
|
|
113
|
+
<button data-tool="circ_pattern" class="sketch-tool-btn" title="Circular Pattern">⊙</button>
|
|
114
|
+
<button data-tool="path_pattern" class="sketch-tool-btn" title="Pattern Along Path">▦→</button>
|
|
115
|
+
|
|
116
|
+
<!-- CONSTRUCTION & GEOMETRY -->
|
|
92
117
|
<button data-tool="construction" class="sketch-tool-btn" title="Toggle Construction (G)">⋯</button>
|
|
118
|
+
<button data-tool="project" class="sketch-tool-btn" title="Project Edge">⌜</button>
|
|
119
|
+
<button data-tool="include" class="sketch-tool-btn" title="Include From Sketch">⊂</button>
|
|
120
|
+
<button data-tool="intersection" class="sketch-tool-btn" title="Intersection Curve">✕</button>
|
|
121
|
+
|
|
122
|
+
<!-- DIMENSIONS -->
|
|
93
123
|
<button id="sketch-dimension-btn" class="sketch-tool-btn" title="Add Dimension (D)">📏</button>
|
|
124
|
+
<button data-tool="ordinate" class="sketch-tool-btn" title="Ordinate Dimension">📍</button>
|
|
125
|
+
<button data-tool="reference" class="sketch-tool-btn" title="Reference Dimension">⌀</button>
|
|
126
|
+
<button data-tool="auto_dim" class="sketch-tool-btn" title="Auto Dimension">✓📏</button>
|
|
127
|
+
|
|
128
|
+
<!-- FINISH -->
|
|
94
129
|
<button id="sketch-finish-btn" style="margin-left: 16px; background: #00aa00; color: white;" title="Finish Sketch (Esc)">✓ Finish</button>
|
|
95
130
|
</div>
|
|
96
131
|
<div id="sketch-status-bar" style="display: none; color: #aaa; font-size: 12px; padding: 4px 8px; border-top: 1px solid #444; background: #1a1a1a;">
|
|
97
|
-
Tool: <span id="sketch-tool-name">Line</span> | Grid: <span id="sketch-grid-size">5mm</span> | Entities: <span id="sketch-entity-count">0</span>
|
|
132
|
+
Tool: <span id="sketch-tool-name">Line</span> | Grid: <span id="sketch-grid-size">5mm</span> | Entities: <span id="sketch-entity-count">0</span> | DOF: <span id="sketch-dof">0</span>
|
|
98
133
|
</div>
|
|
99
134
|
<div id="sketch-dimension-input" style="display: none; position: fixed; background: #2a2a2a; border: 1px solid #444; border-radius: 4px; padding: 12px; z-index: 10000;">
|
|
100
135
|
<label style="display: block; font-size: 12px; color: #aaa; margin-bottom: 4px;">Dimension Value (mm)</label>
|
|
@@ -519,6 +554,342 @@ const SketchModule = {
|
|
|
519
554
|
});
|
|
520
555
|
},
|
|
521
556
|
|
|
557
|
+
drawSlotCenterPoint(center, width, height, rotation = 0) {
|
|
558
|
+
/**
|
|
559
|
+
* CENTER-POINT SLOT: Slot defined by center, width, and height
|
|
560
|
+
*
|
|
561
|
+
* A slot is a rounded rectangle with semicircular ends.
|
|
562
|
+
* Specified by center point, width, and height of the slot.
|
|
563
|
+
*/
|
|
564
|
+
return this.addEntity('slot_center', {
|
|
565
|
+
points: [center],
|
|
566
|
+
data: { width, height, rotation },
|
|
567
|
+
constraints: [{ type: 'fixed', point: center }]
|
|
568
|
+
});
|
|
569
|
+
},
|
|
570
|
+
|
|
571
|
+
drawSlot3Point(p1, p2, radius) {
|
|
572
|
+
/**
|
|
573
|
+
* 3-POINT SLOT: Define by two endpoints and radius (arc radius)
|
|
574
|
+
*
|
|
575
|
+
* Creates a slot with semicircular ends of given radius,
|
|
576
|
+
* connecting two specified endpoints.
|
|
577
|
+
*/
|
|
578
|
+
const center = new THREE.Vector2(
|
|
579
|
+
(p1.x + p2.x) / 2,
|
|
580
|
+
(p1.y + p2.y) / 2
|
|
581
|
+
);
|
|
582
|
+
const length = p1.distanceTo(p2);
|
|
583
|
+
return this.addEntity('slot_3point', {
|
|
584
|
+
points: [p1, p2],
|
|
585
|
+
data: { radius, length, center }
|
|
586
|
+
});
|
|
587
|
+
},
|
|
588
|
+
|
|
589
|
+
drawSlotCenterToCenter(center1, center2, radius) {
|
|
590
|
+
/**
|
|
591
|
+
* CENTER-TO-CENTER SLOT: Define by arc centers and radius
|
|
592
|
+
*
|
|
593
|
+
* Slot with circular centers at two specified points,
|
|
594
|
+
* connected by tangent lines.
|
|
595
|
+
*/
|
|
596
|
+
return this.addEntity('slot_ctc', {
|
|
597
|
+
points: [center1, center2],
|
|
598
|
+
data: { radius }
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
drawConic(type, params) {
|
|
603
|
+
/**
|
|
604
|
+
* CONIC SECTION DRAWING: Parabola, Hyperbola, or Ellipse
|
|
605
|
+
*
|
|
606
|
+
* @param {string} type - 'parabola' or 'hyperbola'
|
|
607
|
+
* @param {object} params - { focus, directrix, ... } or { foci, a, ... }
|
|
608
|
+
*
|
|
609
|
+
* Parabola: locus of points equidistant from focus and directrix
|
|
610
|
+
* Hyperbola: locus of points where |PF1| - |PF2| = 2a
|
|
611
|
+
*/
|
|
612
|
+
if (type === 'parabola') {
|
|
613
|
+
const { focus, directrixLine } = params;
|
|
614
|
+
return this.addEntity('parabola', {
|
|
615
|
+
points: [focus],
|
|
616
|
+
data: { directrixLine, type: 'parabola' }
|
|
617
|
+
});
|
|
618
|
+
} else if (type === 'hyperbola') {
|
|
619
|
+
const { focus1, focus2, a } = params;
|
|
620
|
+
return this.addEntity('hyperbola', {
|
|
621
|
+
points: [focus1, focus2],
|
|
622
|
+
data: { a, type: 'hyperbola' }
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
drawRectangularPattern(entityIds, columns, rows, spacingX, spacingY) {
|
|
629
|
+
/**
|
|
630
|
+
* RECTANGULAR PATTERN: Array entity copies in grid
|
|
631
|
+
*
|
|
632
|
+
* Creates copies of selected entities in a column×row grid
|
|
633
|
+
* with specified spacing between copies.
|
|
634
|
+
*/
|
|
635
|
+
if (entityIds.length === 0) return [];
|
|
636
|
+
|
|
637
|
+
const patterns = [];
|
|
638
|
+
const baseEntities = this.state.entities.filter(e => entityIds.includes(e.id));
|
|
639
|
+
|
|
640
|
+
for (let row = 0; row < rows; row++) {
|
|
641
|
+
for (let col = 0; col < columns; col++) {
|
|
642
|
+
if (row === 0 && col === 0) continue; // Skip original
|
|
643
|
+
|
|
644
|
+
const offset = new THREE.Vector2(col * spacingX, row * spacingY);
|
|
645
|
+
|
|
646
|
+
baseEntities.forEach(entity => {
|
|
647
|
+
const copiedPoints = entity.points.map(p => p.clone().add(offset));
|
|
648
|
+
const patternEntity = this.addEntity(entity.type, {
|
|
649
|
+
points: copiedPoints,
|
|
650
|
+
data: { ...entity.data, isPattern: true, baseId: entity.id },
|
|
651
|
+
isConstruction: entity.isConstruction
|
|
652
|
+
});
|
|
653
|
+
patterns.push(patternEntity);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return patterns;
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
drawCircularPattern(entityIds, center, count, angleSpan = Math.PI * 2) {
|
|
662
|
+
/**
|
|
663
|
+
* CIRCULAR PATTERN: Array entity copies around center
|
|
664
|
+
*
|
|
665
|
+
* Creates copies of selected entities arranged radially
|
|
666
|
+
* around a center point.
|
|
667
|
+
*/
|
|
668
|
+
if (entityIds.length === 0) return [];
|
|
669
|
+
|
|
670
|
+
const patterns = [];
|
|
671
|
+
const baseEntities = this.state.entities.filter(e => entityIds.includes(e.id));
|
|
672
|
+
const angleStep = angleSpan / count;
|
|
673
|
+
|
|
674
|
+
for (let i = 1; i < count; i++) {
|
|
675
|
+
const angle = i * angleStep;
|
|
676
|
+
const cos = Math.cos(angle);
|
|
677
|
+
const sin = Math.sin(angle);
|
|
678
|
+
|
|
679
|
+
baseEntities.forEach(entity => {
|
|
680
|
+
const copiedPoints = entity.points.map(p => {
|
|
681
|
+
const relative = new THREE.Vector2(p.x - center.x, p.y - center.y);
|
|
682
|
+
const rotated = new THREE.Vector2(
|
|
683
|
+
relative.x * cos - relative.y * sin,
|
|
684
|
+
relative.x * sin + relative.y * cos
|
|
685
|
+
);
|
|
686
|
+
return rotated.add(center);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
const patternEntity = this.addEntity(entity.type, {
|
|
690
|
+
points: copiedPoints,
|
|
691
|
+
data: { ...entity.data, isPattern: true, baseId: entity.id },
|
|
692
|
+
isConstruction: entity.isConstruction
|
|
693
|
+
});
|
|
694
|
+
patterns.push(patternEntity);
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return patterns;
|
|
699
|
+
},
|
|
700
|
+
|
|
701
|
+
drawPatternAlongPath(entityIds, pathEntityId, count, spacing = null) {
|
|
702
|
+
/**
|
|
703
|
+
* PATTERN ALONG PATH: Array entity copies along a curve
|
|
704
|
+
*
|
|
705
|
+
* Creates copies of selected entities distributed along
|
|
706
|
+
* a line, arc, or spline path.
|
|
707
|
+
*/
|
|
708
|
+
const pathEntity = this.state.entities.find(e => e.id === pathEntityId);
|
|
709
|
+
if (!pathEntity || !['line', 'arc', 'spline'].includes(pathEntity.type)) {
|
|
710
|
+
console.warn('Path must be line, arc, or spline');
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const baseEntities = this.state.entities.filter(e => entityIds.includes(e.id));
|
|
715
|
+
const patterns = [];
|
|
716
|
+
|
|
717
|
+
for (let i = 1; i < count; i++) {
|
|
718
|
+
const t = i / count; // Parameter along path [0, 1]
|
|
719
|
+
const pathPoint = this.evaluateEntityAtParameter(pathEntity, t);
|
|
720
|
+
const pathTangent = this.evaluateEntityTangentAtParameter(pathEntity, t);
|
|
721
|
+
const angle = Math.atan2(pathTangent.y, pathTangent.x);
|
|
722
|
+
|
|
723
|
+
baseEntities.forEach(entity => {
|
|
724
|
+
const copiedPoints = entity.points.map(p => {
|
|
725
|
+
const relative = new THREE.Vector2(p.x - entity.points[0].x, p.y - entity.points[0].y);
|
|
726
|
+
const rotated = new THREE.Vector2(
|
|
727
|
+
relative.x * Math.cos(angle) - relative.y * Math.sin(angle),
|
|
728
|
+
relative.x * Math.sin(angle) + relative.y * Math.cos(angle)
|
|
729
|
+
);
|
|
730
|
+
return pathPoint.clone().add(rotated);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
const patternEntity = this.addEntity(entity.type, {
|
|
734
|
+
points: copiedPoints,
|
|
735
|
+
data: { ...entity.data, isPattern: true, baseId: entity.id },
|
|
736
|
+
isConstruction: entity.isConstruction
|
|
737
|
+
});
|
|
738
|
+
patterns.push(patternEntity);
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return patterns;
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
projectEdgeOntoSketch(edgeId) {
|
|
746
|
+
/**
|
|
747
|
+
* PROJECT 3D EDGE ONTO SKETCH PLANE
|
|
748
|
+
*
|
|
749
|
+
* Takes a 3D edge from the model and projects it orthogonally
|
|
750
|
+
* onto the current sketch plane. Useful for alignment.
|
|
751
|
+
*/
|
|
752
|
+
// In production, would ray-cast edge with sketch plane
|
|
753
|
+
// For now, return a projected line entity
|
|
754
|
+
return {
|
|
755
|
+
message: 'Project edge ' + edgeId + ' onto sketch plane',
|
|
756
|
+
isConstruction: true
|
|
757
|
+
};
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
includeGeometryFromSketch(sourceSketchId) {
|
|
761
|
+
/**
|
|
762
|
+
* INCLUDE GEOMETRY FROM ANOTHER SKETCH
|
|
763
|
+
*
|
|
764
|
+
* References entities from another sketch in the current sketch.
|
|
765
|
+
* Changes to source sketch automatically update references.
|
|
766
|
+
*/
|
|
767
|
+
return {
|
|
768
|
+
message: 'Include geometry from sketch ' + sourceSketchId,
|
|
769
|
+
linkedSketchId: sourceSketchId
|
|
770
|
+
};
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
drawIntersectionCurve(body1Id, body2Id, surface1Id, surface2Id) {
|
|
774
|
+
/**
|
|
775
|
+
* INTERSECTION CURVE: Sketch curve from intersecting surfaces
|
|
776
|
+
*
|
|
777
|
+
* Computes the intersection of two 3D surfaces/bodies and
|
|
778
|
+
* projects it onto the sketch plane as a construction curve.
|
|
779
|
+
*/
|
|
780
|
+
return this.addEntity('intersection_curve', {
|
|
781
|
+
data: {
|
|
782
|
+
body1Id,
|
|
783
|
+
body2Id,
|
|
784
|
+
surface1Id,
|
|
785
|
+
surface2Id,
|
|
786
|
+
isConstruction: true
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
drawTextAlongPath(text, pathEntityId, fontSize = 10) {
|
|
792
|
+
/**
|
|
793
|
+
* TEXT ALONG PATH: Text that follows a curve
|
|
794
|
+
*
|
|
795
|
+
* Distributes text characters along a line, arc, or spline.
|
|
796
|
+
*/
|
|
797
|
+
const pathEntity = this.state.entities.find(e => e.id === pathEntityId);
|
|
798
|
+
if (!pathEntity) return null;
|
|
799
|
+
|
|
800
|
+
return this.addEntity('text_along_path', {
|
|
801
|
+
data: { text, fontSize, pathEntityId, isConstruction: false }
|
|
802
|
+
});
|
|
803
|
+
},
|
|
804
|
+
|
|
805
|
+
drawFitPointSpline(points) {
|
|
806
|
+
/**
|
|
807
|
+
* FIT POINT SPLINE (through-point B-spline)
|
|
808
|
+
*
|
|
809
|
+
* Creates a spline that passes THROUGH all specified points
|
|
810
|
+
* (unlike control-point spline which passes near control points).
|
|
811
|
+
* Uses automatic knot vector generation for smooth interpolation.
|
|
812
|
+
*/
|
|
813
|
+
if (points.length < 2) {
|
|
814
|
+
console.warn('Fit point spline requires at least 2 points');
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return this.addEntity('spline_fit', {
|
|
819
|
+
points,
|
|
820
|
+
data: {
|
|
821
|
+
degree: Math.min(3, points.length - 1),
|
|
822
|
+
isFitPoint: true,
|
|
823
|
+
knotVector: this.generateFitPointKnots(points.length)
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
},
|
|
827
|
+
|
|
828
|
+
generateFitPointKnots(n) {
|
|
829
|
+
/**
|
|
830
|
+
* Generate knot vector for fit-point (interpolating) spline
|
|
831
|
+
* Uses Centripetal Catmull-Rom parameterization
|
|
832
|
+
*/
|
|
833
|
+
const knots = [0, 0, 0, 0];
|
|
834
|
+
for (let i = 1; i <= n - 2; i++) {
|
|
835
|
+
knots.push(i);
|
|
836
|
+
}
|
|
837
|
+
knots.push(n - 1, n - 1, n - 1, n - 1);
|
|
838
|
+
return knots;
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
drawMidpoint(entityId) {
|
|
842
|
+
/**
|
|
843
|
+
* MIDPOINT: Create a point at midpoint of any edge
|
|
844
|
+
*
|
|
845
|
+
* Adds a construction point at the midpoint of a line, arc, or spline.
|
|
846
|
+
* Useful as reference for other constraints.
|
|
847
|
+
*/
|
|
848
|
+
const entity = this.state.entities.find(e => e.id === entityId);
|
|
849
|
+
if (!entity || !['line', 'arc', 'spline'].includes(entity.type)) {
|
|
850
|
+
console.warn('Midpoint tool requires line, arc, or spline');
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
let midpoint;
|
|
855
|
+
if (entity.type === 'line') {
|
|
856
|
+
const [p1, p2] = entity.points;
|
|
857
|
+
midpoint = new THREE.Vector2(
|
|
858
|
+
(p1.x + p2.x) / 2,
|
|
859
|
+
(p1.y + p2.y) / 2
|
|
860
|
+
);
|
|
861
|
+
} else if (entity.type === 'arc') {
|
|
862
|
+
const [start, end] = entity.points;
|
|
863
|
+
midpoint = new THREE.Vector2(
|
|
864
|
+
(start.x + end.x) / 2,
|
|
865
|
+
(start.y + end.y) / 2
|
|
866
|
+
);
|
|
867
|
+
} else if (entity.type === 'spline') {
|
|
868
|
+
// Evaluate spline at t=0.5
|
|
869
|
+
midpoint = this.evaluateBSpline(entity.points, 0.5, entity.data.degree);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return this.addEntity('point', {
|
|
873
|
+
points: [midpoint],
|
|
874
|
+
data: { linkedEntityId: entityId, type: 'midpoint' },
|
|
875
|
+
isConstruction: true
|
|
876
|
+
});
|
|
877
|
+
},
|
|
878
|
+
|
|
879
|
+
drawPoint(point, isConstruction = true) {
|
|
880
|
+
/**
|
|
881
|
+
* POINT TOOL: Standalone point or center mark
|
|
882
|
+
*
|
|
883
|
+
* Creates a construction point (small circle) at specified location.
|
|
884
|
+
* Useful for reference geometry and constraint anchors.
|
|
885
|
+
*/
|
|
886
|
+
return this.addEntity('point', {
|
|
887
|
+
points: [point],
|
|
888
|
+
data: { type: 'standalone' },
|
|
889
|
+
isConstruction
|
|
890
|
+
});
|
|
891
|
+
},
|
|
892
|
+
|
|
522
893
|
// ===== EDITING TOOLS =====
|
|
523
894
|
|
|
524
895
|
trim(entityId, clickPoint) {
|
|
@@ -571,6 +942,97 @@ const SketchModule = {
|
|
|
571
942
|
window.dispatchEvent(new CustomEvent('sketch:entityModified', { detail: { entity } }));
|
|
572
943
|
},
|
|
573
944
|
|
|
945
|
+
powerTrim(clickPoints) {
|
|
946
|
+
/**
|
|
947
|
+
* POWER TRIM: Drag to trim multiple entities at once
|
|
948
|
+
*
|
|
949
|
+
* Click and drag along entities to remove all segments
|
|
950
|
+
* that the drag line crosses. Works with line, arc, spline.
|
|
951
|
+
*/
|
|
952
|
+
if (clickPoints.length < 2) return [];
|
|
953
|
+
|
|
954
|
+
const trimmedEntities = [];
|
|
955
|
+
const dragLine = { p1: clickPoints[0], p2: clickPoints[clickPoints.length - 1] };
|
|
956
|
+
|
|
957
|
+
this.state.entities.forEach(entity => {
|
|
958
|
+
if (!['line', 'arc', 'spline'].includes(entity.type)) return;
|
|
959
|
+
|
|
960
|
+
const crossings = this.findEntityDragCrossings(entity, dragLine);
|
|
961
|
+
if (crossings.length >= 2) {
|
|
962
|
+
// Remove segments between pairs of crossings
|
|
963
|
+
crossings.sort((a, b) => a.t - b.t);
|
|
964
|
+
for (let i = 0; i < crossings.length - 1; i++) {
|
|
965
|
+
this.trim(entity.id, new THREE.Vector2(
|
|
966
|
+
(crossings[i].point.x + crossings[i + 1].point.x) / 2,
|
|
967
|
+
(crossings[i].point.y + crossings[i + 1].point.y) / 2
|
|
968
|
+
));
|
|
969
|
+
trimmedEntities.push(entity);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
return trimmedEntities;
|
|
975
|
+
},
|
|
976
|
+
|
|
977
|
+
trimToIntersection(entityId, otherEntityId) {
|
|
978
|
+
/**
|
|
979
|
+
* TRIM TO NEAREST INTERSECTION
|
|
980
|
+
*
|
|
981
|
+
* Automatically trims entity to its nearest intersection
|
|
982
|
+
* with another specific entity.
|
|
983
|
+
*/
|
|
984
|
+
const entity = this.state.entities.find(e => e.id === entityId);
|
|
985
|
+
const other = this.state.entities.find(e => e.id === otherEntityId);
|
|
986
|
+
if (!entity || !other) return;
|
|
987
|
+
|
|
988
|
+
const ints = this.findIntersection(entity, other);
|
|
989
|
+
if (ints.length === 0) return;
|
|
990
|
+
|
|
991
|
+
// Trim to nearest intersection
|
|
992
|
+
const nearest = ints.reduce((a, b) =>
|
|
993
|
+
a.t < b.t ? a : b
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
this.trim(entityId, nearest.point);
|
|
997
|
+
},
|
|
998
|
+
|
|
999
|
+
breakAtPoint(entityId, point) {
|
|
1000
|
+
/**
|
|
1001
|
+
* BREAK AT POINT: Split entity at specified point
|
|
1002
|
+
*
|
|
1003
|
+
* Breaks a line or arc into two segments at the given point
|
|
1004
|
+
* (useful for adding construction references).
|
|
1005
|
+
*/
|
|
1006
|
+
const entity = this.state.entities.find(e => e.id === entityId);
|
|
1007
|
+
if (!entity || !['line', 'arc', 'spline'].includes(entity.type)) return;
|
|
1008
|
+
|
|
1009
|
+
const t = this.findParameterAlongEntity(entity, point);
|
|
1010
|
+
if (t === null) return;
|
|
1011
|
+
|
|
1012
|
+
if (entity.type === 'line') {
|
|
1013
|
+
const [p1, p2] = entity.points;
|
|
1014
|
+
this.addEntity('line', { points: [p1, point] });
|
|
1015
|
+
this.addEntity('line', { points: [point, p2] });
|
|
1016
|
+
const idx = this.state.entities.indexOf(entity);
|
|
1017
|
+
if (idx > -1) this.state.entities.splice(idx, 1);
|
|
1018
|
+
} else if (entity.type === 'arc') {
|
|
1019
|
+
const [start, end, center] = entity.points;
|
|
1020
|
+
const angle1 = Math.atan2(point.y - center.y, point.x - center.x);
|
|
1021
|
+
this.addEntity('arc', {
|
|
1022
|
+
points: [start, point, center],
|
|
1023
|
+
data: { ...entity.data, endAngle: angle1 }
|
|
1024
|
+
});
|
|
1025
|
+
this.addEntity('arc', {
|
|
1026
|
+
points: [point, end, center],
|
|
1027
|
+
data: { ...entity.data, startAngle: angle1 }
|
|
1028
|
+
});
|
|
1029
|
+
const idx = this.state.entities.indexOf(entity);
|
|
1030
|
+
if (idx > -1) this.state.entities.splice(idx, 1);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
window.dispatchEvent(new CustomEvent('sketch:entityModified', { detail: { entity } }));
|
|
1034
|
+
},
|
|
1035
|
+
|
|
574
1036
|
splitLineAtTrim(entity, intStart, intEnd) {
|
|
575
1037
|
// For line: keep segments before intStart and after intEnd, discard middle
|
|
576
1038
|
const [p1, p2] = entity.points;
|
|
@@ -1480,6 +1942,173 @@ const SketchModule = {
|
|
|
1480
1942
|
this.angleInRange(angle, arc.data.startAngle, arc.data.endAngle);
|
|
1481
1943
|
},
|
|
1482
1944
|
|
|
1945
|
+
// ===== ADVANCED DIMENSIONS =====
|
|
1946
|
+
|
|
1947
|
+
addOrdinateDimension(entityId, baselineEntity, direction = 'X') {
|
|
1948
|
+
/**
|
|
1949
|
+
* ORDINATE DIMENSION: Baseline reference dimension system
|
|
1950
|
+
*
|
|
1951
|
+
* Creates a series of dimensions measured from a baseline
|
|
1952
|
+
* entity. All dimensions reference the same baseline (e.g., left edge).
|
|
1953
|
+
* More compact than individual linear dimensions.
|
|
1954
|
+
*/
|
|
1955
|
+
const entity = this.state.entities.find(e => e.id === entityId);
|
|
1956
|
+
const baseline = this.state.entities.find(e => e.id === baselineEntity.id);
|
|
1957
|
+
|
|
1958
|
+
if (!entity || !baseline) return null;
|
|
1959
|
+
|
|
1960
|
+
const dim = {
|
|
1961
|
+
id: `dim_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
1962
|
+
type: 'ordinate',
|
|
1963
|
+
entities: [entity.id, baseline.id],
|
|
1964
|
+
direction,
|
|
1965
|
+
driven: true,
|
|
1966
|
+
value: this.computeOrdinateMeasurement(entity, baseline, direction)
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
this.state.dimensions.push(dim);
|
|
1970
|
+
window.dispatchEvent(new CustomEvent('sketch:dimensionAdded', { detail: { dimension: dim } }));
|
|
1971
|
+
return dim;
|
|
1972
|
+
},
|
|
1973
|
+
|
|
1974
|
+
autoDimension() {
|
|
1975
|
+
/**
|
|
1976
|
+
* AUTO-DIMENSION: Detect and apply minimal sufficient dimension set
|
|
1977
|
+
*
|
|
1978
|
+
* Analyzes sketch geometry and constraints to determine
|
|
1979
|
+
* the minimum number of dimensions needed to fully constrain the sketch.
|
|
1980
|
+
* Uses a greedy algorithm to select important dimensions.
|
|
1981
|
+
*/
|
|
1982
|
+
const essentialDims = [];
|
|
1983
|
+
const profile = this.getProfile();
|
|
1984
|
+
|
|
1985
|
+
// Count degrees of freedom
|
|
1986
|
+
let dof = profile.entities.length * 3; // Each entity: 2 position + 1 orientation
|
|
1987
|
+
dof -= profile.entities.filter(e => e.constraints.some(c => c.type === 'fixed')).length * 3;
|
|
1988
|
+
|
|
1989
|
+
// Add dimensions for unconstrained entities
|
|
1990
|
+
profile.entities.forEach(entity => {
|
|
1991
|
+
if (dof <= 0) return;
|
|
1992
|
+
|
|
1993
|
+
const isConstrained = entity.constraints && entity.constraints.length > 0;
|
|
1994
|
+
if (!isConstrained) {
|
|
1995
|
+
let dimType = null;
|
|
1996
|
+
let value = null;
|
|
1997
|
+
|
|
1998
|
+
if (entity.type === 'line') {
|
|
1999
|
+
dimType = 'distance';
|
|
2000
|
+
value = entity.points[0].distanceTo(entity.points[1]);
|
|
2001
|
+
dof -= 1;
|
|
2002
|
+
} else if (entity.type === 'circle') {
|
|
2003
|
+
dimType = 'radius';
|
|
2004
|
+
value = entity.data.radius;
|
|
2005
|
+
dof -= 1;
|
|
2006
|
+
} else if (entity.type === 'arc') {
|
|
2007
|
+
dimType = 'radius';
|
|
2008
|
+
value = entity.data.radius;
|
|
2009
|
+
dof -= 1;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
if (dimType) {
|
|
2013
|
+
essentialDims.push({
|
|
2014
|
+
id: `auto_dim_${Date.now()}_${essentialDims.length}`,
|
|
2015
|
+
type: dimType,
|
|
2016
|
+
entities: [entity.id],
|
|
2017
|
+
value,
|
|
2018
|
+
driven: true
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
this.state.dimensions.push(...essentialDims);
|
|
2025
|
+
return essentialDims;
|
|
2026
|
+
},
|
|
2027
|
+
|
|
2028
|
+
addReferenceDimension(entityId, type = 'distance', value = null) {
|
|
2029
|
+
/**
|
|
2030
|
+
* REFERENCE DIMENSION: Non-driving dimension for display
|
|
2031
|
+
*
|
|
2032
|
+
* Creates a dimension that displays the measurement but
|
|
2033
|
+
* does not constrain the geometry. Useful for documenting
|
|
2034
|
+
* features and creating assembly notes.
|
|
2035
|
+
*/
|
|
2036
|
+
const entity = this.state.entities.find(e => e.id === entityId);
|
|
2037
|
+
if (!entity) return null;
|
|
2038
|
+
|
|
2039
|
+
const computedValue = value !== null ? value : this.computeEntityMeasurement(entity, type);
|
|
2040
|
+
|
|
2041
|
+
const dim = {
|
|
2042
|
+
id: `ref_dim_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
2043
|
+
type,
|
|
2044
|
+
entities: [entityId],
|
|
2045
|
+
value: computedValue,
|
|
2046
|
+
driven: false, // Key: reference dimensions are NOT driven
|
|
2047
|
+
isReference: true
|
|
2048
|
+
};
|
|
2049
|
+
|
|
2050
|
+
this.state.dimensions.push(dim);
|
|
2051
|
+
window.dispatchEvent(new CustomEvent('sketch:dimensionAdded', { detail: { dimension: dim } }));
|
|
2052
|
+
return dim;
|
|
2053
|
+
},
|
|
2054
|
+
|
|
2055
|
+
computeEntityMeasurement(entity, measurementType) {
|
|
2056
|
+
/**
|
|
2057
|
+
* Compute measurement value for an entity
|
|
2058
|
+
* @param {object} entity - sketch entity
|
|
2059
|
+
* @param {string} measurementType - 'distance', 'radius', 'diameter', 'angle', etc.
|
|
2060
|
+
* @returns {number} measurement value in current units
|
|
2061
|
+
*/
|
|
2062
|
+
switch (measurementType) {
|
|
2063
|
+
case 'distance':
|
|
2064
|
+
if (entity.type === 'line') {
|
|
2065
|
+
return entity.points[0].distanceTo(entity.points[1]);
|
|
2066
|
+
}
|
|
2067
|
+
return 0;
|
|
2068
|
+
|
|
2069
|
+
case 'radius':
|
|
2070
|
+
if (entity.type === 'circle' || entity.type === 'arc') {
|
|
2071
|
+
return entity.data.radius || 0;
|
|
2072
|
+
}
|
|
2073
|
+
return 0;
|
|
2074
|
+
|
|
2075
|
+
case 'diameter':
|
|
2076
|
+
if (entity.type === 'circle' || entity.type === 'arc') {
|
|
2077
|
+
return (entity.data.radius || 0) * 2;
|
|
2078
|
+
}
|
|
2079
|
+
return 0;
|
|
2080
|
+
|
|
2081
|
+
case 'angle':
|
|
2082
|
+
if (entity.type === 'arc') {
|
|
2083
|
+
const { startAngle, endAngle } = entity.data;
|
|
2084
|
+
return (endAngle - startAngle) * (180 / Math.PI);
|
|
2085
|
+
}
|
|
2086
|
+
return 0;
|
|
2087
|
+
|
|
2088
|
+
default:
|
|
2089
|
+
return 0;
|
|
2090
|
+
}
|
|
2091
|
+
},
|
|
2092
|
+
|
|
2093
|
+
computeOrdinateMeasurement(entity, baseline, direction) {
|
|
2094
|
+
/**
|
|
2095
|
+
* Compute ordinate measurement (distance from baseline)
|
|
2096
|
+
* @param {object} entity - sketch entity to measure
|
|
2097
|
+
* @param {object} baseline - reference baseline entity
|
|
2098
|
+
* @param {string} direction - 'X' or 'Y'
|
|
2099
|
+
* @returns {number} measurement value
|
|
2100
|
+
*/
|
|
2101
|
+
const baselinePos = baseline.points[0];
|
|
2102
|
+
const entityPos = entity.points[0];
|
|
2103
|
+
|
|
2104
|
+
if (direction === 'X') {
|
|
2105
|
+
return Math.abs(entityPos.x - baselinePos.x);
|
|
2106
|
+
} else if (direction === 'Y') {
|
|
2107
|
+
return Math.abs(entityPos.y - baselinePos.y);
|
|
2108
|
+
}
|
|
2109
|
+
return 0;
|
|
2110
|
+
},
|
|
2111
|
+
|
|
1483
2112
|
getProfile() {
|
|
1484
2113
|
// Extract closed wire from entities for extrude/revolve
|
|
1485
2114
|
return {
|
|
@@ -1489,6 +2118,121 @@ const SketchModule = {
|
|
|
1489
2118
|
};
|
|
1490
2119
|
},
|
|
1491
2120
|
|
|
2121
|
+
// ===== HELPER METHODS FOR NEW TOOLS =====
|
|
2122
|
+
|
|
2123
|
+
evaluateEntityAtParameter(entity, t) {
|
|
2124
|
+
/**
|
|
2125
|
+
* Evaluate entity position at parameter t ∈ [0, 1]
|
|
2126
|
+
*/
|
|
2127
|
+
if (entity.type === 'line') {
|
|
2128
|
+
const [p1, p2] = entity.points;
|
|
2129
|
+
return new THREE.Vector2(
|
|
2130
|
+
p1.x + t * (p2.x - p1.x),
|
|
2131
|
+
p1.y + t * (p2.y - p1.y)
|
|
2132
|
+
);
|
|
2133
|
+
} else if (entity.type === 'arc') {
|
|
2134
|
+
const [start, end] = entity.points;
|
|
2135
|
+
const [, , center] = entity.points;
|
|
2136
|
+
const angle = entity.data.startAngle + t * (entity.data.endAngle - entity.data.startAngle);
|
|
2137
|
+
return new THREE.Vector2(
|
|
2138
|
+
center.x + entity.data.radius * Math.cos(angle),
|
|
2139
|
+
center.y + entity.data.radius * Math.sin(angle)
|
|
2140
|
+
);
|
|
2141
|
+
} else if (entity.type === 'spline') {
|
|
2142
|
+
return this.evaluateBSpline(entity.points, t, entity.data.degree);
|
|
2143
|
+
}
|
|
2144
|
+
return entity.points[0];
|
|
2145
|
+
},
|
|
2146
|
+
|
|
2147
|
+
evaluateEntityTangentAtParameter(entity, t) {
|
|
2148
|
+
/**
|
|
2149
|
+
* Evaluate entity tangent vector at parameter t ∈ [0, 1]
|
|
2150
|
+
*/
|
|
2151
|
+
const delta = 0.001;
|
|
2152
|
+
const p1 = this.evaluateEntityAtParameter(entity, Math.max(0, t - delta));
|
|
2153
|
+
const p2 = this.evaluateEntityAtParameter(entity, Math.min(1, t + delta));
|
|
2154
|
+
return new THREE.Vector2(p2.x - p1.x, p2.y - p1.y).normalize();
|
|
2155
|
+
},
|
|
2156
|
+
|
|
2157
|
+
findParameterAlongEntity(entity, point) {
|
|
2158
|
+
/**
|
|
2159
|
+
* Find parameter t along entity closest to given point
|
|
2160
|
+
* @returns {number|null} parameter t ∈ [0, 1], or null if not found
|
|
2161
|
+
*/
|
|
2162
|
+
if (entity.type === 'line') {
|
|
2163
|
+
const [p1, p2] = entity.points;
|
|
2164
|
+
const dx = p2.x - p1.x;
|
|
2165
|
+
const dy = p2.y - p1.y;
|
|
2166
|
+
const len2 = dx * dx + dy * dy;
|
|
2167
|
+
if (len2 === 0) return 0;
|
|
2168
|
+
|
|
2169
|
+
const t = ((point.x - p1.x) * dx + (point.y - p1.y) * dy) / len2;
|
|
2170
|
+
return Math.max(0, Math.min(1, t));
|
|
2171
|
+
}
|
|
2172
|
+
return null;
|
|
2173
|
+
},
|
|
2174
|
+
|
|
2175
|
+
findEntityDragCrossings(entity, dragLine) {
|
|
2176
|
+
/**
|
|
2177
|
+
* Find all points where entity crosses a drag line
|
|
2178
|
+
*/
|
|
2179
|
+
const crossings = [];
|
|
2180
|
+
|
|
2181
|
+
if (entity.type === 'line') {
|
|
2182
|
+
const int = this.lineLineIntersection(entity.points[0], entity.points[1], dragLine.p1, dragLine.p2);
|
|
2183
|
+
if (int) crossings.push(int);
|
|
2184
|
+
} else if (entity.type === 'arc') {
|
|
2185
|
+
const ints = this.lineArcIntersection(dragLine.p1, dragLine.p2, entity);
|
|
2186
|
+
crossings.push(...ints);
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
return crossings;
|
|
2190
|
+
},
|
|
2191
|
+
|
|
2192
|
+
findAllIntersectionsOnEntity(entity) {
|
|
2193
|
+
/**
|
|
2194
|
+
* Find all intersections of an entity with other entities
|
|
2195
|
+
*/
|
|
2196
|
+
const intersections = [];
|
|
2197
|
+
|
|
2198
|
+
this.state.entities.forEach(other => {
|
|
2199
|
+
if (other.id === entity.id) return;
|
|
2200
|
+
const ints = this.findIntersection(entity, other);
|
|
2201
|
+
intersections.push(...ints);
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
return intersections;
|
|
2205
|
+
},
|
|
2206
|
+
|
|
2207
|
+
findIntersection(entity1, entity2) {
|
|
2208
|
+
/**
|
|
2209
|
+
* Find all intersections between two entities
|
|
2210
|
+
*/
|
|
2211
|
+
const ints = [];
|
|
2212
|
+
|
|
2213
|
+
if (entity1.type === 'line' && entity2.type === 'line') {
|
|
2214
|
+
const int = this.lineLineIntersection(
|
|
2215
|
+
entity1.points[0], entity1.points[1],
|
|
2216
|
+
entity2.points[0], entity2.points[1]
|
|
2217
|
+
);
|
|
2218
|
+
if (int) ints.push(int);
|
|
2219
|
+
} else if (entity1.type === 'line' && entity2.type === 'circle') {
|
|
2220
|
+
const circleInts = this.lineCircleIntersection(
|
|
2221
|
+
entity1.points[0], entity1.points[1],
|
|
2222
|
+
entity2.points[0], entity2.data.radius
|
|
2223
|
+
);
|
|
2224
|
+
ints.push(...circleInts);
|
|
2225
|
+
} else if (entity1.type === 'circle' && entity2.type === 'circle') {
|
|
2226
|
+
const circleInts = this.circleCircleIntersection(
|
|
2227
|
+
entity1.points[0], entity1.data.radius,
|
|
2228
|
+
entity2.points[0], entity2.data.radius
|
|
2229
|
+
);
|
|
2230
|
+
ints.push(...circleInts);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
return ints;
|
|
2234
|
+
},
|
|
2235
|
+
|
|
1492
2236
|
setupEventHandlers() {
|
|
1493
2237
|
// Tool button clicks
|
|
1494
2238
|
document.addEventListener('click', (e) => {
|
|
@@ -1659,4 +2403,256 @@ const SketchModule = {
|
|
|
1659
2403
|
}
|
|
1660
2404
|
};
|
|
1661
2405
|
|
|
2406
|
+
/**
|
|
2407
|
+
* HELP ENTRIES: Documentation for all sketch tools and features
|
|
2408
|
+
* Exported for Help System integration
|
|
2409
|
+
*/
|
|
2410
|
+
SketchModule.HELP_ENTRIES = [
|
|
2411
|
+
// Basic Drawing Tools
|
|
2412
|
+
{
|
|
2413
|
+
id: 'sketch.line',
|
|
2414
|
+
title: 'Line Tool',
|
|
2415
|
+
description: 'Draw a straight line between two points. Click to set start, click again to set end.',
|
|
2416
|
+
category: 'Drawing',
|
|
2417
|
+
hotkey: 'L'
|
|
2418
|
+
},
|
|
2419
|
+
{
|
|
2420
|
+
id: 'sketch.rectangle',
|
|
2421
|
+
title: 'Rectangle Tool',
|
|
2422
|
+
description: 'Draw axis-aligned rectangle by two corner points.',
|
|
2423
|
+
category: 'Drawing',
|
|
2424
|
+
hotkey: 'R'
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
id: 'sketch.circle',
|
|
2428
|
+
title: 'Circle Tool',
|
|
2429
|
+
description: 'Draw circle by center point and radius point.',
|
|
2430
|
+
category: 'Drawing',
|
|
2431
|
+
hotkey: 'C'
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
id: 'sketch.arc',
|
|
2435
|
+
title: 'Arc Tool',
|
|
2436
|
+
description: 'Draw arc by start, end, and center points.',
|
|
2437
|
+
category: 'Drawing',
|
|
2438
|
+
hotkey: 'A'
|
|
2439
|
+
},
|
|
2440
|
+
{
|
|
2441
|
+
id: 'sketch.ellipse',
|
|
2442
|
+
title: 'Ellipse Tool',
|
|
2443
|
+
description: 'Draw ellipse by center and two axis endpoints.',
|
|
2444
|
+
category: 'Drawing',
|
|
2445
|
+
hotkey: 'E'
|
|
2446
|
+
},
|
|
2447
|
+
{
|
|
2448
|
+
id: 'sketch.spline',
|
|
2449
|
+
title: 'Control Point Spline',
|
|
2450
|
+
description: 'Draw cubic B-spline by control points. Double-click or Enter to finish.',
|
|
2451
|
+
category: 'Drawing',
|
|
2452
|
+
hotkey: 'S'
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
id: 'sketch.spline_fit',
|
|
2456
|
+
title: 'Fit Point Spline',
|
|
2457
|
+
description: 'Draw interpolating spline through specified points (unlike control-point splines).',
|
|
2458
|
+
category: 'Drawing'
|
|
2459
|
+
},
|
|
2460
|
+
{
|
|
2461
|
+
id: 'sketch.polygon',
|
|
2462
|
+
title: 'Polygon Tool',
|
|
2463
|
+
description: 'Draw regular polygon by center and corner point.',
|
|
2464
|
+
category: 'Drawing',
|
|
2465
|
+
hotkey: 'P'
|
|
2466
|
+
},
|
|
2467
|
+
|
|
2468
|
+
// Slot Tools
|
|
2469
|
+
{
|
|
2470
|
+
id: 'sketch.slot',
|
|
2471
|
+
title: 'Slot (Center-Point)',
|
|
2472
|
+
description: 'Draw slot by center point, width, and height.',
|
|
2473
|
+
category: 'Drawing'
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
id: 'sketch.slot_3point',
|
|
2477
|
+
title: 'Slot (3-Point)',
|
|
2478
|
+
description: 'Draw slot by two endpoints and arc radius.',
|
|
2479
|
+
category: 'Drawing'
|
|
2480
|
+
},
|
|
2481
|
+
{
|
|
2482
|
+
id: 'sketch.slot_ctc',
|
|
2483
|
+
title: 'Slot (Center-to-Center)',
|
|
2484
|
+
description: 'Draw slot by arc centers and radius.',
|
|
2485
|
+
category: 'Drawing'
|
|
2486
|
+
},
|
|
2487
|
+
|
|
2488
|
+
// Conic Sections
|
|
2489
|
+
{
|
|
2490
|
+
id: 'sketch.conic',
|
|
2491
|
+
title: 'Conic Sections',
|
|
2492
|
+
description: 'Draw parabola or hyperbola using focus/directrix (parabola) or foci (hyperbola).',
|
|
2493
|
+
category: 'Drawing'
|
|
2494
|
+
},
|
|
2495
|
+
|
|
2496
|
+
// Text Tools
|
|
2497
|
+
{
|
|
2498
|
+
id: 'sketch.text',
|
|
2499
|
+
title: 'Text Tool',
|
|
2500
|
+
description: 'Place text at a specific point in the sketch.',
|
|
2501
|
+
category: 'Drawing',
|
|
2502
|
+
hotkey: 'T'
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
id: 'sketch.text_path',
|
|
2506
|
+
title: 'Text Along Path',
|
|
2507
|
+
description: 'Place text that follows a line, arc, or spline curve.',
|
|
2508
|
+
category: 'Drawing'
|
|
2509
|
+
},
|
|
2510
|
+
|
|
2511
|
+
// Reference Geometry
|
|
2512
|
+
{
|
|
2513
|
+
id: 'sketch.point',
|
|
2514
|
+
title: 'Point Tool',
|
|
2515
|
+
description: 'Create a standalone construction point for reference.',
|
|
2516
|
+
category: 'Reference'
|
|
2517
|
+
},
|
|
2518
|
+
{
|
|
2519
|
+
id: 'sketch.midpoint',
|
|
2520
|
+
title: 'Midpoint',
|
|
2521
|
+
description: 'Create a point at the midpoint of a line, arc, or spline.',
|
|
2522
|
+
category: 'Reference'
|
|
2523
|
+
},
|
|
2524
|
+
|
|
2525
|
+
// Editing Tools
|
|
2526
|
+
{
|
|
2527
|
+
id: 'sketch.trim',
|
|
2528
|
+
title: 'Trim Tool',
|
|
2529
|
+
description: 'Remove segments between intersections. Click on segment to trim.',
|
|
2530
|
+
category: 'Editing'
|
|
2531
|
+
},
|
|
2532
|
+
{
|
|
2533
|
+
id: 'sketch.power_trim',
|
|
2534
|
+
title: 'Power Trim',
|
|
2535
|
+
description: 'Click and drag to trim multiple entities at once.',
|
|
2536
|
+
category: 'Editing'
|
|
2537
|
+
},
|
|
2538
|
+
{
|
|
2539
|
+
id: 'sketch.break',
|
|
2540
|
+
title: 'Break at Point',
|
|
2541
|
+
description: 'Split a line or arc into two segments at a specified point.',
|
|
2542
|
+
category: 'Editing'
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
id: 'sketch.extend',
|
|
2546
|
+
title: 'Extend Tool',
|
|
2547
|
+
description: 'Extend a line toward other geometry.',
|
|
2548
|
+
category: 'Editing'
|
|
2549
|
+
},
|
|
2550
|
+
{
|
|
2551
|
+
id: 'sketch.offset',
|
|
2552
|
+
title: 'Offset Tool',
|
|
2553
|
+
description: 'Create offset copies of lines and curves.',
|
|
2554
|
+
category: 'Editing'
|
|
2555
|
+
},
|
|
2556
|
+
{
|
|
2557
|
+
id: 'sketch.mirror',
|
|
2558
|
+
title: 'Mirror Tool',
|
|
2559
|
+
description: 'Mirror selected entities across a line.',
|
|
2560
|
+
category: 'Editing'
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
id: 'sketch.fillet',
|
|
2564
|
+
title: 'Fillet Tool',
|
|
2565
|
+
description: 'Round corners between intersecting lines.',
|
|
2566
|
+
category: 'Editing'
|
|
2567
|
+
},
|
|
2568
|
+
{
|
|
2569
|
+
id: 'sketch.chamfer',
|
|
2570
|
+
title: 'Chamfer Tool',
|
|
2571
|
+
description: 'Create beveled edges between intersecting lines.',
|
|
2572
|
+
category: 'Editing'
|
|
2573
|
+
},
|
|
2574
|
+
|
|
2575
|
+
// Pattern Tools
|
|
2576
|
+
{
|
|
2577
|
+
id: 'sketch.rect_pattern',
|
|
2578
|
+
title: 'Rectangular Pattern',
|
|
2579
|
+
description: 'Array selected entities in a grid with specified spacing.',
|
|
2580
|
+
category: 'Pattern'
|
|
2581
|
+
},
|
|
2582
|
+
{
|
|
2583
|
+
id: 'sketch.circ_pattern',
|
|
2584
|
+
title: 'Circular Pattern',
|
|
2585
|
+
description: 'Array selected entities radially around a center point.',
|
|
2586
|
+
category: 'Pattern'
|
|
2587
|
+
},
|
|
2588
|
+
{
|
|
2589
|
+
id: 'sketch.path_pattern',
|
|
2590
|
+
title: 'Pattern Along Path',
|
|
2591
|
+
description: 'Array selected entities along a line, arc, or spline.',
|
|
2592
|
+
category: 'Pattern'
|
|
2593
|
+
},
|
|
2594
|
+
|
|
2595
|
+
// Geometry Operations
|
|
2596
|
+
{
|
|
2597
|
+
id: 'sketch.project',
|
|
2598
|
+
title: 'Project Edge',
|
|
2599
|
+
description: 'Project a 3D edge orthogonally onto the sketch plane.',
|
|
2600
|
+
category: 'Geometry'
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
id: 'sketch.include',
|
|
2604
|
+
title: 'Include Geometry',
|
|
2605
|
+
description: 'Reference geometry from another sketch (linked, updates automatically).',
|
|
2606
|
+
category: 'Geometry'
|
|
2607
|
+
},
|
|
2608
|
+
{
|
|
2609
|
+
id: 'sketch.intersection',
|
|
2610
|
+
title: 'Intersection Curve',
|
|
2611
|
+
description: 'Create sketch curve from intersection of two 3D surfaces.',
|
|
2612
|
+
category: 'Geometry'
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
id: 'sketch.construction',
|
|
2616
|
+
title: 'Construction Geometry',
|
|
2617
|
+
description: 'Toggle selected entities as construction (reference-only). (G)',
|
|
2618
|
+
category: 'Geometry',
|
|
2619
|
+
hotkey: 'G'
|
|
2620
|
+
},
|
|
2621
|
+
|
|
2622
|
+
// Dimension Tools
|
|
2623
|
+
{
|
|
2624
|
+
id: 'sketch.dimension',
|
|
2625
|
+
title: 'Add Dimension',
|
|
2626
|
+
description: 'Add linear, radial, angular, or diameter dimension to constrain geometry.',
|
|
2627
|
+
category: 'Dimensions',
|
|
2628
|
+
hotkey: 'D'
|
|
2629
|
+
},
|
|
2630
|
+
{
|
|
2631
|
+
id: 'sketch.ordinate',
|
|
2632
|
+
title: 'Ordinate Dimension',
|
|
2633
|
+
description: 'Create baseline-based ordinate dimensions for aligned measurements.',
|
|
2634
|
+
category: 'Dimensions'
|
|
2635
|
+
},
|
|
2636
|
+
{
|
|
2637
|
+
id: 'sketch.reference',
|
|
2638
|
+
title: 'Reference Dimension',
|
|
2639
|
+
description: 'Add non-driving dimension for documentation (does not constrain).',
|
|
2640
|
+
category: 'Dimensions'
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
id: 'sketch.auto_dim',
|
|
2644
|
+
title: 'Auto Dimension',
|
|
2645
|
+
description: 'Automatically detect and apply minimal sufficient dimension set.',
|
|
2646
|
+
category: 'Dimensions'
|
|
2647
|
+
},
|
|
2648
|
+
|
|
2649
|
+
// Constraint System
|
|
2650
|
+
{
|
|
2651
|
+
id: 'sketch.constraints',
|
|
2652
|
+
title: 'Constraints Overview',
|
|
2653
|
+
description: 'Sketch constraints: coincident, horizontal, vertical, parallel, perpendicular, tangent, equal, fix, concentric, symmetric, collinear, midpoint, coradial.',
|
|
2654
|
+
category: 'Constraints'
|
|
2655
|
+
}
|
|
2656
|
+
];
|
|
2657
|
+
|
|
1662
2658
|
export default SketchModule;
|