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,955 @@
|
|
|
1
|
+
|
|
2
|
+
import { ConstraintEngine } from './sketchSolver2D/ConstraintEngine.js';
|
|
3
|
+
import { BREP } from "../../BREP/BREP.js";
|
|
4
|
+
const THREE = BREP.THREE;
|
|
5
|
+
import { LineGeometry } from 'three/examples/jsm/Addons.js';
|
|
6
|
+
import { deepClone } from '../../utils/deepClone.js';
|
|
7
|
+
|
|
8
|
+
const inputParamsSchema = {
|
|
9
|
+
id: {
|
|
10
|
+
type: "string",
|
|
11
|
+
default_value: null,
|
|
12
|
+
hint: "unique identifier for the sketch feature",
|
|
13
|
+
},
|
|
14
|
+
sketchPlane: {
|
|
15
|
+
type: "reference_selection",
|
|
16
|
+
selectionFilter: ["PLANE", "FACE"],
|
|
17
|
+
multiple: false,
|
|
18
|
+
default_value: null,
|
|
19
|
+
hint: "Select the plane or face for the sketch",
|
|
20
|
+
},
|
|
21
|
+
editSketch: {
|
|
22
|
+
type: "button",
|
|
23
|
+
label: "Edit Sketch",
|
|
24
|
+
default_value: null,
|
|
25
|
+
hint: "Launch the 2D sketch editor",
|
|
26
|
+
actionFunction: (ctx) => {
|
|
27
|
+
try {
|
|
28
|
+
if (ctx && ctx.viewer && typeof ctx.viewer.startSketchMode === 'function') {
|
|
29
|
+
ctx.viewer.startSketchMode(ctx.featureID);
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error('viewer.startSketchMode unavailable');
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.warn('[SketchFeature] Failed to start sketch mode:', e?.message || e);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
dumpSketchDiagnostics: {
|
|
39
|
+
type: "button",
|
|
40
|
+
label: "Dump Diagnostics",
|
|
41
|
+
default_value: null,
|
|
42
|
+
hint: "Download the current sketch and triangulation data for debugging",
|
|
43
|
+
actionFunction: (ctx) => {
|
|
44
|
+
try {
|
|
45
|
+
const ph = ctx?.partHistory || null;
|
|
46
|
+
const fid = ctx?.featureID ?? ctx?.feature?.inputParams?.featureID ?? null;
|
|
47
|
+
let featureData = (ctx && typeof ctx === 'object') ? ctx.feature : null;
|
|
48
|
+
if ((!featureData || typeof featureData !== 'object') && ph && fid != null) {
|
|
49
|
+
const arr = Array.isArray(ph?.features) ? ph.features : [];
|
|
50
|
+
featureData = arr.find((f) => f && f.inputParams && String(f.inputParams.featureID) === String(fid)) || featureData;
|
|
51
|
+
}
|
|
52
|
+
if (!featureData || typeof featureData !== 'object') {
|
|
53
|
+
console.warn('[SketchFeature] Unable to locate sketch feature data for diagnostics');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const instance = new SketchFeature(ph);
|
|
57
|
+
instance.inputParams = deepClone(featureData.inputParams || {});
|
|
58
|
+
if (fid != null && (instance.inputParams == null || instance.inputParams.featureID == null)) {
|
|
59
|
+
instance.inputParams = instance.inputParams || {};
|
|
60
|
+
instance.inputParams.featureID = fid;
|
|
61
|
+
}
|
|
62
|
+
instance.persistentData = deepClone(featureData.persistentData || {});
|
|
63
|
+
const payload = instance.dumpDiagnostics({ partHistory: ph, download: true });
|
|
64
|
+
if (!payload) {
|
|
65
|
+
console.warn('[SketchFeature] Diagnostics export produced no payload');
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error('[SketchFeature] Failed to dump diagnostics:', e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
curveResolution: {
|
|
73
|
+
type: "number",
|
|
74
|
+
default_value: 32,
|
|
75
|
+
min: 32,
|
|
76
|
+
max: 512,
|
|
77
|
+
hint: "Segments for circles; arcs scale proportionally",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export class SketchFeature {
|
|
82
|
+
static shortName = "S";
|
|
83
|
+
static longName = "Sketch";
|
|
84
|
+
static inputParamsSchema = inputParamsSchema;
|
|
85
|
+
|
|
86
|
+
constructor() {
|
|
87
|
+
this.inputParams = {};
|
|
88
|
+
|
|
89
|
+
// Persisted between edits: { basis, sketch }
|
|
90
|
+
this.persistentData = this.persistentData || {};
|
|
91
|
+
this._sketchChanged = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Build (and persist) a plane basis from the selected sketchPlane.
|
|
95
|
+
// Always recompute from the current referenced object transform if available,
|
|
96
|
+
// so the sketch follows moves/updates of the face/plane.
|
|
97
|
+
// basis = { origin: [x,y,z], x: [x,y,z], y: [x,y,z], z: [x,y,z], refName?: string }
|
|
98
|
+
_getOrCreateBasis(partHistory) {
|
|
99
|
+
const currentRef = this.inputParams?.sketchPlane || null;
|
|
100
|
+
const pdBasis = this.persistentData?.basis || null;
|
|
101
|
+
const ph = partHistory;
|
|
102
|
+
// Accept object (preferred, from sanitizeInputParams) or fallback to name
|
|
103
|
+
let refObj = null;
|
|
104
|
+
if (Array.isArray(currentRef)) {
|
|
105
|
+
refObj = currentRef[0] || null;
|
|
106
|
+
} else if (currentRef && typeof currentRef === 'object') {
|
|
107
|
+
refObj = currentRef;
|
|
108
|
+
} else if (currentRef) {
|
|
109
|
+
refObj = ph?.scene?.getObjectByName(currentRef);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const x = new THREE.Vector3(1,0,0);
|
|
113
|
+
const y = new THREE.Vector3(0,1,0);
|
|
114
|
+
const z = new THREE.Vector3(0,0,1);
|
|
115
|
+
const origin = new THREE.Vector3();
|
|
116
|
+
|
|
117
|
+
if (refObj) {
|
|
118
|
+
refObj.updateWorldMatrix(true, true);
|
|
119
|
+
// Prefer geometric center if available
|
|
120
|
+
try {
|
|
121
|
+
const g = refObj.geometry;
|
|
122
|
+
if (g) {
|
|
123
|
+
const bs = g.boundingSphere || (g.computeBoundingSphere(), g.boundingSphere);
|
|
124
|
+
if (bs) origin.copy(refObj.localToWorld(bs.center.clone()));
|
|
125
|
+
else origin.copy(refObj.getWorldPosition(new THREE.Vector3()));
|
|
126
|
+
} else origin.copy(refObj.getWorldPosition(new THREE.Vector3()));
|
|
127
|
+
} catch { origin.copy(refObj.getWorldPosition(new THREE.Vector3())); }
|
|
128
|
+
// For Face, use its avg normal; otherwise use object orientation
|
|
129
|
+
if (refObj.type === 'FACE' && typeof refObj.getAverageNormal === 'function') {
|
|
130
|
+
const n = refObj.getAverageNormal();
|
|
131
|
+
const worldUp = new THREE.Vector3(0,1,0);
|
|
132
|
+
const tmp = new THREE.Vector3();
|
|
133
|
+
const zx = Math.abs(n.dot(worldUp)) > 0.9 ? new THREE.Vector3(1,0,0) : worldUp;
|
|
134
|
+
x.copy(tmp.crossVectors(zx, n).normalize());
|
|
135
|
+
y.copy(tmp.crossVectors(n, x).normalize());
|
|
136
|
+
z.copy(n.clone().normalize());
|
|
137
|
+
} else {
|
|
138
|
+
const n = new THREE.Vector3(0,0,1).applyQuaternion(refObj.getWorldQuaternion(new THREE.Quaternion())).normalize();
|
|
139
|
+
const worldUp = new THREE.Vector3(0,1,0);
|
|
140
|
+
const tmp = new THREE.Vector3();
|
|
141
|
+
const zx = Math.abs(n.dot(worldUp)) > 0.9 ? new THREE.Vector3(1,0,0) : worldUp;
|
|
142
|
+
x.copy(tmp.crossVectors(zx, n).normalize());
|
|
143
|
+
y.copy(tmp.crossVectors(n, x).normalize());
|
|
144
|
+
z.copy(n);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// If the reference object is missing (e.g., deleted), keep prior basis if present
|
|
149
|
+
if (!refObj && pdBasis) return pdBasis;
|
|
150
|
+
|
|
151
|
+
const basis = {
|
|
152
|
+
origin: [origin.x, origin.y, origin.z],
|
|
153
|
+
x: [x.x, x.y, x.z],
|
|
154
|
+
y: [y.x, y.y, y.z],
|
|
155
|
+
z: [z.x, z.y, z.z],
|
|
156
|
+
refName: (refObj?.name) || undefined,
|
|
157
|
+
};
|
|
158
|
+
this.persistentData = this.persistentData || {};
|
|
159
|
+
this.persistentData.basis = basis;
|
|
160
|
+
return basis;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_sketchSignature(sketch) {
|
|
164
|
+
if (!sketch) return null;
|
|
165
|
+
try {
|
|
166
|
+
return JSON.stringify(sketch);
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_updateSketchChangeState(sketch) {
|
|
173
|
+
this.persistentData = this.persistentData || {};
|
|
174
|
+
try {
|
|
175
|
+
const currentSignature = this._sketchSignature(sketch);
|
|
176
|
+
const prevSignature = this.persistentData.lastSketchSignature;
|
|
177
|
+
const changed = prevSignature != null ? prevSignature !== currentSignature : false;
|
|
178
|
+
this.persistentData.lastSketchSignature = currentSignature;
|
|
179
|
+
this.persistentData.lastSketchChanged = changed;
|
|
180
|
+
this._sketchChanged = changed;
|
|
181
|
+
} catch {
|
|
182
|
+
this._sketchChanged = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
hasSketchChanged() {
|
|
187
|
+
if (typeof this._sketchChanged === 'boolean') {
|
|
188
|
+
return this._sketchChanged;
|
|
189
|
+
}
|
|
190
|
+
const persisted = this.persistentData?.lastSketchChanged;
|
|
191
|
+
return Boolean(persisted);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_cloneForDump(data) {
|
|
195
|
+
if (data == null) return null;
|
|
196
|
+
try {
|
|
197
|
+
return JSON.parse(JSON.stringify(data));
|
|
198
|
+
} catch {
|
|
199
|
+
return data;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_basisToWorldFn(basis) {
|
|
204
|
+
if (!basis || typeof basis !== 'object') return null;
|
|
205
|
+
const origin = Array.isArray(basis.origin) ? basis.origin : [0, 0, 0];
|
|
206
|
+
const bx = Array.isArray(basis.x) ? basis.x : [1, 0, 0];
|
|
207
|
+
const by = Array.isArray(basis.y) ? basis.y : [0, 1, 0];
|
|
208
|
+
let bz = Array.isArray(basis.z) ? basis.z : null;
|
|
209
|
+
if (!bz) {
|
|
210
|
+
const [bx0, bx1, bx2] = bx;
|
|
211
|
+
const [by0, by1, by2] = by;
|
|
212
|
+
const cx = bx1 * by2 - bx2 * by1;
|
|
213
|
+
const cy = bx2 * by0 - bx0 * by2;
|
|
214
|
+
const cz = bx0 * by1 - bx1 * by0;
|
|
215
|
+
const len = Math.hypot(cx, cy, cz) || 1;
|
|
216
|
+
bz = [cx / len, cy / len, cz / len];
|
|
217
|
+
}
|
|
218
|
+
return (u, v, w = 0) => ([
|
|
219
|
+
origin[0] + u * bx[0] + v * by[0] + w * bz[0],
|
|
220
|
+
origin[1] + u * bx[1] + v * by[1] + w * bz[1],
|
|
221
|
+
origin[2] + u * bx[2] + v * by[2] + w * bz[2],
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_buildDiagnosticsFilename(featureID) {
|
|
226
|
+
const safeId = featureID != null && featureID !== ''
|
|
227
|
+
? String(featureID).replace(/[^a-z0-9_-]/gi, '_')
|
|
228
|
+
: 'sketch';
|
|
229
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
230
|
+
return `${safeId}-diagnostics-${stamp}.json`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_downloadDiagnosticsFile(fileName, payload) {
|
|
234
|
+
if (typeof document === 'undefined' || typeof URL === 'undefined' || typeof Blob === 'undefined') {
|
|
235
|
+
console.warn('[SketchFeature] Browser file APIs unavailable; cannot download diagnostics');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const json = JSON.stringify(payload, null, 2);
|
|
240
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
241
|
+
const url = URL.createObjectURL(blob);
|
|
242
|
+
try {
|
|
243
|
+
const link = document.createElement('a');
|
|
244
|
+
link.href = url;
|
|
245
|
+
link.download = fileName;
|
|
246
|
+
link.rel = 'noopener';
|
|
247
|
+
link.style.display = 'none';
|
|
248
|
+
document.body.appendChild(link);
|
|
249
|
+
link.click();
|
|
250
|
+
document.body.removeChild(link);
|
|
251
|
+
} finally {
|
|
252
|
+
setTimeout(() => { try { URL.revokeObjectURL(url); } catch { /* noop */ } }, 0);
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[SketchFeature] Failed to prepare diagnostics download:', err);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
dumpDiagnostics({ partHistory, download = false, fileName } = {}) {
|
|
260
|
+
try {
|
|
261
|
+
const featureID = this.inputParams?.featureID ?? null;
|
|
262
|
+
const basis = this.persistentData?.basis || (partHistory ? this._getOrCreateBasis(partHistory) : null);
|
|
263
|
+
const sketch = this._cloneForDump(this.persistentData?.sketch);
|
|
264
|
+
const profile = this._cloneForDump(this.persistentData?.lastProfileDiagnostics);
|
|
265
|
+
const payload = {
|
|
266
|
+
featureID,
|
|
267
|
+
timestamp: new Date().toISOString(),
|
|
268
|
+
sketchSignature: this._sketchSignature(this.persistentData?.sketch || null),
|
|
269
|
+
sketch,
|
|
270
|
+
profile,
|
|
271
|
+
basis: this._cloneForDump(basis),
|
|
272
|
+
};
|
|
273
|
+
if (payload.profile && payload.profile.triangles2D && !payload.profile.trianglesWorld && basis) {
|
|
274
|
+
try {
|
|
275
|
+
const toWorld = this._basisToWorldFn(basis);
|
|
276
|
+
if (typeof toWorld === 'function') {
|
|
277
|
+
payload.profile.trianglesWorld = payload.profile.triangles2D.map((tri) => tri.map((pt) => {
|
|
278
|
+
if (!Array.isArray(pt)) return pt;
|
|
279
|
+
const u = Number(pt[0]) || 0;
|
|
280
|
+
const v = Number(pt[1]) || 0;
|
|
281
|
+
const w = Number(pt[2]) || 0;
|
|
282
|
+
return toWorld(u, v, w);
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
} catch (e) {
|
|
286
|
+
payload.profile.trianglesWorldError = e?.message || String(e);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const label = featureID ? `[SketchFeature] Diagnostics (${featureID})` : '[SketchFeature] Diagnostics';
|
|
290
|
+
try {
|
|
291
|
+
console.groupCollapsed(label);
|
|
292
|
+
} catch {
|
|
293
|
+
console.log(label);
|
|
294
|
+
}
|
|
295
|
+
console.log(payload);
|
|
296
|
+
try { console.groupEnd(); } catch {}
|
|
297
|
+
if (download) {
|
|
298
|
+
const name = fileName || this._buildDiagnosticsFilename(featureID);
|
|
299
|
+
this._downloadDiagnosticsFile(name, payload);
|
|
300
|
+
try {
|
|
301
|
+
console.info(`[SketchFeature] Diagnostics saved as ${name}`);
|
|
302
|
+
} catch { /* noop */ }
|
|
303
|
+
}
|
|
304
|
+
return payload;
|
|
305
|
+
} catch (e) {
|
|
306
|
+
console.error('[SketchFeature] Diagnostic dump failed:', e);
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Visualize sketch curves and points as a Group for selection (type='SKETCH').
|
|
312
|
+
// Returns [group]
|
|
313
|
+
async run(partHistory) {
|
|
314
|
+
const sceneGroup = new THREE.Group();
|
|
315
|
+
sceneGroup.name = this.inputParams.featureID || 'Sketch';
|
|
316
|
+
const featureId = (typeof sceneGroup.name === 'string' && sceneGroup.name.length)
|
|
317
|
+
? sceneGroup.name
|
|
318
|
+
: (this.inputParams?.featureID ? String(this.inputParams.featureID) : 'Sketch');
|
|
319
|
+
const edgeNamePrefix = featureId ? `${featureId}:` : '';
|
|
320
|
+
sceneGroup.type = 'SKETCH';
|
|
321
|
+
// Provide a harmless onClick so Scene Manager rows don't error
|
|
322
|
+
sceneGroup.onClick = () => {};
|
|
323
|
+
|
|
324
|
+
const basis = this._getOrCreateBasis(partHistory);
|
|
325
|
+
sceneGroup.userData = sceneGroup.userData || {};
|
|
326
|
+
// Expose the sketch basis so downstream features (e.g., holes) can use the plane normal.
|
|
327
|
+
sceneGroup.userData.sketchBasis = {
|
|
328
|
+
origin: Array.isArray(basis.origin) ? basis.origin.slice() : [0, 0, 0],
|
|
329
|
+
x: Array.isArray(basis.x) ? basis.x.slice() : [1, 0, 0],
|
|
330
|
+
y: Array.isArray(basis.y) ? basis.y.slice() : [0, 1, 0],
|
|
331
|
+
z: Array.isArray(basis.z) ? basis.z.slice() : null,
|
|
332
|
+
};
|
|
333
|
+
const bO = new THREE.Vector3().fromArray(basis.origin);
|
|
334
|
+
const bX = new THREE.Vector3().fromArray(basis.x);
|
|
335
|
+
const bY = new THREE.Vector3().fromArray(basis.y);
|
|
336
|
+
|
|
337
|
+
// Start from persisted sketch
|
|
338
|
+
let sketch = this.persistentData?.sketch || { points: [{ id:0, x:0, y:0, fixed:true }], geometries: [], constraints: [{ id:0, type:"⏚", points:[0]}] };
|
|
339
|
+
this.persistentData = this.persistentData || {};
|
|
340
|
+
this.persistentData.lastProfileDiagnostics = null;
|
|
341
|
+
|
|
342
|
+
// Evaluate any expression-backed values on points/constraints using global expressions
|
|
343
|
+
try {
|
|
344
|
+
const exprSrc = partHistory?.expressions || '';
|
|
345
|
+
const runExpr = (expressions, equation) => {
|
|
346
|
+
try {
|
|
347
|
+
const fn = `${expressions}; return ${equation} ;`;
|
|
348
|
+
let result = Function(fn)();
|
|
349
|
+
if (typeof result === 'string') {
|
|
350
|
+
const num = Number(result);
|
|
351
|
+
if (!Number.isNaN(num)) return num;
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
} catch { return null; }
|
|
355
|
+
};
|
|
356
|
+
if (Array.isArray(sketch?.points)) {
|
|
357
|
+
for (const p of sketch.points) {
|
|
358
|
+
if (typeof p.x === 'string') {
|
|
359
|
+
const n = runExpr(exprSrc, p.x);
|
|
360
|
+
if (n != null && Number.isFinite(n)) p.x = Number(n);
|
|
361
|
+
}
|
|
362
|
+
if (typeof p.y === 'string') {
|
|
363
|
+
const n = runExpr(exprSrc, p.y);
|
|
364
|
+
if (n != null && Number.isFinite(n)) p.y = Number(n);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (Array.isArray(sketch?.constraints)) {
|
|
369
|
+
for (const c of sketch.constraints) {
|
|
370
|
+
if (typeof c?.valueExpr === 'string') {
|
|
371
|
+
const n = runExpr(exprSrc, c.valueExpr);
|
|
372
|
+
if (n != null && Number.isFinite(n)) c.value = Number(n);
|
|
373
|
+
} else if (typeof c?.value === 'string') {
|
|
374
|
+
const n = runExpr(exprSrc, c.value);
|
|
375
|
+
if (n != null && Number.isFinite(n)) c.value = Number(n);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Re-solve sketch with evaluated values to reflect latest expressions
|
|
380
|
+
try {
|
|
381
|
+
const engine = new ConstraintEngine(JSON.stringify(sketch));
|
|
382
|
+
const solved = engine.solve(500);
|
|
383
|
+
sketch = solved;
|
|
384
|
+
this.persistentData.sketch = solved;
|
|
385
|
+
} catch {}
|
|
386
|
+
} catch {}
|
|
387
|
+
// Update external reference points by projecting selected model edge endpoints
|
|
388
|
+
try {
|
|
389
|
+
const scene = partHistory?.scene;
|
|
390
|
+
const refs = Array.isArray(this.persistentData?.externalRefs) ? this.persistentData.externalRefs : [];
|
|
391
|
+
if (scene && refs.length) {
|
|
392
|
+
const toUV = (w)=>{ const d = new THREE.Vector3().copy(w).sub(bO); return { u: d.dot(bX), v: d.dot(bY) }; };
|
|
393
|
+
const edgeEndpoints = (edge)=>{
|
|
394
|
+
if (!edge) return null;
|
|
395
|
+
const a = new THREE.Vector3();
|
|
396
|
+
const b = new THREE.Vector3();
|
|
397
|
+
const toW = (v)=> v.applyMatrix4(edge.matrixWorld);
|
|
398
|
+
const pts = Array.isArray(edge?.userData?.polylineLocal) ? edge.userData.polylineLocal : null;
|
|
399
|
+
if (pts && pts.length >= 2) {
|
|
400
|
+
a.set(pts[0][0], pts[0][1], pts[0][2]);
|
|
401
|
+
b.set(pts[pts.length-1][0], pts[pts.length-1][1], pts[pts.length-1][2]);
|
|
402
|
+
return { a: toW(a), b: toW(b) };
|
|
403
|
+
}
|
|
404
|
+
const pos = edge?.geometry?.getAttribute?.('position');
|
|
405
|
+
if (pos && pos.itemSize === 3 && pos.count >= 2) {
|
|
406
|
+
a.set(pos.getX(0), pos.getY(0), pos.getZ(0));
|
|
407
|
+
b.set(pos.getX(pos.count-1), pos.getY(pos.count-1), pos.getZ(pos.count-1));
|
|
408
|
+
return { a: toW(a), b: toW(b) };
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
};
|
|
412
|
+
const ptById = new Map(sketch.points.map(p=>[p.id,p]));
|
|
413
|
+
let changed = false;
|
|
414
|
+
for (const r of refs) {
|
|
415
|
+
try {
|
|
416
|
+
let edge = scene.getObjectById(r.edgeId);
|
|
417
|
+
if (!edge || edge.type !== 'EDGE') {
|
|
418
|
+
// Fallback by solidName + edgeName, then global by edgeName
|
|
419
|
+
if (r.solidName) {
|
|
420
|
+
const solid = scene?.getObjectByName(r.solidName);
|
|
421
|
+
if (solid) {
|
|
422
|
+
let found = null;
|
|
423
|
+
solid.traverse((obj) => { if (!found && obj.type === 'EDGE' && obj.name === r.edgeName) found = obj; });
|
|
424
|
+
if (found) edge = found;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if ((!edge || edge.type !== 'EDGE') && r.edgeName) {
|
|
428
|
+
let found = null;
|
|
429
|
+
scene?.traverse((obj) => { if (!found && obj.type === 'EDGE' && obj.name === r.edgeName) found = obj; });
|
|
430
|
+
if (found) edge = found;
|
|
431
|
+
}
|
|
432
|
+
if (edge && edge.type === 'EDGE') {
|
|
433
|
+
// refresh stored id/name metadata
|
|
434
|
+
r.edgeId = edge.id;
|
|
435
|
+
try { r.edgeName = edge.name || r.edgeName || null; } catch {}
|
|
436
|
+
try { r.solidName = edge.parent?.name || r.solidName || null; } catch {}
|
|
437
|
+
changed = true;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (!edge || edge.type !== 'EDGE') continue; // keep existing points if edge vanished
|
|
441
|
+
const ends = edgeEndpoints(edge);
|
|
442
|
+
if (!ends) continue;
|
|
443
|
+
const uvA = toUV(ends.a);
|
|
444
|
+
const uvB = toUV(ends.b);
|
|
445
|
+
const p0 = ptById.get(r.p0);
|
|
446
|
+
const p1 = ptById.get(r.p1);
|
|
447
|
+
if (p0 && (p0.x !== uvA.u || p0.y !== uvA.v)) { p0.x = uvA.u; p0.y = uvA.v; changed = true; }
|
|
448
|
+
if (p1 && (p1.x !== uvB.u || p1.y !== uvB.v)) { p1.x = uvB.u; p1.y = uvB.v; changed = true; }
|
|
449
|
+
if (p0) p0.fixed = true; if (p1) p1.fixed = true;
|
|
450
|
+
// Ensure ground constraints exist for these points so solver treats them fixed
|
|
451
|
+
const ensureGround = (pid)=>{
|
|
452
|
+
if (!sketch.constraints.some(c=>c.type==='⏚' && Array.isArray(c.points) && c.points[0]===pid)){
|
|
453
|
+
const cid = Math.max(0, ...sketch.constraints.map(c=> +c.id || 0)) + 1;
|
|
454
|
+
sketch.constraints.push({ id: cid, type: '⏚', points:[pid] });
|
|
455
|
+
changed = true;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
if (p0) ensureGround(p0.id);
|
|
459
|
+
if (p1) ensureGround(p1.id);
|
|
460
|
+
} catch {}
|
|
461
|
+
}
|
|
462
|
+
if (changed) {
|
|
463
|
+
try {
|
|
464
|
+
const engine = new ConstraintEngine(JSON.stringify(sketch));
|
|
465
|
+
const solved = engine.solve(500);
|
|
466
|
+
sketch = solved;
|
|
467
|
+
this.persistentData.sketch = solved;
|
|
468
|
+
} catch {}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch {}
|
|
472
|
+
const curveRes = Math.max(8, Math.floor(Number(this.inputParams?.curveResolution) || 64));
|
|
473
|
+
|
|
474
|
+
// Helper: 2D → 3D
|
|
475
|
+
const to3D = (u, v) => new THREE.Vector3().copy(bO).addScaledVector(bX, u).addScaledVector(bY, v);
|
|
476
|
+
|
|
477
|
+
// Add vertex visuals in 3D for every sketch point (including isolated points)
|
|
478
|
+
try {
|
|
479
|
+
if (Array.isArray(sketch?.points)) {
|
|
480
|
+
let autoId = 0;
|
|
481
|
+
for (const p of sketch.points) {
|
|
482
|
+
if (p == null) continue;
|
|
483
|
+
const u = Number(p.x); const v = Number(p.y);
|
|
484
|
+
if (!Number.isFinite(u) || !Number.isFinite(v)) continue;
|
|
485
|
+
const w = to3D(u, v);
|
|
486
|
+
const hasExplicitId = p.id !== undefined && p.id !== null && `${p.id}` !== '';
|
|
487
|
+
const pointLabel = hasExplicitId ? p.id : autoId++;
|
|
488
|
+
const vertexName = featureId ? `${featureId}:P${pointLabel}` : `P${pointLabel}`;
|
|
489
|
+
try {
|
|
490
|
+
const vertex = new BREP.Vertex([w.x, w.y, w.z], { name: vertexName });
|
|
491
|
+
vertex.userData = vertex.userData || {};
|
|
492
|
+
vertex.userData.sketchPointId = hasExplicitId ? p.id : pointLabel;
|
|
493
|
+
vertex.userData.sketchFeatureId = featureId;
|
|
494
|
+
sceneGroup.add(vertex);
|
|
495
|
+
} catch {}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch {}
|
|
499
|
+
|
|
500
|
+
// Do not add curve preview lines in scene; editor handles those.
|
|
501
|
+
|
|
502
|
+
// ---- Build PROFILE face from sketch with loop detection + holes ----
|
|
503
|
+
const pointById = new Map(sketch.points.map(p => [p.id, { x: p.x, y: p.y }]));
|
|
504
|
+
const segs = [];
|
|
505
|
+
const edges = [];
|
|
506
|
+
const openChains = [];
|
|
507
|
+
const toWorld = (u,v)=> to3D(u,v);
|
|
508
|
+
|
|
509
|
+
const edgeBySegId = new Map();
|
|
510
|
+
for (const g of (sketch.geometries||[])) {
|
|
511
|
+
// Skip construction geometry: used only for constraints, not model edges
|
|
512
|
+
if (g && g.construction) continue;
|
|
513
|
+
if (g.type==='line' && g.points?.length===2) {
|
|
514
|
+
const a = pointById.get(g.points[0]); const b = pointById.get(g.points[1]); if(!a||!b) continue;
|
|
515
|
+
segs.push({ id:g.id, pts:[[a.x,a.y],[b.x,b.y]] });
|
|
516
|
+
const aw = toWorld(a.x,a.y); const bw = toWorld(b.x,b.y);
|
|
517
|
+
const lg = new LineGeometry();
|
|
518
|
+
lg.setPositions([aw.x, aw.y, aw.z, bw.x, bw.y, bw.z]);
|
|
519
|
+
const edgeName = `${edgeNamePrefix}G${g.id}`;
|
|
520
|
+
const e = new BREP.Edge(lg); e.name = edgeName; e.userData = { polylineLocal:[[aw.x,aw.y,aw.z],[bw.x,bw.y,bw.z]], polylineWorld:true, sketchFeatureId: featureId, sketchGeometryId: g.id }; edges.push(e); edgeBySegId.set(g.id, e);
|
|
521
|
+
} else if (g.type==='arc' && g.points?.length===3) {
|
|
522
|
+
const c = pointById.get(g.points[0]); const sa=pointById.get(g.points[1]); const sb=pointById.get(g.points[2]); if(!c||!sa||!sb) continue;
|
|
523
|
+
const cx=c.x, cy=c.y; const r=Math.hypot(sa.x-cx, sa.y-cy);
|
|
524
|
+
let a0=Math.atan2(sa.y-cy, sa.x-cx), a1=Math.atan2(sb.y-cy, sb.x-cx);
|
|
525
|
+
// CCW sweep in [0, 2π). If start≈end, treat as full circle.
|
|
526
|
+
let d = a1 - a0; d = ((d % (2*Math.PI)) + 2*Math.PI) % (2*Math.PI); if (Math.abs(d) < 1e-6) d = 2*Math.PI;
|
|
527
|
+
const n=Math.max(8, Math.ceil(curveRes*(d)/(2*Math.PI)));
|
|
528
|
+
const pts=[];
|
|
529
|
+
for(let i=0;i<=n;i++){
|
|
530
|
+
const t=a0+d*(i/n);
|
|
531
|
+
pts.push([cx+r*Math.cos(t), cy+r*Math.sin(t)]);
|
|
532
|
+
}
|
|
533
|
+
if (pts.length){
|
|
534
|
+
// Snap endpoints to exact sketch values so shared joints line up after discretization
|
|
535
|
+
pts[0] = [sa.x, sa.y];
|
|
536
|
+
pts[pts.length-1] = [sb.x, sb.y];
|
|
537
|
+
}
|
|
538
|
+
segs.push({ id:g.id, pts });
|
|
539
|
+
const flat=[]; const worldPts=[]; for(const p of pts){ const v=toWorld(p[0],p[1]); flat.push(v.x,v.y,v.z); worldPts.push([v.x,v.y,v.z]); }
|
|
540
|
+
const lg = new LineGeometry(); lg.setPositions(flat);
|
|
541
|
+
const edgeName = `${edgeNamePrefix}G${g.id}`;
|
|
542
|
+
const e = new BREP.Edge(lg); e.name = edgeName;
|
|
543
|
+
const cw = toWorld(cx, cy);
|
|
544
|
+
e.userData = { polylineLocal: worldPts, polylineWorld:true, sketchGeomType:'arc', arcCenter:[cw.x, cw.y, cw.z], arcRadius:r, sketchFeatureId: featureId, sketchGeometryId: g.id };
|
|
545
|
+
edges.push(e); edgeBySegId.set(g.id, e);
|
|
546
|
+
} else if (g.type==='circle' && g.points?.length===2) {
|
|
547
|
+
const c = pointById.get(g.points[0]); const rp=pointById.get(g.points[1]); if(!c||!rp) continue;
|
|
548
|
+
const cx=c.x, cy=c.y; const r=Math.hypot(rp.x-cx, rp.y-cy); const n=Math.max(8, curveRes); const pts=[];
|
|
549
|
+
for(let i=0;i<=n;i++){ const t=(i/n)*Math.PI*2; pts.push([cx+r*Math.cos(t), cy+r*Math.sin(t)]);}
|
|
550
|
+
if (pts.length){
|
|
551
|
+
// Ensure perfect closure so hole loops stay connected
|
|
552
|
+
const first=[cx+r, cy];
|
|
553
|
+
pts[0] = first;
|
|
554
|
+
pts[pts.length-1] = [first[0], first[1]];
|
|
555
|
+
}
|
|
556
|
+
segs.push({ id:g.id, pts });
|
|
557
|
+
const flat=[]; const worldPts=[]; for(const p of pts){ const v=toWorld(p[0],p[1]); flat.push(v.x,v.y,v.z); worldPts.push([v.x,v.y,v.z]); }
|
|
558
|
+
const lg = new LineGeometry(); lg.setPositions(flat);
|
|
559
|
+
const edgeName = `${edgeNamePrefix}G${g.id}`;
|
|
560
|
+
const e = new BREP.Edge(lg); e.name = edgeName;
|
|
561
|
+
const cw = toWorld(cx, cy);
|
|
562
|
+
e.userData = { polylineLocal: worldPts, polylineWorld:true, sketchGeomType:'circle', circleCenter:[cw.x,cw.y,cw.z], circleRadius:r, sketchFeatureId: featureId, sketchGeometryId: g.id };
|
|
563
|
+
edges.push(e); edgeBySegId.set(g.id, e);
|
|
564
|
+
} else if (g.type==='bezier' && g.points?.length>=4) {
|
|
565
|
+
const ids = g.points || [];
|
|
566
|
+
const segCount = Math.floor((ids.length - 1) / 3);
|
|
567
|
+
if (segCount < 1) continue;
|
|
568
|
+
const n = Math.max(8, curveRes);
|
|
569
|
+
const pts = [];
|
|
570
|
+
for (let seg = 0; seg < segCount; seg++) {
|
|
571
|
+
const i0 = seg * 3;
|
|
572
|
+
const p0 = pointById.get(ids[i0]);
|
|
573
|
+
const p1 = pointById.get(ids[i0 + 1]);
|
|
574
|
+
const p2 = pointById.get(ids[i0 + 2]);
|
|
575
|
+
const p3 = pointById.get(ids[i0 + 3]);
|
|
576
|
+
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
577
|
+
for (let i=0;i<=n;i++){
|
|
578
|
+
if (seg > 0 && i === 0) continue;
|
|
579
|
+
const t = i/n; const mt = 1 - t;
|
|
580
|
+
const bx = mt*mt*mt*p0.x + 3*mt*mt*t*p1.x + 3*mt*t*t*p2.x + t*t*t*p3.x;
|
|
581
|
+
const by = mt*mt*mt*p0.y + 3*mt*mt*t*p1.y + 3*mt*t*t*p2.y + t*t*t*p3.y;
|
|
582
|
+
pts.push([bx, by]);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (!pts.length) continue;
|
|
586
|
+
const firstAnchor = pointById.get(ids[0]);
|
|
587
|
+
const lastAnchor = pointById.get(ids[segCount * 3]);
|
|
588
|
+
if (firstAnchor) pts[0] = [firstAnchor.x, firstAnchor.y];
|
|
589
|
+
if (lastAnchor) pts[pts.length - 1] = [lastAnchor.x, lastAnchor.y];
|
|
590
|
+
segs.push({ id:g.id, pts });
|
|
591
|
+
const flat=[]; const worldPts=[]; for(const p of pts){ const v=toWorld(p[0],p[1]); flat.push(v.x,v.y,v.z); worldPts.push([v.x,v.y,v.z]); }
|
|
592
|
+
const lg = new LineGeometry(); lg.setPositions(flat);
|
|
593
|
+
const edgeName = `${edgeNamePrefix}G${g.id}`;
|
|
594
|
+
const e = new BREP.Edge(lg); e.name = edgeName; e.userData = { polylineLocal: worldPts, polylineWorld:true, sketchFeatureId: featureId, sketchGeometryId: g.id }; edges.push(e); edgeBySegId.set(g.id, e);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Utility helpers for loops
|
|
599
|
+
const key=(x,y)=> `${x.toFixed(6)},${y.toFixed(6)}`;
|
|
600
|
+
const nearlyEqual=(a,b,eps=1e-6)=> Math.abs(a-b)<=eps;
|
|
601
|
+
const closePt=(p,q)=> nearlyEqual(p[0],q[0]) && nearlyEqual(p[1],q[1]);
|
|
602
|
+
const ensureClosed=(arr)=>{
|
|
603
|
+
if (arr.length<3) return arr;
|
|
604
|
+
const f=arr[0], l=arr[arr.length-1];
|
|
605
|
+
if (!closePt(f,l)) arr.push([f[0],f[1]]);
|
|
606
|
+
return arr;
|
|
607
|
+
};
|
|
608
|
+
const dedupeConsecutive=(arr)=>{
|
|
609
|
+
const out=[]; let prev=null;
|
|
610
|
+
for(const p of arr){ if(!prev || !closePt(prev,p)){ out.push([p[0],p[1]]); prev=p; } }
|
|
611
|
+
return out;
|
|
612
|
+
};
|
|
613
|
+
const removeCollinear=(arr, eps=1e-9)=>{
|
|
614
|
+
if (arr.length <= 3) return arr;
|
|
615
|
+
const ring = arr.slice();
|
|
616
|
+
const n0 = ring.length;
|
|
617
|
+
const out = [];
|
|
618
|
+
for (let i=0;i<n0;i++){
|
|
619
|
+
const a = ring[(i-1+n0)%n0];
|
|
620
|
+
const b = ring[i];
|
|
621
|
+
const c = ring[(i+1)%n0];
|
|
622
|
+
const abx = b[0]-a[0], aby=b[1]-a[1];
|
|
623
|
+
const bcx = c[0]-b[0], bcy=c[1]-b[1];
|
|
624
|
+
const cross = abx*bcy - aby*bcx;
|
|
625
|
+
if (Math.abs(cross) > eps) out.push(b);
|
|
626
|
+
}
|
|
627
|
+
return out.length>=3 ? out : arr;
|
|
628
|
+
};
|
|
629
|
+
const signedArea = (loop)=>{
|
|
630
|
+
let a=0; for(let i=0;i<loop.length-1;i++){ const p=loop[i], q=loop[i+1]; a+= (p[0]*q[1]-q[0]*p[1]); } return 0.5*a;
|
|
631
|
+
};
|
|
632
|
+
const pointInPoly = (pt, poly)=>{
|
|
633
|
+
// Winding number test. Poly may be closed; trim duplicate.
|
|
634
|
+
const n = poly.length; if (n<3) return false;
|
|
635
|
+
const first=poly[0], last=poly[n-1];
|
|
636
|
+
const ring = (nearlyEqual(first[0],last[0])&&nearlyEqual(first[1],last[1]))? poly.slice(0,n-1): poly;
|
|
637
|
+
const x = pt[0], y = pt[1];
|
|
638
|
+
let wn=0;
|
|
639
|
+
const isLeft=(ax,ay,bx,by,cx,cy)=> (bx-ax)*(cy-ay) - (by-ay)*(cx-ax);
|
|
640
|
+
for (let i=0;i<ring.length;i++){
|
|
641
|
+
const a = ring[i];
|
|
642
|
+
const b = ring[(i+1)%ring.length];
|
|
643
|
+
if (a[1] <= y) {
|
|
644
|
+
if (b[1] > y && isLeft(a[0],a[1],b[0],b[1],x,y) > 0) wn++;
|
|
645
|
+
} else {
|
|
646
|
+
if (b[1] <= y && isLeft(a[0],a[1],b[0],b[1],x,y) < 0) wn--;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return wn !== 0;
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// Build multiple loops by chaining segments greedily per connected component
|
|
653
|
+
const unused = new Set(segs.map((_,i)=>i));
|
|
654
|
+
const startKey = new Map(); // pointKey -> Set(segIndex as start)
|
|
655
|
+
const endKey = new Map(); // pointKey -> Set(segIndex as end)
|
|
656
|
+
const addTo = (map, k, v)=>{ let s=map.get(k); if(!s){ s=new Set(); map.set(k,s);} s.add(v); };
|
|
657
|
+
segs.forEach((s,i)=>{ const a=s.pts[0], b=s.pts[s.pts.length-1]; addTo(startKey, key(a[0],a[1]), i); addTo(endKey, key(b[0],b[1]), i); });
|
|
658
|
+
|
|
659
|
+
const loopsInfo=[]; // { pts, segIDs }
|
|
660
|
+
while (unused.size){
|
|
661
|
+
// seed with any remaining segment
|
|
662
|
+
const seedIndex = unused.values().next().value;
|
|
663
|
+
unused.delete(seedIndex);
|
|
664
|
+
let chain = segs[seedIndex].pts.slice();
|
|
665
|
+
const usedSegs = [seedIndex];
|
|
666
|
+
|
|
667
|
+
let extended=true;
|
|
668
|
+
while(extended){
|
|
669
|
+
extended=false;
|
|
670
|
+
// try extend forward
|
|
671
|
+
const tail = chain[chain.length-1]; const tk = key(tail[0],tail[1]);
|
|
672
|
+
let nextIdx = null; let reverse=false;
|
|
673
|
+
for (const si of (startKey.get(tk)||[])) { if (unused.has(si)) { nextIdx=si; reverse=false; break; } }
|
|
674
|
+
if (nextIdx===null){ for (const ei of (endKey.get(tk)||[])) { if (unused.has(ei)) { nextIdx=ei; reverse=true; break; } } }
|
|
675
|
+
if (nextIdx!==null){
|
|
676
|
+
const pts = segs[nextIdx].pts;
|
|
677
|
+
const add = reverse ? pts.slice().reverse() : pts.slice();
|
|
678
|
+
// avoid duplicating joint point
|
|
679
|
+
chain.pop();
|
|
680
|
+
chain.push(...add);
|
|
681
|
+
unused.delete(nextIdx);
|
|
682
|
+
usedSegs.push(nextIdx);
|
|
683
|
+
extended=true;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
// try extend backward
|
|
687
|
+
const head = chain[0]; const hk = key(head[0],head[1]);
|
|
688
|
+
nextIdx = null; reverse=false;
|
|
689
|
+
for (const ei of (endKey.get(hk)||[])) { if (unused.has(ei)) { nextIdx=ei; reverse=false; break; } }
|
|
690
|
+
if (nextIdx===null){ for (const si of (startKey.get(hk)||[])) { if (unused.has(si)) { nextIdx=si; reverse=true; break; } } }
|
|
691
|
+
if (nextIdx!==null){
|
|
692
|
+
const pts = segs[nextIdx].pts;
|
|
693
|
+
const add = reverse ? pts.slice().reverse() : pts.slice();
|
|
694
|
+
// avoid duplicating joint point
|
|
695
|
+
add.pop();
|
|
696
|
+
chain = add.concat(chain);
|
|
697
|
+
unused.delete(nextIdx);
|
|
698
|
+
usedSegs.push(nextIdx);
|
|
699
|
+
extended=true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
chain = dedupeConsecutive(chain);
|
|
704
|
+
if (chain.length < 3 || !closePt(chain[0], chain[chain.length-1])) {
|
|
705
|
+
openChains.push({ pts: chain.slice(), segIDs: usedSegs.slice() });
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
chain = ensureClosed(chain);
|
|
709
|
+
// Simplify to avoid near-collinear noise
|
|
710
|
+
let simple = chain.slice(0, chain.length-1);
|
|
711
|
+
simple = removeCollinear(simple);
|
|
712
|
+
simple.push(simple[0]);
|
|
713
|
+
if (simple.length>=4){ loopsInfo.push({ pts: simple, segIDs: usedSegs.slice() }); }
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Always expose sketch edges in 3D, even if no closed profile can be triangulated
|
|
717
|
+
for (const e of edges) {
|
|
718
|
+
sceneGroup.add(e);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Classify loops (outer/holes) by nesting parity and normalize winding
|
|
722
|
+
const normalizedLoops = loopsInfo.map(obj=>{
|
|
723
|
+
const lp = obj.pts;
|
|
724
|
+
// ensure closed single duplicate at end
|
|
725
|
+
let l = lp.slice();
|
|
726
|
+
if (!closePt(l[0], l[l.length-1])) l.push([l[0][0], l[0][1]]);
|
|
727
|
+
// robust area; skip degenerate
|
|
728
|
+
const a = Math.abs(signedArea(l));
|
|
729
|
+
return a < 1e-12 ? null : l;
|
|
730
|
+
}).filter(Boolean);
|
|
731
|
+
// Keep segID lists aligned; drop those for degenerate loops we filtered
|
|
732
|
+
const loopSegIDs = [];
|
|
733
|
+
for (const info of loopsInfo){
|
|
734
|
+
const lp = info.pts;
|
|
735
|
+
let l = lp.slice();
|
|
736
|
+
if (!closePt(l[0], l[l.length-1])) l.push([l[0][0], l[0][1]]);
|
|
737
|
+
const a = Math.abs(signedArea(l));
|
|
738
|
+
if (a >= 1e-12) loopSegIDs.push(info.segIDs.slice());
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Compute depth (number of containers)
|
|
742
|
+
const depth = new Array(normalizedLoops.length).fill(0);
|
|
743
|
+
const repPoint = (loop)=>{
|
|
744
|
+
const n = loop.length; if (n===0) return [0,0];
|
|
745
|
+
const first = loop[0]; const last = loop[n-1];
|
|
746
|
+
const ring = (nearlyEqual(first[0], last[0]) && nearlyEqual(first[1], last[1])) ? loop.slice(0, n-1) : loop;
|
|
747
|
+
return ring[0];
|
|
748
|
+
};
|
|
749
|
+
const reps = normalizedLoops.map(repPoint);
|
|
750
|
+
for (let i=0;i<normalizedLoops.length;i++){
|
|
751
|
+
for (let j=0;j<normalizedLoops.length;j++){
|
|
752
|
+
if (i===j) continue;
|
|
753
|
+
if (pointInPoly(reps[i], normalizedLoops[j])) depth[i]++;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Group into shapes: each even-depth loop is an outer; assign immediate odd-depth children as holes
|
|
758
|
+
const groups=[]; // { outer, holes: [] }
|
|
759
|
+
for (let i=0;i<normalizedLoops.length;i++){
|
|
760
|
+
if ((depth[i] % 2) === 0){
|
|
761
|
+
groups.push({ outer:i, holes:[] });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
// Assign holes to nearest containing outer
|
|
765
|
+
for (let h=0; h<normalizedLoops.length; h++){
|
|
766
|
+
if ((depth[h] % 2) !== 1) continue; // only odd-depth are holes
|
|
767
|
+
// find smallest-depth containing outer
|
|
768
|
+
let bestOuter = -1; let bestOuterDepth = Infinity;
|
|
769
|
+
for (let g=0; g<groups.length; g++){
|
|
770
|
+
const oi = groups[g].outer;
|
|
771
|
+
if (pointInPoly(reps[h], normalizedLoops[oi])){
|
|
772
|
+
if (depth[oi] < bestOuterDepth){ bestOuter = g; bestOuterDepth = depth[oi]; }
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (bestOuter>=0) groups[bestOuter].holes.push(h);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Triangulate groups using THREE.ShapeUtils.triangulateShape
|
|
779
|
+
let profileFace=null;
|
|
780
|
+
if (groups.length){
|
|
781
|
+
const triPositions = [];
|
|
782
|
+
const boundaryEdges = new Set();
|
|
783
|
+
const boundaryLoopsWorld = [];
|
|
784
|
+
const profileGroups = [];
|
|
785
|
+
const diagTriangles2D = [];
|
|
786
|
+
const diagTrianglesWorld = [];
|
|
787
|
+
for (const grp of groups){
|
|
788
|
+
// Prepare contour and holes (remove duplicate last point for API)
|
|
789
|
+
let contour = normalizedLoops[grp.outer].slice(); contour.pop();
|
|
790
|
+
// Earcut expects outer CW, holes CCW. Enforce CW for outer
|
|
791
|
+
if (signedArea([...contour, contour[0]]) > 0) contour = contour.slice().reverse();
|
|
792
|
+
// Record boundary edges for outer
|
|
793
|
+
for (const sid of (loopSegIDs[grp.outer] || [])) {
|
|
794
|
+
const e = edgeBySegId.get(segs[sid]?.id);
|
|
795
|
+
if (e) {
|
|
796
|
+
try { e.userData = e.userData || {}; e.userData.isHole = false; } catch {}
|
|
797
|
+
boundaryEdges.add(e);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const holes = grp.holes.map(idx=>{
|
|
801
|
+
let h = normalizedLoops[idx].slice(); h.pop();
|
|
802
|
+
// Ensure CCW for holes (outer is CW per earcut convention)
|
|
803
|
+
if (signedArea([...h, h[0]]) < 0) h = h.slice().reverse();
|
|
804
|
+
for (const sid of (loopSegIDs[idx] || [])) {
|
|
805
|
+
const e = edgeBySegId.get(segs[sid]?.id);
|
|
806
|
+
if (e) {
|
|
807
|
+
try { e.userData = e.userData || {}; e.userData.isHole = true; } catch {}
|
|
808
|
+
boundaryEdges.add(e);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return h;
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
const contourV2 = contour.map(p=> new THREE.Vector2(p[0], p[1]));
|
|
815
|
+
const holesV2 = holes.map(arr => arr.map(p=> new THREE.Vector2(p[0], p[1])));
|
|
816
|
+
|
|
817
|
+
// Triangulate using ShapeUtils (earcut) directly
|
|
818
|
+
const tris = THREE.ShapeUtils.triangulateShape(contourV2, holesV2);
|
|
819
|
+
const allPts = contour.concat(...holes);
|
|
820
|
+
for (const t of tris){
|
|
821
|
+
const a = allPts[t[0]], b = allPts[t[1]], c = allPts[t[2]];
|
|
822
|
+
triPositions.push(a[0],a[1],0, b[0],b[1],0, c[0],c[1],0);
|
|
823
|
+
diagTriangles2D.push([[a[0], a[1], 0], [b[0], b[1], 0], [c[0], c[1], 0]]);
|
|
824
|
+
try {
|
|
825
|
+
const wa = toWorld(a[0], a[1]);
|
|
826
|
+
const wb = toWorld(b[0], b[1]);
|
|
827
|
+
const wc = toWorld(c[0], c[1]);
|
|
828
|
+
diagTrianglesWorld.push([[wa.x, wa.y, wa.z], [wb.x, wb.y, wb.z], [wc.x, wc.y, wc.z]]);
|
|
829
|
+
} catch {}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Save world-space loops for robust sweep side construction
|
|
833
|
+
const toW = (p)=> toWorld(p[0], p[1]);
|
|
834
|
+
const worldOuter = contour.map(p=>{ const v=toW(p); return [v.x,v.y,v.z]; });
|
|
835
|
+
const worldHoles = holes.map(h=> h.map(p=>{ const v=toW(p); return [v.x,v.y,v.z]; }));
|
|
836
|
+
boundaryLoopsWorld.push({ pts: worldOuter, isHole: false });
|
|
837
|
+
for (const h of worldHoles) boundaryLoopsWorld.push({ pts: h, isHole: true });
|
|
838
|
+
profileGroups.push({ contour2D: contour.slice(), holes2D: holes.map(h=>h.slice()), contourW: worldOuter.slice(), holesW: worldHoles.map(h=>h.slice()) });
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const diagEdges = edges.map((e) => {
|
|
842
|
+
const ud = e?.userData || {};
|
|
843
|
+
const safePts = Array.isArray(ud.polylineLocal)
|
|
844
|
+
? ud.polylineLocal.map((pt) => Array.isArray(pt) ? [Number(pt[0]) || 0, Number(pt[1]) || 0, Number(pt[2]) || 0] : pt)
|
|
845
|
+
: null;
|
|
846
|
+
return {
|
|
847
|
+
name: e?.name || null,
|
|
848
|
+
sketchGeometryId: ud.sketchGeometryId ?? null,
|
|
849
|
+
sketchFeatureId: ud.sketchFeatureId ?? null,
|
|
850
|
+
isHole: Boolean(ud.isHole),
|
|
851
|
+
sketchGeomType: ud.sketchGeomType || null,
|
|
852
|
+
arcCenter: Array.isArray(ud.arcCenter) ? ud.arcCenter.slice() : null,
|
|
853
|
+
arcRadius: typeof ud.arcRadius === 'number' ? ud.arcRadius : null,
|
|
854
|
+
circleCenter: Array.isArray(ud.circleCenter) ? ud.circleCenter.slice() : null,
|
|
855
|
+
circleRadius: typeof ud.circleRadius === 'number' ? ud.circleRadius : null,
|
|
856
|
+
polyline: safePts,
|
|
857
|
+
};
|
|
858
|
+
});
|
|
859
|
+
const diagOpenChains = openChains.map((chain) => chain.pts.map((pt) => [Number(pt[0]) || 0, Number(pt[1]) || 0]));
|
|
860
|
+
|
|
861
|
+
if (triPositions.length){
|
|
862
|
+
const geom2D = new THREE.BufferGeometry();
|
|
863
|
+
geom2D.setAttribute('position', new THREE.Float32BufferAttribute(triPositions,3));
|
|
864
|
+
// Map from plane to world
|
|
865
|
+
const m = new THREE.Matrix4();
|
|
866
|
+
const bO2 = new THREE.Vector3().fromArray(basis.origin);
|
|
867
|
+
const bX2 = new THREE.Vector3().fromArray(basis.x);
|
|
868
|
+
const bY2 = new THREE.Vector3().fromArray(basis.y);
|
|
869
|
+
const bZ2 = new THREE.Vector3().crossVectors(bX2,bY2).normalize();
|
|
870
|
+
m.makeBasis(bX2,bY2,bZ2); m.setPosition(bO2);
|
|
871
|
+
geom2D.applyMatrix4(m); geom2D.computeVertexNormals(); geom2D.computeBoundingSphere();
|
|
872
|
+
const face = new BREP.Face(geom2D);
|
|
873
|
+
face.name = `${sceneGroup.name}:PROFILE`;
|
|
874
|
+
face.userData.faceName = face.name;
|
|
875
|
+
face.edges = Array.from(boundaryEdges);
|
|
876
|
+
face.userData.boundaryLoopsWorld = boundaryLoopsWorld;
|
|
877
|
+
face.userData.profileGroups = profileGroups;
|
|
878
|
+
try {
|
|
879
|
+
const baseMat = face.material;
|
|
880
|
+
const sketchMat = (baseMat && typeof baseMat.clone === 'function') ? baseMat.clone() : null;
|
|
881
|
+
if (sketchMat) {
|
|
882
|
+
sketchMat.side = THREE.DoubleSide;
|
|
883
|
+
sketchMat.needsUpdate = true;
|
|
884
|
+
face.material = sketchMat;
|
|
885
|
+
face.userData.__baseMaterial = sketchMat;
|
|
886
|
+
}
|
|
887
|
+
} catch { }
|
|
888
|
+
sceneGroup.add(face);
|
|
889
|
+
profileFace = face;
|
|
890
|
+
this.persistentData.lastProfileDiagnostics = {
|
|
891
|
+
status: 'ok',
|
|
892
|
+
loops2D: normalizedLoops.map((loop) => loop.map((pt) => [Number(pt[0]) || 0, Number(pt[1]) || 0])),
|
|
893
|
+
loopDepth: depth.slice(),
|
|
894
|
+
loopSegmentIds: loopSegIDs.map((ids) => ids.slice()),
|
|
895
|
+
groups: groups.map((g) => ({ outer: g.outer, holes: g.holes.slice() })),
|
|
896
|
+
triangles2D: diagTriangles2D.map((tri) => tri.map((pt) => pt.slice())),
|
|
897
|
+
trianglesWorld: diagTrianglesWorld.map((tri) => tri.map((pt) => pt.slice())),
|
|
898
|
+
boundaryLoopsWorld: boundaryLoopsWorld.map((loop) => ({ isHole: Boolean(loop.isHole), pts: loop.pts.map((pt) => pt.slice()) })),
|
|
899
|
+
profileGroups: profileGroups.map((grp) => ({
|
|
900
|
+
contour2D: grp.contour2D.map((pt) => pt.slice()),
|
|
901
|
+
holes2D: grp.holes2D.map((hole) => hole.map((pt) => pt.slice())),
|
|
902
|
+
contourW: grp.contourW.map((pt) => pt.slice()),
|
|
903
|
+
holesW: grp.holesW.map((hole) => hole.map((pt) => pt.slice())),
|
|
904
|
+
})),
|
|
905
|
+
boundaryEdges: Array.from(boundaryEdges).map((edge) => edge?.name || null),
|
|
906
|
+
edges: diagEdges,
|
|
907
|
+
openChains2D: diagOpenChains,
|
|
908
|
+
triangleCount: diagTriangles2D.length,
|
|
909
|
+
};
|
|
910
|
+
} else {
|
|
911
|
+
this.persistentData.lastProfileDiagnostics = {
|
|
912
|
+
status: 'no-triangulation',
|
|
913
|
+
reason: 'Triangulation did not return any triangles',
|
|
914
|
+
loops2D: normalizedLoops.map((loop) => loop.map((pt) => [Number(pt[0]) || 0, Number(pt[1]) || 0])),
|
|
915
|
+
loopDepth: depth.slice(),
|
|
916
|
+
loopSegmentIds: loopSegIDs.map((ids) => ids.slice()),
|
|
917
|
+
groups: groups.map((g) => ({ outer: g.outer, holes: g.holes.slice() })),
|
|
918
|
+
triangles2D: [],
|
|
919
|
+
trianglesWorld: [],
|
|
920
|
+
boundaryLoopsWorld: boundaryLoopsWorld.map((loop) => ({ isHole: Boolean(loop.isHole), pts: loop.pts.map((pt) => pt.slice()) })),
|
|
921
|
+
profileGroups: profileGroups.map((grp) => ({
|
|
922
|
+
contour2D: grp.contour2D.map((pt) => pt.slice()),
|
|
923
|
+
holes2D: grp.holes2D.map((hole) => hole.map((pt) => pt.slice())),
|
|
924
|
+
contourW: grp.contourW.map((pt) => pt.slice()),
|
|
925
|
+
holesW: grp.holesW.map((hole) => hole.map((pt) => pt.slice())),
|
|
926
|
+
})),
|
|
927
|
+
boundaryEdges: Array.from(boundaryEdges).map((edge) => edge?.name || null),
|
|
928
|
+
edges: diagEdges,
|
|
929
|
+
openChains2D: diagOpenChains,
|
|
930
|
+
triangleCount: 0,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
} else {
|
|
934
|
+
this.persistentData.lastProfileDiagnostics = {
|
|
935
|
+
status: 'no-profile',
|
|
936
|
+
reason: 'No closed sketch loops available for triangulation',
|
|
937
|
+
loops2D: normalizedLoops.map((loop) => loop.map((pt) => [Number(pt[0]) || 0, Number(pt[1]) || 0])),
|
|
938
|
+
loopDepth: [],
|
|
939
|
+
loopSegmentIds: [],
|
|
940
|
+
groups: [],
|
|
941
|
+
triangles2D: [],
|
|
942
|
+
trianglesWorld: [],
|
|
943
|
+
boundaryLoopsWorld: [],
|
|
944
|
+
profileGroups: [],
|
|
945
|
+
boundaryEdges: [],
|
|
946
|
+
edges: [],
|
|
947
|
+
openChains2D: openChains.map((chain) => chain.pts.map((pt) => [Number(pt[0]) || 0, Number(pt[1]) || 0])),
|
|
948
|
+
triangleCount: 0,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
this._updateSketchChangeState(this.persistentData?.sketch || sketch);
|
|
953
|
+
return { added: [sceneGroup], removed: [] };
|
|
954
|
+
}
|
|
955
|
+
}
|