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,389 @@
|
|
|
1
|
+
// ViewCube.js
|
|
2
|
+
// Minimal view cube overlay rendered with scissor viewport.
|
|
3
|
+
// - Syncs orientation with a target camera
|
|
4
|
+
// - Click faces to reorient target camera to axis-aligned views
|
|
5
|
+
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
|
|
8
|
+
export class ViewCube {
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object} opts
|
|
11
|
+
* @param {THREE.WebGLRenderer} opts.renderer
|
|
12
|
+
* @param {THREE.Camera} opts.targetCamera
|
|
13
|
+
* @param {Object} [opts.controls] - ArcballControls (optional)
|
|
14
|
+
* @param {number} [opts.size=110] - widget size in pixels
|
|
15
|
+
* @param {number} [opts.margin=10] - margin from top-right
|
|
16
|
+
*/
|
|
17
|
+
constructor({ renderer, targetCamera, controls = null, size = 110, margin = 10, colors = null } = {}) {
|
|
18
|
+
if (!renderer || !targetCamera) throw new Error('ViewCube requires { renderer, targetCamera }');
|
|
19
|
+
this.renderer = renderer;
|
|
20
|
+
this.targetCamera = targetCamera;
|
|
21
|
+
this.controls = controls;
|
|
22
|
+
this.size = size;
|
|
23
|
+
this.margin = margin;
|
|
24
|
+
|
|
25
|
+
// Scene + camera for the cube
|
|
26
|
+
this.scene = new THREE.Scene();
|
|
27
|
+
this.scene.autoUpdate = true;
|
|
28
|
+
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
29
|
+
this.camera.position.set(0, 0, 3);
|
|
30
|
+
this.camera.lookAt(0, 0, 0);
|
|
31
|
+
|
|
32
|
+
// Root that mirrors target camera orientation
|
|
33
|
+
this.root = new THREE.Group();
|
|
34
|
+
this.scene.add(this.root);
|
|
35
|
+
|
|
36
|
+
// Sub-group for picking faces
|
|
37
|
+
this.pickGroup = new THREE.Group();
|
|
38
|
+
this.root.add(this.pickGroup);
|
|
39
|
+
|
|
40
|
+
// Visual cube (subtle base)
|
|
41
|
+
const baseGeom = new THREE.BoxGeometry(1, 1, 1);
|
|
42
|
+
const baseMat = new THREE.MeshBasicMaterial({ color: 0x9a9a9a, opacity: 0.18, transparent: true });
|
|
43
|
+
const baseMesh = new THREE.Mesh(baseGeom, baseMat);
|
|
44
|
+
this.root.add(baseMesh);
|
|
45
|
+
|
|
46
|
+
// Edges for contrast
|
|
47
|
+
try {
|
|
48
|
+
const edges = new THREE.EdgesGeometry(baseGeom);
|
|
49
|
+
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
|
|
50
|
+
this.root.add(line);
|
|
51
|
+
} catch { }
|
|
52
|
+
|
|
53
|
+
// Small helpers for color + label texture
|
|
54
|
+
const hexToRgb = (hex) => ({ r: (hex >> 16) & 255, g: (hex >> 8) & 255, b: hex & 255 });
|
|
55
|
+
const relLuma = (hex) => {
|
|
56
|
+
const { r, g, b } = hexToRgb(hex);
|
|
57
|
+
const s = [r, g, b].map(v => v / 255);
|
|
58
|
+
const lin = (c) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
59
|
+
const [R, G, B] = s.map(lin);
|
|
60
|
+
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
|
61
|
+
};
|
|
62
|
+
// Convert a CSS color or hex-int to a normalized css hex string and hex-int
|
|
63
|
+
const toCssAndHex = (input) => {
|
|
64
|
+
let css = '#ffffff';
|
|
65
|
+
if (typeof input === 'number') {
|
|
66
|
+
css = `#${input.toString(16).padStart(6, '0')}`;
|
|
67
|
+
} else if (typeof input === 'string') {
|
|
68
|
+
try {
|
|
69
|
+
const c = document.createElement('canvas');
|
|
70
|
+
c.width = c.height = 1;
|
|
71
|
+
const ctx2 = c.getContext('2d');
|
|
72
|
+
ctx2.fillStyle = '#000';
|
|
73
|
+
ctx2.fillStyle = input; // lets canvas parse CSS colors
|
|
74
|
+
const val = ctx2.fillStyle; // canonical string
|
|
75
|
+
if (typeof val === 'string') {
|
|
76
|
+
if (val.startsWith('#')) {
|
|
77
|
+
// #rgb, #rrggbb, or #rrggbbaa
|
|
78
|
+
let hex = val.replace('#', '');
|
|
79
|
+
if (hex.length === 3) hex = hex.split('').map(ch => ch + ch).join('');
|
|
80
|
+
else if (hex.length === 8) hex = hex.slice(0, 6);
|
|
81
|
+
css = `#${hex.toLowerCase()}`;
|
|
82
|
+
} else {
|
|
83
|
+
// rgb/rgba(r,g,b[,a])
|
|
84
|
+
const m = val.match(/rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([0-9.]+))?\)/i);
|
|
85
|
+
if (m) {
|
|
86
|
+
const r = Math.max(0, Math.min(255, parseInt(m[1], 10)));
|
|
87
|
+
const g = Math.max(0, Math.min(255, parseInt(m[2], 10)));
|
|
88
|
+
const b = Math.max(0, Math.min(255, parseInt(m[3], 10)));
|
|
89
|
+
const hex = ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
|
|
90
|
+
css = `#${hex}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch { }
|
|
95
|
+
}
|
|
96
|
+
const hex = parseInt(css.slice(1), 16) & 0xffffff;
|
|
97
|
+
return { css, hex };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Create a texture with the face color and imprinted label
|
|
101
|
+
const makeFaceTexture = (text, faceColor) => {
|
|
102
|
+
const size = 512; // square to avoid distortion on a square plane
|
|
103
|
+
const canvas = document.createElement('canvas');
|
|
104
|
+
canvas.width = size; canvas.height = size;
|
|
105
|
+
const ctx = canvas.getContext('2d');
|
|
106
|
+
// Background fill
|
|
107
|
+
const { css: faceCss, hex: faceHex } = toCssAndHex(faceColor);
|
|
108
|
+
ctx.fillStyle = faceCss;
|
|
109
|
+
ctx.fillRect(0, 0, size, size);
|
|
110
|
+
// Imprinted text effect: shadow + highlight to look engraved
|
|
111
|
+
const fontSize = 100; // smaller labels for better balance
|
|
112
|
+
ctx.font = `bold ${fontSize}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`;
|
|
113
|
+
ctx.textAlign = 'center';
|
|
114
|
+
ctx.textBaseline = 'middle';
|
|
115
|
+
const cx = size / 2, cy = size / 2;
|
|
116
|
+
const luma = relLuma(faceHex);
|
|
117
|
+
const baseText = luma < 0.45 ? '#f0f0f0' : '#111111';
|
|
118
|
+
const shadow = luma < 0.45 ? 'rgba(0,0,0,0.55)' : 'rgba(0,0,0,0.35)';
|
|
119
|
+
const highlight = luma < 0.45 ? 'rgba(255,255,255,0.35)' : 'rgba(255,255,255,0.55)';
|
|
120
|
+
// Soft inner shadow (dark offset)
|
|
121
|
+
ctx.fillStyle = shadow;
|
|
122
|
+
ctx.fillText(text, cx + 3, cy + 3);
|
|
123
|
+
// Highlight edge (light offset)
|
|
124
|
+
ctx.fillStyle = highlight;
|
|
125
|
+
ctx.fillText(text, cx - 1, cy - 1);
|
|
126
|
+
// Base text color chosen for contrast
|
|
127
|
+
ctx.fillStyle = baseText;
|
|
128
|
+
ctx.fillText(text, cx, cy);
|
|
129
|
+
const tex = new THREE.CanvasTexture(canvas);
|
|
130
|
+
tex.minFilter = THREE.LinearFilter;
|
|
131
|
+
tex.magFilter = THREE.LinearFilter;
|
|
132
|
+
return tex;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Helper: distance from origin so a plane with normal from {0,±1}^3 passes through cube boundary
|
|
136
|
+
const planeOffsetForMask = (maskVec) => {
|
|
137
|
+
const k = Math.abs(maskVec.x) + Math.abs(maskVec.y) + Math.abs(maskVec.z); // 1, 2 or 3
|
|
138
|
+
return 0.5 * Math.sqrt(k);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Face planes for picking + labels (main 6 faces)
|
|
142
|
+
const mkFace = (dir, color, name) => {
|
|
143
|
+
const g = new THREE.PlaneGeometry(0.98, 0.98);
|
|
144
|
+
const m = new THREE.MeshBasicMaterial({ map: makeFaceTexture(name, color), side: THREE.FrontSide });
|
|
145
|
+
const p = new THREE.Mesh(g, m);
|
|
146
|
+
// place at distance where face coincides with cube side (0.5)
|
|
147
|
+
const off = planeOffsetForMask(dir);
|
|
148
|
+
const n = dir.clone().normalize();
|
|
149
|
+
p.position.copy(n.multiplyScalar(off));
|
|
150
|
+
// orient plane to face outward
|
|
151
|
+
const q = new THREE.Quaternion();
|
|
152
|
+
q.setFromUnitVectors(new THREE.Vector3(0, 0, 1), dir.clone().normalize());
|
|
153
|
+
p.quaternion.copy(q);
|
|
154
|
+
p.userData = { dir: dir.clone().normalize(), name };
|
|
155
|
+
p.renderOrder = 1; // draw on top of base box
|
|
156
|
+
this.pickGroup.add(p);
|
|
157
|
+
|
|
158
|
+
return p;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Dark-mode friendly distinct colors for the 6 faces
|
|
162
|
+
// Accept any CSS color string or hex. You can override via constructor
|
|
163
|
+
// with { colors: { faces: { RIGHT: 'tomato', ... }, edge: '...', corner: '...' } }
|
|
164
|
+
const FACE_DEFAULTS = {
|
|
165
|
+
RIGHT: '#ff0000',
|
|
166
|
+
LEFT: '#0055ffff',
|
|
167
|
+
TOP: '#00ff77ff',
|
|
168
|
+
BOTTOM: '#ffb300ff',
|
|
169
|
+
FRONT: '#ff00b7ff',
|
|
170
|
+
BACK: '#00ffe5ff',
|
|
171
|
+
};
|
|
172
|
+
let faceOverrides = {};
|
|
173
|
+
if (colors) {
|
|
174
|
+
if (colors.faces) faceOverrides = colors.faces;
|
|
175
|
+
else if (colors.RIGHT || colors.LEFT || colors.TOP || colors.BOTTOM || colors.FRONT || colors.BACK) faceOverrides = colors;
|
|
176
|
+
}
|
|
177
|
+
const FACE = Object.assign({}, FACE_DEFAULTS, faceOverrides);
|
|
178
|
+
|
|
179
|
+
// Edge/corner colors (define before creating materials)
|
|
180
|
+
const EDGE_COLOR = (colors && colors.edge) || '#ffffffff';
|
|
181
|
+
const CORNER_COLOR = (colors && colors.corner) || '#3a3636ff';
|
|
182
|
+
const EDGE_COLOR_CSS = toCssAndHex(EDGE_COLOR).css;
|
|
183
|
+
const CORNER_COLOR_CSS = toCssAndHex(CORNER_COLOR).css;
|
|
184
|
+
mkFace(new THREE.Vector3(1, 0, 0), FACE.RIGHT, 'RIGHT');
|
|
185
|
+
mkFace(new THREE.Vector3(-1, 0, 0), FACE.LEFT, 'LEFT');
|
|
186
|
+
mkFace(new THREE.Vector3(0, 1, 0), FACE.TOP, 'TOP');
|
|
187
|
+
mkFace(new THREE.Vector3(0, -1, 0), FACE.BOTTOM, 'BOTTOM');
|
|
188
|
+
mkFace(new THREE.Vector3(0, 0, 1), FACE.FRONT, 'FRONT');
|
|
189
|
+
mkFace(new THREE.Vector3(0, 0, -1), FACE.BACK, 'BACK');
|
|
190
|
+
|
|
191
|
+
// Edge faces (12) - beveled rectangles to mimic chamfered edges
|
|
192
|
+
const mkEdge = (normalMask, along, name) => {
|
|
193
|
+
const n = normalMask.clone().normalize();
|
|
194
|
+
const u = along.clone().normalize(); // width axis on the plane (edge direction)
|
|
195
|
+
const v = new THREE.Vector3().crossVectors(n, u).normalize();
|
|
196
|
+
const matBasis = new THREE.Matrix4().makeBasis(u, v, n);
|
|
197
|
+
const q = new THREE.Quaternion().setFromRotationMatrix(matBasis);
|
|
198
|
+
const g = new THREE.PlaneGeometry(0.98, 0.16);
|
|
199
|
+
const m = new THREE.MeshBasicMaterial({ color: EDGE_COLOR_CSS, opacity: 1.0, transparent: false, side: THREE.FrontSide });
|
|
200
|
+
const mesh = new THREE.Mesh(g, m);
|
|
201
|
+
mesh.quaternion.copy(q);
|
|
202
|
+
const off = planeOffsetForMask(normalMask);
|
|
203
|
+
mesh.position.copy(n.clone().multiplyScalar(off));
|
|
204
|
+
mesh.userData = { dir: n.clone(), name };
|
|
205
|
+
mesh.renderOrder = 2;
|
|
206
|
+
this.pickGroup.add(mesh);
|
|
207
|
+
return mesh;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// X± Y± edges -> along Z
|
|
211
|
+
mkEdge(new THREE.Vector3(1, 1, 0), new THREE.Vector3(0, 0, 1), 'TOP RIGHT EDGE');
|
|
212
|
+
mkEdge(new THREE.Vector3(-1, 1, 0), new THREE.Vector3(0, 0, 1), 'TOP LEFT EDGE');
|
|
213
|
+
mkEdge(new THREE.Vector3(1, -1, 0), new THREE.Vector3(0, 0, 1), 'BOTTOM RIGHT EDGE');
|
|
214
|
+
mkEdge(new THREE.Vector3(-1, -1, 0), new THREE.Vector3(0, 0, 1), 'BOTTOM LEFT EDGE');
|
|
215
|
+
|
|
216
|
+
// X± Z± edges -> along Y
|
|
217
|
+
mkEdge(new THREE.Vector3(1, 0, 1), new THREE.Vector3(0, 1, 0), 'FRONT RIGHT EDGE');
|
|
218
|
+
mkEdge(new THREE.Vector3(-1, 0, 1), new THREE.Vector3(0, 1, 0), 'FRONT LEFT EDGE');
|
|
219
|
+
mkEdge(new THREE.Vector3(1, 0, -1), new THREE.Vector3(0, 1, 0), 'BACK RIGHT EDGE');
|
|
220
|
+
mkEdge(new THREE.Vector3(-1, 0, -1), new THREE.Vector3(0, 1, 0), 'BACK LEFT EDGE');
|
|
221
|
+
|
|
222
|
+
// Y± Z± edges -> along X
|
|
223
|
+
mkEdge(new THREE.Vector3(0, 1, 1), new THREE.Vector3(1, 0, 0), 'TOP FRONT EDGE');
|
|
224
|
+
mkEdge(new THREE.Vector3(0, -1, 1), new THREE.Vector3(1, 0, 0), 'BOTTOM FRONT EDGE');
|
|
225
|
+
mkEdge(new THREE.Vector3(0, 1, -1), new THREE.Vector3(1, 0, 0), 'TOP BACK EDGE');
|
|
226
|
+
mkEdge(new THREE.Vector3(0, -1, -1), new THREE.Vector3(1, 0, 0), 'BOTTOM BACK EDGE');
|
|
227
|
+
|
|
228
|
+
// Corner knobs for isometric views (clickable)
|
|
229
|
+
// Slightly protruding spheres at cube corners, each maps to a diagonal view
|
|
230
|
+
const mkCorner = (dirMask, color, name) => {
|
|
231
|
+
const n = dirMask.clone().normalize();
|
|
232
|
+
// Triangular disk to resemble a chamfered corner
|
|
233
|
+
const g = new THREE.CircleGeometry(0.14, 3);
|
|
234
|
+
const m = new THREE.MeshBasicMaterial({ color: CORNER_COLOR_CSS, opacity: 1.0, transparent: false, side: THREE.FrontSide });
|
|
235
|
+
const tri = new THREE.Mesh(g, m);
|
|
236
|
+
// Build basis so X axis is some stable vector in the plane
|
|
237
|
+
let u = new THREE.Vector3(0, 1, 0);
|
|
238
|
+
if (Math.abs(n.dot(u)) > 0.9) u = new THREE.Vector3(1, 0, 0); // avoid parallel
|
|
239
|
+
u = new THREE.Vector3().crossVectors(u, n).normalize();
|
|
240
|
+
const v = new THREE.Vector3().crossVectors(n, u).normalize();
|
|
241
|
+
const matBasis = new THREE.Matrix4().makeBasis(u, v, n);
|
|
242
|
+
const q = new THREE.Quaternion().setFromRotationMatrix(matBasis);
|
|
243
|
+
tri.quaternion.copy(q);
|
|
244
|
+
const off = planeOffsetForMask(dirMask);
|
|
245
|
+
tri.position.copy(n.clone().multiplyScalar(off));
|
|
246
|
+
tri.userData = { dir: n, name };
|
|
247
|
+
tri.renderOrder = 3;
|
|
248
|
+
this.pickGroup.add(tri);
|
|
249
|
+
return tri;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Define all 8 corners with readable names
|
|
253
|
+
const C = (x, y, z) => new THREE.Vector3(x, y, z);
|
|
254
|
+
mkCorner(C(1, 1, 1), CORNER_COLOR, 'TOP FRONT RIGHT');
|
|
255
|
+
mkCorner(C(-1, 1, 1), CORNER_COLOR, 'TOP FRONT LEFT');
|
|
256
|
+
mkCorner(C(1, 1, -1), CORNER_COLOR, 'TOP BACK RIGHT');
|
|
257
|
+
mkCorner(C(-1, 1, -1), CORNER_COLOR, 'TOP BACK LEFT');
|
|
258
|
+
mkCorner(C(1, -1, 1), CORNER_COLOR, 'BOTTOM FRONT RIGHT');
|
|
259
|
+
mkCorner(C(-1, -1, 1), CORNER_COLOR, 'BOTTOM FRONT LEFT');
|
|
260
|
+
mkCorner(C(1, -1, -1), CORNER_COLOR, 'BOTTOM BACK RIGHT');
|
|
261
|
+
mkCorner(C(-1, -1, -1), CORNER_COLOR, 'BOTTOM BACK LEFT');
|
|
262
|
+
|
|
263
|
+
// Soft ambient to ensure steady colors regardless of renderer state
|
|
264
|
+
const amb = new THREE.AmbientLight(0xffffff, 0.9);
|
|
265
|
+
this.scene.add(amb);
|
|
266
|
+
|
|
267
|
+
// Raycaster for cube picking
|
|
268
|
+
this._raycaster = new THREE.Raycaster();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Keep cube orientation in sync with target camera
|
|
272
|
+
syncWithCamera() {
|
|
273
|
+
if (!this.targetCamera) return;
|
|
274
|
+
// Use the inverse of the target camera's rotation so the widget
|
|
275
|
+
// represents world orientation as seen from the camera (avoids mirroring).
|
|
276
|
+
this.root.quaternion.copy(this.targetCamera.quaternion).invert();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Compute viewport rectangle (bottom-right). Returns both CSS(top-left) and GL(bottom-left) coords.
|
|
280
|
+
_viewportRect() {
|
|
281
|
+
const el = this.renderer.domElement;
|
|
282
|
+
const width = el.clientWidth || 1;
|
|
283
|
+
const height = el.clientHeight || 1;
|
|
284
|
+
const w = Math.min(this.size, width);
|
|
285
|
+
const h = Math.min(this.size, height);
|
|
286
|
+
const xCss = width - w - this.margin; // from top-left
|
|
287
|
+
const yCss = height - h - this.margin; // bottom-right in CSS coords
|
|
288
|
+
const xGL = xCss; // same horizontally
|
|
289
|
+
const yGL = this.margin; // bottom margin in GL coords
|
|
290
|
+
return { xCss, yCss, xGL, yGL, w, h, width, height };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Render the view cube using scissor in the top-right corner
|
|
294
|
+
render() {
|
|
295
|
+
const { xGL, yGL, w, h, width, height } = this._viewportRect();
|
|
296
|
+
const r = this.renderer;
|
|
297
|
+
const prev = {
|
|
298
|
+
scissorTest: r.getScissorTest && r.getScissorTest(),
|
|
299
|
+
autoClear: r.autoClear,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Update camera for aspect
|
|
303
|
+
const aspect = w / h || 1;
|
|
304
|
+
this.camera.left = -1 * aspect;
|
|
305
|
+
this.camera.right = 1 * aspect;
|
|
306
|
+
this.camera.top = 1;
|
|
307
|
+
this.camera.bottom = -1;
|
|
308
|
+
this.camera.updateProjectionMatrix();
|
|
309
|
+
|
|
310
|
+
// Render cube without clearing color to keep background transparent
|
|
311
|
+
r.setScissorTest(true);
|
|
312
|
+
r.autoClear = false;
|
|
313
|
+
r.setScissor(xGL, yGL, w, h);
|
|
314
|
+
r.setViewport(xGL, yGL, w, h);
|
|
315
|
+
r.clearDepth();
|
|
316
|
+
this.syncWithCamera();
|
|
317
|
+
r.render(this.scene, this.camera);
|
|
318
|
+
|
|
319
|
+
// Restore viewport/scissor for main renderer
|
|
320
|
+
r.setViewport(0, 0, width, height);
|
|
321
|
+
r.setScissor(0, 0, width, height);
|
|
322
|
+
r.setScissorTest(!!prev.scissorTest);
|
|
323
|
+
r.autoClear = prev.autoClear;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check if a DOM pointer event is inside the cube viewport
|
|
327
|
+
isEventInside(event) {
|
|
328
|
+
const rect = this._viewportRect();
|
|
329
|
+
const elRect = this.renderer.domElement.getBoundingClientRect();
|
|
330
|
+
const px = event.clientX - elRect.left;
|
|
331
|
+
const py = event.clientY - elRect.top;
|
|
332
|
+
return (px >= rect.xCss && px <= rect.xCss + rect.w &&
|
|
333
|
+
py >= rect.yCss && py <= rect.yCss + rect.h);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Attempt to handle a click; returns true if consumed
|
|
337
|
+
handleClick(event) {
|
|
338
|
+
if (!this.isEventInside(event)) return false;
|
|
339
|
+
const { xCss, yCss, w, h } = this._viewportRect();
|
|
340
|
+
const elRect = this.renderer.domElement.getBoundingClientRect();
|
|
341
|
+
const cx = event.clientX - elRect.left;
|
|
342
|
+
const cy = event.clientY - elRect.top;
|
|
343
|
+
const nx = ((cx - xCss) / w) * 2 - 1;
|
|
344
|
+
const ny = -((cy - yCss) / h) * 2 + 1;
|
|
345
|
+
const ndc = new THREE.Vector2(nx, ny);
|
|
346
|
+
|
|
347
|
+
this._raycaster.setFromCamera(ndc, this.camera);
|
|
348
|
+
const intersects = this._raycaster.intersectObjects(this.pickGroup.children, false);
|
|
349
|
+
if (intersects && intersects.length) {
|
|
350
|
+
const face = intersects[0].object;
|
|
351
|
+
const dir = face?.userData?.dir;
|
|
352
|
+
const name = face?.userData?.name || '';
|
|
353
|
+
if (dir) this._reorientCamera(dir, name);
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
_reorientCamera(dir, faceName = '') {
|
|
360
|
+
const cam = this.targetCamera;
|
|
361
|
+
if (!cam) return;
|
|
362
|
+
|
|
363
|
+
// Determine current pivot (ArcballControls center) and keep distance to it
|
|
364
|
+
const pivot = (this.controls && this.controls._gizmos && this.controls._gizmos.position)
|
|
365
|
+
? this.controls._gizmos.position.clone()
|
|
366
|
+
: new THREE.Vector3(0, 0, 0);
|
|
367
|
+
const dist = cam.position.distanceTo(pivot) || cam.position.length() || 10;
|
|
368
|
+
const pos = pivot.clone().add(dir.clone().normalize().multiplyScalar(dist));
|
|
369
|
+
|
|
370
|
+
// Choose a stable up vector for the final view
|
|
371
|
+
const useZup = Math.abs(dir.y) > 0.9; // top/bottom -> Z up to avoid roll
|
|
372
|
+
const up = useZup ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0);
|
|
373
|
+
|
|
374
|
+
const toPos = pos;
|
|
375
|
+
|
|
376
|
+
// Immediate reorientation: absolute pose using lookAt toward pivot
|
|
377
|
+
cam.position.copy(toPos);
|
|
378
|
+
cam.up.copy(up);
|
|
379
|
+
cam.lookAt(pivot);
|
|
380
|
+
cam.updateMatrixWorld(true);
|
|
381
|
+
|
|
382
|
+
// Sync controls to the new absolute state
|
|
383
|
+
const controls = this.controls;
|
|
384
|
+
if (controls && controls.updateMatrixState) {
|
|
385
|
+
try { controls.updateMatrixState(); } catch { }
|
|
386
|
+
}
|
|
387
|
+
if (controls) controls.enabled = true;
|
|
388
|
+
}
|
|
389
|
+
}
|