brep-io-kernel 1.0.0-ci.9
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/LICENSE.md +32 -0
- package/README.md +154 -0
- package/dist-kernel/brep-kernel.js +74699 -0
- package/package.json +58 -0
- package/src/BREP/AssemblyComponent.js +42 -0
- package/src/BREP/BREP.js +43 -0
- package/src/BREP/BetterSolid.js +805 -0
- package/src/BREP/Edge.js +103 -0
- package/src/BREP/Extrude.js +403 -0
- package/src/BREP/Face.js +187 -0
- package/src/BREP/MeshRepairer.js +634 -0
- package/src/BREP/OffsetShellSolid.js +614 -0
- package/src/BREP/PointCloudWrap.js +302 -0
- package/src/BREP/Revolve.js +345 -0
- package/src/BREP/SolidMethods/authoring.js +112 -0
- package/src/BREP/SolidMethods/booleanOps.js +230 -0
- package/src/BREP/SolidMethods/chamfer.js +122 -0
- package/src/BREP/SolidMethods/edgeResolution.js +25 -0
- package/src/BREP/SolidMethods/fillet.js +792 -0
- package/src/BREP/SolidMethods/index.js +72 -0
- package/src/BREP/SolidMethods/io.js +105 -0
- package/src/BREP/SolidMethods/lifecycle.js +103 -0
- package/src/BREP/SolidMethods/manifoldOps.js +375 -0
- package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
- package/src/BREP/SolidMethods/meshQueries.js +264 -0
- package/src/BREP/SolidMethods/metadata.js +106 -0
- package/src/BREP/SolidMethods/metrics.js +51 -0
- package/src/BREP/SolidMethods/transforms.js +361 -0
- package/src/BREP/SolidMethods/visualize.js +508 -0
- package/src/BREP/SolidShared.js +26 -0
- package/src/BREP/Sweep.js +1596 -0
- package/src/BREP/Tube.js +857 -0
- package/src/BREP/Vertex.js +43 -0
- package/src/BREP/applyBooleanOperation.js +704 -0
- package/src/BREP/boundsUtils.js +48 -0
- package/src/BREP/chamfer.js +551 -0
- package/src/BREP/edgePolylineUtils.js +85 -0
- package/src/BREP/fillets/common.js +388 -0
- package/src/BREP/fillets/fillet.js +1422 -0
- package/src/BREP/fillets/filletGeometry.js +15 -0
- package/src/BREP/fillets/inset.js +389 -0
- package/src/BREP/fillets/offsetHelper.js +143 -0
- package/src/BREP/fillets/outset.js +88 -0
- package/src/BREP/helix.js +193 -0
- package/src/BREP/meshToBrep.js +234 -0
- package/src/BREP/primitives.js +279 -0
- package/src/BREP/setupManifold.js +71 -0
- package/src/BREP/threadGeometry.js +1120 -0
- package/src/BREP/triangleUtils.js +8 -0
- package/src/BREP/triangulate.js +608 -0
- package/src/FeatureRegistry.js +183 -0
- package/src/PartHistory.js +1132 -0
- package/src/UI/AccordionWidget.js +292 -0
- package/src/UI/CADmaterials.js +850 -0
- package/src/UI/EnvMonacoEditor.js +522 -0
- package/src/UI/FloatingWindow.js +396 -0
- package/src/UI/HistoryWidget.js +457 -0
- package/src/UI/MainToolbar.js +131 -0
- package/src/UI/ModelLibraryView.js +194 -0
- package/src/UI/OrthoCameraIdle.js +206 -0
- package/src/UI/PluginsWidget.js +280 -0
- package/src/UI/SceneListing.js +606 -0
- package/src/UI/SelectionFilter.js +629 -0
- package/src/UI/ViewCube.js +389 -0
- package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
- package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
- package/src/UI/assembly/constraintFaceUtils.js +115 -0
- package/src/UI/assembly/constraintHighlightUtils.js +70 -0
- package/src/UI/assembly/constraintLabelUtils.js +31 -0
- package/src/UI/assembly/constraintPointUtils.js +64 -0
- package/src/UI/assembly/constraintSelectionUtils.js +185 -0
- package/src/UI/assembly/constraintStatusUtils.js +142 -0
- package/src/UI/componentSelectorModal.js +240 -0
- package/src/UI/controls/CombinedTransformControls.js +386 -0
- package/src/UI/dialogs.js +351 -0
- package/src/UI/expressionsManager.js +100 -0
- package/src/UI/featureDialogWidgets/booleanField.js +25 -0
- package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
- package/src/UI/featureDialogWidgets/buttonField.js +45 -0
- package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
- package/src/UI/featureDialogWidgets/defaultField.js +23 -0
- package/src/UI/featureDialogWidgets/fileField.js +66 -0
- package/src/UI/featureDialogWidgets/index.js +34 -0
- package/src/UI/featureDialogWidgets/numberField.js +165 -0
- package/src/UI/featureDialogWidgets/optionsField.js +33 -0
- package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
- package/src/UI/featureDialogWidgets/stringField.js +24 -0
- package/src/UI/featureDialogWidgets/textareaField.js +28 -0
- package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
- package/src/UI/featureDialogWidgets/transformField.js +252 -0
- package/src/UI/featureDialogWidgets/utils.js +43 -0
- package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
- package/src/UI/featureDialogs.js +1414 -0
- package/src/UI/fileManagerWidget.js +615 -0
- package/src/UI/history/HistoryCollectionWidget.js +1294 -0
- package/src/UI/history/historyCollectionWidget.css.js +257 -0
- package/src/UI/history/historyDisplayInfo.js +133 -0
- package/src/UI/mobile.js +28 -0
- package/src/UI/objectDump.js +442 -0
- package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
- package/src/UI/pmi/AnnotationHistory.js +353 -0
- package/src/UI/pmi/AnnotationRegistry.js +90 -0
- package/src/UI/pmi/BaseAnnotation.js +269 -0
- package/src/UI/pmi/LabelOverlay.css +102 -0
- package/src/UI/pmi/LabelOverlay.js +191 -0
- package/src/UI/pmi/PMIMode.js +1550 -0
- package/src/UI/pmi/PMIViewsWidget.js +1098 -0
- package/src/UI/pmi/annUtils.js +729 -0
- package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
- package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
- package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
- package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
- package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
- package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
- package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
- package/src/UI/pmi/pmiStyle.js +44 -0
- package/src/UI/sketcher/SketchMode3D.js +4095 -0
- package/src/UI/sketcher/dimensions.js +674 -0
- package/src/UI/sketcher/glyphs.js +236 -0
- package/src/UI/sketcher/highlights.js +60 -0
- package/src/UI/toolbarButtons/aboutButton.js +5 -0
- package/src/UI/toolbarButtons/exportButton.js +609 -0
- package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
- package/src/UI/toolbarButtons/importButton.js +160 -0
- package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
- package/src/UI/toolbarButtons/metadataButton.js +1063 -0
- package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
- package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
- package/src/UI/toolbarButtons/saveButton.js +99 -0
- package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
- package/src/UI/toolbarButtons/testsButton.js +26 -0
- package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
- package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
- package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
- package/src/UI/triangleDebuggerWindow.js +945 -0
- package/src/UI/viewer.js +4228 -0
- package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
- package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
- package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
- package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
- package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
- package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
- package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
- package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
- package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
- package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
- package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
- package/src/core/entities/HistoryCollectionBase.js +72 -0
- package/src/core/entities/ListEntityBase.js +109 -0
- package/src/core/entities/schemaProcesser.js +121 -0
- package/src/exporters/sheetMetalFlatPattern.js +659 -0
- package/src/exporters/sheetMetalUnfold.js +862 -0
- package/src/exporters/step.js +1135 -0
- package/src/exporters/threeMF.js +575 -0
- package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
- package/src/features/boolean/BooleanFeature.js +94 -0
- package/src/features/chamfer/ChamferFeature.js +116 -0
- package/src/features/datium/DatiumFeature.js +80 -0
- package/src/features/edgeFeatureUtils.js +41 -0
- package/src/features/extrude/ExtrudeFeature.js +143 -0
- package/src/features/fillet/FilletFeature.js +197 -0
- package/src/features/helix/HelixFeature.js +405 -0
- package/src/features/hole/HoleFeature.js +1050 -0
- package/src/features/hole/screwClearance.js +86 -0
- package/src/features/hole/threadDesignationCatalog.js +149 -0
- package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
- package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
- package/src/features/imageToFace/imageEditor.js +1270 -0
- package/src/features/imageToFace/traceUtils.js +971 -0
- package/src/features/import3dModel/Import3dModelFeature.js +151 -0
- package/src/features/loft/LoftFeature.js +605 -0
- package/src/features/mirror/MirrorFeature.js +151 -0
- package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
- package/src/features/offsetShell/OffsetShellFeature.js +89 -0
- package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
- package/src/features/pattern/PatternFeature.js +275 -0
- package/src/features/patternLinear/PatternLinearFeature.js +120 -0
- package/src/features/patternRadial/PatternRadialFeature.js +186 -0
- package/src/features/plane/PlaneFeature.js +154 -0
- package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
- package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
- package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
- package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
- package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
- package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
- package/src/features/remesh/RemeshFeature.js +97 -0
- package/src/features/revolve/RevolveFeature.js +111 -0
- package/src/features/selectionUtils.js +118 -0
- package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
- package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
- package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
- package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
- package/src/features/sheetMetal/SheetMetalObject.js +141 -0
- package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
- package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
- package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
- package/src/features/sheetMetal/profileUtils.js +25 -0
- package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
- package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
- package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
- package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
- package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
- package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
- package/src/features/sheetMetal/sheetMetalTree.js +210 -0
- package/src/features/sketch/SketchFeature.js +955 -0
- package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
- package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
- package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
- package/src/features/spline/SplineEditorSession.js +988 -0
- package/src/features/spline/SplineFeature.js +1388 -0
- package/src/features/spline/splineUtils.js +218 -0
- package/src/features/sweep/SweepFeature.js +110 -0
- package/src/features/transform/TransformFeature.js +152 -0
- package/src/features/tube/TubeFeature.js +635 -0
- package/src/fs.proxy.js +625 -0
- package/src/idbStorage.js +254 -0
- package/src/index.js +12 -0
- package/src/main.js +15 -0
- package/src/metadataManager.js +64 -0
- package/src/path.proxy.js +277 -0
- package/src/plugins/ghLoader.worker.js +151 -0
- package/src/plugins/pluginManager.js +286 -0
- package/src/pmi/PMIViewsManager.js +134 -0
- package/src/services/componentLibrary.js +198 -0
- package/src/tests/ConsoleCapture.js +189 -0
- package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
- package/src/tests/browserTests.js +597 -0
- package/src/tests/debugBoolean.js +225 -0
- package/src/tests/partFiles/badBoolean.json +957 -0
- package/src/tests/partFiles/extrudeTest.json +88 -0
- package/src/tests/partFiles/filletFail.json +58 -0
- package/src/tests/partFiles/import_TEst.part.part.json +646 -0
- package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
- package/src/tests/test_boolean_subtract.js +27 -0
- package/src/tests/test_chamfer.js +17 -0
- package/src/tests/test_extrudeFace.js +24 -0
- package/src/tests/test_fillet.js +17 -0
- package/src/tests/test_fillet_nonClosed.js +45 -0
- package/src/tests/test_filletsMoreDifficult.js +46 -0
- package/src/tests/test_history_features_basic.js +149 -0
- package/src/tests/test_hole.js +282 -0
- package/src/tests/test_mirror.js +16 -0
- package/src/tests/test_offsetShellGrouping.js +85 -0
- package/src/tests/test_plane.js +4 -0
- package/src/tests/test_primitiveCone.js +11 -0
- package/src/tests/test_primitiveCube.js +7 -0
- package/src/tests/test_primitiveCylinder.js +8 -0
- package/src/tests/test_primitivePyramid.js +9 -0
- package/src/tests/test_primitiveSphere.js +17 -0
- package/src/tests/test_primitiveTorus.js +21 -0
- package/src/tests/test_pushFace.js +126 -0
- package/src/tests/test_sheetMetalContourFlange.js +125 -0
- package/src/tests/test_sheetMetal_features.js +80 -0
- package/src/tests/test_sketch_openLoop.js +45 -0
- package/src/tests/test_solidMetrics.js +58 -0
- package/src/tests/test_stlLoader.js +1889 -0
- package/src/tests/test_sweepFace.js +55 -0
- package/src/tests/test_tube.js +45 -0
- package/src/tests/test_tube_closedLoop.js +67 -0
- package/src/tests/tests.js +493 -0
- package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
- package/src/tools/dialogCapturePageFactory.js +227 -0
- package/src/tools/featureDialogCapturePage.js +47 -0
- package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
- package/src/utils/axisHelpers.js +99 -0
- package/src/utils/deepClone.js +69 -0
- package/src/utils/geometryTolerance.js +37 -0
- package/src/utils/normalizeTypeString.js +8 -0
- package/src/utils/xformMath.js +51 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { listComponentRecords, getComponentRecord, extractThumbnailFrom3MFBase64 } from '../services/componentLibrary.js';
|
|
2
|
+
import { ModelLibraryView } from './ModelLibraryView.js';
|
|
3
|
+
|
|
4
|
+
export function openComponentSelectorModal({ title = 'Select Component' } = {}) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const records = listComponentRecords();
|
|
7
|
+
|
|
8
|
+
const overlay = document.createElement('div');
|
|
9
|
+
overlay.className = 'component-selector-overlay';
|
|
10
|
+
|
|
11
|
+
const panel = document.createElement('div');
|
|
12
|
+
panel.className = 'component-selector-panel';
|
|
13
|
+
|
|
14
|
+
const header = document.createElement('div');
|
|
15
|
+
header.className = 'cs-header';
|
|
16
|
+
header.textContent = title;
|
|
17
|
+
|
|
18
|
+
const controls = document.createElement('div');
|
|
19
|
+
controls.className = 'cs-controls';
|
|
20
|
+
|
|
21
|
+
const search = document.createElement('input');
|
|
22
|
+
search.type = 'search';
|
|
23
|
+
search.placeholder = 'Search components…';
|
|
24
|
+
search.className = 'cs-search';
|
|
25
|
+
controls.appendChild(search);
|
|
26
|
+
|
|
27
|
+
let iconsOnly = false;
|
|
28
|
+
const viewToggle = document.createElement('button');
|
|
29
|
+
viewToggle.type = 'button';
|
|
30
|
+
viewToggle.className = 'fm-btn cs-view-toggle';
|
|
31
|
+
controls.appendChild(viewToggle);
|
|
32
|
+
|
|
33
|
+
const list = document.createElement('div');
|
|
34
|
+
list.className = 'cs-list';
|
|
35
|
+
|
|
36
|
+
const footer = document.createElement('div');
|
|
37
|
+
footer.className = 'cs-footer';
|
|
38
|
+
const cancel = document.createElement('button');
|
|
39
|
+
cancel.type = 'button';
|
|
40
|
+
cancel.textContent = 'Cancel';
|
|
41
|
+
cancel.className = 'cs-btn';
|
|
42
|
+
footer.appendChild(cancel);
|
|
43
|
+
|
|
44
|
+
panel.appendChild(header);
|
|
45
|
+
panel.appendChild(controls);
|
|
46
|
+
panel.appendChild(list);
|
|
47
|
+
panel.appendChild(footer);
|
|
48
|
+
overlay.appendChild(panel);
|
|
49
|
+
document.body.appendChild(overlay);
|
|
50
|
+
|
|
51
|
+
const cleanup = (result) => {
|
|
52
|
+
try { document.body.removeChild(overlay); } catch {}
|
|
53
|
+
resolve(result);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
cancel.addEventListener('click', () => cleanup(null));
|
|
57
|
+
overlay.addEventListener('click', (ev) => {
|
|
58
|
+
if (ev.target === overlay) cleanup(null);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const thumbCache = new Map();
|
|
62
|
+
const gallery = new ModelLibraryView({
|
|
63
|
+
container: list,
|
|
64
|
+
iconsOnly,
|
|
65
|
+
allowDelete: false,
|
|
66
|
+
showOpenButton: false,
|
|
67
|
+
loadThumbnail: async (item, imgEl) => {
|
|
68
|
+
if (!item || !imgEl) return;
|
|
69
|
+
if (item.thumbnail) {
|
|
70
|
+
imgEl.src = item.thumbnail;
|
|
71
|
+
thumbCache.set(item.name, item.thumbnail);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!item.data3mf) {
|
|
75
|
+
if (thumbCache.has(item.name)) {
|
|
76
|
+
const cached = thumbCache.get(item.name);
|
|
77
|
+
if (cached) imgEl.src = cached;
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (thumbCache.has(item.name)) {
|
|
82
|
+
const cached = thumbCache.get(item.name);
|
|
83
|
+
if (cached) {
|
|
84
|
+
imgEl.src = cached;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const src = await extractThumbnailFrom3MFBase64(item.data3mf);
|
|
89
|
+
if (src) {
|
|
90
|
+
thumbCache.set(item.name, src);
|
|
91
|
+
imgEl.src = src;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onOpen: async (name) => {
|
|
95
|
+
const record = getComponentRecord(name);
|
|
96
|
+
if (!record || !record.data3mf) {
|
|
97
|
+
cleanup(null);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
cleanup(record);
|
|
101
|
+
},
|
|
102
|
+
emptyMessage: records.length ? 'No components match the search.' : 'No stored components found.',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const updateViewToggle = () => {
|
|
106
|
+
if (!viewToggle) return;
|
|
107
|
+
if (iconsOnly) {
|
|
108
|
+
viewToggle.textContent = '☰';
|
|
109
|
+
viewToggle.title = 'Switch to list view';
|
|
110
|
+
} else {
|
|
111
|
+
viewToggle.textContent = '🔳';
|
|
112
|
+
viewToggle.title = 'Switch to grid view';
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
updateViewToggle();
|
|
116
|
+
|
|
117
|
+
const render = () => {
|
|
118
|
+
const term = (search.value || '').trim().toLowerCase();
|
|
119
|
+
const matches = !term
|
|
120
|
+
? records
|
|
121
|
+
: records.filter((rec) => rec.name.toLowerCase().includes(term));
|
|
122
|
+
const mapped = matches.map(({ name, savedAt, record }) => ({
|
|
123
|
+
name,
|
|
124
|
+
savedAt,
|
|
125
|
+
data3mf: record?.data3mf || null,
|
|
126
|
+
thumbnail: record?.thumbnail || null,
|
|
127
|
+
}));
|
|
128
|
+
const emptyMessage = records.length ? 'No components match the search.' : 'No stored components found.';
|
|
129
|
+
gallery.setEmptyMessage(emptyMessage);
|
|
130
|
+
gallery.setIconsOnly(iconsOnly);
|
|
131
|
+
gallery.render(mapped);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
viewToggle.addEventListener('click', () => {
|
|
135
|
+
iconsOnly = !iconsOnly;
|
|
136
|
+
updateViewToggle();
|
|
137
|
+
gallery.setIconsOnly(iconsOnly);
|
|
138
|
+
render();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
search.addEventListener('input', render);
|
|
142
|
+
render();
|
|
143
|
+
|
|
144
|
+
requestAnimationFrame(() => {
|
|
145
|
+
try { search.focus(); } catch {}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
(function ensureStyles() {
|
|
151
|
+
if (typeof document === 'undefined') return;
|
|
152
|
+
if (document.getElementById('component-selector-styles')) return;
|
|
153
|
+
const style = document.createElement('style');
|
|
154
|
+
style.id = 'component-selector-styles';
|
|
155
|
+
style.textContent = `
|
|
156
|
+
.component-selector-overlay {
|
|
157
|
+
position: fixed;
|
|
158
|
+
inset: 0;
|
|
159
|
+
background: rgba(0, 0, 0, 0.45);
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
z-index: 2000;
|
|
164
|
+
}
|
|
165
|
+
.component-selector-panel {
|
|
166
|
+
width: min(560px, 90vw);
|
|
167
|
+
max-height: 80vh;
|
|
168
|
+
background: #111827;
|
|
169
|
+
border: 1px solid #1f2937;
|
|
170
|
+
border-radius: 12px;
|
|
171
|
+
box-shadow: 0 24px 48px rgba(0,0,0,0.45);
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-direction: column;
|
|
174
|
+
overflow: hidden;
|
|
175
|
+
color: #e5e7eb;
|
|
176
|
+
font: 14px/1.4 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
177
|
+
}
|
|
178
|
+
.cs-header {
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
padding: 14px 18px;
|
|
181
|
+
border-bottom: 1px solid #1f2937;
|
|
182
|
+
background: rgba(255,255,255,0.03);
|
|
183
|
+
}
|
|
184
|
+
.cs-controls {
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
gap: 8px;
|
|
188
|
+
padding: 12px 18px 0 18px;
|
|
189
|
+
}
|
|
190
|
+
.cs-search {
|
|
191
|
+
margin: 0;
|
|
192
|
+
padding: 8px 10px;
|
|
193
|
+
border-radius: 8px;
|
|
194
|
+
border: 1px solid #374151;
|
|
195
|
+
background: #0b0e14;
|
|
196
|
+
color: inherit;
|
|
197
|
+
outline: none;
|
|
198
|
+
flex: 1 1 auto;
|
|
199
|
+
}
|
|
200
|
+
.cs-search:focus {
|
|
201
|
+
border-color: #3b82f6;
|
|
202
|
+
box-shadow: 0 0 0 3px rgba(59,130,246,0.25);
|
|
203
|
+
}
|
|
204
|
+
.cs-list {
|
|
205
|
+
flex: 1 1 auto;
|
|
206
|
+
overflow-y: auto;
|
|
207
|
+
padding: 12px;
|
|
208
|
+
}
|
|
209
|
+
.cs-view-toggle {
|
|
210
|
+
flex: 0 0 auto;
|
|
211
|
+
}
|
|
212
|
+
.cs-empty {
|
|
213
|
+
padding: 32px 18px;
|
|
214
|
+
text-align: center;
|
|
215
|
+
color: #9ca3af;
|
|
216
|
+
}
|
|
217
|
+
.cs-footer {
|
|
218
|
+
padding: 12px 18px;
|
|
219
|
+
border-top: 1px solid #1f2937;
|
|
220
|
+
display: flex;
|
|
221
|
+
justify-content: flex-end;
|
|
222
|
+
}
|
|
223
|
+
.cs-btn {
|
|
224
|
+
appearance: none;
|
|
225
|
+
border: 1px solid #374151;
|
|
226
|
+
background: rgba(255,255,255,0.05);
|
|
227
|
+
color: #f9fafb;
|
|
228
|
+
padding: 6px 12px;
|
|
229
|
+
border-radius: 8px;
|
|
230
|
+
font-weight: 600;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
233
|
+
}
|
|
234
|
+
.cs-btn:hover {
|
|
235
|
+
border-color: #3b82f6;
|
|
236
|
+
background: rgba(59,130,246,0.12);
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
document.head.appendChild(style);
|
|
240
|
+
})();
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// CombinedTransformControls - lightweight move + rotate gizmo
|
|
2
|
+
// Drop-in replacement for three/examples TransformControls used by this app.
|
|
3
|
+
// Focuses on orthographic cameras and the needs of the BREP Viewer.
|
|
4
|
+
//
|
|
5
|
+
// Public API compatibility (subset):
|
|
6
|
+
// - constructor(camera, domElement)
|
|
7
|
+
// - extends THREE.Object3D so it can be added to the scene
|
|
8
|
+
// - properties: enabled, dragging, mode, showX/Y/Z, isTransformGizmo
|
|
9
|
+
// - methods: attach(obj), detach(), setMode(mode), getMode(), update(), dispose(), getHelper()
|
|
10
|
+
// - events: 'change', 'dragging-changed', 'objectChange'
|
|
11
|
+
// - picking roots available at this.gizmo.picker.translate / .rotate
|
|
12
|
+
//
|
|
13
|
+
import * as THREE from 'three';
|
|
14
|
+
|
|
15
|
+
export class CombinedTransformControls extends THREE.Object3D {
|
|
16
|
+
constructor(camera, domElement) {
|
|
17
|
+
super();
|
|
18
|
+
this.type = 'CombinedTransformControls';
|
|
19
|
+
this.camera = camera;
|
|
20
|
+
this.domElement = domElement;
|
|
21
|
+
this.enabled = true;
|
|
22
|
+
this.dragging = false;
|
|
23
|
+
this.mode = 'translate'; // kept for compatibility; both gizmos are active
|
|
24
|
+
this.showX = true; this.showY = true; this.showZ = true;
|
|
25
|
+
this.isTransformGizmo = true; // used by PartHistory cleanup logic
|
|
26
|
+
this._sizeMultiplier = 2; // larger default on‑screen size
|
|
27
|
+
this.renderOrder = 50000; // Render on top of all other geometry
|
|
28
|
+
|
|
29
|
+
this.target = null; // Object3D we drive
|
|
30
|
+
|
|
31
|
+
this._raycaster = new THREE.Raycaster();
|
|
32
|
+
this._pointer = new THREE.Vector2();
|
|
33
|
+
this._tmpV = new THREE.Vector3();
|
|
34
|
+
this._tmpV2 = new THREE.Vector3();
|
|
35
|
+
this._tmpQ = new THREE.Quaternion();
|
|
36
|
+
this._plane = new THREE.Plane();
|
|
37
|
+
|
|
38
|
+
// Visuals
|
|
39
|
+
this.gizmo = this._buildGizmo();
|
|
40
|
+
this.add(this.gizmo.root);
|
|
41
|
+
|
|
42
|
+
// Events
|
|
43
|
+
this._onPointerDown = this._handlePointerDown.bind(this);
|
|
44
|
+
this._onPointerMove = this._handlePointerMove.bind(this);
|
|
45
|
+
this._onPointerUp = this._handlePointerUp.bind(this);
|
|
46
|
+
if (this.domElement) {
|
|
47
|
+
this.domElement.addEventListener('pointerdown', this._onPointerDown, { passive: false });
|
|
48
|
+
window.addEventListener('pointermove', this._onPointerMove, { passive: false });
|
|
49
|
+
window.addEventListener('pointerup', this._onPointerUp, { passive: false, capture: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
dispose() {
|
|
54
|
+
try { this.domElement?.removeEventListener('pointerdown', this._onPointerDown); } catch {}
|
|
55
|
+
try { window.removeEventListener('pointermove', this._onPointerMove); } catch {}
|
|
56
|
+
try { window.removeEventListener('pointerup', this._onPointerUp, { capture: true }); } catch {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getHelper() { return this; }
|
|
60
|
+
getMode() { return this.mode; }
|
|
61
|
+
setMode(mode) { this.mode = String(mode || 'translate'); }
|
|
62
|
+
setSize(s) { this._sizeMultiplier = Number(s) || 1; this.update(); }
|
|
63
|
+
|
|
64
|
+
attach(object) {
|
|
65
|
+
this.target = object || null;
|
|
66
|
+
if (this.target) {
|
|
67
|
+
try { this.target.updateMatrixWorld(true); } catch {}
|
|
68
|
+
this.position.copy(this.target.getWorldPosition(new THREE.Vector3()));
|
|
69
|
+
this.quaternion.copy(this.target.getWorldQuaternion(new THREE.Quaternion()));
|
|
70
|
+
this.updateMatrixWorld(true);
|
|
71
|
+
try { this.update(); } catch {}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
detach() { this.target = null; }
|
|
75
|
+
|
|
76
|
+
update() {
|
|
77
|
+
// Keep a roughly constant on‑screen scale (ortho-friendly)
|
|
78
|
+
const scale = this._computeGizmoScale() * (this._sizeMultiplier || 1);
|
|
79
|
+
this.gizmo.root.scale.setScalar(scale);
|
|
80
|
+
// Face camera for labels
|
|
81
|
+
if (this.gizmo && this.gizmo.labels) {
|
|
82
|
+
const q = this.camera.quaternion;
|
|
83
|
+
for (const s of this.gizmo.labels) s.quaternion.copy(q);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ----------------------------------------
|
|
88
|
+
// Internals: visuals
|
|
89
|
+
// ----------------------------------------
|
|
90
|
+
_buildGizmo() {
|
|
91
|
+
const root = new THREE.Group();
|
|
92
|
+
root.name = 'HybridXformGizmoRoot';
|
|
93
|
+
root.userData.excludeFromFit = true;
|
|
94
|
+
root.renderOrder = this.renderOrder; // Ensure gizmo renders on top of all other geometry
|
|
95
|
+
|
|
96
|
+
// For compatibility with the viewer's hover checks, expose picker roots.
|
|
97
|
+
// Point them at the root so our oriented per-handle meshes are included.
|
|
98
|
+
const picker = { translate: root, rotate: root };
|
|
99
|
+
|
|
100
|
+
// Materials - using overlay pattern (depthTest: false, depthWrite: false)
|
|
101
|
+
const mAxis = new THREE.MeshBasicMaterial({ color: 0xbfbfbf, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
|
|
102
|
+
const mArrow = new THREE.MeshBasicMaterial({ color: 0xf2c14e, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
|
|
103
|
+
const mDot = new THREE.MeshBasicMaterial({ color: 0xf29e4c, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
|
|
104
|
+
|
|
105
|
+
// Geometries (shared)
|
|
106
|
+
const gRod = new THREE.CylinderGeometry(0.03, 0.03, 1.0, 16);
|
|
107
|
+
const gArrow = new THREE.ConeGeometry(0.12, 0.4, 20);
|
|
108
|
+
const gDot = new THREE.SphereGeometry(0.12, 16, 12);
|
|
109
|
+
|
|
110
|
+
// Axis builders
|
|
111
|
+
const axes = [];
|
|
112
|
+
const addAxis = (axis, colorText, spriteLabel) => {
|
|
113
|
+
const group = new THREE.Group();
|
|
114
|
+
group.renderOrder = this.renderOrder; // Ensure gizmo renders on top of all other geometry
|
|
115
|
+
group.name = `Axis${axis}`;
|
|
116
|
+
|
|
117
|
+
const rod = new THREE.Mesh(gRod, mAxis);
|
|
118
|
+
rod.renderOrder = this.renderOrder;
|
|
119
|
+
rod.position.y = 0.5; // rod extends from center along +Y before orientation
|
|
120
|
+
group.add(rod);
|
|
121
|
+
|
|
122
|
+
const tip = new THREE.Mesh(gArrow, mArrow);
|
|
123
|
+
tip.renderOrder = this.renderOrder;
|
|
124
|
+
tip.position.y = 1.0 + 0.125;
|
|
125
|
+
tip.userData.handle = { kind: 'translate', axis };
|
|
126
|
+
group.add(tip);
|
|
127
|
+
|
|
128
|
+
// Orient group
|
|
129
|
+
if (axis === 'X') group.rotation.z = -Math.PI / 2;
|
|
130
|
+
if (axis === 'Z') group.rotation.x = -Math.PI / 2;
|
|
131
|
+
|
|
132
|
+
// Label sprite (XC/YC/ZC)
|
|
133
|
+
const spr = this._makeTextSprite(`${axis}C`, colorText);
|
|
134
|
+
// Place label along the axis positive direction (local +Y before rotation)
|
|
135
|
+
spr.position.set(0, 1.3, 0);
|
|
136
|
+
group.add(spr);
|
|
137
|
+
|
|
138
|
+
root.add(group);
|
|
139
|
+
axes.push({ group, spr });
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
addAxis('X', '#ff6666');
|
|
143
|
+
addAxis('Y', '#7ddc6f');
|
|
144
|
+
addAxis('Z', '#6aa9ff');
|
|
145
|
+
|
|
146
|
+
// Rotation arcs: quarter circles in XY (Z axis), YZ (X axis), ZX (Y axis)
|
|
147
|
+
const rot = {};
|
|
148
|
+
const addRotate = (axis) => {
|
|
149
|
+
const grp = new THREE.Group();
|
|
150
|
+
grp.name = `Rotate${axis}`;
|
|
151
|
+
grp.renderOrder = this.renderOrder;
|
|
152
|
+
const r = 0.9;
|
|
153
|
+
const arcShape = new THREE.BufferGeometry().setFromPoints(
|
|
154
|
+
Array.from({ length: 33 }, (_, i) => {
|
|
155
|
+
const t = (i / 32) * (Math.PI / 2);
|
|
156
|
+
return new THREE.Vector3(Math.cos(t) * r, Math.sin(t) * r, 0);
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
const arcMat = new THREE.LineBasicMaterial({ color: 0xe0e0e0, linewidth: 2, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
|
|
160
|
+
const arc = new THREE.Line(arcShape, arcMat);
|
|
161
|
+
arc.renderOrder = this.renderOrder;
|
|
162
|
+
grp.add(arc);
|
|
163
|
+
|
|
164
|
+
// Single decorative dot along the arc (one per axis)
|
|
165
|
+
const tDot = Math.PI / 4; // 45° along the arc
|
|
166
|
+
const dot = new THREE.Mesh(gDot, mDot);
|
|
167
|
+
dot.position.set(Math.cos(tDot) * r, Math.sin(tDot) * r, 0);
|
|
168
|
+
dot.renderOrder = this.renderOrder;
|
|
169
|
+
// Make the dot itself a rotate handle so dragging it feels natural
|
|
170
|
+
dot.userData.handle = { kind: 'rotate', axis };
|
|
171
|
+
grp.add(dot);
|
|
172
|
+
|
|
173
|
+
// Orient to axis
|
|
174
|
+
if (axis === 'X') grp.rotation.y = Math.PI / 2; // arc in YZ plane -> rotate around X
|
|
175
|
+
if (axis === 'Y') grp.rotation.x = -Math.PI / 2; // arc in ZX plane -> rotate around Y
|
|
176
|
+
// axis Z: default in XY plane
|
|
177
|
+
|
|
178
|
+
root.add(grp);
|
|
179
|
+
rot[axis] = { group: grp, dot, radius: r };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
addRotate('Z');
|
|
183
|
+
addRotate('Y');
|
|
184
|
+
addRotate('X');
|
|
185
|
+
|
|
186
|
+
return { root, picker, labels: axes.map(a => a.spr), rot };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_makeTextSprite(text, color = '#ffffff') {
|
|
190
|
+
const size = 256;
|
|
191
|
+
const cvs = document.createElement('canvas');
|
|
192
|
+
cvs.width = cvs.height = size;
|
|
193
|
+
const ctx = cvs.getContext('2d');
|
|
194
|
+
ctx.clearRect(0, 0, size, size);
|
|
195
|
+
ctx.fillStyle = 'rgba(0,0,0,0)';
|
|
196
|
+
ctx.fillRect(0, 0, size, size);
|
|
197
|
+
ctx.font = 'bold 64px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
198
|
+
ctx.textAlign = 'center';
|
|
199
|
+
ctx.textBaseline = 'middle';
|
|
200
|
+
ctx.fillStyle = color;
|
|
201
|
+
ctx.fillText(String(text || ''), size / 2, size / 2);
|
|
202
|
+
const tex = new THREE.CanvasTexture(cvs);
|
|
203
|
+
tex.colorSpace = THREE.SRGBColorSpace;
|
|
204
|
+
const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false, depthWrite: false });
|
|
205
|
+
const spr = new THREE.Sprite(mat);
|
|
206
|
+
spr.scale.setScalar(0.6);
|
|
207
|
+
spr.renderOrder = this.renderOrder;
|
|
208
|
+
return spr;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_computeGizmoScale() {
|
|
212
|
+
// For OrthographicCamera, constant screen size ≈ inverse of zoom.
|
|
213
|
+
const cam = this.camera;
|
|
214
|
+
if (cam && cam.isOrthographicCamera) {
|
|
215
|
+
const z = Math.max(0.0001, cam.zoom || 1);
|
|
216
|
+
return 1 / z;
|
|
217
|
+
}
|
|
218
|
+
// Perspective: scale with distance, using simple heuristic
|
|
219
|
+
const pos = this.getWorldPosition(this._tmpV);
|
|
220
|
+
const camPos = this.camera.getWorldPosition(this._tmpV2);
|
|
221
|
+
const d = pos.distanceTo(camPos);
|
|
222
|
+
const f = Math.tan((this.camera.fov || 50) * Math.PI / 360) * 2.0;
|
|
223
|
+
return (d * f) / 10; // heuristic constant
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ----------------------------------------
|
|
227
|
+
// Internals: interaction
|
|
228
|
+
// ----------------------------------------
|
|
229
|
+
_setPointerFromEvent(e) {
|
|
230
|
+
const rect = this.domElement.getBoundingClientRect();
|
|
231
|
+
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
|
232
|
+
const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
233
|
+
this._pointer.set(x, y);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_intersections(root) {
|
|
237
|
+
this._raycaster.setFromCamera(this._pointer, this.camera);
|
|
238
|
+
|
|
239
|
+
// Fix ray origin - ensure it starts from behind the camera
|
|
240
|
+
const ray = this._raycaster.ray;
|
|
241
|
+
if (this.camera.isOrthographicCamera) {
|
|
242
|
+
// For orthographic cameras, move the origin back along the camera's forward direction
|
|
243
|
+
const backwardDistance = 1000; // Large distance to ensure we're behind all objects
|
|
244
|
+
ray.origin.add(ray.direction.clone().multiplyScalar(-backwardDistance));
|
|
245
|
+
} else if (this.camera.isPerspectiveCamera) {
|
|
246
|
+
// For perspective cameras, use the camera position as origin
|
|
247
|
+
ray.origin.copy(this.camera.position);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return this._raycaster.intersectObject(root, true) || [];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_handlePointerDown(e) {
|
|
254
|
+
if (!this.enabled || !this.visible) return;
|
|
255
|
+
if (!this.target) return;
|
|
256
|
+
this._setPointerFromEvent(e);
|
|
257
|
+
// Prefer picker matching current mode, then fallback to all
|
|
258
|
+
const giz = this.gizmo;
|
|
259
|
+
const pickRoot = giz.picker[this.mode] || giz.root; // tolerate missing picker grouping
|
|
260
|
+
const hits = this._intersections(pickRoot);
|
|
261
|
+
const hit = Array.isArray(hits) ? hits.find(it => it?.object?.userData?.handle) : null;
|
|
262
|
+
if (!hit) return;
|
|
263
|
+
const h = hit.object.userData.handle;
|
|
264
|
+
if (!h || !h.kind) return;
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
e.stopPropagation?.();
|
|
267
|
+
|
|
268
|
+
this._drag = this._drag || {};
|
|
269
|
+
this._drag.handle = h; // { kind, axis }
|
|
270
|
+
// Use the gizmo's current world pose as the drag reference so
|
|
271
|
+
// subsequent drags operate relative to the gizmo orientation/position
|
|
272
|
+
try { this.updateMatrixWorld(true); } catch {}
|
|
273
|
+
this._drag.startPos = this.getWorldPosition(new THREE.Vector3());
|
|
274
|
+
this._drag.startQuat = this.getWorldQuaternion(new THREE.Quaternion());
|
|
275
|
+
this._drag.axis = this._axisWorld(h.axis);
|
|
276
|
+
|
|
277
|
+
// Establish reference plane and initial point
|
|
278
|
+
if (h.kind === 'translate') {
|
|
279
|
+
const camDir = this.camera.getWorldDirection(new THREE.Vector3());
|
|
280
|
+
// screen plane through startPos
|
|
281
|
+
this._plane.setFromNormalAndCoplanarPoint(camDir, this._drag.startPos);
|
|
282
|
+
} else if (h.kind === 'rotate') {
|
|
283
|
+
this._plane.setFromNormalAndCoplanarPoint(this._drag.axis, this._drag.startPos);
|
|
284
|
+
}
|
|
285
|
+
this._drag.startPoint = this._planeIntersect();
|
|
286
|
+
if (!this._drag.startPoint) { this._drag = null; return; }
|
|
287
|
+
|
|
288
|
+
// For rotation, track incremental deltas so angles can exceed 180°
|
|
289
|
+
if (h.kind === 'rotate') {
|
|
290
|
+
this._drag.prevPoint = this._drag.startPoint.clone();
|
|
291
|
+
const rotRef = (this.gizmo && this.gizmo.rot) ? this.gizmo.rot[h.axis] : null;
|
|
292
|
+
this._drag.rotVis = rotRef || null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.dragging = true;
|
|
296
|
+
this.dispatchEvent({ type: 'dragging-changed', value: true });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_handlePointerMove(e) {
|
|
300
|
+
if (!this.dragging || !this._drag) return;
|
|
301
|
+
this._setPointerFromEvent(e);
|
|
302
|
+
const p = this._planeIntersect();
|
|
303
|
+
if (!p) return;
|
|
304
|
+
|
|
305
|
+
const { handle, startPos, startQuat, axis, startPoint } = this._drag;
|
|
306
|
+
if (handle.kind === 'translate') {
|
|
307
|
+
const diff = this._tmpV.copy(p).sub(startPoint);
|
|
308
|
+
const amt = diff.dot(axis);
|
|
309
|
+
const pos = this._tmpV2.copy(startPos).add(this._tmpV.copy(axis).multiplyScalar(amt));
|
|
310
|
+
this.target.position.copy(pos);
|
|
311
|
+
} else if (handle.kind === 'rotate') {
|
|
312
|
+
// Compute incremental angle since last move to avoid wrap-around at 180°
|
|
313
|
+
const prev = (this._drag.prevPoint || startPoint);
|
|
314
|
+
const vPrev = this._tmpV.copy(prev).sub(startPos).normalize();
|
|
315
|
+
const vNow = this._tmpV2.copy(p).sub(startPos).normalize();
|
|
316
|
+
const cross = new THREE.Vector3().crossVectors(vPrev, vNow);
|
|
317
|
+
const dot = THREE.MathUtils.clamp(vPrev.dot(vNow), -1, 1);
|
|
318
|
+
const dAngle = Math.atan2(cross.dot(axis), dot);
|
|
319
|
+
|
|
320
|
+
// Apply incremental rotation using the current axis orientation
|
|
321
|
+
const currentAxis = this._axisWorld(handle.axis); // Get current axis orientation
|
|
322
|
+
const deltaQ = new THREE.Quaternion().setFromAxisAngle(currentAxis, dAngle);
|
|
323
|
+
this.target.quaternion.multiplyQuaternions(deltaQ, this.target.quaternion);
|
|
324
|
+
|
|
325
|
+
this._drag.prevPoint = p.clone();
|
|
326
|
+
|
|
327
|
+
// Move the decorative dot along the circle perpendicular to the axis
|
|
328
|
+
try {
|
|
329
|
+
const rotVis = this._drag.rotVis;
|
|
330
|
+
if (rotVis && rotVis.dot && rotVis.group && handle.axis) {
|
|
331
|
+
const r = rotVis.radius || 0.9;
|
|
332
|
+
|
|
333
|
+
// Keep the dot at a fixed position during drag to avoid jumping
|
|
334
|
+
// The visual feedback is primarily from the object rotation itself
|
|
335
|
+
// We could calculate the exact angle, but a fixed position works fine for UX
|
|
336
|
+
const fixedAngle = Math.PI / 4; // 45° - same as initial position
|
|
337
|
+
rotVis.dot.position.set(Math.cos(fixedAngle) * r, Math.sin(fixedAngle) * r, 0);
|
|
338
|
+
}
|
|
339
|
+
} catch {}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Keep gizmo aligned with target (position + rotation)
|
|
343
|
+
this.position.copy(this.target.position);
|
|
344
|
+
this.quaternion.copy(this.target.quaternion);
|
|
345
|
+
this.updateMatrixWorld(true);
|
|
346
|
+
|
|
347
|
+
this.dispatchEvent({ type: 'change' });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
_handlePointerUp(e) {
|
|
351
|
+
if (!this.dragging) return;
|
|
352
|
+
this.dragging = false;
|
|
353
|
+
this._drag = null;
|
|
354
|
+
this.dispatchEvent({ type: 'dragging-changed', value: false });
|
|
355
|
+
this.dispatchEvent({ type: 'objectChange' });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
_axisWorld(axis) {
|
|
359
|
+
const v = new THREE.Vector3(
|
|
360
|
+
axis === 'X' ? 1 : 0,
|
|
361
|
+
axis === 'Y' ? 1 : 0,
|
|
362
|
+
axis === 'Z' ? 1 : 0,
|
|
363
|
+
);
|
|
364
|
+
// Axis is defined in gizmo/target local; rotate to world using current gizmo quaternion
|
|
365
|
+
return v.applyQuaternion(this.quaternion).normalize();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
_planeIntersect() {
|
|
369
|
+
this._raycaster.setFromCamera(this._pointer, this.camera);
|
|
370
|
+
|
|
371
|
+
// Fix ray origin - ensure it starts from behind the camera
|
|
372
|
+
const ray = this._raycaster.ray;
|
|
373
|
+
if (this.camera.isOrthographicCamera) {
|
|
374
|
+
// For orthographic cameras, move the origin back along the camera's forward direction
|
|
375
|
+
const backwardDistance = 1000; // Large distance to ensure we're behind all objects
|
|
376
|
+
ray.origin.add(ray.direction.clone().multiplyScalar(-backwardDistance));
|
|
377
|
+
} else if (this.camera.isPerspectiveCamera) {
|
|
378
|
+
// For perspective cameras, use the camera position as origin
|
|
379
|
+
ray.origin.copy(this.camera.position);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const p = new THREE.Vector3();
|
|
383
|
+
const hit = this._raycaster.ray.intersectPlane(this._plane, p);
|
|
384
|
+
return hit ? p.clone() : null;
|
|
385
|
+
}
|
|
386
|
+
}
|