brep-io-kernel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +32 -0
- package/README.md +144 -0
- package/dist-kernel/brep-kernel.js +74699 -0
- package/dist-kernel/help/CONTRIBUTING.html +248 -0
- package/dist-kernel/help/LICENSE.html +248 -0
- package/dist-kernel/help/MODELING.png +0 -0
- package/dist-kernel/help/PMI.png +0 -0
- package/dist-kernel/help/SKETCH.png +0 -0
- package/dist-kernel/help/assembly-constraints__Coincident_Constraint_dialog.png +0 -0
- package/dist-kernel/help/assembly-constraints___Angle_Constraint_dialog.png +0 -0
- package/dist-kernel/help/assembly-constraints___Distance_Constraint_dialog.png +0 -0
- package/dist-kernel/help/assembly-constraints___Fixed_Constraint_dialog.png +0 -0
- package/dist-kernel/help/assembly-constraints___Parallel_Constraint_dialog.png +0 -0
- package/dist-kernel/help/assembly-constraints___Touch_Align_Constraint_dialog.png +0 -0
- package/dist-kernel/help/assembly-constraints__angle-constraint.html +248 -0
- package/dist-kernel/help/assembly-constraints__coincident-constraint.html +248 -0
- package/dist-kernel/help/assembly-constraints__distance-constraint.html +248 -0
- package/dist-kernel/help/assembly-constraints__fixed-constraint.html +248 -0
- package/dist-kernel/help/assembly-constraints__parallel-constraint.html +248 -0
- package/dist-kernel/help/assembly-constraints__solver.html +248 -0
- package/dist-kernel/help/assembly-constraints__touch-align-constraint.html +248 -0
- package/dist-kernel/help/brep-api.html +263 -0
- package/dist-kernel/help/brep-kernel.html +258 -0
- package/dist-kernel/help/brep-model.html +248 -0
- package/dist-kernel/help/cylindrical-face-radius-embedding.html +290 -0
- package/dist-kernel/help/dialog-screenshots.html +248 -0
- package/dist-kernel/help/extruded-sketch-radius-embedding.html +336 -0
- package/dist-kernel/help/features__Assembly_Component_dialog.png +0 -0
- package/dist-kernel/help/features__Boolean_dialog.png +0 -0
- package/dist-kernel/help/features__Chamfer_dialog.png +0 -0
- package/dist-kernel/help/features__Datium_dialog.png +0 -0
- package/dist-kernel/help/features__Extrude_dialog.png +0 -0
- package/dist-kernel/help/features__Fillet_dialog.png +0 -0
- package/dist-kernel/help/features__Helix_dialog.png +0 -0
- package/dist-kernel/help/features__Hole_dialog.png +0 -0
- package/dist-kernel/help/features__Image_Heightmap_Solid_dialog.png +0 -0
- package/dist-kernel/help/features__Image_to_Face_dialog.png +0 -0
- package/dist-kernel/help/features__Import_3D_Model_dialog.png +0 -0
- package/dist-kernel/help/features__Loft_dialog.png +0 -0
- package/dist-kernel/help/features__Mirror_dialog.png +0 -0
- package/dist-kernel/help/features__Offset_Shell_dialog.png +0 -0
- package/dist-kernel/help/features__Overlap_Cleanup_dialog.png +0 -0
- package/dist-kernel/help/features__Pattern_Linear_dialog.png +0 -0
- package/dist-kernel/help/features__Pattern_Radial_dialog.png +0 -0
- package/dist-kernel/help/features__Pattern_dialog.png +0 -0
- package/dist-kernel/help/features__Plane_dialog.png +0 -0
- package/dist-kernel/help/features__Primitive_Cone_dialog.png +0 -0
- package/dist-kernel/help/features__Primitive_Cube_dialog.png +0 -0
- package/dist-kernel/help/features__Primitive_Cylinder_dialog.png +0 -0
- package/dist-kernel/help/features__Primitive_Pyramid_dialog.png +0 -0
- package/dist-kernel/help/features__Primitive_Sphere_dialog.png +0 -0
- package/dist-kernel/help/features__Primitive_Torus_dialog.png +0 -0
- package/dist-kernel/help/features__Remesh_dialog.png +0 -0
- package/dist-kernel/help/features__Revolve_dialog.png +0 -0
- package/dist-kernel/help/features__Sheet_Metal_Contour_Flange_dialog.png +0 -0
- package/dist-kernel/help/features__Sheet_Metal_Cutout_dialog.png +0 -0
- package/dist-kernel/help/features__Sheet_Metal_Flange_dialog.png +0 -0
- package/dist-kernel/help/features__Sheet_Metal_Tab_dialog.png +0 -0
- package/dist-kernel/help/features__Sketch_dialog.png +0 -0
- package/dist-kernel/help/features__Spline_dialog.png +0 -0
- package/dist-kernel/help/features__Sweep_dialog.png +0 -0
- package/dist-kernel/help/features__Transform_dialog.png +0 -0
- package/dist-kernel/help/features__Tube_dialog.png +0 -0
- package/dist-kernel/help/features__assembly-component.html +248 -0
- package/dist-kernel/help/features__boolean.html +248 -0
- package/dist-kernel/help/features__chamfer.html +248 -0
- package/dist-kernel/help/features__datium.html +248 -0
- package/dist-kernel/help/features__datum.html +248 -0
- package/dist-kernel/help/features__extrude.html +248 -0
- package/dist-kernel/help/features__fillet.html +248 -0
- package/dist-kernel/help/features__helix.html +248 -0
- package/dist-kernel/help/features__hole.html +248 -0
- package/dist-kernel/help/features__image-heightmap-solid.html +248 -0
- package/dist-kernel/help/features__image-to-face-2D_dialog.png +0 -0
- package/dist-kernel/help/features__image-to-face-3D_dialog.png +0 -0
- package/dist-kernel/help/features__image-to-face.html +248 -0
- package/dist-kernel/help/features__import-3d-model.html +248 -0
- package/dist-kernel/help/features__index.html +248 -0
- package/dist-kernel/help/features__loft.html +248 -0
- package/dist-kernel/help/features__mirror.html +248 -0
- package/dist-kernel/help/features__offset-shell.html +248 -0
- package/dist-kernel/help/features__pattern-linear.html +248 -0
- package/dist-kernel/help/features__pattern-radial.html +248 -0
- package/dist-kernel/help/features__pattern.html +248 -0
- package/dist-kernel/help/features__plane.html +248 -0
- package/dist-kernel/help/features__primitive-cone.html +248 -0
- package/dist-kernel/help/features__primitive-cube.html +248 -0
- package/dist-kernel/help/features__primitive-cylinder.html +248 -0
- package/dist-kernel/help/features__primitive-pyramid.html +248 -0
- package/dist-kernel/help/features__primitive-sphere.html +248 -0
- package/dist-kernel/help/features__primitive-torus.html +248 -0
- package/dist-kernel/help/features__remesh.html +248 -0
- package/dist-kernel/help/features__revolve.html +248 -0
- package/dist-kernel/help/features__sheet-metal-contour-flange.html +248 -0
- package/dist-kernel/help/features__sheet-metal-flange.html +248 -0
- package/dist-kernel/help/features__sheet-metal-tab.html +248 -0
- package/dist-kernel/help/features__sketch.html +248 -0
- package/dist-kernel/help/features__spline.html +248 -0
- package/dist-kernel/help/features__sweep.html +248 -0
- package/dist-kernel/help/features__transform.html +248 -0
- package/dist-kernel/help/features__tube.html +248 -0
- package/dist-kernel/help/file-formats.html +248 -0
- package/dist-kernel/help/getting-started.html +248 -0
- package/dist-kernel/help/highlights.html +248 -0
- package/dist-kernel/help/history-systems.html +248 -0
- package/dist-kernel/help/how-it-works.html +248 -0
- package/dist-kernel/help/index.html +862 -0
- package/dist-kernel/help/input-params-schema.html +363 -0
- package/dist-kernel/help/inspector-improvements.html +248 -0
- package/dist-kernel/help/inspector.html +248 -0
- package/dist-kernel/help/modes__modeling.html +248 -0
- package/dist-kernel/help/modes__pmi.html +248 -0
- package/dist-kernel/help/modes__sketch.html +248 -0
- package/dist-kernel/help/plugins.html +248 -0
- package/dist-kernel/help/pmi-annotations__Angle_Dimension_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__Explode_Body_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__Hole_Callout_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__Leader_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__Linear_Dimension_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__Note_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__Radial_Dimension_dialog.png +0 -0
- package/dist-kernel/help/pmi-annotations__angle-dimension.html +248 -0
- package/dist-kernel/help/pmi-annotations__explode-body.html +248 -0
- package/dist-kernel/help/pmi-annotations__hole-callout.html +248 -0
- package/dist-kernel/help/pmi-annotations__index.html +248 -0
- package/dist-kernel/help/pmi-annotations__leader.html +248 -0
- package/dist-kernel/help/pmi-annotations__linear-dimension.html +248 -0
- package/dist-kernel/help/pmi-annotations__note.html +248 -0
- package/dist-kernel/help/pmi-annotations__radial-dimension.html +248 -0
- package/dist-kernel/help/search-index.json +464 -0
- package/dist-kernel/help/simplified-radial-dimensions.html +298 -0
- package/dist-kernel/help/solid-methods.html +359 -0
- package/dist-kernel/help/table-of-contents.html +330 -0
- package/dist-kernel/help/ui-overview.html +248 -0
- package/dist-kernel/help/whats-new.html +248 -0
- package/package.json +54 -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,1550 @@
|
|
|
1
|
+
// PMIMode.js
|
|
2
|
+
// Lightweight PMI editing mode modeled after SketchMode3D UI patterns.
|
|
3
|
+
// - Hides Viewer sidebar and main toolbar
|
|
4
|
+
// - Adds a top-right Finish control
|
|
5
|
+
// - Adds a simple top toolbar for annotation tools
|
|
6
|
+
// - Adds a right-side overlay panel listing annotations for the current PMI view
|
|
7
|
+
// - Persists annotations back into the PMI view entry on Finish
|
|
8
|
+
|
|
9
|
+
import * as THREE from 'three';
|
|
10
|
+
import { annotationRegistry } from './AnnotationRegistry.js';
|
|
11
|
+
import { getPMIStyle, setPMIStyle, sanitizePMIStyle } from './pmiStyle.js';
|
|
12
|
+
import { AnnotationHistory } from './AnnotationHistory.js';
|
|
13
|
+
import { LabelOverlay } from './LabelOverlay.js';
|
|
14
|
+
import { AnnotationCollectionWidget } from './AnnotationCollectionWidget.js';
|
|
15
|
+
import { localStorage as LS } from '../../idbStorage.js';
|
|
16
|
+
|
|
17
|
+
const cssEscape = (value) => {
|
|
18
|
+
if (window.CSS && typeof window.CSS.escape === 'function') {
|
|
19
|
+
return window.CSS.escape(value);
|
|
20
|
+
}
|
|
21
|
+
return String(value).replace(/"/g, '\\"');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Register built-in annotation types
|
|
25
|
+
export class PMIMode {
|
|
26
|
+
/**
|
|
27
|
+
* @param {Viewer} viewer
|
|
28
|
+
* @param {Object} viewEntry - reference to the PMI view object from PMIViewsWidget
|
|
29
|
+
* @param {number} viewIndex - index of the view in PMIViewsWidget.views
|
|
30
|
+
* @param {PMIViewsWidget} pmiWidget - widget instance for persistence/refresh
|
|
31
|
+
*/
|
|
32
|
+
constructor(viewer, viewEntry, viewIndex, pmiWidget) {
|
|
33
|
+
this.viewer = viewer;
|
|
34
|
+
this.viewEntry = (viewEntry && typeof viewEntry === 'object')
|
|
35
|
+
? viewEntry
|
|
36
|
+
: { viewName: 'View', name: 'View', camera: {}, annotations: [] };
|
|
37
|
+
if (!Array.isArray(this.viewEntry.annotations)) {
|
|
38
|
+
this.viewEntry.annotations = [];
|
|
39
|
+
}
|
|
40
|
+
const resolvedName = typeof this.viewEntry.viewName === 'string'
|
|
41
|
+
? this.viewEntry.viewName
|
|
42
|
+
: (typeof this.viewEntry.name === 'string' ? this.viewEntry.name : 'View');
|
|
43
|
+
this.viewEntry.viewName = String(resolvedName || 'View').trim() || 'View';
|
|
44
|
+
this.viewEntry.name = this.viewEntry.viewName;
|
|
45
|
+
if (!this.viewEntry.camera || typeof this.viewEntry.camera !== 'object') {
|
|
46
|
+
this.viewEntry.camera = {};
|
|
47
|
+
}
|
|
48
|
+
this.viewIndex = viewIndex;
|
|
49
|
+
this.pmiWidget = pmiWidget;
|
|
50
|
+
|
|
51
|
+
this._uiTopRight = null;
|
|
52
|
+
this._annGroup = null;
|
|
53
|
+
this._originalSections = null;
|
|
54
|
+
this._pmiModeViewsSection = null;
|
|
55
|
+
this._pmiViewsDomRestore = null;
|
|
56
|
+
this._pmiAnnotationsSection = null;
|
|
57
|
+
this._pmiToolOptionsSection = null;
|
|
58
|
+
this._pmiStyleStorageKey = '__PMI_STYLE_SETTINGS__';
|
|
59
|
+
this._sectionCreationPromises = [];
|
|
60
|
+
this._opts = { noteText: '', leaderText: 'TEXT HERE', dimDecimals: 3 };
|
|
61
|
+
this._onCanvasDown = this._handlePointerDown.bind(this);
|
|
62
|
+
this._onControlsChange = this._refreshOverlays.bind(this);
|
|
63
|
+
this._labelOverlay = null; // manages overlay labels
|
|
64
|
+
this._baseMatrixSessionKey = `pmi-base-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
65
|
+
this._hasBaseMatrices = false;
|
|
66
|
+
this._annotationWidget = null;
|
|
67
|
+
this._dragPlaneHelper = null;
|
|
68
|
+
|
|
69
|
+
// Annotation history stores inputParams/persistentData similar to PartHistory
|
|
70
|
+
this._annotationHistory = new AnnotationHistory(this);
|
|
71
|
+
const src = Array.isArray(this.viewEntry.annotations) ? this.viewEntry.annotations : [];
|
|
72
|
+
this._annotationHistory.load(JSON.parse(JSON.stringify(src)));
|
|
73
|
+
this.#loadPMIStyle();
|
|
74
|
+
try {
|
|
75
|
+
for (const entity of this._annotationHistory.getEntries()) {
|
|
76
|
+
try { this.#normalizeAnnotation(entity.inputParams); } catch { }
|
|
77
|
+
if (!entity.runtimeAttributes || typeof entity.runtimeAttributes !== 'object') {
|
|
78
|
+
entity.runtimeAttributes = {};
|
|
79
|
+
}
|
|
80
|
+
entity.runtimeAttributes.__open = false;
|
|
81
|
+
if (entity.inputParams && typeof entity.inputParams === 'object') {
|
|
82
|
+
entity.inputParams.__open = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch { }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
open() {
|
|
89
|
+
const v = this.viewer;
|
|
90
|
+
if (!v || !v.container) return;
|
|
91
|
+
|
|
92
|
+
// Save and hide existing accordion sections instead of hiding the whole sidebar
|
|
93
|
+
this.#hideOriginalSidebarSections();
|
|
94
|
+
|
|
95
|
+
// Build styles once
|
|
96
|
+
this.#ensureStyles();
|
|
97
|
+
|
|
98
|
+
// Mount overlay UI
|
|
99
|
+
this.#mountTopRightControls();
|
|
100
|
+
// Add PMI sections to existing accordion instead of creating a new sidebar
|
|
101
|
+
this.#mountPMISections();
|
|
102
|
+
|
|
103
|
+
// Apply stored view settings for this PMI view (e.g., wireframe)
|
|
104
|
+
try {
|
|
105
|
+
const vs = this.viewEntry?.viewSettings || this.viewEntry?.settings;
|
|
106
|
+
if (vs && typeof vs.wireframe === 'boolean') {
|
|
107
|
+
this.#toggleWireframeMode(Boolean(vs.wireframe));
|
|
108
|
+
}
|
|
109
|
+
} catch { }
|
|
110
|
+
|
|
111
|
+
// Build annotation group and render existing annotations
|
|
112
|
+
this._annGroup = new THREE.Group();
|
|
113
|
+
this._annGroup.name = `__PMI_ANN__:${this.#getViewDisplayName('view')}`;
|
|
114
|
+
this._annGroup.renderOrder = 9995;
|
|
115
|
+
try { v.scene.add(this._annGroup); } catch { }
|
|
116
|
+
this._annotationsDirty = true; // Flag to track when rebuild is needed
|
|
117
|
+
this._lastCameraState = null; // Track camera changes for overlay updates
|
|
118
|
+
this.#rebuildAnnotationObjects();
|
|
119
|
+
|
|
120
|
+
// Remember modeling-space transforms so we can restore/apply PMI offsets deterministically
|
|
121
|
+
this.#ensureBaseSolidMatrices();
|
|
122
|
+
this.#resetSolidsToBaseMatrices();
|
|
123
|
+
|
|
124
|
+
// Apply view-specific transforms from ViewTransform annotations AFTER annotations are processed
|
|
125
|
+
this.#applyViewTransforms();
|
|
126
|
+
// Initialize label overlay manager
|
|
127
|
+
try {
|
|
128
|
+
this._labelOverlay = new LabelOverlay(this.viewer,
|
|
129
|
+
(idx, ann, ev) => this.#startLabelDrag(idx, ann, ev),
|
|
130
|
+
(idx, ann, ev) => this.#focusAnnotationDialog(idx, ann, ev),
|
|
131
|
+
(idx, ann, ev) => this.#handleLabelClick(idx, ann, ev),
|
|
132
|
+
(idx, ann, ev) => this.#handleLabelDragEnd(idx, ann, ev));
|
|
133
|
+
} catch { }
|
|
134
|
+
|
|
135
|
+
// Initial refresh of overlay positions
|
|
136
|
+
setTimeout(() => this._refreshOverlays(), 100);
|
|
137
|
+
|
|
138
|
+
// Periodically refresh to follow model changes, but only if needed
|
|
139
|
+
try {
|
|
140
|
+
this._refreshTimer = setInterval(() => {
|
|
141
|
+
try {
|
|
142
|
+
if (this._annotationsDirty) {
|
|
143
|
+
this.#rebuildAnnotationObjects();
|
|
144
|
+
this._annotationsDirty = false;
|
|
145
|
+
}
|
|
146
|
+
// Also check if camera has changed as fallback for overlay updates
|
|
147
|
+
this.#checkCameraChange();
|
|
148
|
+
} catch { }
|
|
149
|
+
}, 1000);
|
|
150
|
+
} catch { }
|
|
151
|
+
|
|
152
|
+
// Listen on canvas for tool inputs
|
|
153
|
+
// Use capture to preempt Viewer handlers and ArcballControls
|
|
154
|
+
try { v.renderer.domElement.addEventListener('pointerdown', this._onCanvasDown, { passive: false, capture: true }); } catch { }
|
|
155
|
+
// Listen for camera/controls changes to update label positions
|
|
156
|
+
try {
|
|
157
|
+
if (v.controls && typeof this._onControlsChange === 'function') {
|
|
158
|
+
v.controls.addEventListener('change', this._onControlsChange);
|
|
159
|
+
// Some controls use 'end' instead of 'change' for the final position
|
|
160
|
+
if (typeof v.controls.addEventListener === 'function') {
|
|
161
|
+
try { v.controls.addEventListener('end', this._onControlsChange); } catch { }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch { }
|
|
165
|
+
|
|
166
|
+
// Apply camera controls policy based on current tool
|
|
167
|
+
try { this._controlsEnabledPrev = !!v.controls?.enabled; } catch { this._controlsEnabledPrev = true; }
|
|
168
|
+
try { if (v.controls) v.controls.enabled = true; } catch { }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
applyViewTransformsSequential() {
|
|
172
|
+
try {
|
|
173
|
+
this.#applyViewTransforms();
|
|
174
|
+
this._refreshOverlays();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn('Failed to apply view transforms sequentially:', error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async finish() {
|
|
181
|
+
// Persist annotations back into the view entry and refresh PMI widget
|
|
182
|
+
try { this.#_persistView(true); } catch { }
|
|
183
|
+
// Immediately return scene solids to modeling state before we notify the viewer
|
|
184
|
+
try { this.#restoreViewTransforms(); } catch { }
|
|
185
|
+
try { this.#resetSolidsToBaseMatrices(); } catch { }
|
|
186
|
+
try { this.viewer.onPMIFinished?.(this.viewEntry); } catch { }
|
|
187
|
+
await this.dispose();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async dispose() {
|
|
191
|
+
const v = this.viewer;
|
|
192
|
+
|
|
193
|
+
// Restore original transforms when exiting PMI mode
|
|
194
|
+
this.#restoreViewTransforms();
|
|
195
|
+
this.#resetSolidsToBaseMatrices();
|
|
196
|
+
|
|
197
|
+
try { v.renderer.domElement.removeEventListener('pointerdown', this._onCanvasDown, { capture: true }); } catch { }
|
|
198
|
+
// Remove controls change listeners
|
|
199
|
+
try {
|
|
200
|
+
if (v.controls && typeof this._onControlsChange === 'function') {
|
|
201
|
+
v.controls.removeEventListener('change', this._onControlsChange);
|
|
202
|
+
try { v.controls.removeEventListener('end', this._onControlsChange); } catch { }
|
|
203
|
+
}
|
|
204
|
+
} catch { }
|
|
205
|
+
// Remove overlay UI
|
|
206
|
+
try { this._uiTopRight?.remove(); } catch { }
|
|
207
|
+
|
|
208
|
+
// IMPORTANT: Remove PMI-specific accordion sections FIRST, then restore original sections
|
|
209
|
+
// This prevents visual glitches where both sets of sections are visible simultaneously
|
|
210
|
+
await this.#removePMISections();
|
|
211
|
+
|
|
212
|
+
// Now restore original sidebar sections after PMI sections are completely removed
|
|
213
|
+
this.#restoreOriginalSidebarSections();
|
|
214
|
+
|
|
215
|
+
// Remove annotation group
|
|
216
|
+
try { if (this._annGroup && this._annGroup.parent) this._annGroup.parent.remove(this._annGroup); } catch { }
|
|
217
|
+
this._annGroup = null;
|
|
218
|
+
try { if (this._refreshTimer) clearInterval(this._refreshTimer); } catch { } this._refreshTimer = null;
|
|
219
|
+
try { this.hideDragPlaneHelper(); } catch { }
|
|
220
|
+
// Remove labels overlay and destroy feature UIs
|
|
221
|
+
try { this._labelOverlay?.dispose?.(); } catch { }
|
|
222
|
+
this._labelOverlay = null;
|
|
223
|
+
try { this._annotationWidget?.dispose?.(); } catch { }
|
|
224
|
+
|
|
225
|
+
// Clear PMI base matrices once we're back in modeling mode
|
|
226
|
+
this.#clearBaseSolidMatrices();
|
|
227
|
+
|
|
228
|
+
// Note: Main toolbar is no longer hidden so no restoration needed
|
|
229
|
+
// Restore camera controls enabled state
|
|
230
|
+
try { if (this.viewer?.controls) this.viewer.controls.enabled = !!this._controlsEnabledPrev; } catch { }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Persist the current in-memory annotations back onto the view entry and save via PMI widget
|
|
234
|
+
#_persistView(refreshList = false) {
|
|
235
|
+
try {
|
|
236
|
+
if (!this.viewEntry) return;
|
|
237
|
+
// Serialize annotations using annotation history (inputParams + persistentData)
|
|
238
|
+
const history = this._annotationHistory;
|
|
239
|
+
const baseSerialized = history ? history.toSerializable() : [];
|
|
240
|
+
const entities = history ? history.getEntries() : [];
|
|
241
|
+
const serializedAnnotations = baseSerialized.map((entry, idx) => {
|
|
242
|
+
const entity = entities[idx] || null;
|
|
243
|
+
const ann = entity?.inputParams || null;
|
|
244
|
+
const handler = annotationRegistry.getSafe?.(ann?.type || entry.type) || annotationRegistry.getSafe?.(entry.type) || null;
|
|
245
|
+
if (handler && typeof handler.serialize === 'function') {
|
|
246
|
+
try {
|
|
247
|
+
const custom = handler.serialize(ann, entry, { entity });
|
|
248
|
+
if (custom) return custom;
|
|
249
|
+
} catch {
|
|
250
|
+
// fall back to base entry if serialize throws
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return entry;
|
|
254
|
+
});
|
|
255
|
+
this.viewEntry.annotations = JSON.parse(JSON.stringify(serializedAnnotations));
|
|
256
|
+
|
|
257
|
+
this.#notifyViewMutated(refreshList);
|
|
258
|
+
} catch { /* ignore */ }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
#notifyViewMutated(refreshList = false) {
|
|
262
|
+
let updated = null;
|
|
263
|
+
try {
|
|
264
|
+
const manager = this.viewer?.partHistory?.pmiViewsManager;
|
|
265
|
+
if (manager) {
|
|
266
|
+
if (Number.isFinite(this.viewIndex) && typeof manager.updateView === 'function') {
|
|
267
|
+
updated = manager.updateView(this.viewIndex, this.viewEntry);
|
|
268
|
+
}
|
|
269
|
+
if (!updated && typeof manager.notifyChanged === 'function') {
|
|
270
|
+
manager.notifyChanged();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch { }
|
|
274
|
+
|
|
275
|
+
if (!updated && this.pmiWidget && Number.isFinite(this.viewIndex) && Array.isArray(this.pmiWidget.views)) {
|
|
276
|
+
this.pmiWidget.views[this.viewIndex] = this.viewEntry;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (refreshList) {
|
|
280
|
+
try { this.pmiWidget?.refreshFromHistory?.(); } catch { }
|
|
281
|
+
try { this.pmiWidget?._renderList?.(); } catch { }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#getViewDisplayName(fallback = 'View') {
|
|
286
|
+
const entry = this.viewEntry;
|
|
287
|
+
if (!entry || typeof entry !== 'object') return fallback;
|
|
288
|
+
const nm = typeof entry.viewName === 'string' ? entry.viewName : entry.name;
|
|
289
|
+
const trimmed = String(nm || '').trim();
|
|
290
|
+
return trimmed || fallback;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// --- UI construction ---
|
|
294
|
+
#ensureStyles() {
|
|
295
|
+
if (document.getElementById('pmi-mode-styles')) return;
|
|
296
|
+
const style = document.createElement('style');
|
|
297
|
+
style.id = 'pmi-mode-styles';
|
|
298
|
+
style.textContent = `
|
|
299
|
+
/* Top-right buttons */
|
|
300
|
+
.pmi-top-right { position: absolute; top: 48px; right: 0px; display: flex; gap: 8px; z-index: 1001; }
|
|
301
|
+
.pmi-btn { appearance: none; border: 1px solid #262b36; border-radius: 8px; padding: 6px 10px; cursor: pointer; background: rgba(255,255,255,.05); color: #e6e6e6; font-weight: 700; }
|
|
302
|
+
.pmi-btn.primary { background: linear-gradient(180deg, rgba(110,168,254,.25), rgba(110,168,254,.15)); }
|
|
303
|
+
|
|
304
|
+
/* Annotations list */
|
|
305
|
+
.pmi-ann-list { flex: 1 1 auto; overflow: auto; display: flex; flex-direction: column; gap: 4px; }
|
|
306
|
+
|
|
307
|
+
/* Mini accordion for per-annotation dialogs */
|
|
308
|
+
.pmi-acc { display: flex; flex-direction: column; gap: 4px; }
|
|
309
|
+
.pmi-acc-item { background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(255,255,255,.01)); border: 1px solid #1f2937; border-radius: 10px; overflow: hidden; }
|
|
310
|
+
.pmi-acc-header { display: grid; grid-template-columns: 1fr auto; align-items: stretch; }
|
|
311
|
+
.pmi-acc-headbtn { appearance: none; width: 100%; text-align: left; background: transparent; color: #e5e7eb; border: 0; padding: 8px 10px; display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
312
|
+
.pmi-acc-title { flex: 1; }
|
|
313
|
+
.pmi-acc-status { margin-left: 8px; color: #9ca3af; font-size: 12px; line-height: 1; }
|
|
314
|
+
.pmi-acc-actions { display: flex; align-items: center; gap: 4px; padding: 6px 8px 6px 0; }
|
|
315
|
+
.pmi-acc-content { padding: 8px 10px 10px 10px; border-top: 1px solid #1f2937; }
|
|
316
|
+
.pmi-acc-item.collapsed .pmi-acc-content { display: none; }
|
|
317
|
+
.pmi-acc-del { appearance: none; border: 1px solid #374151; background: rgba(255,255,255,.03); color: #e5e7eb; border-radius: 8px; padding: 4px 8px; cursor: pointer; }
|
|
318
|
+
.pmi-acc-del:hover { border-color: #ef4444; color: #fff; background: rgba(239,68,68,.15); }
|
|
319
|
+
|
|
320
|
+
/* Overlay labels are defined in LabelOverlay.css */
|
|
321
|
+
|
|
322
|
+
/* Form fields for View Settings / Tool Options */
|
|
323
|
+
.pmi-vfield { display: flex; flex-direction: column; gap: 6px; margin: 6px 0; }
|
|
324
|
+
.pmi-vlabel { color: #9ca3af; font-size: 12px; }
|
|
325
|
+
.pmi-input { border: 1px solid #374151; border-radius: 6px; padding: 4px 6px; }
|
|
326
|
+
.pmi-number { width: 80px; border: 1px solid #374151; border-radius: 6px; padding: 4px 6px; }
|
|
327
|
+
.pmi-vfield .pmi-input, .pmi-vfield .pmi-number { width: 100%; box-sizing: border-box; background: #0b0e14; color: #e5e7eb; border: 1px solid #374151; border-radius: 6px; padding: 6px 8px; }
|
|
328
|
+
.pmi-vcheck { display: flex; align-items: center; gap: 8px; }
|
|
329
|
+
`;
|
|
330
|
+
document.head.appendChild(style);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#mountTopRightControls() {
|
|
334
|
+
const host = this.viewer.container;
|
|
335
|
+
host.style.position = host.style.position || 'relative';
|
|
336
|
+
const wrap = document.createElement('div');
|
|
337
|
+
wrap.className = 'pmi-top-right';
|
|
338
|
+
|
|
339
|
+
const btnFinish = document.createElement('button');
|
|
340
|
+
btnFinish.className = 'pmi-btn primary';
|
|
341
|
+
btnFinish.textContent = 'Finish';
|
|
342
|
+
btnFinish.addEventListener('click', () => this.finish());
|
|
343
|
+
wrap.appendChild(btnFinish);
|
|
344
|
+
host.appendChild(wrap);
|
|
345
|
+
this._uiTopRight = wrap;
|
|
346
|
+
}
|
|
347
|
+
#hideOriginalSidebarSections() {
|
|
348
|
+
try {
|
|
349
|
+
const v = this.viewer;
|
|
350
|
+
if (!v || !v.accordion) return;
|
|
351
|
+
|
|
352
|
+
// Store original accordion sections for restoration later
|
|
353
|
+
this._originalSections = [];
|
|
354
|
+
const accordion = v.accordion.uiElement;
|
|
355
|
+
|
|
356
|
+
// Find all accordion sections and hide them
|
|
357
|
+
const titles = accordion.querySelectorAll('.accordion-title');
|
|
358
|
+
const contents = accordion.querySelectorAll('.accordion-content');
|
|
359
|
+
|
|
360
|
+
titles.forEach(title => {
|
|
361
|
+
this._originalSections.push({
|
|
362
|
+
element: title,
|
|
363
|
+
display: title.style.display || '',
|
|
364
|
+
visibility: title.style.visibility || ''
|
|
365
|
+
});
|
|
366
|
+
title.style.display = 'none';
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
contents.forEach(content => {
|
|
370
|
+
this._originalSections.push({
|
|
371
|
+
element: content,
|
|
372
|
+
display: content.style.display || '',
|
|
373
|
+
visibility: content.style.visibility || ''
|
|
374
|
+
});
|
|
375
|
+
content.style.display = 'none';
|
|
376
|
+
});
|
|
377
|
+
} catch (e) {
|
|
378
|
+
console.warn('Failed to hide original sidebar sections:', e);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#restoreOriginalSidebarSections() {
|
|
383
|
+
try {
|
|
384
|
+
if (!this._originalSections) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
// Restore all original sections
|
|
390
|
+
this._originalSections.forEach(({ element, display, visibility }, index) => {
|
|
391
|
+
if (element && element.parentNode) {
|
|
392
|
+
element.style.display = display;
|
|
393
|
+
element.style.visibility = visibility;
|
|
394
|
+
} else {
|
|
395
|
+
console.warn(`Section ${index} element no longer exists in DOM`);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
this._originalSections = null;
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.warn('Failed to restore original sidebar sections:', e);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async #removePMISections() {
|
|
406
|
+
try {
|
|
407
|
+
const v = this.viewer;
|
|
408
|
+
if (!v || !v.accordion) return;
|
|
409
|
+
|
|
410
|
+
// Wait for any pending section creation to complete first
|
|
411
|
+
if (this._sectionCreationPromises && this._sectionCreationPromises.length > 0) {
|
|
412
|
+
try {
|
|
413
|
+
await Promise.allSettled(this._sectionCreationPromises);
|
|
414
|
+
} catch (e) {
|
|
415
|
+
console.warn('Some section creation promises failed:', e);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
// Remove PMI sections from the accordion
|
|
421
|
+
const sectionsToRemove = [
|
|
422
|
+
'PMI Views (PMI Mode)',
|
|
423
|
+
'Annotations - ' + this.#getViewDisplayName(''),
|
|
424
|
+
'View Settings',
|
|
425
|
+
'PMI Settings'
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
if (this._pmiViewsDomRestore && this.pmiWidget?.uiElement) {
|
|
430
|
+
try {
|
|
431
|
+
const widgetEl = this.pmiWidget.uiElement;
|
|
432
|
+
const { parent, next } = this._pmiViewsDomRestore;
|
|
433
|
+
if (widgetEl && parent) {
|
|
434
|
+
if (next && next.parentNode === parent) {
|
|
435
|
+
parent.insertBefore(widgetEl, next);
|
|
436
|
+
} else {
|
|
437
|
+
parent.appendChild(widgetEl);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
} catch (err) {
|
|
441
|
+
console.warn('Failed to restore PMI Views widget before removal:', err);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
this._pmiViewsDomRestore = null;
|
|
445
|
+
|
|
446
|
+
// First, try to use the stored section references for direct removal
|
|
447
|
+
const storedSections = [this._pmiModeViewsSection, this._pmiAnnotationsSection, this._pmiToolOptionsSection];
|
|
448
|
+
storedSections.forEach((section, index) => {
|
|
449
|
+
if (section && section.uiElement) {
|
|
450
|
+
try {
|
|
451
|
+
// Remove the title element
|
|
452
|
+
const titleEl = section.uiElement.previousElementSibling;
|
|
453
|
+
if (titleEl && titleEl.classList.contains('accordion-title')) {
|
|
454
|
+
titleEl.remove();
|
|
455
|
+
}
|
|
456
|
+
// Remove the content element
|
|
457
|
+
section.uiElement.remove();
|
|
458
|
+
} catch (e) {
|
|
459
|
+
console.warn(`Failed to remove stored section ${index}:`, e);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Aggressively search and remove any PMI-related elements
|
|
465
|
+
try {
|
|
466
|
+
const accordion = v.accordion.uiElement;
|
|
467
|
+
|
|
468
|
+
// Look for elements with PMI-related text content
|
|
469
|
+
const allTitles = Array.from(accordion.querySelectorAll('.accordion-title'));
|
|
470
|
+
const allContents = Array.from(accordion.querySelectorAll('.accordion-content'));
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
// Remove elements that match PMI section patterns
|
|
474
|
+
allTitles.forEach(titleEl => {
|
|
475
|
+
const text = titleEl.textContent || '';
|
|
476
|
+
if (text.includes('Annotations') || text === 'View Settings' || text === 'PMI Settings') {
|
|
477
|
+
// Find and remove the associated content element as well
|
|
478
|
+
const nextEl = titleEl.nextElementSibling;
|
|
479
|
+
if (nextEl && nextEl.classList.contains('accordion-content')) {
|
|
480
|
+
nextEl.remove();
|
|
481
|
+
}
|
|
482
|
+
titleEl.remove();
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Remove any remaining content elements that might have been missed
|
|
487
|
+
allContents.forEach(contentEl => {
|
|
488
|
+
if (!contentEl.parentNode) return; // Already removed
|
|
489
|
+
const id = contentEl.id || '';
|
|
490
|
+
const name = contentEl.getAttribute('name') || '';
|
|
491
|
+
if (name.includes('Annotations') || name === 'accordion-content-View Settings' || name === 'accordion-content-PMI Settings' ||
|
|
492
|
+
id.includes('Annotations') || id === 'accordion-content-View Settings' || id === 'accordion-content-PMI Settings') {
|
|
493
|
+
contentEl.remove();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Additional cleanup: remove any elements that contain PMI-specific classes or content
|
|
498
|
+
const pmiElements = accordion.querySelectorAll('.pmi-ann-list, .pmi-scrollable-content, .pmi-inline-menu, .pmi-ann-footer, .pmi-vfield');
|
|
499
|
+
pmiElements.forEach(el => {
|
|
500
|
+
// Remove the entire parent accordion section if this is PMI content
|
|
501
|
+
let parent = el.parentNode;
|
|
502
|
+
while (parent && !parent.classList.contains('accordion-content')) {
|
|
503
|
+
parent = parent.parentNode;
|
|
504
|
+
}
|
|
505
|
+
if (parent && parent.classList.contains('accordion-content')) {
|
|
506
|
+
const titleEl = parent.previousElementSibling;
|
|
507
|
+
if (titleEl && titleEl.classList.contains('accordion-title')) {
|
|
508
|
+
titleEl.remove();
|
|
509
|
+
}
|
|
510
|
+
parent.remove();
|
|
511
|
+
} else {
|
|
512
|
+
el.remove();
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Final nuclear option: remove any sections that weren't there originally
|
|
517
|
+
// This is a bit aggressive but ensures complete cleanup
|
|
518
|
+
} catch (e) {
|
|
519
|
+
console.warn('Failed to manually clean up PMI section elements:', e);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Try to remove sections using the accordion API as a fallback
|
|
523
|
+
for (const title of sectionsToRemove) {
|
|
524
|
+
try {
|
|
525
|
+
if (v.accordion && typeof v.accordion.removeSection === 'function') {
|
|
526
|
+
await v.accordion.removeSection(title);
|
|
527
|
+
}
|
|
528
|
+
} catch (e) {
|
|
529
|
+
console.warn(`Failed to remove section "${title}" via API:`, e);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Clear stored section references
|
|
534
|
+
this._pmiModeViewsSection = null;
|
|
535
|
+
this._pmiAnnotationsSection = null;
|
|
536
|
+
this._pmiToolOptionsSection = null;
|
|
537
|
+
this._sectionCreationPromises = [];
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
} catch (e) {
|
|
541
|
+
console.warn('Failed to remove PMI sections:', e);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
#mountPMISections() {
|
|
546
|
+
try {
|
|
547
|
+
const v = this.viewer;
|
|
548
|
+
if (!v || !v.accordion) return;
|
|
549
|
+
|
|
550
|
+
// Use the existing accordion instead of creating a new one
|
|
551
|
+
this._acc = v.accordion;
|
|
552
|
+
|
|
553
|
+
const pmiViewsPromise = this._acc.addSection('PMI Views (PMI Mode)').then((sec) => {
|
|
554
|
+
try {
|
|
555
|
+
this._pmiModeViewsSection = sec;
|
|
556
|
+
const titleEl = sec.uiElement.previousElementSibling;
|
|
557
|
+
if (titleEl) {
|
|
558
|
+
titleEl.textContent = 'PMI Views';
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const widget = this.pmiWidget;
|
|
562
|
+
const widgetEl = widget?.uiElement;
|
|
563
|
+
if (widgetEl) {
|
|
564
|
+
if (!this._pmiViewsDomRestore) {
|
|
565
|
+
this._pmiViewsDomRestore = {
|
|
566
|
+
parent: widgetEl.parentNode || null,
|
|
567
|
+
next: widgetEl.nextSibling || null,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
sec.uiElement.appendChild(widgetEl);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
this.#applyPMIPanelLayout();
|
|
574
|
+
} catch (e) {
|
|
575
|
+
console.warn('Failed to setup PMI Views section:', e);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
this._sectionCreationPromises.push(pmiViewsPromise);
|
|
579
|
+
|
|
580
|
+
const annotationsPromise = this._acc.addSection(`Annotations - ${this.#getViewDisplayName('')}`).then((sec) => {
|
|
581
|
+
try {
|
|
582
|
+
const widgetWrap = document.createElement('div');
|
|
583
|
+
widgetWrap.className = 'pmi-ann-widget-wrap';
|
|
584
|
+
sec.uiElement.appendChild(widgetWrap);
|
|
585
|
+
|
|
586
|
+
this._annotationWidget = new AnnotationCollectionWidget({
|
|
587
|
+
history: this._annotationHistory,
|
|
588
|
+
pmimode: this,
|
|
589
|
+
onCollectionChange: () => {
|
|
590
|
+
this.#updateAnnotationSectionTitle();
|
|
591
|
+
this.#markAnnotationsDirty();
|
|
592
|
+
},
|
|
593
|
+
onEntryChange: () => {
|
|
594
|
+
this.#updateAnnotationSectionTitle();
|
|
595
|
+
this.#markAnnotationsDirty();
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
widgetWrap.appendChild(this._annotationWidget.uiElement);
|
|
599
|
+
|
|
600
|
+
this._pmiAnnotationsSection = sec;
|
|
601
|
+
this.#updateAnnotationSectionTitle();
|
|
602
|
+
this.#applyPMIPanelLayout();
|
|
603
|
+
} catch (e) {
|
|
604
|
+
console.warn('Failed to setup annotations section:', e);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
this._sectionCreationPromises.push(annotationsPromise);
|
|
608
|
+
|
|
609
|
+
// PMI Settings section
|
|
610
|
+
this._toolOptsEl = document.createElement('div');
|
|
611
|
+
this._toolOptsEl.style.padding = '6px';
|
|
612
|
+
const toolOptionsPromise = this._acc.addSection('PMI Settings').then((sec) => {
|
|
613
|
+
try {
|
|
614
|
+
sec.uiElement.appendChild(this._toolOptsEl);
|
|
615
|
+
this.#renderToolOptions();
|
|
616
|
+
this._pmiToolOptionsSection = sec;
|
|
617
|
+
this.#applyPMIPanelLayout();
|
|
618
|
+
} catch (e) {
|
|
619
|
+
console.warn('Failed to setup tool options section:', e);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
this._sectionCreationPromises.push(toolOptionsPromise);
|
|
623
|
+
|
|
624
|
+
this._annotationWidget?.render();
|
|
625
|
+
} catch (e) {
|
|
626
|
+
console.warn('Failed to mount PMI sections:', e);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
#applyPMIPanelLayout() {
|
|
631
|
+
try {
|
|
632
|
+
const accordion = this.viewer?.accordion?.uiElement;
|
|
633
|
+
if (!accordion) return;
|
|
634
|
+
const sections = [
|
|
635
|
+
this._pmiModeViewsSection,
|
|
636
|
+
this._pmiAnnotationsSection,
|
|
637
|
+
this._pmiToolOptionsSection,
|
|
638
|
+
];
|
|
639
|
+
const fragment = document.createDocumentFragment();
|
|
640
|
+
let hasAny = false;
|
|
641
|
+
for (const section of sections) {
|
|
642
|
+
if (!section || !section.uiElement) continue;
|
|
643
|
+
const titleEl = section.uiElement.previousElementSibling;
|
|
644
|
+
if (!titleEl) continue;
|
|
645
|
+
fragment.appendChild(titleEl);
|
|
646
|
+
fragment.appendChild(section.uiElement);
|
|
647
|
+
hasAny = true;
|
|
648
|
+
}
|
|
649
|
+
if (!hasAny) return;
|
|
650
|
+
accordion.insertBefore(fragment, accordion.firstChild || null);
|
|
651
|
+
} catch (e) {
|
|
652
|
+
console.warn('Failed to apply PMI panel layout:', e);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
markAnnotationsDirty() {
|
|
657
|
+
this.#markAnnotationsDirty();
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
normalizeAnnotation(annotation) {
|
|
661
|
+
if (!annotation) return annotation;
|
|
662
|
+
return this.#normalizeAnnotation(annotation);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
handleAnnotationRemoval(entry) {
|
|
666
|
+
if (!entry) return;
|
|
667
|
+
try {
|
|
668
|
+
const handler = annotationRegistry.getSafe?.(entry.type) || entry.constructor || null;
|
|
669
|
+
const ann = entry.inputParams || {};
|
|
670
|
+
if (handler && typeof handler._resolveSolidReferences === 'function') {
|
|
671
|
+
try { handler._resolveSolidReferences(ann, this, false); } catch { /* ignore */ }
|
|
672
|
+
}
|
|
673
|
+
if (handler && typeof handler.restoreOriginalTransforms === 'function') {
|
|
674
|
+
try { handler.restoreOriginalTransforms(ann, this); } catch { /* ignore */ }
|
|
675
|
+
}
|
|
676
|
+
} catch (error) {
|
|
677
|
+
console.warn('PMI: handleAnnotationRemoval failed:', error);
|
|
678
|
+
}
|
|
679
|
+
try { this.applyViewTransformsSequential?.(); } catch { /* ignore */ }
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
#updateAnnotationSectionTitle() {
|
|
683
|
+
try {
|
|
684
|
+
const sec = this._pmiAnnotationsSection;
|
|
685
|
+
if (!sec || !sec.uiElement) return;
|
|
686
|
+
const titleEl = sec.uiElement.previousElementSibling;
|
|
687
|
+
if (titleEl) {
|
|
688
|
+
titleEl.textContent = `Annotations - ${this.#getViewDisplayName('')}`;
|
|
689
|
+
}
|
|
690
|
+
} catch { /* ignore */ }
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
#renderToolOptions() {
|
|
694
|
+
const el = this._toolOptsEl;
|
|
695
|
+
if (!el) return;
|
|
696
|
+
el.textContent = '';
|
|
697
|
+
|
|
698
|
+
const makeVField = (label, input) => {
|
|
699
|
+
const wrap = document.createElement('div');
|
|
700
|
+
wrap.className = 'pmi-vfield';
|
|
701
|
+
const lab = document.createElement('div');
|
|
702
|
+
lab.className = 'pmi-vlabel';
|
|
703
|
+
lab.textContent = label;
|
|
704
|
+
wrap.appendChild(lab);
|
|
705
|
+
wrap.appendChild(input);
|
|
706
|
+
return wrap;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
const mkText = (placeholder, value, onChange) => {
|
|
710
|
+
const inp = document.createElement('input');
|
|
711
|
+
inp.type = 'text';
|
|
712
|
+
inp.placeholder = placeholder;
|
|
713
|
+
inp.value = value || '';
|
|
714
|
+
inp.className = 'pmi-input';
|
|
715
|
+
inp.addEventListener('change', () => onChange(inp.value));
|
|
716
|
+
return inp;
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const mkNumber = (value, onChange, { min = 0, max = 8 } = {}) => {
|
|
720
|
+
const inp = document.createElement('input');
|
|
721
|
+
inp.type = 'number';
|
|
722
|
+
inp.min = String(min); inp.max = String(max);
|
|
723
|
+
inp.value = String(value);
|
|
724
|
+
inp.className = 'pmi-number';
|
|
725
|
+
inp.addEventListener('change', () => {
|
|
726
|
+
let v = Number(inp.value);
|
|
727
|
+
if (!Number.isFinite(v)) v = 3;
|
|
728
|
+
v = Math.max(min, Math.min(max, v));
|
|
729
|
+
onChange(v);
|
|
730
|
+
});
|
|
731
|
+
return inp;
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const noteDefault = mkText('Default note text', this._opts.noteText, (v) => { this._opts.noteText = v; });
|
|
735
|
+
el.appendChild(makeVField('Note text', noteDefault));
|
|
736
|
+
|
|
737
|
+
const leaderDefault = mkText('Default leader text', this._opts.leaderText, (v) => { this._opts.leaderText = v; });
|
|
738
|
+
el.appendChild(makeVField('Leader text', leaderDefault));
|
|
739
|
+
|
|
740
|
+
const dimDec = mkNumber(this._opts.dimDecimals, (v) => { this._opts.dimDecimals = v | 0; this._annotationWidget?.render(); }, { min: 0, max: 8 });
|
|
741
|
+
el.appendChild(makeVField('Dim decimals', dimDec));
|
|
742
|
+
|
|
743
|
+
// Global PMI style controls
|
|
744
|
+
const style = getPMIStyle();
|
|
745
|
+
const mkColor = (value, onChange) => {
|
|
746
|
+
const wrap = document.createElement('div');
|
|
747
|
+
wrap.className = 'pmi-color-wrap';
|
|
748
|
+
const inp = document.createElement('input');
|
|
749
|
+
inp.type = 'color';
|
|
750
|
+
inp.value = toHex(value);
|
|
751
|
+
inp.className = 'pmi-color';
|
|
752
|
+
inp.addEventListener('input', () => onChange(inp.value));
|
|
753
|
+
wrap.appendChild(inp);
|
|
754
|
+
return wrap;
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const updateStyle = (patch) => {
|
|
758
|
+
setPMIStyle(patch);
|
|
759
|
+
this.#markAnnotationsDirty();
|
|
760
|
+
this._annotationWidget?.render();
|
|
761
|
+
this.#savePMIStyle();
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
el.appendChild(makeVField('Line color', mkColor(style.lineColor, (v) => updateStyle({ lineColor: hexToInt(v) }))));
|
|
765
|
+
el.appendChild(makeVField('Dot color', mkColor(style.dotColor, (v) => updateStyle({ dotColor: hexToInt(v) }))));
|
|
766
|
+
el.appendChild(makeVField('Arrow color', mkColor(style.arrowColor, (v) => updateStyle({ arrowColor: hexToInt(v) }))));
|
|
767
|
+
const mkPxNumber = (label, key, min = 1, max = 64) => {
|
|
768
|
+
const inp = mkNumber(style[key] ?? 0, (v) => updateStyle({ [key]: v }), { min, max });
|
|
769
|
+
el.appendChild(makeVField(label, inp));
|
|
770
|
+
};
|
|
771
|
+
mkPxNumber('Line width', 'lineWidth', 1, 8);
|
|
772
|
+
mkPxNumber('Arrow length (px)', 'arrowLengthPx', 1, 64);
|
|
773
|
+
mkPxNumber('Arrow width (px)', 'arrowWidthPx', 1, 32);
|
|
774
|
+
mkPxNumber('Leader dot (px)', 'leaderDotRadiusPx', 1, 32);
|
|
775
|
+
mkPxNumber('Hole dot (px)', 'holeDotRadiusPx', 1, 32);
|
|
776
|
+
mkPxNumber('Note dot radius', 'noteDotRadius', 0.01, 1);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
#loadPMIStyle() {
|
|
780
|
+
try {
|
|
781
|
+
const raw = LS.getItem(this._pmiStyleStorageKey);
|
|
782
|
+
if (!raw) return;
|
|
783
|
+
const parsed = JSON.parse(raw);
|
|
784
|
+
if (parsed && typeof parsed === 'object') {
|
|
785
|
+
setPMIStyle(sanitizePMIStyle(parsed));
|
|
786
|
+
}
|
|
787
|
+
} catch (e) {
|
|
788
|
+
console.warn('[PMI] Failed to load PMI style settings:', e);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
#savePMIStyle() {
|
|
793
|
+
try {
|
|
794
|
+
const style = sanitizePMIStyle(getPMIStyle());
|
|
795
|
+
LS.setItem(this._pmiStyleStorageKey, JSON.stringify(style));
|
|
796
|
+
} catch (e) {
|
|
797
|
+
console.warn('[PMI] Failed to save PMI style settings:', e);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
#toggleWireframeMode(isWireframe) {
|
|
802
|
+
try {
|
|
803
|
+
const scene = this.viewer?.scene;
|
|
804
|
+
if (!scene) return;
|
|
805
|
+
|
|
806
|
+
const isFace = (obj) => !!obj && (obj.type === 'FACE' || typeof obj.userData?.faceName === 'string');
|
|
807
|
+
const apply = (mat) => { if (mat && 'wireframe' in mat) mat.wireframe = !!isWireframe; };
|
|
808
|
+
// Toggle wireframe only on face materials.
|
|
809
|
+
scene.traverse((obj) => {
|
|
810
|
+
if (!isFace(obj)) return;
|
|
811
|
+
const m = obj.material;
|
|
812
|
+
if (!m) return;
|
|
813
|
+
if (Array.isArray(m)) m.forEach(apply); else apply(m);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// Trigger a render update
|
|
817
|
+
if (this.viewer?.render) {
|
|
818
|
+
this.viewer.render();
|
|
819
|
+
}
|
|
820
|
+
} catch { }
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
#checkCameraChange() {
|
|
824
|
+
// Check if camera has changed and refresh overlays if needed
|
|
825
|
+
try {
|
|
826
|
+
const camera = this.viewer?.camera;
|
|
827
|
+
if (!camera) return;
|
|
828
|
+
|
|
829
|
+
// Get current camera state
|
|
830
|
+
const currentState = {
|
|
831
|
+
px: camera.position.x, py: camera.position.y, pz: camera.position.z,
|
|
832
|
+
rx: camera.rotation.x, ry: camera.rotation.y, rz: camera.rotation.z,
|
|
833
|
+
zoom: camera.zoom || 1
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// Compare with previous state
|
|
837
|
+
if (!this._lastCameraState) {
|
|
838
|
+
this._lastCameraState = currentState;
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const prev = this._lastCameraState;
|
|
843
|
+
const threshold = 0.0001; // Small threshold for floating point comparison
|
|
844
|
+
|
|
845
|
+
const changed =
|
|
846
|
+
Math.abs(currentState.px - prev.px) > threshold ||
|
|
847
|
+
Math.abs(currentState.py - prev.py) > threshold ||
|
|
848
|
+
Math.abs(currentState.pz - prev.pz) > threshold ||
|
|
849
|
+
Math.abs(currentState.rx - prev.rx) > threshold ||
|
|
850
|
+
Math.abs(currentState.ry - prev.ry) > threshold ||
|
|
851
|
+
Math.abs(currentState.rz - prev.rz) > threshold ||
|
|
852
|
+
Math.abs(currentState.zoom - prev.zoom) > threshold;
|
|
853
|
+
|
|
854
|
+
if (changed) {
|
|
855
|
+
this._lastCameraState = currentState;
|
|
856
|
+
this._refreshOverlays();
|
|
857
|
+
}
|
|
858
|
+
} catch (e) {
|
|
859
|
+
console.warn('Error checking camera change:', e);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Apply view-specific transforms from ViewTransform annotations
|
|
864
|
+
#applyViewTransforms() {
|
|
865
|
+
try {
|
|
866
|
+
this.#ensureBaseSolidMatrices();
|
|
867
|
+
// Always return solids to their modeling positions before applying PMI offsets
|
|
868
|
+
this.#resetSolidsToBaseMatrices();
|
|
869
|
+
|
|
870
|
+
const annotationEntities = this._annotationHistory ? this._annotationHistory.getEntries() : [];
|
|
871
|
+
if (!Array.isArray(annotationEntities) || annotationEntities.length === 0) return;
|
|
872
|
+
const activeEntities = annotationEntities.filter((entity) => entity?.enabled !== false);
|
|
873
|
+
if (!activeEntities.length) return;
|
|
874
|
+
|
|
875
|
+
const anns = activeEntities.map((entity) => entity?.inputParams || {});
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
const handler = annotationRegistry.getSafe?.('viewTransform') || null;
|
|
879
|
+
if (!handler) {
|
|
880
|
+
console.warn('No handler found for viewTransform');
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const viewAnns = [];
|
|
885
|
+
for (const ann of anns) {
|
|
886
|
+
if (ann.type === 'viewTransform' || ann.type === 'explodeBody' || ann.type === 'exp') viewAnns.push(ann);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const cumulativeState = new Map();
|
|
890
|
+
|
|
891
|
+
for (const ann of viewAnns) {
|
|
892
|
+
|
|
893
|
+
if (typeof handler._resolveSolidReferences === 'function') {
|
|
894
|
+
handler._resolveSolidReferences(ann, this, false);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (typeof handler._ensureOriginalSnapshots === 'function') {
|
|
898
|
+
const solids = Array.isArray(ann.solids) ? ann.solids : [];
|
|
899
|
+
handler._ensureOriginalSnapshots(ann, solids, false, this.viewer);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const cloneSnapshot = (snapshot) => {
|
|
904
|
+
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
905
|
+
return {
|
|
906
|
+
position: Array.isArray(snapshot.position) ? snapshot.position.slice() : [0, 0, 0],
|
|
907
|
+
quaternion: Array.isArray(snapshot.quaternion) ? snapshot.quaternion.slice() : [0, 0, 0, 1],
|
|
908
|
+
scale: Array.isArray(snapshot.scale) ? snapshot.scale.slice() : [1, 1, 1],
|
|
909
|
+
worldPosition: Array.isArray(snapshot.worldPosition) ? snapshot.worldPosition.slice() : null,
|
|
910
|
+
};
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
for (const ann of viewAnns) {
|
|
914
|
+
const solids = Array.isArray(ann.solids) ? ann.solids : [];
|
|
915
|
+
if (!solids.length) {
|
|
916
|
+
if (typeof handler.applyTransformsToSolids === 'function') {
|
|
917
|
+
handler.applyTransformsToSolids(ann, this, { startSnapshots: new Map(), cumulativeState });
|
|
918
|
+
}
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
let startSnapshots = null;
|
|
923
|
+
if (typeof handler.getOriginalSnapshotMap === 'function') {
|
|
924
|
+
const origMap = handler.getOriginalSnapshotMap(ann);
|
|
925
|
+
startSnapshots = new Map();
|
|
926
|
+
for (const solid of solids) {
|
|
927
|
+
if (!solid || !solid.uuid) continue;
|
|
928
|
+
if (cumulativeState.has(solid.uuid)) {
|
|
929
|
+
const snap = cloneSnapshot(cumulativeState.get(solid.uuid));
|
|
930
|
+
if (snap) startSnapshots.set(solid.uuid, snap);
|
|
931
|
+
} else if (origMap && origMap.has(solid.uuid)) {
|
|
932
|
+
const snap = cloneSnapshot(origMap.get(solid.uuid));
|
|
933
|
+
if (snap) startSnapshots.set(solid.uuid, snap);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (typeof handler.applyTransformsToSolids === 'function') {
|
|
939
|
+
handler.applyTransformsToSolids(ann, this, { startSnapshots, cumulativeState });
|
|
940
|
+
} else if (typeof handler._applyTransformsToSolids === 'function') {
|
|
941
|
+
handler._applyTransformsToSolids(ann, this);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Trigger a render to show the transformed objects
|
|
946
|
+
if (this.viewer?.render) {
|
|
947
|
+
this.viewer.render();
|
|
948
|
+
}
|
|
949
|
+
} catch (error) {
|
|
950
|
+
console.warn('Failed to apply view transforms:', error);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Restore original transforms for all ViewTransform annotations
|
|
955
|
+
#restoreViewTransforms() {
|
|
956
|
+
try {
|
|
957
|
+
const entities = this._annotationHistory ? this._annotationHistory.getEntries() : [];
|
|
958
|
+
if (!Array.isArray(entities) || entities.length === 0) return;
|
|
959
|
+
|
|
960
|
+
const handler = annotationRegistry.getSafe?.('viewTransform') || null;
|
|
961
|
+
if (!handler) return;
|
|
962
|
+
|
|
963
|
+
for (const entity of entities) {
|
|
964
|
+
if (entity?.enabled === false) continue;
|
|
965
|
+
const ann = entity?.inputParams;
|
|
966
|
+
if (!ann || (ann.type !== 'viewTransform' && ann.type !== 'explodeBody' && ann.type !== 'exp')) continue;
|
|
967
|
+
|
|
968
|
+
if (typeof handler.restoreOriginalTransforms === 'function') {
|
|
969
|
+
handler.restoreOriginalTransforms(ann, this);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Trigger a render to show the restored objects
|
|
974
|
+
if (this.viewer?.render) {
|
|
975
|
+
this.viewer.render();
|
|
976
|
+
}
|
|
977
|
+
} catch (error) {
|
|
978
|
+
console.warn('Failed to restore view transforms:', error);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
#ensureBaseSolidMatrices() {
|
|
983
|
+
if (this._hasBaseMatrices) return;
|
|
984
|
+
try {
|
|
985
|
+
const scene = this.viewer?.scene;
|
|
986
|
+
if (!scene || typeof scene.traverse !== 'function') return;
|
|
987
|
+
const sessionKey = this._baseMatrixSessionKey;
|
|
988
|
+
scene.traverse((obj) => {
|
|
989
|
+
if (!obj || !obj.isObject3D || obj.type !== 'SOLID') return;
|
|
990
|
+
const data = obj.userData || (obj.userData = {});
|
|
991
|
+
try { obj.updateMatrixWorld(true); } catch { }
|
|
992
|
+
const matrix = data.__pmiBaseMatrix;
|
|
993
|
+
if (matrix && typeof matrix.copy === 'function' && matrix.isMatrix4) {
|
|
994
|
+
matrix.copy(obj.matrix);
|
|
995
|
+
} else {
|
|
996
|
+
data.__pmiBaseMatrix = obj.matrix.clone();
|
|
997
|
+
}
|
|
998
|
+
data.__pmiBaseMatrixSession = sessionKey;
|
|
999
|
+
});
|
|
1000
|
+
this._hasBaseMatrices = true;
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
console.warn('Failed to record base matrices for PMI mode:', error);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
#resetSolidsToBaseMatrices() {
|
|
1007
|
+
if (!this._hasBaseMatrices) return;
|
|
1008
|
+
try {
|
|
1009
|
+
const scene = this.viewer?.scene;
|
|
1010
|
+
if (!scene || typeof scene.traverse !== 'function') return;
|
|
1011
|
+
const sessionKey = this._baseMatrixSessionKey;
|
|
1012
|
+
scene.traverse((obj) => {
|
|
1013
|
+
if (!obj || !obj.isObject3D || obj.type !== 'SOLID') return;
|
|
1014
|
+
const data = obj.userData;
|
|
1015
|
+
const base = data?.__pmiBaseMatrix;
|
|
1016
|
+
if (!base || !base.isMatrix4 || data.__pmiBaseMatrixSession !== sessionKey) return;
|
|
1017
|
+
try {
|
|
1018
|
+
obj.matrix.copy(base);
|
|
1019
|
+
obj.matrix.decompose(obj.position, obj.quaternion, obj.scale);
|
|
1020
|
+
if (obj.matrixAutoUpdate) {
|
|
1021
|
+
obj.updateMatrix();
|
|
1022
|
+
}
|
|
1023
|
+
obj.updateMatrixWorld(true);
|
|
1024
|
+
} catch { /* ignore per-object restore errors */ }
|
|
1025
|
+
});
|
|
1026
|
+
try { this.viewer?.render?.(); } catch { }
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
console.warn('Failed to reset solids to PMI base matrices:', error);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
#clearBaseSolidMatrices() {
|
|
1033
|
+
if (!this._hasBaseMatrices) {
|
|
1034
|
+
this._baseMatrixSessionKey = `pmi-base-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
try {
|
|
1038
|
+
const scene = this.viewer?.scene;
|
|
1039
|
+
if (!scene || typeof scene.traverse !== 'function') return;
|
|
1040
|
+
const sessionKey = this._baseMatrixSessionKey;
|
|
1041
|
+
scene.traverse((obj) => {
|
|
1042
|
+
if (!obj || !obj.isObject3D || obj.type !== 'SOLID') return;
|
|
1043
|
+
const data = obj.userData;
|
|
1044
|
+
if (!data || data.__pmiBaseMatrixSession !== sessionKey) return;
|
|
1045
|
+
delete data.__pmiBaseMatrix;
|
|
1046
|
+
delete data.__pmiBaseMatrixSession;
|
|
1047
|
+
});
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
console.warn('Failed to clear PMI base matrices:', error);
|
|
1050
|
+
} finally {
|
|
1051
|
+
this._hasBaseMatrices = false;
|
|
1052
|
+
this._baseMatrixSessionKey = `pmi-base-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// --- Annotation 3D visuals ---
|
|
1057
|
+
#clearAnnGroup() {
|
|
1058
|
+
try {
|
|
1059
|
+
if (!this._annGroup) return;
|
|
1060
|
+
for (let i = this._annGroup.children.length - 1; i >= 0; i--) {
|
|
1061
|
+
const c = this._annGroup.children[i];
|
|
1062
|
+
this._annGroup.remove(c);
|
|
1063
|
+
if (c.geometry) c.geometry.dispose?.();
|
|
1064
|
+
if (c.material) c.material.dispose?.();
|
|
1065
|
+
}
|
|
1066
|
+
} catch { }
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
_refreshOverlays() {
|
|
1070
|
+
// Rebuild overlays on camera changes (simpler and type-agnostic)
|
|
1071
|
+
if (this._refreshPending) return;
|
|
1072
|
+
this._refreshPending = true;
|
|
1073
|
+
requestAnimationFrame(() => {
|
|
1074
|
+
this._refreshPending = false;
|
|
1075
|
+
try { this.#rebuildAnnotationObjects(); } catch { }
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
#markAnnotationsDirty() {
|
|
1083
|
+
this._annotationsDirty = true;
|
|
1084
|
+
// Immediately rebuild annotations instead of waiting for timer
|
|
1085
|
+
try {
|
|
1086
|
+
this.#rebuildAnnotationObjects();
|
|
1087
|
+
this._annotationsDirty = false;
|
|
1088
|
+
this.#_persistView();
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
console.warn('Failed to rebuild annotations:', error);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Public: allow external handlers to refresh the side list and 3D objects
|
|
1095
|
+
refreshAnnotationsUI() {
|
|
1096
|
+
try { this._annotationWidget?.render(); } catch { }
|
|
1097
|
+
try {
|
|
1098
|
+
this.#rebuildAnnotationObjects();
|
|
1099
|
+
this._annotationsDirty = false;
|
|
1100
|
+
this.#_persistView();
|
|
1101
|
+
} catch { }
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
#rebuildAnnotationObjects() {
|
|
1105
|
+
this.#clearAnnGroup();
|
|
1106
|
+
const group = this._annGroup;
|
|
1107
|
+
if (!group) return;
|
|
1108
|
+
try { this._labelOverlay?.clear?.(); } catch { }
|
|
1109
|
+
// Ensure overlay exists before we start populating labels
|
|
1110
|
+
const entities = this._annotationHistory ? this._annotationHistory.getEntries() : [];
|
|
1111
|
+
const ctx = {
|
|
1112
|
+
pmimode: this,
|
|
1113
|
+
screenSizeWorld: (px) => { try { return this.#_screenSizeWorld(px); } catch { return 0; } },
|
|
1114
|
+
alignNormal: (alignment, ann) => { try { return this.#_alignNormal(alignment, ann); } catch { return new THREE.Vector3(0, 0, 1); } },
|
|
1115
|
+
updateLabel: (idx, text, worldPos, ann) => { try { this._labelOverlay?.updateLabel?.(idx, text, worldPos, ann); } catch { } },
|
|
1116
|
+
formatReferenceLabel: (ann, text) => { try { return this.#formatReferenceLabel(ann, text); } catch { return text; } },
|
|
1117
|
+
// keep only generic helpers
|
|
1118
|
+
// specific drawing/measuring handled by annotation handlers now
|
|
1119
|
+
};
|
|
1120
|
+
this.__explodeTraceState = new Map();
|
|
1121
|
+
entities.forEach((entity, i) => {
|
|
1122
|
+
try {
|
|
1123
|
+
if (entity?.enabled === false) return;
|
|
1124
|
+
if (!entity || typeof entity.run !== 'function') return;
|
|
1125
|
+
if (!entity.persistentData || typeof entity.persistentData !== 'object') {
|
|
1126
|
+
entity.setPersistentData({});
|
|
1127
|
+
}
|
|
1128
|
+
const renderingContext = {
|
|
1129
|
+
pmimode: this,
|
|
1130
|
+
group,
|
|
1131
|
+
idx: i,
|
|
1132
|
+
ctx,
|
|
1133
|
+
};
|
|
1134
|
+
const runResult = entity.run(renderingContext);
|
|
1135
|
+
if (runResult && typeof runResult.then === 'function') {
|
|
1136
|
+
runResult.catch(() => {});
|
|
1137
|
+
}
|
|
1138
|
+
} catch { }
|
|
1139
|
+
});
|
|
1140
|
+
try { this.viewer.render(); } catch { }
|
|
1141
|
+
// No post-check necessary
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Wrap label text in parentheses when marked as a reference dimension
|
|
1145
|
+
#formatReferenceLabel(ann, text) {
|
|
1146
|
+
try {
|
|
1147
|
+
const t = String(text ?? '');
|
|
1148
|
+
if (!t) return t;
|
|
1149
|
+
if (ann && (ann.isReference === true)) return `(${t})`;
|
|
1150
|
+
return t;
|
|
1151
|
+
} catch { return text; }
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
#focusAnnotationDialog(idx, ann, e) {
|
|
1159
|
+
e.preventDefault(); e.stopImmediatePropagation?.(); e.stopPropagation();
|
|
1160
|
+
|
|
1161
|
+
try {
|
|
1162
|
+
const entries = this._annotationHistory ? this._annotationHistory.getEntries() : [];
|
|
1163
|
+
const entity = entries[idx];
|
|
1164
|
+
if (!entity) return;
|
|
1165
|
+
const entryId = entity.inputParams?.id || entity.id || idx;
|
|
1166
|
+
this._annotationWidget?.render();
|
|
1167
|
+
requestAnimationFrame(() => {
|
|
1168
|
+
try {
|
|
1169
|
+
const form = this._annotationWidget?.getFormForEntry(String(entryId));
|
|
1170
|
+
const host = form?.uiElement;
|
|
1171
|
+
if (!host) return;
|
|
1172
|
+
const root = host.shadowRoot || host;
|
|
1173
|
+
const row = root?.querySelector('[data-key="text"]');
|
|
1174
|
+
const textField = row ? row.querySelector('textarea, input[type="text"], input') : null;
|
|
1175
|
+
if (textField) {
|
|
1176
|
+
textField.focus();
|
|
1177
|
+
textField.select?.();
|
|
1178
|
+
}
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
console.warn('Could not focus annotation dialog text field:', error);
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
console.warn('Failed to focus annotation dialog:', error);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
_handlePointerDown(e) {
|
|
1193
|
+
// Only left-clicks
|
|
1194
|
+
if (e.button !== 0) return;
|
|
1195
|
+
// Avoid interfering if clicking overlays
|
|
1196
|
+
try {
|
|
1197
|
+
const path = e.composedPath?.() || [];
|
|
1198
|
+
if (path.some((el) => el === this._uiTopRight || (el?.classList?.contains?.('pmi-side')))) return;
|
|
1199
|
+
} catch { }
|
|
1200
|
+
|
|
1201
|
+
// If a feature reference_selection is active, let selection widget handle it
|
|
1202
|
+
try { const activeRef = document.querySelector('[active-reference-selection="true"],[active-reference-selection=true]'); if (activeRef) return; } catch { }
|
|
1203
|
+
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
#handleLabelClick(idx, ann, e) {
|
|
1208
|
+
try { e.preventDefault(); e.stopImmediatePropagation?.(); e.stopPropagation(); } catch { }
|
|
1209
|
+
this.#collapseAnnotationsToIndex(idx);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
#handleLabelDragEnd(idx, ann, e) {
|
|
1213
|
+
// Expand the dialog associated with the label that was dragged
|
|
1214
|
+
this.#collapseAnnotationsToIndex(idx);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
#collapseAnnotationsToIndex(targetIdx) {
|
|
1218
|
+
const entries = this._annotationHistory ? this._annotationHistory.getEntries() : [];
|
|
1219
|
+
if (!entries.length) return;
|
|
1220
|
+
if (!Number.isInteger(targetIdx) || targetIdx < 0 || targetIdx >= entries.length) return;
|
|
1221
|
+
let changed = false;
|
|
1222
|
+
entries.forEach((entry, i) => {
|
|
1223
|
+
const shouldOpen = i === targetIdx;
|
|
1224
|
+
if (!entry.runtimeAttributes || typeof entry.runtimeAttributes !== 'object') entry.runtimeAttributes = {};
|
|
1225
|
+
if (entry.runtimeAttributes.__open !== shouldOpen) {
|
|
1226
|
+
entry.runtimeAttributes.__open = shouldOpen;
|
|
1227
|
+
changed = true;
|
|
1228
|
+
}
|
|
1229
|
+
if (entry.inputParams && typeof entry.inputParams === 'object') {
|
|
1230
|
+
entry.inputParams.__open = shouldOpen;
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
if (!changed) return;
|
|
1234
|
+
const targetEntry = entries[targetIdx];
|
|
1235
|
+
const targetId = targetEntry ? (targetEntry.inputParams?.id || targetEntry.id || targetIdx) : targetIdx;
|
|
1236
|
+
this._annotationWidget?.render();
|
|
1237
|
+
requestAnimationFrame(() => {
|
|
1238
|
+
try {
|
|
1239
|
+
const root = this._annotationWidget?._shadow;
|
|
1240
|
+
if (!root) return;
|
|
1241
|
+
const selector = `[data-entry-id="${cssEscape(String(targetId))}"]`;
|
|
1242
|
+
const item = root.querySelector(selector);
|
|
1243
|
+
if (item && typeof item.scrollIntoView === 'function') item.scrollIntoView({ block: 'nearest' });
|
|
1244
|
+
} catch { }
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
#startLabelDrag(idx, ann, e) {
|
|
1251
|
+
e.preventDefault(); e.stopImmediatePropagation?.(); e.stopPropagation();
|
|
1252
|
+
try { if (this.viewer?.controls) this.viewer.controls.enabled = false; } catch { }
|
|
1253
|
+
const v = this.viewer; const cam = v?.camera; if (!cam) return;
|
|
1254
|
+
|
|
1255
|
+
try {
|
|
1256
|
+
const handler = annotationRegistry.getSafe?.(ann?.type) || null;
|
|
1257
|
+
if (handler && typeof handler.onLabelPointerDown === "function") {
|
|
1258
|
+
const ctx = {
|
|
1259
|
+
pmimode: this,
|
|
1260
|
+
screenSizeWorld: (px) => { try { return this.#_screenSizeWorld(px); } catch { return 0; } },
|
|
1261
|
+
alignNormal: (alignment, a) => { try { return this.#_alignNormal(alignment, a); } catch { return new THREE.Vector3(0, 0, 1); } },
|
|
1262
|
+
updateLabel: (i, text, worldPos, a) => { try { this._labelOverlay?.updateLabel?.(i, text, worldPos, a); } catch { } },
|
|
1263
|
+
intersectPlane: (ray, plane, out) => { try { return this.#_intersectPlaneBothSides(ray, plane, out); } catch { return null; } },
|
|
1264
|
+
raycastFromEvent: (ev) => {
|
|
1265
|
+
const rect = v.renderer.domElement.getBoundingClientRect();
|
|
1266
|
+
const ndc = new THREE.Vector2(((ev.clientX - rect.left) / rect.width) * 2 - 1, -(((ev.clientY - rect.top) / rect.height) * 2 - 1));
|
|
1267
|
+
v.raycaster.setFromCamera(ndc, cam);
|
|
1268
|
+
return v.raycaster.ray;
|
|
1269
|
+
},
|
|
1270
|
+
};
|
|
1271
|
+
handler.onLabelPointerDown(this, idx, ann, e, ctx);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
} catch { }
|
|
1275
|
+
|
|
1276
|
+
try { if (this.viewer?.controls) this.viewer.controls.enabled = true; } catch { }
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Debug helper: visualize the plane used during label drags
|
|
1280
|
+
showDragPlaneHelper(plane) {
|
|
1281
|
+
try {
|
|
1282
|
+
if (!plane || !plane.normal) return;
|
|
1283
|
+
this.hideDragPlaneHelper();
|
|
1284
|
+
const scene = this.viewer?.scene;
|
|
1285
|
+
if (!scene) return;
|
|
1286
|
+
const size = this.#estimateSceneSpan();
|
|
1287
|
+
const divisions = Math.max(6, Math.min(60, Math.round(size)));
|
|
1288
|
+
const grid = new THREE.GridHelper(size, divisions, 0x00ffff, 0x00ffff);
|
|
1289
|
+
const mats = Array.isArray(grid.material) ? grid.material : [grid.material];
|
|
1290
|
+
mats.forEach((m) => { if (m) { m.transparent = true; m.opacity = 0.35; m.depthWrite = false; } });
|
|
1291
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
1292
|
+
const n = plane.normal.clone().normalize();
|
|
1293
|
+
if (n.lengthSq() < 1e-12) return;
|
|
1294
|
+
const q = new THREE.Quaternion().setFromUnitVectors(up, n);
|
|
1295
|
+
grid.quaternion.copy(q);
|
|
1296
|
+
const center = plane.coplanarPoint(new THREE.Vector3());
|
|
1297
|
+
grid.position.copy(center);
|
|
1298
|
+
grid.renderOrder = 9998;
|
|
1299
|
+
grid.name = '__PMI_DRAG_PLANE__';
|
|
1300
|
+
scene.add(grid);
|
|
1301
|
+
this._dragPlaneHelper = grid;
|
|
1302
|
+
} catch { }
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
hideDragPlaneHelper() {
|
|
1306
|
+
try {
|
|
1307
|
+
const g = this._dragPlaneHelper;
|
|
1308
|
+
if (g && g.parent) g.parent.remove(g);
|
|
1309
|
+
if (g?.geometry) g.geometry.dispose?.();
|
|
1310
|
+
if (g?.material) {
|
|
1311
|
+
const mats = Array.isArray(g.material) ? g.material : [g.material];
|
|
1312
|
+
mats.forEach((m) => m?.dispose?.());
|
|
1313
|
+
}
|
|
1314
|
+
} catch { }
|
|
1315
|
+
this._dragPlaneHelper = null;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
#estimateSceneSpan() {
|
|
1319
|
+
try {
|
|
1320
|
+
const scene = this.viewer?.partHistory?.scene || this.viewer?.scene;
|
|
1321
|
+
if (scene) {
|
|
1322
|
+
const box = new THREE.Box3().setFromObject(scene);
|
|
1323
|
+
if (!box.isEmpty()) {
|
|
1324
|
+
const size = box.getSize(new THREE.Vector3());
|
|
1325
|
+
const span = Math.max(size.x, size.y, size.z);
|
|
1326
|
+
if (Number.isFinite(span) && span > 0) return Math.min(Math.max(span * 1.25, 1), 2000);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
} catch { }
|
|
1330
|
+
return 20;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Allow ray-plane intersection even when the plane is "behind" the ray origin
|
|
1334
|
+
// by retrying with a flipped ray direction (infinite line cast).
|
|
1335
|
+
#_intersectPlaneBothSides(ray, plane, out = new THREE.Vector3()) {
|
|
1336
|
+
try {
|
|
1337
|
+
if (!ray || !plane) return null;
|
|
1338
|
+
const direct = ray.intersectPlane(plane, out);
|
|
1339
|
+
if (direct) return out;
|
|
1340
|
+
const invRay = new THREE.Ray(ray.origin.clone(), ray.direction.clone().negate());
|
|
1341
|
+
return invRay.intersectPlane(plane, out);
|
|
1342
|
+
} catch { return null; }
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
// Convert legacy annotation shapes to anchor-based ones (in-memory only)
|
|
1347
|
+
#normalizeAnnotation(a) {
|
|
1348
|
+
try {
|
|
1349
|
+
if (!a || typeof a !== 'object') return a;
|
|
1350
|
+
if (a.type === 'leader') {
|
|
1351
|
+
if (!a.anchor && a.start) {
|
|
1352
|
+
// Attempt to map start to nearest edge point fraction
|
|
1353
|
+
const near = this.#nearestEdgeAnchor(new THREE.Vector3(a.start.x || 0, a.start.y || 0, a.start.z || 0));
|
|
1354
|
+
if (near) a.anchor = near;
|
|
1355
|
+
}
|
|
1356
|
+
} else if (a.type === 'dim') {
|
|
1357
|
+
if (!a.a && a.p0) {
|
|
1358
|
+
const near = this.#nearestEdgeAnchor(new THREE.Vector3(a.p0.x || 0, a.p0.y || 0, a.p0.z || 0));
|
|
1359
|
+
if (near) a.a = near;
|
|
1360
|
+
}
|
|
1361
|
+
if (!a.b && a.p1) {
|
|
1362
|
+
const near = this.#nearestEdgeAnchor(new THREE.Vector3(a.p1.x || 0, a.p1.y || 0, a.p1.z || 0));
|
|
1363
|
+
if (near) a.b = near;
|
|
1364
|
+
}
|
|
1365
|
+
} else if (a.type === 'linear') {
|
|
1366
|
+
if ((!Array.isArray(a.targets) || a.targets.length === 0) && (a.aRefName || a.bRefName)) {
|
|
1367
|
+
a.targets = [a.aRefName, a.bRefName].filter(Boolean);
|
|
1368
|
+
}
|
|
1369
|
+
} else if (a.type === 'angle') {
|
|
1370
|
+
if ((!Array.isArray(a.targets) || a.targets.length === 0) && (a.elementARefName || a.elementBRefName)) {
|
|
1371
|
+
a.targets = [a.elementARefName, a.elementBRefName].filter(Boolean);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
} catch { }
|
|
1375
|
+
return a;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// Return an anchor ref near a world point by scanning edges/vertices
|
|
1379
|
+
#nearestEdgeAnchor(world) {
|
|
1380
|
+
try {
|
|
1381
|
+
const v = this.viewer;
|
|
1382
|
+
const edges = [];
|
|
1383
|
+
v.scene.traverse((obj) => { if (obj && obj.type === 'EDGE' && obj.visible !== false) edges.push(obj); });
|
|
1384
|
+
let best = null, bestD = Infinity;
|
|
1385
|
+
for (const e of edges) {
|
|
1386
|
+
const info = this.#edgeFractionAtWorld(e, world);
|
|
1387
|
+
if (info && info.dist < bestD) { best = { type: 'edge', edgeId: e.id, edgeName: e.name || null, solidName: e.parent?.name || null, t: info.t }; bestD = info.dist; }
|
|
1388
|
+
}
|
|
1389
|
+
if (best) return best;
|
|
1390
|
+
} catch { }
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// Hit-test for anchor: prefer VERTEX, else EDGE at fraction along length
|
|
1395
|
+
#pickAnchor(e) {
|
|
1396
|
+
const v = this.viewer;
|
|
1397
|
+
if (!v) return null;
|
|
1398
|
+
// First, try vertices via raycast
|
|
1399
|
+
try {
|
|
1400
|
+
const rect = v.renderer.domElement.getBoundingClientRect();
|
|
1401
|
+
const ndc = new THREE.Vector2(
|
|
1402
|
+
((e.clientX - rect.left) / rect.width) * 2 - 1,
|
|
1403
|
+
-(((e.clientY - rect.top) / rect.height) * 2 - 1),
|
|
1404
|
+
);
|
|
1405
|
+
v.raycaster.setFromCamera(ndc, v.camera);
|
|
1406
|
+
// include Points children; we'll walk up to VERTEX parents
|
|
1407
|
+
const targets = [];
|
|
1408
|
+
v.scene.traverse((obj) => { if (obj && (obj.type === 'VERTEX' || obj.isPoints) && obj.visible !== false) targets.push(obj); });
|
|
1409
|
+
const hits = targets.length ? v.raycaster.intersectObjects(targets, true) : [];
|
|
1410
|
+
if (hits && hits.length) {
|
|
1411
|
+
let obj = hits[0].object;
|
|
1412
|
+
while (obj && obj.type !== 'VERTEX' && obj.parent) obj = obj.parent;
|
|
1413
|
+
if (obj && obj.type === 'VERTEX') {
|
|
1414
|
+
const w = obj.getWorldPosition(new THREE.Vector3());
|
|
1415
|
+
return { anchor: { type: 'vertex', vertexId: obj.id, name: obj.name || null, solidName: obj.parent?.name || null }, world: w };
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
} catch { }
|
|
1419
|
+
|
|
1420
|
+
// Next, try edges using a generous Line/Line2 threshold
|
|
1421
|
+
try {
|
|
1422
|
+
const rect = v.renderer.domElement.getBoundingClientRect();
|
|
1423
|
+
const ndc = new THREE.Vector2(
|
|
1424
|
+
((e.clientX - rect.left) / rect.width) * 2 - 1,
|
|
1425
|
+
-(((e.clientY - rect.top) / rect.height) * 2 - 1),
|
|
1426
|
+
);
|
|
1427
|
+
v.raycaster.setFromCamera(ndc, v.camera);
|
|
1428
|
+
try {
|
|
1429
|
+
const { width, height } = { width: rect.width, height: rect.height };
|
|
1430
|
+
const wpp = this.#worldPerPixel(v.camera, width, height);
|
|
1431
|
+
v.raycaster.params.Line = v.raycaster.params.Line || {};
|
|
1432
|
+
v.raycaster.params.Line.threshold = Math.max(0.05, wpp * 6);
|
|
1433
|
+
const dpr = (window.devicePixelRatio || 1);
|
|
1434
|
+
v.raycaster.params.Line2 = v.raycaster.params.Line2 || {};
|
|
1435
|
+
v.raycaster.params.Line2.threshold = Math.max(1, 2 * dpr);
|
|
1436
|
+
} catch { }
|
|
1437
|
+
const edges = [];
|
|
1438
|
+
v.scene.traverse((obj) => { if (obj && obj.type === 'EDGE' && obj.visible !== false) edges.push(obj); });
|
|
1439
|
+
const hits = edges.length ? v.raycaster.intersectObjects(edges, true) : [];
|
|
1440
|
+
if (hits && hits.length) {
|
|
1441
|
+
const hit = hits[0];
|
|
1442
|
+
const edge = hit.object;
|
|
1443
|
+
const info = this.#edgeFractionAtWorld(edge, hit.point);
|
|
1444
|
+
if (info) {
|
|
1445
|
+
return { anchor: { type: 'edge', edgeId: edge.id, edgeName: edge.name || null, solidName: edge.parent?.name || null, t: info.t }, world: info.point };
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
} catch { }
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Compute closest fraction t along EDGE polyline to a world point
|
|
1453
|
+
#edgeFractionAtWorld(edge, worldPoint) {
|
|
1454
|
+
try {
|
|
1455
|
+
const pts = edge.points(true);
|
|
1456
|
+
if (!Array.isArray(pts) || pts.length < 2) return null;
|
|
1457
|
+
const a = new THREE.Vector3(), b = new THREE.Vector3(), p = worldPoint.clone();
|
|
1458
|
+
let total = 0, best = { t: 0, dist: Infinity, point: pts[0] };
|
|
1459
|
+
let accum = 0;
|
|
1460
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
1461
|
+
a.set(pts[i].x, pts[i].y, pts[i].z);
|
|
1462
|
+
b.set(pts[i + 1].x, pts[i + 1].y, pts[i + 1].z);
|
|
1463
|
+
const segLen = a.distanceTo(b) || 1e-12;
|
|
1464
|
+
// project p onto segment ab
|
|
1465
|
+
const ab = b.clone().sub(a);
|
|
1466
|
+
const ap = p.clone().sub(a);
|
|
1467
|
+
let t = ab.dot(ap) / (segLen * segLen);
|
|
1468
|
+
if (t < 0) t = 0; else if (t > 1) t = 1;
|
|
1469
|
+
const q = a.clone().addScaledVector(ab, t);
|
|
1470
|
+
const d = q.distanceTo(p);
|
|
1471
|
+
if (d < best.dist) {
|
|
1472
|
+
const tTotal = (accum + t * segLen);
|
|
1473
|
+
best = { t: tTotal, dist: d, point: { x: q.x, y: q.y, z: q.z } };
|
|
1474
|
+
}
|
|
1475
|
+
accum += segLen; total += segLen;
|
|
1476
|
+
}
|
|
1477
|
+
if (total <= 1e-9) return null;
|
|
1478
|
+
return { t: best.t / total, dist: best.dist, point: best.point };
|
|
1479
|
+
} catch { return null; }
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// Convert anchor ref to current world position
|
|
1483
|
+
#worldPerPixel(camera, width, height) {
|
|
1484
|
+
try {
|
|
1485
|
+
if (camera && camera.isOrthographicCamera) {
|
|
1486
|
+
const zoom = (typeof camera.zoom === 'number' && camera.zoom > 0) ? camera.zoom : 1;
|
|
1487
|
+
const wppX = (camera.right - camera.left) / (width * zoom);
|
|
1488
|
+
const wppY = (camera.top - camera.bottom) / (height * zoom);
|
|
1489
|
+
return Math.max(Math.abs(wppX), Math.abs(wppY));
|
|
1490
|
+
}
|
|
1491
|
+
const dist = camera.position.length();
|
|
1492
|
+
const fovRad = (camera.fov * Math.PI) / 180;
|
|
1493
|
+
const h = 2 * Math.tan(fovRad / 2) * dist;
|
|
1494
|
+
return h / height;
|
|
1495
|
+
} catch { return 1; }
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
#_screenSizeWorld(pixels) {
|
|
1499
|
+
try {
|
|
1500
|
+
const rect = this.viewer?.renderer?.domElement?.getBoundingClientRect?.() || { width: 800, height: 600 };
|
|
1501
|
+
const wpp = this.#worldPerPixel(this.viewer.camera, rect.width, rect.height);
|
|
1502
|
+
return Math.max(0.0001, wpp * (pixels || 1));
|
|
1503
|
+
} catch { return 0.01; }
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
#_alignNormal(alignment, ann) {
|
|
1507
|
+
// If a face/plane reference is provided, use its world normal
|
|
1508
|
+
try {
|
|
1509
|
+
const name = ann?.planeRefName || '';
|
|
1510
|
+
if (name) {
|
|
1511
|
+
const scene = this.viewer?.partHistory?.scene;
|
|
1512
|
+
const obj = scene?.getObjectByName(name);
|
|
1513
|
+
if (obj) {
|
|
1514
|
+
// Face average normal → world
|
|
1515
|
+
if (obj.type === 'FACE' && typeof obj.getAverageNormal === 'function') {
|
|
1516
|
+
const local = obj.getAverageNormal().clone();
|
|
1517
|
+
const nm = new THREE.Matrix3(); nm.getNormalMatrix(obj.matrixWorld);
|
|
1518
|
+
return local.applyMatrix3(nm).normalize();
|
|
1519
|
+
}
|
|
1520
|
+
// PLANE or any Object3D: attempt to use its Z axis as normal
|
|
1521
|
+
const w = new THREE.Vector3(0, 0, 1);
|
|
1522
|
+
try { obj.updateMatrixWorld(true); w.applyMatrix3(new THREE.Matrix3().getNormalMatrix(obj.matrixWorld)); } catch { }
|
|
1523
|
+
if (w.lengthSq()) return w.normalize();
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
} catch { }
|
|
1527
|
+
// Fallback: explicit axis or camera view direction
|
|
1528
|
+
const mode = String(alignment || 'view').toLowerCase();
|
|
1529
|
+
if (mode === 'xy') return new THREE.Vector3(0, 0, 1);
|
|
1530
|
+
if (mode === 'yz') return new THREE.Vector3(1, 0, 0);
|
|
1531
|
+
if (mode === 'zx') return new THREE.Vector3(0, 1, 0);
|
|
1532
|
+
const n = new THREE.Vector3();
|
|
1533
|
+
try { this.viewer?.camera?.getWorldDirection?.(n); } catch { }
|
|
1534
|
+
return n.lengthSq() ? n : new THREE.Vector3(0, 0, 1);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function toHex(value) {
|
|
1539
|
+
const n = Number(value);
|
|
1540
|
+
const safe = Number.isFinite(n) ? n : 0;
|
|
1541
|
+
const hex = `000000${(safe >>> 0).toString(16)}`.slice(-6);
|
|
1542
|
+
return `#${hex}`;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function hexToInt(value) {
|
|
1546
|
+
if (typeof value !== 'string') return 0;
|
|
1547
|
+
const str = value.startsWith('#') ? value.slice(1) : value;
|
|
1548
|
+
const n = parseInt(str, 16);
|
|
1549
|
+
return Number.isFinite(n) ? n : 0;
|
|
1550
|
+
}
|