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,659 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { BaseAnnotation } from '../BaseAnnotation.js';
|
|
3
|
+
import { makeOverlayLine, makeOverlaySphere, 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 radial dimension',
|
|
11
|
+
},
|
|
12
|
+
decimals: {
|
|
13
|
+
type: 'number',
|
|
14
|
+
default_value: 3,
|
|
15
|
+
defaultResolver: ({ pmimode }) => {
|
|
16
|
+
const dec = Number.isFinite(pmimode?._opts?.dimDecimals)
|
|
17
|
+
? (pmimode._opts.dimDecimals | 0)
|
|
18
|
+
: undefined;
|
|
19
|
+
if (!Number.isFinite(dec)) return undefined;
|
|
20
|
+
return Math.max(0, Math.min(8, dec));
|
|
21
|
+
},
|
|
22
|
+
label: 'Decimals',
|
|
23
|
+
hint: 'Number of decimal places to display',
|
|
24
|
+
min: 0,
|
|
25
|
+
max: 8,
|
|
26
|
+
step: 1,
|
|
27
|
+
},
|
|
28
|
+
cylindricalFaceRef: {
|
|
29
|
+
type: 'reference_selection',
|
|
30
|
+
selectionFilter: ['FACE'],
|
|
31
|
+
multiple: false,
|
|
32
|
+
default_value: '',
|
|
33
|
+
label: 'Cylindrical Face',
|
|
34
|
+
hint: 'Select cylindrical face',
|
|
35
|
+
},
|
|
36
|
+
planeRef: {
|
|
37
|
+
type: 'reference_selection',
|
|
38
|
+
selectionFilter: ['FACE', 'PLANE'],
|
|
39
|
+
multiple: false,
|
|
40
|
+
default_value: '',
|
|
41
|
+
label: 'Projection Plane',
|
|
42
|
+
hint: 'Optional plane used to project the dimension',
|
|
43
|
+
},
|
|
44
|
+
displayStyle: {
|
|
45
|
+
type: 'options',
|
|
46
|
+
default_value: 'radius',
|
|
47
|
+
options: ['radius', 'diameter'],
|
|
48
|
+
label: 'Display Style',
|
|
49
|
+
hint: 'Display as radius or diameter',
|
|
50
|
+
},
|
|
51
|
+
alignment: {
|
|
52
|
+
type: 'options',
|
|
53
|
+
default_value: 'view',
|
|
54
|
+
options: ['view', 'XY', 'YZ', 'ZX'],
|
|
55
|
+
label: 'Alignment',
|
|
56
|
+
hint: 'Dimension alignment mode',
|
|
57
|
+
},
|
|
58
|
+
offset: {
|
|
59
|
+
type: 'number',
|
|
60
|
+
default_value: 0,
|
|
61
|
+
label: 'Offset',
|
|
62
|
+
hint: 'Distance to offset the dimension line',
|
|
63
|
+
step: 'any',
|
|
64
|
+
},
|
|
65
|
+
isReference: {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
default_value: false,
|
|
68
|
+
label: 'Reference',
|
|
69
|
+
hint: 'Mark as reference dimension (parentheses)',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export class RadialDimensionAnnotation extends BaseAnnotation {
|
|
74
|
+
static entityType = 'radial';
|
|
75
|
+
static type = 'radial';
|
|
76
|
+
static shortName = 'RAD';
|
|
77
|
+
static longName = 'Radial Dimension';
|
|
78
|
+
static title = 'Radial';
|
|
79
|
+
static inputParamsSchema = inputParamsSchema;
|
|
80
|
+
|
|
81
|
+
constructor(opts = {}) {
|
|
82
|
+
super(opts);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
uiFieldsTest(context) {
|
|
86
|
+
const planeRef = this.inputParams?.planeRef;
|
|
87
|
+
const hasPlane = Array.isArray(planeRef)
|
|
88
|
+
? planeRef.length > 0
|
|
89
|
+
: Boolean(String(planeRef || '').trim());
|
|
90
|
+
return hasPlane ? ['alignment'] : [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async run(renderingContext) {
|
|
94
|
+
const { pmimode, group, idx, ctx } = renderingContext;
|
|
95
|
+
const ann = this.inputParams;
|
|
96
|
+
const measured = measureRadialValue(pmimode, ann);
|
|
97
|
+
const labelInfo = formatRadialLabel(measured, ann);
|
|
98
|
+
ann.value = labelInfo.display;
|
|
99
|
+
|
|
100
|
+
ensurePersistent(ann);
|
|
101
|
+
try {
|
|
102
|
+
const data = computeRadialPoints(pmimode, ann, ctx);
|
|
103
|
+
if (!data || !data.center || !data.radiusPoint) return [];
|
|
104
|
+
const { center, radiusPoint, planeNormal, planePoint, radius } = data;
|
|
105
|
+
const color = 0xff6b35;
|
|
106
|
+
|
|
107
|
+
let direction = new THREE.Vector3().subVectors(radiusPoint, center);
|
|
108
|
+
if (direction.lengthSq() < 1e-6) direction.set(1, 0, 0);
|
|
109
|
+
direction.normalize();
|
|
110
|
+
|
|
111
|
+
let constraintNormal = planeNormal;
|
|
112
|
+
if (!constraintNormal) {
|
|
113
|
+
constraintNormal = ctx.alignNormal ? ctx.alignNormal(ann.alignment || 'view', ann) : null;
|
|
114
|
+
}
|
|
115
|
+
if (constraintNormal && constraintNormal.lengthSq() > 1e-6) {
|
|
116
|
+
const projected = direction.clone().projectOnPlane(constraintNormal).normalize();
|
|
117
|
+
if (projected.lengthSq() > 1e-6) direction = projected;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const arrowLength = ctx.screenSizeWorld ? ctx.screenSizeWorld(12) : screenSizeWorld(pmimode?.viewer, 12);
|
|
121
|
+
const arrowWidth = ctx.screenSizeWorld ? ctx.screenSizeWorld(4) : screenSizeWorld(pmimode?.viewer, 4);
|
|
122
|
+
|
|
123
|
+
const drawDiameter = ann.displayStyle === 'diameter';
|
|
124
|
+
const storedLabel = ann.persistentData?.labelWorld ? vectorFromAny(ann.persistentData.labelWorld) : null;
|
|
125
|
+
|
|
126
|
+
if (drawDiameter) {
|
|
127
|
+
const lineDir = storedLabel ? storedLabel.clone().sub(center).normalize() : direction.clone();
|
|
128
|
+
const endPoint = storedLabel
|
|
129
|
+
? center.clone().addScaledVector(lineDir, storedLabel.clone().sub(center).length())
|
|
130
|
+
: center.clone().addScaledVector(lineDir, radius + Math.abs(Number(ann.offset) || ctx.screenSizeWorld ? ctx.screenSizeWorld(50) : screenSizeWorld(pmimode?.viewer, 50)));
|
|
131
|
+
|
|
132
|
+
const start = center.clone().addScaledVector(lineDir, -radius);
|
|
133
|
+
const positive = center.clone().addScaledVector(lineDir, radius);
|
|
134
|
+
const negative = center.clone().addScaledVector(lineDir, -radius);
|
|
135
|
+
|
|
136
|
+
group.add(makeOverlayLine(start, endPoint, color));
|
|
137
|
+
addArrowCone(group, positive, lineDir.clone(), arrowLength, arrowWidth, color);
|
|
138
|
+
addArrowCone(group, negative, lineDir.clone().negate(), arrowLength, arrowWidth, color);
|
|
139
|
+
|
|
140
|
+
const centerMarker = makeOverlaySphere(ctx.screenSizeWorld ? ctx.screenSizeWorld(6) : screenSizeWorld(pmimode?.viewer, 6), color);
|
|
141
|
+
centerMarker.position.copy(center);
|
|
142
|
+
group.add(centerMarker);
|
|
143
|
+
} else {
|
|
144
|
+
const labelTarget = storedLabel || computeRadialLabelPosition(pmimode, ann, center, radiusPoint, planeNormal, planePoint, ctx);
|
|
145
|
+
const lineVector = labelTarget.clone().sub(center);
|
|
146
|
+
let lineDir = lineVector.clone();
|
|
147
|
+
if (!lineDir.lengthSq()) lineDir = direction.clone();
|
|
148
|
+
lineDir.normalize();
|
|
149
|
+
const endPoint = labelTarget;
|
|
150
|
+
group.add(makeOverlayLine(center, endPoint, color));
|
|
151
|
+
const arrowHead = center.clone().addScaledVector(lineDir, radius);
|
|
152
|
+
addArrowCone(group, arrowHead, lineDir.clone(), arrowLength, arrowWidth, color);
|
|
153
|
+
const centerMarker = makeOverlaySphere(ctx.screenSizeWorld ? ctx.screenSizeWorld(6) : screenSizeWorld(pmimode?.viewer, 6), color);
|
|
154
|
+
centerMarker.position.copy(center);
|
|
155
|
+
group.add(centerMarker);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof measured === 'number') {
|
|
159
|
+
const info = formatRadialLabel(measured, ann);
|
|
160
|
+
ann.value = info.display;
|
|
161
|
+
const txt = ctx.formatReferenceLabel ? ctx.formatReferenceLabel(ann, info.raw) : info.display;
|
|
162
|
+
const labelPos = resolveLabelPosition(pmimode, ann, center, radiusPoint, planeNormal, planePoint, ctx);
|
|
163
|
+
if (labelPos) ctx.updateLabel(idx, txt, labelPos, ann);
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.warn('RadialDimensionAnnotation render error:', e);
|
|
167
|
+
}
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static onLabelPointerDown(pmimode, idx, ann, e, ctx) {
|
|
172
|
+
try {
|
|
173
|
+
const data = computeRadialPoints(pmimode, ann, ctx);
|
|
174
|
+
if (!data || !data.center || !data.radiusPoint) return;
|
|
175
|
+
const planeNormal = (data.planeNormal && data.planeNormal.lengthSq() > 1e-6)
|
|
176
|
+
? data.planeNormal.clone().normalize()
|
|
177
|
+
: (ctx.alignNormal ? ctx.alignNormal(ann.alignment || 'view', ann) : new THREE.Vector3(0, 0, 1));
|
|
178
|
+
const planePoint = data.planePoint || data.radiusPoint;
|
|
179
|
+
const plane = (planeNormal && planePoint) ? new THREE.Plane().setFromNormalAndCoplanarPoint(planeNormal, planePoint) : null;
|
|
180
|
+
const radialDir = new THREE.Vector3().subVectors(data.radiusPoint, data.center).normalize();
|
|
181
|
+
|
|
182
|
+
RadialDimensionAnnotation.dragLabelOnPlane(pmimode, ctx, {
|
|
183
|
+
makePlane: () => plane,
|
|
184
|
+
onDrag: (hit) => {
|
|
185
|
+
plane.projectPoint(hit, hit);
|
|
186
|
+
ensurePersistent(ann);
|
|
187
|
+
ann.persistentData.labelWorld = [hit.x, hit.y, hit.z];
|
|
188
|
+
const toMouse = new THREE.Vector3().subVectors(hit, data.center);
|
|
189
|
+
ann.offset = toMouse.dot(radialDir) - data.radius;
|
|
190
|
+
ctx.updateLabel(idx, null, hit, ann);
|
|
191
|
+
pmimode.refreshAnnotationsUI?.();
|
|
192
|
+
},
|
|
193
|
+
onEnd: () => {
|
|
194
|
+
try { if (pmimode?.viewer?.controls) pmimode.viewer.controls.enabled = true; } catch { }
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
} catch { /* ignore */ }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function formatRadialLabel(measured, ann) {
|
|
203
|
+
if (typeof measured !== 'number' || !Number.isFinite(measured)) {
|
|
204
|
+
return { raw: '-', display: '-' };
|
|
205
|
+
}
|
|
206
|
+
const baseValue = ann?.displayStyle === 'diameter' ? measured * 2 : measured;
|
|
207
|
+
const decRaw = Number(ann?.decimals);
|
|
208
|
+
const decimals = Number.isFinite(decRaw) ? Math.max(0, Math.min(8, decRaw | 0)) : 3;
|
|
209
|
+
const prefix = ann?.displayStyle === 'diameter' ? '⌀' : 'R';
|
|
210
|
+
const raw = `${prefix}${baseValue.toFixed(decimals)}`;
|
|
211
|
+
const displayBase = `${raw} (wu)`;
|
|
212
|
+
const display = ann?.isReference ? `(${displayBase})` : displayBase;
|
|
213
|
+
return { raw, display };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function ensurePersistent(ann) {
|
|
217
|
+
if (!ann.persistentData || typeof ann.persistentData !== 'object') ann.persistentData = {};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function computeRadialPoints(pmimode, ann, ctx) {
|
|
221
|
+
try {
|
|
222
|
+
const scene = pmimode?.viewer?.partHistory?.scene;
|
|
223
|
+
if (!scene || !ann.cylindricalFaceRef) return null;
|
|
224
|
+
const faceObj = scene.getObjectByName(ann.cylindricalFaceRef);
|
|
225
|
+
if (!faceObj) return null;
|
|
226
|
+
|
|
227
|
+
let owner = faceObj;
|
|
228
|
+
while (owner && typeof owner.getFaceMetadata !== 'function') owner = owner.parent;
|
|
229
|
+
let center = null;
|
|
230
|
+
let radius = null;
|
|
231
|
+
let axis = null;
|
|
232
|
+
let radiusPoint = null;
|
|
233
|
+
let perpendicular = null;
|
|
234
|
+
const originalCenter = new THREE.Vector3();
|
|
235
|
+
let metadata = null;
|
|
236
|
+
let radiusOverride = null;
|
|
237
|
+
|
|
238
|
+
if (owner && typeof owner.getFaceMetadata === 'function') {
|
|
239
|
+
metadata = owner.getFaceMetadata(ann.cylindricalFaceRef);
|
|
240
|
+
if (metadata && (metadata.type === 'cylindrical' || metadata.type === 'conical')) {
|
|
241
|
+
if (metadata.type === 'cylindrical') {
|
|
242
|
+
center = new THREE.Vector3(metadata.center[0], metadata.center[1], metadata.center[2]);
|
|
243
|
+
radius = metadata.radius;
|
|
244
|
+
axis = new THREE.Vector3(metadata.axis[0], metadata.axis[1], metadata.axis[2]).normalize();
|
|
245
|
+
} else if (Math.abs(metadata.radiusBottom - metadata.radiusTop) < 1e-6) {
|
|
246
|
+
center = new THREE.Vector3(metadata.center[0], metadata.center[1], metadata.center[2]);
|
|
247
|
+
radius = metadata.radiusBottom;
|
|
248
|
+
axis = new THREE.Vector3(metadata.axis[0], metadata.axis[1], metadata.axis[2]).normalize();
|
|
249
|
+
}
|
|
250
|
+
if (center && axis) {
|
|
251
|
+
if (faceObj.parent && faceObj.parent.matrixWorld) {
|
|
252
|
+
center.applyMatrix4(faceObj.parent.matrixWorld);
|
|
253
|
+
axis.transformDirection(faceObj.parent.matrixWorld).normalize();
|
|
254
|
+
}
|
|
255
|
+
perpendicular = new THREE.Vector3();
|
|
256
|
+
if (Math.abs(axis.x) < 0.9) {
|
|
257
|
+
perpendicular.crossVectors(axis, new THREE.Vector3(1, 0, 0)).normalize();
|
|
258
|
+
} else {
|
|
259
|
+
perpendicular.crossVectors(axis, new THREE.Vector3(0, 1, 0)).normalize();
|
|
260
|
+
}
|
|
261
|
+
radiusPoint = center.clone().addScaledVector(perpendicular, radius);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (metadata && typeof metadata === 'object') {
|
|
266
|
+
const overrideCandidate = metadata.pmiRadiusOverride ?? metadata.radiusOverride;
|
|
267
|
+
if (Number.isFinite(overrideCandidate) && overrideCandidate > 0) {
|
|
268
|
+
radiusOverride = Math.abs(overrideCandidate);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if ((!center || !axis || !Number.isFinite(radius) || radius <= 0) && owner) {
|
|
274
|
+
const pipeData = inferPipeFaceDataFromAuxEdges(owner, faceObj);
|
|
275
|
+
if (pipeData) {
|
|
276
|
+
center = pipeData.center;
|
|
277
|
+
axis = pipeData.axis;
|
|
278
|
+
radius = pipeData.radius;
|
|
279
|
+
radiusPoint = pipeData.radiusPoint;
|
|
280
|
+
perpendicular = pipeData.perpendicular;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!center || !axis || !Number.isFinite(radius) || radius <= 0) {
|
|
285
|
+
const inferred = inferCylinderFromGeometry(faceObj);
|
|
286
|
+
if (!inferred) return null;
|
|
287
|
+
center = inferred.center;
|
|
288
|
+
axis = inferred.axis;
|
|
289
|
+
radius = inferred.radius;
|
|
290
|
+
radiusPoint = inferred.radiusPoint;
|
|
291
|
+
perpendicular = new THREE.Vector3().subVectors(radiusPoint, center).normalize();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (radiusOverride && center) {
|
|
295
|
+
let dir = null;
|
|
296
|
+
if (perpendicular && perpendicular.lengthSq() > 1e-12) {
|
|
297
|
+
dir = perpendicular.clone().normalize();
|
|
298
|
+
} else if (radiusPoint) {
|
|
299
|
+
dir = new THREE.Vector3().subVectors(radiusPoint, center);
|
|
300
|
+
if (dir.lengthSq() > 1e-12) dir.normalize();
|
|
301
|
+
}
|
|
302
|
+
radius = radiusOverride;
|
|
303
|
+
if (dir) {
|
|
304
|
+
perpendicular = dir.clone();
|
|
305
|
+
radiusPoint = center.clone().addScaledVector(dir, radius);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
originalCenter.copy(center);
|
|
310
|
+
|
|
311
|
+
let planeNormal = null;
|
|
312
|
+
let planePoint = null;
|
|
313
|
+
if (ann.planeRef) {
|
|
314
|
+
const planeObj = scene.getObjectByName(ann.planeRef);
|
|
315
|
+
if (planeObj) {
|
|
316
|
+
planeNormal = getElementDirection(pmimode.viewer, planeObj);
|
|
317
|
+
planePoint = objectRepresentativePoint(pmimode.viewer, planeObj);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (planeNormal && planePoint && planeNormal.lengthSq() > 1e-6) {
|
|
322
|
+
const n = planeNormal.clone().normalize();
|
|
323
|
+
planeNormal = n;
|
|
324
|
+
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(n, planePoint);
|
|
325
|
+
const projectedCenter = new THREE.Vector3();
|
|
326
|
+
plane.projectPoint(center, projectedCenter);
|
|
327
|
+
center = projectedCenter;
|
|
328
|
+
let radialDir = new THREE.Vector3().subVectors(radiusPoint, originalCenter);
|
|
329
|
+
if (radialDir.lengthSq() < 1e-12) radialDir = perpendicular.clone();
|
|
330
|
+
let inPlaneDir = radialDir.projectOnPlane(n);
|
|
331
|
+
if (inPlaneDir.lengthSq() < 1e-12) {
|
|
332
|
+
inPlaneDir = axis.clone().cross(n);
|
|
333
|
+
if (inPlaneDir.lengthSq() < 1e-12) {
|
|
334
|
+
if (Math.abs(n.x) < 0.9) inPlaneDir = new THREE.Vector3().crossVectors(n, new THREE.Vector3(1, 0, 0));
|
|
335
|
+
else inPlaneDir = new THREE.Vector3().crossVectors(n, new THREE.Vector3(0, 1, 0));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
inPlaneDir.normalize();
|
|
339
|
+
radiusPoint = center.clone().addScaledVector(inPlaneDir, radius);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { center, radiusPoint, planeNormal, planePoint, axis, radius };
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function measureRadialValue(pmimode, ann) {
|
|
349
|
+
try {
|
|
350
|
+
const data = computeRadialPoints(pmimode, ann);
|
|
351
|
+
if (data && typeof data.radius === 'number') return data.radius;
|
|
352
|
+
if (data && data.center && data.radiusPoint) return data.center.distanceTo(data.radiusPoint);
|
|
353
|
+
return null;
|
|
354
|
+
} catch {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function resolveLabelPosition(pmimode, ann, center, radiusPoint, planeNormal, planePoint, ctx) {
|
|
360
|
+
try {
|
|
361
|
+
let label = null;
|
|
362
|
+
if (ann.persistentData?.labelWorld) label = vectorFromAny(ann.persistentData.labelWorld);
|
|
363
|
+
else if (ann.labelWorld) label = vectorFromAny(ann.labelWorld);
|
|
364
|
+
if (!label) label = computeRadialLabelPosition(pmimode, ann, center, radiusPoint, planeNormal, planePoint, ctx);
|
|
365
|
+
if (planeNormal && planePoint && planeNormal.lengthSq() > 1e-6 && label) {
|
|
366
|
+
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(planeNormal.clone().normalize(), planePoint);
|
|
367
|
+
plane.projectPoint(label, label);
|
|
368
|
+
}
|
|
369
|
+
return label;
|
|
370
|
+
} catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function computeRadialLabelPosition(pmimode, ann, center, radiusPoint, planeNormal, planePoint, ctx) {
|
|
376
|
+
try {
|
|
377
|
+
const dir = new THREE.Vector3().subVectors(radiusPoint, center);
|
|
378
|
+
if (dir.lengthSq() < 1e-12) return radiusPoint.clone();
|
|
379
|
+
dir.normalize();
|
|
380
|
+
let offsetDistance = Number(ann?.offset);
|
|
381
|
+
if (!Number.isFinite(offsetDistance) || offsetDistance === 0) offsetDistance = ctx.screenSizeWorld ? ctx.screenSizeWorld(50) : screenSizeWorld(pmimode?.viewer, 50);
|
|
382
|
+
const baseDistance = center.distanceTo(radiusPoint);
|
|
383
|
+
const label = center.clone().addScaledVector(dir, baseDistance + Math.abs(offsetDistance));
|
|
384
|
+
if (planeNormal && planePoint && planeNormal.lengthSq() > 1e-6) {
|
|
385
|
+
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(planeNormal.clone().normalize(), planePoint);
|
|
386
|
+
plane.projectPoint(label, label);
|
|
387
|
+
}
|
|
388
|
+
return label;
|
|
389
|
+
} catch {
|
|
390
|
+
return radiusPoint ? radiusPoint.clone() : null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function inferPipeFaceDataFromAuxEdges(owner, faceObj) {
|
|
395
|
+
try {
|
|
396
|
+
if (!owner || !faceObj) return null;
|
|
397
|
+
const auxEdges = Array.isArray(owner._auxEdges) ? owner._auxEdges : null;
|
|
398
|
+
if (!auxEdges || auxEdges.length === 0) return null;
|
|
399
|
+
const faceName = (faceObj.name || faceObj.userData?.faceName || '').trim();
|
|
400
|
+
if (!faceName) return null;
|
|
401
|
+
const baseName = pipeFaceBaseName(faceName);
|
|
402
|
+
if (!baseName) return null;
|
|
403
|
+
const aux = findAuxEdgeByName(auxEdges, `${baseName}_PATH`);
|
|
404
|
+
if (!aux || !Array.isArray(aux.points) || aux.points.length < 2) return null;
|
|
405
|
+
|
|
406
|
+
owner.updateMatrixWorld?.(true);
|
|
407
|
+
faceObj.updateMatrixWorld?.(true);
|
|
408
|
+
|
|
409
|
+
const polyline = buildAuxPolylineWorld(aux, owner);
|
|
410
|
+
if (!polyline || polyline.length < 2) return null;
|
|
411
|
+
const samples = collectFaceSamplePoints(faceObj, 16);
|
|
412
|
+
if (!samples.length) return null;
|
|
413
|
+
|
|
414
|
+
let best = null;
|
|
415
|
+
for (const sample of samples) {
|
|
416
|
+
const info = closestPointOnPolyline(sample, polyline, !!aux.closedLoop);
|
|
417
|
+
if (!info || !info.point) continue;
|
|
418
|
+
const radialVec = new THREE.Vector3().subVectors(sample, info.point);
|
|
419
|
+
const lenSq = radialVec.lengthSq();
|
|
420
|
+
if (!(lenSq > 1e-10)) continue;
|
|
421
|
+
const radius = Math.sqrt(lenSq);
|
|
422
|
+
radialVec.normalize();
|
|
423
|
+
if (!best || radius > best.radius) {
|
|
424
|
+
best = {
|
|
425
|
+
center: info.point.clone(),
|
|
426
|
+
axis: info.direction ? info.direction.clone() : null,
|
|
427
|
+
radius,
|
|
428
|
+
radiusPoint: info.point.clone().addScaledVector(radialVec, radius),
|
|
429
|
+
perpendicular: radialVec.clone(),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!best) return null;
|
|
435
|
+
if (!best.axis || best.axis.lengthSq() < 1e-10) {
|
|
436
|
+
best.axis = estimatePolylineTangent(polyline, best.center, !!aux.closedLoop) || new THREE.Vector3(0, 1, 0);
|
|
437
|
+
}
|
|
438
|
+
return best;
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function findAuxEdgeByName(auxEdges, name) {
|
|
445
|
+
if (!Array.isArray(auxEdges) || !name) return null;
|
|
446
|
+
for (const aux of auxEdges) {
|
|
447
|
+
if (aux?.name === name) return aux;
|
|
448
|
+
}
|
|
449
|
+
const lower = name.toLowerCase();
|
|
450
|
+
for (const aux of auxEdges) {
|
|
451
|
+
if (typeof aux?.name === 'string' && aux.name.toLowerCase() === lower) return aux;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function buildAuxPolylineWorld(aux, owner) {
|
|
457
|
+
try {
|
|
458
|
+
const pts = [];
|
|
459
|
+
const matrix = (!aux.polylineWorld && owner?.matrixWorld) ? owner.matrixWorld : null;
|
|
460
|
+
const tmp = new THREE.Vector3();
|
|
461
|
+
for (const p of aux.points) {
|
|
462
|
+
if (Array.isArray(p) && p.length >= 3) {
|
|
463
|
+
tmp.set(p[0], p[1], p[2]);
|
|
464
|
+
} else if (p && typeof p === 'object') {
|
|
465
|
+
tmp.set(Number(p.x) || 0, Number(p.y) || 0, Number(p.z) || 0);
|
|
466
|
+
} else {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (matrix) tmp.applyMatrix4(matrix);
|
|
470
|
+
pts.push(tmp.clone());
|
|
471
|
+
}
|
|
472
|
+
return pts;
|
|
473
|
+
} catch {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function collectFaceSamplePoints(faceObj, maxSamples = 16) {
|
|
479
|
+
try {
|
|
480
|
+
const geom = faceObj?.geometry;
|
|
481
|
+
if (!geom) return [];
|
|
482
|
+
const pos = typeof geom.getAttribute === 'function' ? geom.getAttribute('position') : null;
|
|
483
|
+
const matrix = faceObj.matrixWorld || new THREE.Matrix4();
|
|
484
|
+
const samples = [];
|
|
485
|
+
const tmp = new THREE.Vector3();
|
|
486
|
+
if (pos && pos.count > 0) {
|
|
487
|
+
const step = Math.max(1, Math.floor(pos.count / maxSamples));
|
|
488
|
+
for (let i = 0; i < pos.count && samples.length < maxSamples; i += step) {
|
|
489
|
+
tmp.set(pos.getX(i), pos.getY(i), pos.getZ(i)).applyMatrix4(matrix);
|
|
490
|
+
samples.push(tmp.clone());
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (!samples.length) {
|
|
494
|
+
try {
|
|
495
|
+
geom.computeBoundingSphere?.();
|
|
496
|
+
if (geom.boundingSphere) {
|
|
497
|
+
tmp.copy(geom.boundingSphere.center).applyMatrix4(matrix);
|
|
498
|
+
samples.push(tmp.clone());
|
|
499
|
+
}
|
|
500
|
+
} catch { /* ignore */ }
|
|
501
|
+
}
|
|
502
|
+
return samples;
|
|
503
|
+
} catch {
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function closestPointOnPolyline(point, polyline, closedLoop = false) {
|
|
509
|
+
if (!point || !Array.isArray(polyline) || polyline.length < 2) return null;
|
|
510
|
+
const proj = new THREE.Vector3();
|
|
511
|
+
const seg = new THREE.Vector3();
|
|
512
|
+
const delta = new THREE.Vector3();
|
|
513
|
+
let bestPoint = null;
|
|
514
|
+
let bestDir = null;
|
|
515
|
+
let bestDist = Infinity;
|
|
516
|
+
const count = polyline.length;
|
|
517
|
+
const limit = closedLoop ? count : count - 1;
|
|
518
|
+
for (let i = 0; i < limit; i++) {
|
|
519
|
+
const a = polyline[i];
|
|
520
|
+
const b = polyline[(i + 1) % count];
|
|
521
|
+
if (!a || !b) continue;
|
|
522
|
+
seg.copy(b).sub(a);
|
|
523
|
+
const lenSq = seg.lengthSq();
|
|
524
|
+
if (lenSq < 1e-14) continue;
|
|
525
|
+
delta.copy(point).sub(a);
|
|
526
|
+
let t = delta.dot(seg) / lenSq;
|
|
527
|
+
t = Math.max(0, Math.min(1, t));
|
|
528
|
+
proj.copy(a).addScaledVector(seg, t);
|
|
529
|
+
const distSq = proj.distanceToSquared(point);
|
|
530
|
+
if (distSq < bestDist) {
|
|
531
|
+
bestDist = distSq;
|
|
532
|
+
bestPoint = proj.clone();
|
|
533
|
+
bestDir = seg.clone().normalize();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (!bestPoint) return null;
|
|
537
|
+
return { point: bestPoint, direction: bestDir, distanceSq: bestDist };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function estimatePolylineTangent(polyline, center, closedLoop = false) {
|
|
541
|
+
if (!Array.isArray(polyline) || polyline.length < 2 || !center) return null;
|
|
542
|
+
let closest = null;
|
|
543
|
+
let bestDist = Infinity;
|
|
544
|
+
const mid = new THREE.Vector3();
|
|
545
|
+
const seg = new THREE.Vector3();
|
|
546
|
+
const count = polyline.length;
|
|
547
|
+
const limit = closedLoop ? count : count - 1;
|
|
548
|
+
for (let i = 0; i < limit; i++) {
|
|
549
|
+
const a = polyline[i];
|
|
550
|
+
const b = polyline[(i + 1) % count];
|
|
551
|
+
if (!a || !b) continue;
|
|
552
|
+
mid.copy(a).add(b).multiplyScalar(0.5);
|
|
553
|
+
const dist = mid.distanceToSquared(center);
|
|
554
|
+
if (dist < bestDist) {
|
|
555
|
+
bestDist = dist;
|
|
556
|
+
seg.copy(b).sub(a);
|
|
557
|
+
closest = seg.lengthSq() > 1e-14 ? seg.clone().normalize() : null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return closest;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function pipeFaceBaseName(faceName) {
|
|
564
|
+
const match = /^(.*?)(_Outer|_Inner|_CapStart|_CapEnd)$/i.exec(faceName);
|
|
565
|
+
return match ? match[1] : null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function inferCylinderFromGeometry(faceObj) {
|
|
569
|
+
try {
|
|
570
|
+
const geom = faceObj?.geometry;
|
|
571
|
+
if (!geom || !geom.getAttribute) return null;
|
|
572
|
+
const pos = geom.getAttribute('position');
|
|
573
|
+
if (!pos || pos.count < 6) return null;
|
|
574
|
+
const matrix = faceObj.matrixWorld || new THREE.Matrix4();
|
|
575
|
+
const pts = [];
|
|
576
|
+
const v = new THREE.Vector3();
|
|
577
|
+
for (let i = 0; i < pos.count; i++) {
|
|
578
|
+
v.set(pos.getX(i), pos.getY(i), pos.getZ(i)).applyMatrix4(matrix);
|
|
579
|
+
pts.push(v.clone());
|
|
580
|
+
}
|
|
581
|
+
if (!pts.length) return null;
|
|
582
|
+
|
|
583
|
+
const centroid = new THREE.Vector3();
|
|
584
|
+
for (const p of pts) centroid.add(p);
|
|
585
|
+
centroid.divideScalar(pts.length);
|
|
586
|
+
|
|
587
|
+
const cov = new Float64Array(9);
|
|
588
|
+
const diff = new THREE.Vector3();
|
|
589
|
+
for (const p of pts) {
|
|
590
|
+
diff.copy(p).sub(centroid);
|
|
591
|
+
cov[0] += diff.x * diff.x;
|
|
592
|
+
cov[1] += diff.x * diff.y;
|
|
593
|
+
cov[2] += diff.x * diff.z;
|
|
594
|
+
cov[4] += diff.y * diff.y;
|
|
595
|
+
cov[5] += diff.y * diff.z;
|
|
596
|
+
cov[8] += diff.z * diff.z;
|
|
597
|
+
}
|
|
598
|
+
cov[3] = cov[1]; cov[6] = cov[2]; cov[7] = cov[5];
|
|
599
|
+
|
|
600
|
+
const axis = new THREE.Vector3(1, 0, 0);
|
|
601
|
+
for (let iter = 0; iter < 20; iter++) {
|
|
602
|
+
const x = cov[0] * axis.x + cov[1] * axis.y + cov[2] * axis.z;
|
|
603
|
+
const y = cov[1] * axis.x + cov[4] * axis.y + cov[5] * axis.z;
|
|
604
|
+
const z = cov[2] * axis.x + cov[5] * axis.y + cov[8] * axis.z;
|
|
605
|
+
axis.set(x, y, z);
|
|
606
|
+
const len = axis.length();
|
|
607
|
+
if (len < 1e-12) {
|
|
608
|
+
axis.set(0, 1, 0);
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
axis.divideScalar(len);
|
|
612
|
+
}
|
|
613
|
+
if (axis.lengthSq() < 1e-12) axis.set(0, 1, 0);
|
|
614
|
+
|
|
615
|
+
let minProj = Infinity;
|
|
616
|
+
let maxProj = -Infinity;
|
|
617
|
+
let radialSum = 0;
|
|
618
|
+
let radialCount = 0;
|
|
619
|
+
const radialVec = new THREE.Vector3();
|
|
620
|
+
const temp = new THREE.Vector3();
|
|
621
|
+
const firstRadial = new THREE.Vector3();
|
|
622
|
+
|
|
623
|
+
for (const p of pts) {
|
|
624
|
+
diff.copy(p).sub(centroid);
|
|
625
|
+
const t = diff.dot(axis);
|
|
626
|
+
if (t < minProj) minProj = t;
|
|
627
|
+
if (t > maxProj) maxProj = t;
|
|
628
|
+
temp.copy(axis).multiplyScalar(t);
|
|
629
|
+
radialVec.copy(diff).sub(temp);
|
|
630
|
+
const len = radialVec.length();
|
|
631
|
+
if (len > 1e-6) {
|
|
632
|
+
if (radialCount === 0) firstRadial.copy(radialVec).normalize();
|
|
633
|
+
radialSum += len;
|
|
634
|
+
radialCount++;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (!radialCount) return null;
|
|
638
|
+
|
|
639
|
+
const radius = radialSum / radialCount;
|
|
640
|
+
const center = centroid.clone().add(axis.clone().multiplyScalar((minProj + maxProj) * 0.5));
|
|
641
|
+
const dir = firstRadial.lengthSq() > 1e-12 ? firstRadial.clone() : new THREE.Vector3().crossVectors(axis, new THREE.Vector3(1, 0, 0)).normalize();
|
|
642
|
+
if (dir.lengthSq() < 1e-12) dir.set(0, 1, 0).cross(axis).normalize();
|
|
643
|
+
const radiusPoint = center.clone().addScaledVector(dir, radius);
|
|
644
|
+
|
|
645
|
+
return { center, axis: axis.normalize(), radius, radiusPoint };
|
|
646
|
+
} catch {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function vectorFromAny(value) {
|
|
652
|
+
if (!value) return null;
|
|
653
|
+
if (value instanceof THREE.Vector3) return value.clone();
|
|
654
|
+
if (Array.isArray(value)) return new THREE.Vector3(value[0] || 0, value[1] || 0, value[2] || 0);
|
|
655
|
+
if (typeof value === 'object') {
|
|
656
|
+
return new THREE.Vector3(value.x || 0, value.y || 0, value.z || 0);
|
|
657
|
+
}
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const DEFAULT_PMI_STYLE = Object.freeze({
|
|
2
|
+
lineColor: 0x93c5fd,
|
|
3
|
+
dotColor: 0x93c5fd,
|
|
4
|
+
arrowColor: 0x93c5fd,
|
|
5
|
+
arrowLengthPx: 12,
|
|
6
|
+
arrowWidthPx: 4,
|
|
7
|
+
leaderDotRadiusPx: 6,
|
|
8
|
+
noteDotRadius: 0.08,
|
|
9
|
+
noteDotColor: 0x93c5fd,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
let currentStyle = { ...DEFAULT_PMI_STYLE };
|
|
13
|
+
|
|
14
|
+
export function getPMIStyle() {
|
|
15
|
+
return currentStyle;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function setPMIStyle(overrides = {}) {
|
|
19
|
+
if (!overrides || typeof overrides !== 'object') return currentStyle;
|
|
20
|
+
currentStyle = { ...currentStyle, ...overrides };
|
|
21
|
+
return currentStyle;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function sanitizePMIStyle(raw = {}) {
|
|
25
|
+
const out = { ...DEFAULT_PMI_STYLE };
|
|
26
|
+
const assign = (key, fallback) => {
|
|
27
|
+
const v = raw[key];
|
|
28
|
+
if (Number.isFinite(v)) out[key] = v;
|
|
29
|
+
else if (typeof v === 'string' && key.toLowerCase().includes('color')) {
|
|
30
|
+
const n = parseInt(v.replace('#', ''), 16);
|
|
31
|
+
if (Number.isFinite(n)) out[key] = n;
|
|
32
|
+
} else if (v != null) out[key] = fallback;
|
|
33
|
+
};
|
|
34
|
+
assign('lineColor', DEFAULT_PMI_STYLE.lineColor);
|
|
35
|
+
assign('dotColor', DEFAULT_PMI_STYLE.dotColor);
|
|
36
|
+
assign('arrowColor', DEFAULT_PMI_STYLE.arrowColor);
|
|
37
|
+
assign('arrowLengthPx', DEFAULT_PMI_STYLE.arrowLengthPx);
|
|
38
|
+
assign('arrowWidthPx', DEFAULT_PMI_STYLE.arrowWidthPx);
|
|
39
|
+
assign('leaderDotRadiusPx', DEFAULT_PMI_STYLE.leaderDotRadiusPx);
|
|
40
|
+
assign('lineWidth', DEFAULT_PMI_STYLE.lineWidth || 1);
|
|
41
|
+
assign('noteDotRadius', DEFAULT_PMI_STYLE.noteDotRadius);
|
|
42
|
+
assign('noteDotColor', DEFAULT_PMI_STYLE.noteDotColor);
|
|
43
|
+
return out;
|
|
44
|
+
}
|