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,647 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { BaseAnnotation } from '../BaseAnnotation.js';
|
|
3
|
+
import { makeOverlayLine, addArrowCone, getElementDirection, objectRepresentativePoint, screenSizeWorld } from '../annUtils.js';
|
|
4
|
+
|
|
5
|
+
const inputParamsSchema = {
|
|
6
|
+
id: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
default_value: null,
|
|
9
|
+
label: 'ID',
|
|
10
|
+
hint: 'unique identifier for the angle dimension',
|
|
11
|
+
},
|
|
12
|
+
decimals: {
|
|
13
|
+
type: 'number',
|
|
14
|
+
default_value: 1,
|
|
15
|
+
defaultResolver: ({ pmimode }) => {
|
|
16
|
+
const dec = Number.isFinite(pmimode?._opts?.angleDecimals)
|
|
17
|
+
? (pmimode._opts.angleDecimals | 0)
|
|
18
|
+
: undefined;
|
|
19
|
+
if (!Number.isFinite(dec)) return undefined;
|
|
20
|
+
return Math.max(0, Math.min(3, dec));
|
|
21
|
+
},
|
|
22
|
+
label: 'Decimals',
|
|
23
|
+
hint: 'Number of decimal places to display',
|
|
24
|
+
min: 0,
|
|
25
|
+
max: 3,
|
|
26
|
+
step: 1,
|
|
27
|
+
},
|
|
28
|
+
targets: {
|
|
29
|
+
type: 'reference_selection',
|
|
30
|
+
selectionFilter: ['FACE', 'EDGE'],
|
|
31
|
+
multiple: true,
|
|
32
|
+
default_value: [],
|
|
33
|
+
label: 'Elements',
|
|
34
|
+
hint: 'Select two edges/faces to define the angle',
|
|
35
|
+
minSelections: 2,
|
|
36
|
+
maxSelections: 2,
|
|
37
|
+
},
|
|
38
|
+
planeRefName: {
|
|
39
|
+
type: 'reference_selection',
|
|
40
|
+
selectionFilter: ['FACE', 'PLANE'],
|
|
41
|
+
multiple: false,
|
|
42
|
+
default_value: '',
|
|
43
|
+
label: 'Projection Plane',
|
|
44
|
+
hint: 'Override projection plane (optional)',
|
|
45
|
+
},
|
|
46
|
+
reverseElementOrder: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
default_value: false,
|
|
49
|
+
label: 'Reverse Selection Order',
|
|
50
|
+
hint: 'Swap Element A and Element B to flip the measured side',
|
|
51
|
+
},
|
|
52
|
+
isReference: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
default_value: false,
|
|
55
|
+
label: 'Reference',
|
|
56
|
+
hint: 'Mark as reference dimension (parentheses)',
|
|
57
|
+
},
|
|
58
|
+
angleType: {
|
|
59
|
+
type: 'options',
|
|
60
|
+
default_value: 'acute',
|
|
61
|
+
options: ['acute', 'obtuse', 'reflex'],
|
|
62
|
+
label: 'Angle Type',
|
|
63
|
+
hint: 'Choose which angle (acute, obtuse, or reflex) to display',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export class AngleDimensionAnnotation extends BaseAnnotation {
|
|
68
|
+
static entityType = 'angle';
|
|
69
|
+
static type = 'angle';
|
|
70
|
+
static shortName = 'ANG';
|
|
71
|
+
static longName = 'Angle Dimension';
|
|
72
|
+
static title = 'Angle';
|
|
73
|
+
static inputParamsSchema = inputParamsSchema;
|
|
74
|
+
|
|
75
|
+
constructor(opts = {}) {
|
|
76
|
+
super(opts);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async run(renderingContext) {
|
|
80
|
+
const { pmimode, group, idx, ctx } = renderingContext;
|
|
81
|
+
const ann = this.inputParams;
|
|
82
|
+
const measured = measureAngleValue(pmimode, ann);
|
|
83
|
+
const labelInfo = formatAngleLabel(measured, ann);
|
|
84
|
+
ann.value = labelInfo.display;
|
|
85
|
+
|
|
86
|
+
ensurePersistent(ann);
|
|
87
|
+
try {
|
|
88
|
+
const elements = computeAngleElementsWithGeometry(pmimode, ann, ctx);
|
|
89
|
+
if (!elements || !elements.__2d) return [];
|
|
90
|
+
|
|
91
|
+
const color = 0xf59e0b;
|
|
92
|
+
const { N, P, A_p, B_p, A_d, B_d, V2, basis, sweep = 0, dirSign = 1 } = elements.__2d;
|
|
93
|
+
|
|
94
|
+
let R = null;
|
|
95
|
+
if (ann.persistentData?.labelWorld) {
|
|
96
|
+
const labelVec = vectorFromAny(ann.persistentData.labelWorld);
|
|
97
|
+
if (labelVec) {
|
|
98
|
+
const projected = projectPointToPlane(labelVec, P, N);
|
|
99
|
+
const L2 = to2D(projected, P, basis);
|
|
100
|
+
R = L2.clone().sub(V2).length();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!Number.isFinite(R) || R <= 0) {
|
|
104
|
+
R = ctx.screenSizeWorld ? ctx.screenSizeWorld(60) : screenSizeWorld(pmimode?.viewer, 60);
|
|
105
|
+
}
|
|
106
|
+
R = Math.max(R, ctx.screenSizeWorld ? ctx.screenSizeWorld(30) : screenSizeWorld(pmimode?.viewer, 30));
|
|
107
|
+
|
|
108
|
+
const arcPoints = [];
|
|
109
|
+
if (sweep > 1e-6) {
|
|
110
|
+
const steps = Math.max(48, Math.floor(sweep * 64));
|
|
111
|
+
for (let i = 0; i <= steps; i++) {
|
|
112
|
+
const t = i / steps;
|
|
113
|
+
const angle = dirSign * sweep * t;
|
|
114
|
+
const rot = rotate2D(A_d, angle);
|
|
115
|
+
const pt2 = new THREE.Vector2(
|
|
116
|
+
V2.x + rot.x * R,
|
|
117
|
+
V2.y + rot.y * R,
|
|
118
|
+
);
|
|
119
|
+
arcPoints.push(from2D(pt2, P, basis));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (arcPoints.length >= 2) {
|
|
123
|
+
for (let i = 0; i < arcPoints.length - 1; i++) {
|
|
124
|
+
group.add(makeOverlayLine(arcPoints[i], arcPoints[i + 1], color));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const arrowLength = ctx.screenSizeWorld ? ctx.screenSizeWorld(10) : screenSizeWorld(pmimode?.viewer, 10);
|
|
129
|
+
const arrowWidth = ctx.screenSizeWorld ? ctx.screenSizeWorld(4) : screenSizeWorld(pmimode?.viewer, 4);
|
|
130
|
+
if (arcPoints.length >= 2) {
|
|
131
|
+
addArrowCone(group, arcPoints[0], arcPoints[0].clone().sub(arcPoints[1]).normalize(), arrowLength, arrowWidth, color);
|
|
132
|
+
const last = arcPoints[arcPoints.length - 1];
|
|
133
|
+
const beforeLast = arcPoints[arcPoints.length - 2] || last.clone();
|
|
134
|
+
addArrowCone(group, last, last.clone().sub(beforeLast).normalize(), arrowLength, arrowWidth, color);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const ext = R + (ctx.screenSizeWorld ? ctx.screenSizeWorld(25) : screenSizeWorld(pmimode?.viewer, 25));
|
|
138
|
+
const stub = ctx.screenSizeWorld ? ctx.screenSizeWorld(12) : screenSizeWorld(pmimode?.viewer, 12);
|
|
139
|
+
const A1 = from2D(new THREE.Vector2(V2.x + A_d.x * ext, V2.y + A_d.y * ext), P, basis);
|
|
140
|
+
const B1 = from2D(new THREE.Vector2(V2.x + B_d.x * ext, V2.y + B_d.y * ext), P, basis);
|
|
141
|
+
const A0 = from2D(new THREE.Vector2(V2.x - A_d.x * stub, V2.y - A_d.y * stub), P, basis);
|
|
142
|
+
const B0 = from2D(new THREE.Vector2(V2.x - B_d.x * stub, V2.y - B_d.y * stub), P, basis);
|
|
143
|
+
const V3 = from2D(V2, P, basis);
|
|
144
|
+
group.add(makeOverlayLine(V3, A1, color));
|
|
145
|
+
group.add(makeOverlayLine(V3, B1, color));
|
|
146
|
+
group.add(makeOverlayLine(V3, A0, color));
|
|
147
|
+
group.add(makeOverlayLine(V3, B0, color));
|
|
148
|
+
|
|
149
|
+
if (typeof measured === 'number') {
|
|
150
|
+
const info = formatAngleLabel(measured, ann);
|
|
151
|
+
const raw = info.raw;
|
|
152
|
+
ann.value = info.display;
|
|
153
|
+
const txt = ctx.formatReferenceLabel ? ctx.formatReferenceLabel(ann, raw) : info.display;
|
|
154
|
+
const labelPos = resolveLabelPosition(pmimode, ann, elements, R, ctx);
|
|
155
|
+
if (labelPos) ctx.updateLabel(idx, txt, labelPos, ann);
|
|
156
|
+
}
|
|
157
|
+
} catch { /* ignore rendering errors */ }
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static onLabelPointerDown(pmimode, idx, ann, e, ctx) {
|
|
162
|
+
try {
|
|
163
|
+
const elements = computeAngleElementsWithGeometry(pmimode, ann, ctx);
|
|
164
|
+
if (!elements) return;
|
|
165
|
+
const planeInfo = resolveAnglePlane(pmimode, ann, elements, ctx);
|
|
166
|
+
const normal = planeInfo?.n || new THREE.Vector3(0, 0, 1);
|
|
167
|
+
const anchorPoint = planeInfo?.p || new THREE.Vector3();
|
|
168
|
+
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(normal, anchorPoint);
|
|
169
|
+
try { pmimode?.showDragPlaneHelper?.(plane); } catch { }
|
|
170
|
+
|
|
171
|
+
const onMove = (ev) => {
|
|
172
|
+
const ray = ctx.raycastFromEvent ? ctx.raycastFromEvent(ev) : null;
|
|
173
|
+
if (!ray) return;
|
|
174
|
+
const out = new THREE.Vector3();
|
|
175
|
+
if (ctx.intersectPlane ? ctx.intersectPlane(ray, plane, out) : ray.intersectPlane(plane, out)) {
|
|
176
|
+
ensurePersistent(ann);
|
|
177
|
+
ann.persistentData.labelWorld = [out.x, out.y, out.z];
|
|
178
|
+
ctx.updateLabel(idx, null, out, ann);
|
|
179
|
+
pmimode.refreshAnnotationsUI?.();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const onUp = (ev) => {
|
|
184
|
+
try {
|
|
185
|
+
window.removeEventListener('pointermove', onMove, true);
|
|
186
|
+
window.removeEventListener('pointerup', onUp, true);
|
|
187
|
+
} catch { }
|
|
188
|
+
try { pmimode?.hideDragPlaneHelper?.(); } catch { }
|
|
189
|
+
try { if (pmimode.viewer?.controls) pmimode.viewer.controls.enabled = true; } catch { }
|
|
190
|
+
try { ev.preventDefault(); ev.stopImmediatePropagation?.(); ev.stopPropagation(); } catch { }
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
window.addEventListener('pointermove', onMove, true);
|
|
194
|
+
window.addEventListener('pointerup', onUp, true);
|
|
195
|
+
} catch { /* ignore */ }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function ensurePersistent(ann) {
|
|
201
|
+
if (!ann.persistentData || typeof ann.persistentData !== 'object') {
|
|
202
|
+
ann.persistentData = {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeTargetNames(value) {
|
|
207
|
+
const list = Array.isArray(value) ? value : (value == null || value === '' ? [] : [value]);
|
|
208
|
+
const out = [];
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
for (const entry of list) {
|
|
211
|
+
let name = '';
|
|
212
|
+
if (entry && typeof entry === 'object') {
|
|
213
|
+
if (typeof entry.name === 'string') name = entry.name;
|
|
214
|
+
else if (entry.id != null) name = String(entry.id);
|
|
215
|
+
} else if (entry != null) {
|
|
216
|
+
name = String(entry);
|
|
217
|
+
}
|
|
218
|
+
name = name.trim();
|
|
219
|
+
if (!name || seen.has(name)) continue;
|
|
220
|
+
seen.add(name);
|
|
221
|
+
out.push(name);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function resolveElementRefNames(ann) {
|
|
227
|
+
const hasTargets = Array.isArray(ann?.targets);
|
|
228
|
+
const targets = normalizeTargetNames(hasTargets ? ann.targets : null);
|
|
229
|
+
let elementARefName = '';
|
|
230
|
+
let elementBRefName = '';
|
|
231
|
+
if (targets.length) {
|
|
232
|
+
elementARefName = targets[0] || '';
|
|
233
|
+
elementBRefName = targets[1] || '';
|
|
234
|
+
} else if (!hasTargets) {
|
|
235
|
+
elementARefName = ann?.elementARefName || '';
|
|
236
|
+
elementBRefName = ann?.elementBRefName || '';
|
|
237
|
+
}
|
|
238
|
+
if (ann?.reverseElementOrder) {
|
|
239
|
+
return {
|
|
240
|
+
elementARefName: elementBRefName,
|
|
241
|
+
elementBRefName: elementARefName,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return { elementARefName, elementBRefName };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function resolveAngleType(ann) {
|
|
248
|
+
const raw = ann?.angleType;
|
|
249
|
+
if (raw === 'acute' || raw === 'obtuse' || raw === 'reflex') return raw;
|
|
250
|
+
if (ann?.useReflexAngle) return 'reflex';
|
|
251
|
+
return 'acute';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isAngleTypeExplicit(ann) {
|
|
255
|
+
if (!ann || typeof ann !== 'object') return false;
|
|
256
|
+
if (!Object.prototype.hasOwnProperty.call(ann, 'angleType')) return false;
|
|
257
|
+
return ann.angleType === 'acute' || ann.angleType === 'obtuse' || ann.angleType === 'reflex';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function resolveAngleOrientation2D(A_dir, B_dir, angleType, isExplicitType = true) {
|
|
261
|
+
if (!A_dir || !B_dir) return null;
|
|
262
|
+
const combos = [
|
|
263
|
+
createAngleCombo(A_dir, B_dir, false, false),
|
|
264
|
+
createAngleCombo(A_dir, B_dir, false, true),
|
|
265
|
+
createAngleCombo(A_dir, B_dir, true, false),
|
|
266
|
+
createAngleCombo(A_dir, B_dir, true, true),
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const halfPi = Math.PI / 2;
|
|
270
|
+
const eps = 1e-6;
|
|
271
|
+
|
|
272
|
+
const selectByAngle = (items, comparator) => {
|
|
273
|
+
if (!items.length) return null;
|
|
274
|
+
const copy = items.slice().sort(comparator);
|
|
275
|
+
return copy[0];
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
if (angleType === 'reflex') {
|
|
279
|
+
const base = combos[0];
|
|
280
|
+
const baseAbs = Math.abs(base.signedAngle);
|
|
281
|
+
let reflexAngle = baseAbs < 1e-6 ? 0 : (2 * Math.PI) - baseAbs;
|
|
282
|
+
const dirSign = -Math.sign(base.signedAngle || 1);
|
|
283
|
+
const bisector = base.A.clone().add(base.B);
|
|
284
|
+
if (bisector.lengthSq() < 1e-10) bisector.set(-base.A.y, base.A.x);
|
|
285
|
+
bisector.normalize().multiplyScalar(-1);
|
|
286
|
+
return {
|
|
287
|
+
start: base.A.clone(),
|
|
288
|
+
end: base.B.clone(),
|
|
289
|
+
sweep: reflexAngle,
|
|
290
|
+
dirSign,
|
|
291
|
+
angleRad: reflexAngle,
|
|
292
|
+
bisector,
|
|
293
|
+
angleType: 'reflex',
|
|
294
|
+
signedAngle: base.signedAngle,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (angleType === 'acute') {
|
|
299
|
+
if (!isExplicitType) {
|
|
300
|
+
const base = combos[0];
|
|
301
|
+
const bisector = base.A.clone().add(base.B);
|
|
302
|
+
if (bisector.lengthSq() < 1e-10) bisector.set(-base.A.y, base.A.x);
|
|
303
|
+
bisector.normalize();
|
|
304
|
+
const sign = Math.abs(base.signedAngle) < 1e-8 ? 1 : Math.sign(base.signedAngle);
|
|
305
|
+
const sweep = Math.abs(base.signedAngle);
|
|
306
|
+
return {
|
|
307
|
+
start: base.A.clone(),
|
|
308
|
+
end: base.B.clone(),
|
|
309
|
+
sweep,
|
|
310
|
+
dirSign: sign,
|
|
311
|
+
angleRad: sweep,
|
|
312
|
+
bisector,
|
|
313
|
+
angleType: 'acute',
|
|
314
|
+
signedAngle: base.signedAngle,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const candidates = combos.filter((c) => c.angle <= halfPi + eps);
|
|
318
|
+
const selected = selectByAngle(candidates.length ? candidates : combos, (a, b) => a.angle - b.angle);
|
|
319
|
+
if (!selected) return null;
|
|
320
|
+
const bisector = selected.A.clone().add(selected.B);
|
|
321
|
+
if (bisector.lengthSq() < 1e-10) bisector.set(-selected.A.y, selected.A.x);
|
|
322
|
+
bisector.normalize();
|
|
323
|
+
const sign = Math.abs(selected.signedAngle) < 1e-8 ? 1 : Math.sign(selected.signedAngle);
|
|
324
|
+
return {
|
|
325
|
+
start: selected.A.clone(),
|
|
326
|
+
end: selected.B.clone(),
|
|
327
|
+
sweep: selected.angle,
|
|
328
|
+
dirSign: sign,
|
|
329
|
+
angleRad: selected.angle,
|
|
330
|
+
bisector,
|
|
331
|
+
angleType: 'acute',
|
|
332
|
+
signedAngle: selected.signedAngle,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (angleType === 'obtuse') {
|
|
337
|
+
const candidates = combos.filter((c) => c.angle >= halfPi - eps);
|
|
338
|
+
let selected = selectByAngle(candidates, (a, b) => a.angle - b.angle);
|
|
339
|
+
if (!selected) selected = selectByAngle(combos, (a, b) => b.angle - a.angle);
|
|
340
|
+
if (!selected) return null;
|
|
341
|
+
const bisector = selected.A.clone().add(selected.B);
|
|
342
|
+
if (bisector.lengthSq() < 1e-10) bisector.set(-selected.A.y, selected.A.x);
|
|
343
|
+
bisector.normalize();
|
|
344
|
+
const sign = Math.abs(selected.signedAngle) < 1e-8 ? 1 : Math.sign(selected.signedAngle);
|
|
345
|
+
return {
|
|
346
|
+
start: selected.A.clone(),
|
|
347
|
+
end: selected.B.clone(),
|
|
348
|
+
sweep: selected.angle,
|
|
349
|
+
dirSign: sign,
|
|
350
|
+
angleRad: selected.angle,
|
|
351
|
+
bisector,
|
|
352
|
+
angleType: 'obtuse',
|
|
353
|
+
signedAngle: selected.signedAngle,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function createAngleCombo(A_dir, B_dir, flipA, flipB) {
|
|
361
|
+
const a = flipA ? A_dir.clone().multiplyScalar(-1) : A_dir.clone();
|
|
362
|
+
const b = flipB ? B_dir.clone().multiplyScalar(-1) : B_dir.clone();
|
|
363
|
+
const dot = clampToUnit(a.dot(b));
|
|
364
|
+
const angle = Math.acos(dot);
|
|
365
|
+
const signedAngle = signedAngle2D(a, b);
|
|
366
|
+
return { A: a, B: b, angle, signedAngle };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function clampToUnit(value) {
|
|
370
|
+
if (value > 1) return 1;
|
|
371
|
+
if (value < -1) return -1;
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function formatAngleLabel(measured, ann) {
|
|
376
|
+
if (typeof measured !== 'number' || !Number.isFinite(measured)) {
|
|
377
|
+
return { raw: '-', display: '-' };
|
|
378
|
+
}
|
|
379
|
+
const decRaw = Number(ann?.decimals);
|
|
380
|
+
const decimals = Number.isFinite(decRaw) ? Math.max(0, Math.min(3, decRaw | 0)) : 1;
|
|
381
|
+
const raw = `${measured.toFixed(decimals)}°`;
|
|
382
|
+
const display = ann?.isReference ? `(${raw})` : raw;
|
|
383
|
+
return { raw, display };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function measureAngleValue(pmimode, ann) {
|
|
387
|
+
try {
|
|
388
|
+
const elements = computeAngleElements(pmimode, ann);
|
|
389
|
+
const plane = resolveAnglePlane(pmimode, ann, elements);
|
|
390
|
+
if (!plane) return null;
|
|
391
|
+
const { elementARefName, elementBRefName } = resolveElementRefNames(ann);
|
|
392
|
+
const lineA = lineInPlaneForElement(pmimode, elementARefName, plane.n, plane.p);
|
|
393
|
+
const lineB = lineInPlaneForElement(pmimode, elementBRefName, plane.n, plane.p);
|
|
394
|
+
if (!lineA || !lineB) return null;
|
|
395
|
+
const basis = planeBasis(plane.n, lineA.d);
|
|
396
|
+
const dA2 = dirTo2D(lineA.d, basis).normalize();
|
|
397
|
+
const dB2 = dirTo2D(lineB.d, basis).normalize();
|
|
398
|
+
const type = resolveAngleType(ann);
|
|
399
|
+
const typeExplicit = isAngleTypeExplicit(ann) || Boolean(ann?.useReflexAngle);
|
|
400
|
+
const orientation = resolveAngleOrientation2D(dA2, dB2, type, typeExplicit);
|
|
401
|
+
if (!orientation) return null;
|
|
402
|
+
const angle = THREE.MathUtils.radToDeg(orientation.angleRad);
|
|
403
|
+
return angle;
|
|
404
|
+
} catch {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function computeAngleElements(pmimode, ann) {
|
|
410
|
+
try {
|
|
411
|
+
const scene = pmimode?.viewer?.partHistory?.scene;
|
|
412
|
+
if (!scene) return null;
|
|
413
|
+
const { elementARefName, elementBRefName } = resolveElementRefNames(ann);
|
|
414
|
+
const objA = elementARefName ? scene.getObjectByName(elementARefName) : null;
|
|
415
|
+
const objB = elementBRefName ? scene.getObjectByName(elementBRefName) : null;
|
|
416
|
+
if (!objA || !objB) return null;
|
|
417
|
+
const dirA = getElementDirection(pmimode.viewer, objA);
|
|
418
|
+
const dirB = getElementDirection(pmimode.viewer, objB);
|
|
419
|
+
const pointA = objectRepresentativePoint(pmimode.viewer, objA);
|
|
420
|
+
const pointB = objectRepresentativePoint(pmimode.viewer, objB);
|
|
421
|
+
let plane = null;
|
|
422
|
+
if (ann.planeRefName) {
|
|
423
|
+
const planeObj = scene.getObjectByName(ann.planeRefName);
|
|
424
|
+
if (planeObj) plane = getElementDirection(pmimode.viewer, planeObj);
|
|
425
|
+
}
|
|
426
|
+
return { dirA, dirB, pointA, pointB, plane };
|
|
427
|
+
} catch {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function computeAngleElementsWithGeometry(pmimode, ann, ctx) {
|
|
433
|
+
try {
|
|
434
|
+
const elements = computeAngleElements(pmimode, ann);
|
|
435
|
+
if (!elements || !elements.dirA || !elements.dirB) return null;
|
|
436
|
+
const plane = resolveAnglePlane(pmimode, ann, elements, ctx);
|
|
437
|
+
if (!plane) return null;
|
|
438
|
+
const { elementARefName, elementBRefName } = resolveElementRefNames(ann);
|
|
439
|
+
const lineA = lineInPlaneForElement(pmimode, elementARefName, plane.n, plane.p);
|
|
440
|
+
const lineB = lineInPlaneForElement(pmimode, elementBRefName, plane.n, plane.p);
|
|
441
|
+
if (!lineA || !lineB) return null;
|
|
442
|
+
const basis = planeBasis(plane.n, lineA.d);
|
|
443
|
+
const A_p = to2D(lineA.p, plane.p, basis);
|
|
444
|
+
const B_p = to2D(lineB.p, plane.p, basis);
|
|
445
|
+
let A_d = dirTo2D(lineA.d, basis).normalize();
|
|
446
|
+
let B_d = dirTo2D(lineB.d, basis).normalize();
|
|
447
|
+
if (ann?.reverseElementOrder) {
|
|
448
|
+
A_d = A_d.multiplyScalar(-1);
|
|
449
|
+
B_d = B_d.multiplyScalar(-1);
|
|
450
|
+
}
|
|
451
|
+
const angleType = resolveAngleType(ann);
|
|
452
|
+
const angleExplicit = isAngleTypeExplicit(ann) || Boolean(ann?.useReflexAngle);
|
|
453
|
+
const orientation = resolveAngleOrientation2D(A_d, B_d, angleType, angleExplicit);
|
|
454
|
+
if (!orientation) return null;
|
|
455
|
+
const A_ray = orientation.start.clone();
|
|
456
|
+
const B_ray = orientation.end.clone();
|
|
457
|
+
let V2 = intersectLines2D(A_p, A_ray, B_p, B_ray);
|
|
458
|
+
if (!V2) V2 = new THREE.Vector2().addVectors(A_p, B_p).multiplyScalar(0.5);
|
|
459
|
+
return {
|
|
460
|
+
...elements,
|
|
461
|
+
__2d: {
|
|
462
|
+
N: plane.n,
|
|
463
|
+
P: plane.p,
|
|
464
|
+
basis,
|
|
465
|
+
A_p,
|
|
466
|
+
B_p,
|
|
467
|
+
A_d: A_ray,
|
|
468
|
+
B_d: B_ray,
|
|
469
|
+
V2,
|
|
470
|
+
sweep: orientation.sweep,
|
|
471
|
+
dirSign: orientation.dirSign,
|
|
472
|
+
angleRad: orientation.angleRad,
|
|
473
|
+
angleType: orientation.angleType,
|
|
474
|
+
bisector: orientation.bisector,
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function resolveAnglePlane(pmimode, ann, elements, ctx) {
|
|
483
|
+
try {
|
|
484
|
+
if (ann?.planeRefName) {
|
|
485
|
+
const planeObj = pmimode.viewer?.partHistory?.scene?.getObjectByName(ann.planeRefName);
|
|
486
|
+
if (planeObj) {
|
|
487
|
+
const n = getElementDirection(pmimode.viewer, planeObj) || new THREE.Vector3(0, 0, 1);
|
|
488
|
+
if (n.lengthSq() > 1e-12) {
|
|
489
|
+
const p = objectRepresentativePoint(pmimode.viewer, planeObj) || new THREE.Vector3();
|
|
490
|
+
return { n: n.clone().normalize(), p };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (elements?.dirA && elements?.dirB) {
|
|
495
|
+
const cross = new THREE.Vector3().crossVectors(elements.dirA, elements.dirB);
|
|
496
|
+
if (cross.lengthSq() > 1e-12) {
|
|
497
|
+
const p = (elements.pointA && elements.pointB)
|
|
498
|
+
? new THREE.Vector3().addVectors(elements.pointA, elements.pointB).multiplyScalar(0.5)
|
|
499
|
+
: (elements.pointA || elements.pointB || new THREE.Vector3());
|
|
500
|
+
return { n: cross.normalize(), p };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const fallbackNormal = ctx?.alignNormal ? ctx.alignNormal('view', ann) : null;
|
|
504
|
+
const n2 = fallbackNormal || elements?.plane || new THREE.Vector3(0, 0, 1);
|
|
505
|
+
const p2 = (elements?.pointA && elements?.pointB)
|
|
506
|
+
? new THREE.Vector3().addVectors(elements.pointA, elements.pointB).multiplyScalar(0.5)
|
|
507
|
+
: (elements?.pointA || elements?.pointB || new THREE.Vector3());
|
|
508
|
+
return { n: n2.clone().normalize(), p: p2 };
|
|
509
|
+
} catch {
|
|
510
|
+
return { n: new THREE.Vector3(0, 0, 1), p: new THREE.Vector3() };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function lineInPlaneForElement(pmimode, refName, planeNormal, planePoint) {
|
|
515
|
+
try {
|
|
516
|
+
if (!refName) return null;
|
|
517
|
+
const scene = pmimode?.viewer?.partHistory?.scene;
|
|
518
|
+
if (!scene) return null;
|
|
519
|
+
const obj = scene.getObjectByName(refName);
|
|
520
|
+
if (!obj) return null;
|
|
521
|
+
|
|
522
|
+
const N = (planeNormal && planeNormal.lengthSq() > 1e-12)
|
|
523
|
+
? planeNormal.clone().normalize()
|
|
524
|
+
: new THREE.Vector3(0, 0, 1);
|
|
525
|
+
const basePoint = objectRepresentativePoint(pmimode.viewer, obj) || planePoint || new THREE.Vector3();
|
|
526
|
+
const planeAnchor = planePoint || basePoint;
|
|
527
|
+
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(N, planeAnchor);
|
|
528
|
+
|
|
529
|
+
const elementDir = getElementDirection(pmimode.viewer, obj);
|
|
530
|
+
const worldDir = elementDir ? elementDir.clone().normalize() : null;
|
|
531
|
+
|
|
532
|
+
const userData = obj?.userData || {};
|
|
533
|
+
const objType = userData.type || userData.brepType || obj.type;
|
|
534
|
+
|
|
535
|
+
if (objType === 'FACE' && worldDir && worldDir.lengthSq() > 1e-12) {
|
|
536
|
+
const faceNormal = worldDir.clone().normalize();
|
|
537
|
+
const direction = new THREE.Vector3().crossVectors(faceNormal, N);
|
|
538
|
+
const denom = direction.lengthSq();
|
|
539
|
+
if (denom > 1e-12) {
|
|
540
|
+
const d1 = faceNormal.dot(basePoint);
|
|
541
|
+
const d2 = N.dot(planeAnchor);
|
|
542
|
+
const termA = new THREE.Vector3().crossVectors(N, direction).multiplyScalar(d1);
|
|
543
|
+
const termB = new THREE.Vector3().crossVectors(direction, faceNormal).multiplyScalar(d2);
|
|
544
|
+
const pointOnIntersection = termA.add(termB).divideScalar(denom);
|
|
545
|
+
return { p: plane.projectPoint(pointOnIntersection, pointOnIntersection.clone()), d: direction.normalize() };
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
let planePointOnLine = basePoint.clone();
|
|
550
|
+
if (worldDir && worldDir.lengthSq() > 1e-12) {
|
|
551
|
+
const denom = worldDir.dot(N);
|
|
552
|
+
if (Math.abs(denom) > 1e-9) {
|
|
553
|
+
const target = planeAnchor.clone();
|
|
554
|
+
const t = target.clone().sub(basePoint).dot(N) / denom;
|
|
555
|
+
planePointOnLine = basePoint.clone().addScaledVector(worldDir, t);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const projectedPoint = plane.projectPoint(planePointOnLine, planePointOnLine.clone());
|
|
559
|
+
|
|
560
|
+
let projectedDir = worldDir ? worldDir.clone().projectOnPlane(N) : null;
|
|
561
|
+
if (!projectedDir || projectedDir.lengthSq() < 1e-12) {
|
|
562
|
+
const basis = planeBasis(N);
|
|
563
|
+
projectedDir = basis.U.clone();
|
|
564
|
+
}
|
|
565
|
+
projectedDir.normalize();
|
|
566
|
+
return { p: projectedPoint, d: projectedDir };
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function resolveLabelPosition(pmimode, ann, elements, radiusOverride, ctx) {
|
|
573
|
+
try {
|
|
574
|
+
const { N, P, A_d, B_d, V2, basis, bisector: storedBisector } = elements.__2d;
|
|
575
|
+
let bisector = storedBisector ? storedBisector.clone() : new THREE.Vector2().addVectors(A_d, B_d);
|
|
576
|
+
if (bisector.lengthSq() < 1e-10) bisector.set(-A_d.y, A_d.x);
|
|
577
|
+
bisector.normalize();
|
|
578
|
+
const offsetWorld = ctx.screenSizeWorld ? ctx.screenSizeWorld(70) : screenSizeWorld(pmimode?.viewer, 70);
|
|
579
|
+
let off = offsetWorld;
|
|
580
|
+
if (Number.isFinite(radiusOverride) && radiusOverride > 0) off = radiusOverride + offsetWorld * 0.3;
|
|
581
|
+
const label2 = new THREE.Vector2(V2.x + bisector.x * off, V2.y + bisector.y * off);
|
|
582
|
+
return from2D(label2, P, basis);
|
|
583
|
+
} catch {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function planeBasis(normal, preferDir) {
|
|
589
|
+
const N = normal.clone().normalize();
|
|
590
|
+
let U = (preferDir ? preferDir.clone() : new THREE.Vector3(1, 0, 0)).projectOnPlane(N);
|
|
591
|
+
if (U.lengthSq() < 1e-12) {
|
|
592
|
+
U = Math.abs(N.z) < 0.9 ? new THREE.Vector3(0, 0, 1).cross(N) : new THREE.Vector3(0, 1, 0).cross(N);
|
|
593
|
+
}
|
|
594
|
+
U.normalize();
|
|
595
|
+
const V = new THREE.Vector3().crossVectors(N, U).normalize();
|
|
596
|
+
return { U, V, N };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function to2D(point, planePoint, basis) {
|
|
600
|
+
const r = point.clone().sub(planePoint);
|
|
601
|
+
return new THREE.Vector2(r.dot(basis.U), r.dot(basis.V));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function dirTo2D(dir, basis) {
|
|
605
|
+
return new THREE.Vector2(dir.dot(basis.U), dir.dot(basis.V));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function from2D(p2, planePoint, basis) {
|
|
609
|
+
return planePoint.clone()
|
|
610
|
+
.add(basis.U.clone().multiplyScalar(p2.x))
|
|
611
|
+
.add(basis.V.clone().multiplyScalar(p2.y));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function intersectLines2D(p1, d1, p2, d2) {
|
|
615
|
+
const cross = d1.x * d2.y - d1.y * d2.x;
|
|
616
|
+
if (Math.abs(cross) < 1e-12) return null;
|
|
617
|
+
const v = new THREE.Vector2().subVectors(p2, p1);
|
|
618
|
+
const t = (v.x * d2.y - v.y * d2.x) / cross;
|
|
619
|
+
return new THREE.Vector2(p1.x + d1.x * t, p1.y + d1.y * t);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function rotate2D(vec, angle) {
|
|
623
|
+
const c = Math.cos(angle);
|
|
624
|
+
const s = Math.sin(angle);
|
|
625
|
+
return new THREE.Vector2(vec.x * c - vec.y * s, vec.x * s + vec.y * c);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function signedAngle2D(a, b) {
|
|
629
|
+
const cross = a.x * b.y - a.y * b.x;
|
|
630
|
+
const dot = a.x * b.x + a.y * b.y;
|
|
631
|
+
return Math.atan2(cross, dot);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function projectPointToPlane(point, planePoint, planeNormal) {
|
|
635
|
+
const d = point.clone().sub(planePoint).dot(planeNormal);
|
|
636
|
+
return point.clone().sub(planeNormal.clone().multiplyScalar(d));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function vectorFromAny(value) {
|
|
640
|
+
if (!value) return null;
|
|
641
|
+
if (value instanceof THREE.Vector3) return value.clone();
|
|
642
|
+
if (Array.isArray(value)) return new THREE.Vector3(value[0] || 0, value[1] || 0, value[2] || 0);
|
|
643
|
+
if (typeof value === 'object') {
|
|
644
|
+
return new THREE.Vector3(value.x || 0, value.y || 0, value.z || 0);
|
|
645
|
+
}
|
|
646
|
+
return null;
|
|
647
|
+
}
|