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,8 @@
|
|
|
1
|
+
export function computeTriangleArea(ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
2
|
+
const ux = bx - ax, uy = by - ay, uz = bz - az;
|
|
3
|
+
const vx = cx - ax, vy = cy - ay, vz = cz - az;
|
|
4
|
+
const nx = uy * vz - uz * vy;
|
|
5
|
+
const ny = uz * vx - ux * vz;
|
|
6
|
+
const nz = ux * vy - uy * vx;
|
|
7
|
+
return 0.5 * Math.hypot(nx, ny, nz);
|
|
8
|
+
}
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
// CapOpenEndsNonMonotone.js
|
|
2
|
+
// ES6 module. Triangulates non-monotone boundary loops WITHOUT ear-clipping.
|
|
3
|
+
// Algorithm: Project loop to best-fit plane → y-monotone decomposition (sweep) → triangulate each monotone piece.
|
|
4
|
+
// Guarantees: uses ONLY the loop's original vertices (no simplification) to avoid T-junctions; watertight/manifold caps.
|
|
5
|
+
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
|
|
8
|
+
const EPS = 1e-12;
|
|
9
|
+
|
|
10
|
+
// ---------- Vector helpers ----------
|
|
11
|
+
function newellNormal(loop3D) {
|
|
12
|
+
let nx = 0, ny = 0, nz = 0;
|
|
13
|
+
for (let i = 0; i < loop3D.length; i++) {
|
|
14
|
+
const p = loop3D[i], q = loop3D[(i + 1) % loop3D.length];
|
|
15
|
+
nx += (p.y - q.y) * (p.z + q.z);
|
|
16
|
+
ny += (p.z - q.z) * (p.x + q.x);
|
|
17
|
+
nz += (p.x - q.x) * (p.y + q.y);
|
|
18
|
+
}
|
|
19
|
+
const n = new THREE.Vector3(nx, ny, nz);
|
|
20
|
+
if (n.lengthSq() < EPS) {
|
|
21
|
+
// fallback: first non-collinear triple
|
|
22
|
+
for (let i = 0; i < loop3D.length; i++) {
|
|
23
|
+
const a = loop3D[i], b = loop3D[(i + 1) % loop3D.length], c = loop3D[(i + 2) % loop3D.length];
|
|
24
|
+
const v1 = new THREE.Vector3().subVectors(b, a);
|
|
25
|
+
const v2 = new THREE.Vector3().subVectors(c, b);
|
|
26
|
+
const t = new THREE.Vector3().crossVectors(v1, v2);
|
|
27
|
+
if (t.lengthSq() > EPS) return t.normalize();
|
|
28
|
+
}
|
|
29
|
+
return new THREE.Vector3(0, 0, 1);
|
|
30
|
+
}
|
|
31
|
+
return n.normalize();
|
|
32
|
+
}
|
|
33
|
+
function buildLocalFrame(n) {
|
|
34
|
+
const up = Math.abs(n.z) < 0.999 ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0);
|
|
35
|
+
const u = new THREE.Vector3().crossVectors(up, n).normalize();
|
|
36
|
+
const v = new THREE.Vector3().crossVectors(n, u).normalize();
|
|
37
|
+
return { u, v, n };
|
|
38
|
+
}
|
|
39
|
+
function projectTo2D(loop3D, u, v) {
|
|
40
|
+
return loop3D.map(p => ({ x: p.dot(u), y: p.dot(v) }));
|
|
41
|
+
}
|
|
42
|
+
function areaSigned2D(poly) {
|
|
43
|
+
let a = 0;
|
|
44
|
+
for (let i = 0; i < poly.length; i++) {
|
|
45
|
+
const p = poly[i], q = poly[(i + 1) % poly.length];
|
|
46
|
+
a += p.x * q.y - q.x * p.y;
|
|
47
|
+
}
|
|
48
|
+
return 0.5 * a;
|
|
49
|
+
}
|
|
50
|
+
function isCCW(poly) { return areaSigned2D(poly) > 0; }
|
|
51
|
+
function orient(a, b, c) {
|
|
52
|
+
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------- Geometry / boundary detection ----------
|
|
56
|
+
function ensureIndexed(geometry) {
|
|
57
|
+
if (!geometry.index) {
|
|
58
|
+
// build a trivial index; we will not merge vertices to avoid changing boundary vertex set
|
|
59
|
+
const pos = geometry.getAttribute('position');
|
|
60
|
+
const index = new (pos.count > 65535 ? Uint32Array : Uint16Array)(pos.count);
|
|
61
|
+
for (let i = 0; i < pos.count; i++) index[i] = i;
|
|
62
|
+
geometry.setIndex(new THREE.BufferAttribute(index, 1));
|
|
63
|
+
}
|
|
64
|
+
return geometry;
|
|
65
|
+
}
|
|
66
|
+
function edgeKey(a,b){ return a<b?`${a}:${b}`:`${b}:${a}`; }
|
|
67
|
+
|
|
68
|
+
function collectBoundaryLoops(geometry) {
|
|
69
|
+
const idx = geometry.index.array;
|
|
70
|
+
const pos = geometry.getAttribute('position');
|
|
71
|
+
const counts = new Map();
|
|
72
|
+
for (let i = 0; i < idx.length; i += 3) {
|
|
73
|
+
const a = idx[i], b = idx[i+1], c = idx[i+2];
|
|
74
|
+
[[a,b],[b,c],[c,a]].forEach(([u,w])=>{
|
|
75
|
+
const k = edgeKey(u,w);
|
|
76
|
+
counts.set(k,(counts.get(k)||0)+1);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// boundary edges appear exactly once
|
|
80
|
+
const adj = new Map(); // vertex -> neighbors (boundary)
|
|
81
|
+
for (const [k,c] of counts) {
|
|
82
|
+
if (c === 1) {
|
|
83
|
+
const [u,w] = k.split(':').map(Number);
|
|
84
|
+
if (!adj.has(u)) adj.set(u,new Set());
|
|
85
|
+
if (!adj.has(w)) adj.set(w,new Set());
|
|
86
|
+
adj.get(u).add(w);
|
|
87
|
+
adj.get(w).add(u);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// walk loops
|
|
91
|
+
const visitedE = new Set();
|
|
92
|
+
const loops = [];
|
|
93
|
+
const markEdge = (a,b)=>visitedE.add(`${a}>${b}`), seenEdge=(a,b)=>visitedE.has(`${a}>${b}`);
|
|
94
|
+
for (const start of adj.keys()) {
|
|
95
|
+
// try all incident edges
|
|
96
|
+
for (const n of adj.get(start)) {
|
|
97
|
+
if (seenEdge(start,n)) continue;
|
|
98
|
+
const loop = [];
|
|
99
|
+
let a = start, b = n;
|
|
100
|
+
markEdge(a,b);
|
|
101
|
+
loop.push(a);
|
|
102
|
+
while (true) {
|
|
103
|
+
loop.push(b);
|
|
104
|
+
// choose the next neighbor of b that isn't a and boundary edge not visited
|
|
105
|
+
const nbrs = Array.from(adj.get(b) || []);
|
|
106
|
+
let cNext = undefined;
|
|
107
|
+
for (const c of nbrs) {
|
|
108
|
+
if (c === a) continue;
|
|
109
|
+
if (!seenEdge(b,c)) { cNext = c; break; }
|
|
110
|
+
}
|
|
111
|
+
if (cNext === undefined) break;
|
|
112
|
+
a = b; b = cNext; markEdge(a,b);
|
|
113
|
+
if (b === loop[0]) break;
|
|
114
|
+
}
|
|
115
|
+
if (loop.length >= 3 && loop[0] === loop[loop.length-1]) loop.pop();
|
|
116
|
+
if (loop.length >= 3) loops.push(loop);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// return as arrays of vertex indices
|
|
120
|
+
return loops;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------- Monotone decomposition (Lee–Preparata) ----------
|
|
124
|
+
// We assume a simple polygon with unique vertices (no duplicates) and no self intersections.
|
|
125
|
+
// We do NOT remove collinear vertices.
|
|
126
|
+
|
|
127
|
+
function makeVertices(poly2D) {
|
|
128
|
+
// annotate each vertex with indices of prev/next in circular list
|
|
129
|
+
const n = poly2D.length;
|
|
130
|
+
return poly2D.map((p,i)=>({
|
|
131
|
+
x:p.x, y:p.y, i,
|
|
132
|
+
prev:(i+n-1)%n,
|
|
133
|
+
next:(i+1)%n
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function above(p, q) { // p higher than q in sweep order (y descending, x tie by smaller)
|
|
138
|
+
if (Math.abs(p.y - q.y) > EPS) return p.y > q.y;
|
|
139
|
+
return p.x < q.x;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function vertexType(vs, i, ccw=true) {
|
|
143
|
+
// Classify as start, end, split, merge, regular
|
|
144
|
+
const v = vs[i], p = vs[v.prev], n = vs[v.next];
|
|
145
|
+
|
|
146
|
+
const isHigherThanPrev = above(v, p);
|
|
147
|
+
const isHigherThanNext = above(v, n);
|
|
148
|
+
const isLowerThanPrev = above(p, v);
|
|
149
|
+
const isLowerThanNext = above(n, v);
|
|
150
|
+
|
|
151
|
+
const angleIsConvex = (ccw ? orient(p, v, n) > 0 : orient(p, v, n) < 0);
|
|
152
|
+
|
|
153
|
+
if (isHigherThanPrev && isHigherThanNext) {
|
|
154
|
+
// local maximum
|
|
155
|
+
return angleIsConvex ? 'start' : 'split';
|
|
156
|
+
}
|
|
157
|
+
if (isLowerThanPrev && isLowerThanNext) {
|
|
158
|
+
// local minimum
|
|
159
|
+
return angleIsConvex ? 'end' : 'merge';
|
|
160
|
+
}
|
|
161
|
+
return 'regular';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function xAtYOfEdge(a, b, y) {
|
|
165
|
+
if (Math.abs(a.y - b.y) < EPS) return Math.min(a.x, b.x); // horizontal; arbitrary pick min
|
|
166
|
+
return a.x + (y - a.y) * (b.x - a.x) / (b.y - a.y);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class StatusStructure {
|
|
170
|
+
// Balanced tree substitute: we’ll keep a sorted array by x at current sweep y.
|
|
171
|
+
// For typical cap loops (hundreds of verts) this is fine and simpler.
|
|
172
|
+
constructor() { this.edges = []; this.y = Infinity; }
|
|
173
|
+
setY(y) { this.y = y; this.sort(); }
|
|
174
|
+
sort() {
|
|
175
|
+
const y = this.y;
|
|
176
|
+
this.edges.sort((e1,e2)=>{
|
|
177
|
+
const x1 = xAtYOfEdge(e1.a, e1.b, y);
|
|
178
|
+
const x2 = xAtYOfEdge(e2.a, e2.b, y);
|
|
179
|
+
if (Math.abs(x1 - x2) > EPS) return x1 - x2;
|
|
180
|
+
// tie-breaker by ids
|
|
181
|
+
return e1.id - e2.id;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
insert(edge) { this.edges.push(edge); this.sort(); }
|
|
185
|
+
remove(edge) {
|
|
186
|
+
const k = this.edges.findIndex(e=>e.id===edge.id); if (k>=0) this.edges.splice(k,1);
|
|
187
|
+
}
|
|
188
|
+
leftOf(x) {
|
|
189
|
+
// return edge strictly to the left of x (greatest x < x)
|
|
190
|
+
let best = null, bestX = -Infinity;
|
|
191
|
+
for (const e of this.edges) {
|
|
192
|
+
const ex = xAtYOfEdge(e.a, e.b, this.y);
|
|
193
|
+
if (ex < x - EPS && ex > bestX) { best = e; bestX = ex; }
|
|
194
|
+
}
|
|
195
|
+
return best;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function monotoneDecompose(poly2D) {
|
|
200
|
+
// Returns a list of diagonals (pairs of vertex indices) that decompose into y-monotone polygons.
|
|
201
|
+
const n = poly2D.length;
|
|
202
|
+
const vs = makeVertices(poly2D);
|
|
203
|
+
|
|
204
|
+
const ccw = isCCW(poly2D); // sweep assumes CCW interior on left; handle both
|
|
205
|
+
const order = [...vs].sort((a,b)=> {
|
|
206
|
+
if (Math.abs(a.y - b.y) > EPS) return b.y - a.y; // descending y
|
|
207
|
+
return a.x - b.x; // ascending x
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const status = new StatusStructure();
|
|
211
|
+
const helper = new Map(); // edge.id -> vertex index
|
|
212
|
+
let edgeIdSeq = 1;
|
|
213
|
+
|
|
214
|
+
function edgeOf(i, j) { // directed edge vi -> vj
|
|
215
|
+
const a = vs[i], b = vs[j];
|
|
216
|
+
return { a, b, id: edgeIdSeq++, i, j };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const diagonals = [];
|
|
220
|
+
|
|
221
|
+
function addDiagonal(i, j) {
|
|
222
|
+
if (i === j) return;
|
|
223
|
+
diagonals.push([i, j]);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function addOrUpdateEdge(i, j, h) {
|
|
227
|
+
const e = edgeOf(i, j);
|
|
228
|
+
status.insert(e);
|
|
229
|
+
if (h !== undefined) helper.set(e.id, h);
|
|
230
|
+
return e;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function findEdgeLeftOf(x) {
|
|
234
|
+
return status.leftOf(x);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const v of order) {
|
|
238
|
+
status.setY(v.y + 1e-9); // slightly above v to order properly
|
|
239
|
+
|
|
240
|
+
const t = vertexType(vs, v.i, ccw);
|
|
241
|
+
|
|
242
|
+
if (t === 'start') {
|
|
243
|
+
const e = addOrUpdateEdge(v.i, vs[v.next].i, v.i);
|
|
244
|
+
// helper[e] = v
|
|
245
|
+
}
|
|
246
|
+
else if (t === 'end') {
|
|
247
|
+
// check edge (prev, v)
|
|
248
|
+
// find the edge whose (i -> v.i) exists in status: it’s the incoming edge from prev
|
|
249
|
+
// We locate by scanning (small n), acceptable.
|
|
250
|
+
let found = null, keyId = -1;
|
|
251
|
+
for (const e of status.edges) {
|
|
252
|
+
if (e.j === v.i) { found = e; keyId = e.id; break; }
|
|
253
|
+
}
|
|
254
|
+
if (found) {
|
|
255
|
+
const h = helper.get(keyId);
|
|
256
|
+
if (h !== undefined) {
|
|
257
|
+
if (vertexType(vs, h, ccw) === 'merge') {
|
|
258
|
+
addDiagonal(v.i, h);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
status.remove(found);
|
|
262
|
+
helper.delete(keyId);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (t === 'split') {
|
|
266
|
+
// find left edge of v
|
|
267
|
+
const eLeft = findEdgeLeftOf(v.x);
|
|
268
|
+
if (!eLeft) {
|
|
269
|
+
// degenerate; skip adding diagonal
|
|
270
|
+
} else {
|
|
271
|
+
const h = helper.get(eLeft.id);
|
|
272
|
+
if (h !== undefined) addDiagonal(v.i, h);
|
|
273
|
+
helper.set(eLeft.id, v.i);
|
|
274
|
+
}
|
|
275
|
+
// insert edge (v, next)
|
|
276
|
+
const e = addOrUpdateEdge(v.i, vs[v.next].i, v.i);
|
|
277
|
+
}
|
|
278
|
+
else if (t === 'merge') {
|
|
279
|
+
// handle incoming edge (prev, v)
|
|
280
|
+
let incoming = null, incId = -1;
|
|
281
|
+
for (const e of status.edges) {
|
|
282
|
+
if (e.j === v.i) { incoming = e; incId = e.id; break; }
|
|
283
|
+
}
|
|
284
|
+
if (incoming) {
|
|
285
|
+
const h = helper.get(incId);
|
|
286
|
+
if (h !== undefined) addDiagonal(v.i, h);
|
|
287
|
+
status.remove(incoming);
|
|
288
|
+
helper.delete(incId);
|
|
289
|
+
}
|
|
290
|
+
// find left edge and check its helper
|
|
291
|
+
const eLeft = findEdgeLeftOf(v.x);
|
|
292
|
+
if (eLeft) {
|
|
293
|
+
const h = helper.get(eLeft.id);
|
|
294
|
+
if (h !== undefined && vertexType(vs, h, ccw) === 'merge') {
|
|
295
|
+
addDiagonal(v.i, h);
|
|
296
|
+
}
|
|
297
|
+
helper.set(eLeft.id, v.i);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else { // regular
|
|
301
|
+
// Determine if interior lies to the left or right of the vertex
|
|
302
|
+
// Using edge (prev -> v) orientation vs sweep direction
|
|
303
|
+
const onLeftChain = above(vs[v.next], v); // if next is below, we are on left chain (descending along next)
|
|
304
|
+
if (onLeftChain) {
|
|
305
|
+
// like END
|
|
306
|
+
let incoming = null, incId = -1;
|
|
307
|
+
for (const e of status.edges) {
|
|
308
|
+
if (e.j === v.i) { incoming = e; incId = e.id; break; }
|
|
309
|
+
}
|
|
310
|
+
if (incoming) {
|
|
311
|
+
const h = helper.get(incId);
|
|
312
|
+
if (h !== undefined && vertexType(vs, h, ccw) === 'merge') {
|
|
313
|
+
addDiagonal(v.i, h);
|
|
314
|
+
}
|
|
315
|
+
status.remove(incoming);
|
|
316
|
+
helper.delete(incId);
|
|
317
|
+
}
|
|
318
|
+
const e = addOrUpdateEdge(v.i, vs[v.next].i, v.i);
|
|
319
|
+
} else {
|
|
320
|
+
// like START side: connect to helper of left edge
|
|
321
|
+
const eLeft = findEdgeLeftOf(v.x);
|
|
322
|
+
if (eLeft) {
|
|
323
|
+
const h = helper.get(eLeft.id);
|
|
324
|
+
if (h !== undefined && vertexType(vs, h, ccw) === 'merge') {
|
|
325
|
+
addDiagonal(v.i, h);
|
|
326
|
+
}
|
|
327
|
+
helper.set(eLeft.id, v.i);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return diagonals;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Split polygon by diagonals into monotone pieces (each as index cycle)
|
|
337
|
+
function buildPiecesFromDiagonals(loopIdxs, diagonals) {
|
|
338
|
+
// Construct adjacency of a planar straight-line graph formed by original cycle + diagonals.
|
|
339
|
+
// We then traverse faces that correspond to monotone pieces.
|
|
340
|
+
const n = loopIdxs.length;
|
|
341
|
+
const verts = loopIdxs.slice(); // indices into original mesh vertex buffer
|
|
342
|
+
// map each local vertex i to its two neighbors (single cycle)
|
|
343
|
+
const neighbors = new Map(); // localIndex -> Set of localIndices
|
|
344
|
+
function addEdge(i,j) {
|
|
345
|
+
if (!neighbors.has(i)) neighbors.set(i,new Set());
|
|
346
|
+
if (!neighbors.has(j)) neighbors.set(j,new Set());
|
|
347
|
+
neighbors.get(i).add(j);
|
|
348
|
+
neighbors.get(j).add(i);
|
|
349
|
+
}
|
|
350
|
+
for (let i=0;i<n;i++){
|
|
351
|
+
const a=i, b=(i+1)%n; addEdge(a,b);
|
|
352
|
+
}
|
|
353
|
+
for (const [a,b] of diagonals) addEdge(a,b);
|
|
354
|
+
|
|
355
|
+
// Build all simple cycles (faces). We'll extract only inner faces (skip the outer).
|
|
356
|
+
// Use half-edge traversal with angle sorting to walk around faces CCW.
|
|
357
|
+
// For this we need embedded 2D positions; we’ll supply them at callsite.
|
|
358
|
+
// Here we just produce an edge list; actual face extraction is done in triangulateByMonotone, which knows 2D pts.
|
|
359
|
+
return { neighbors };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Triangulate a single y-monotone polygon (given as sequence of local indices and its 2D coords)
|
|
363
|
+
function triangulateMonotonePiece(indices, pts) {
|
|
364
|
+
// pts: array of {x,y} in local order (indices map to indices in this array)
|
|
365
|
+
// Algorithm: sort vertices by y (desc), then use stack on left/right chains.
|
|
366
|
+
// First, ensure the polygon is y-monotone and we have top/bottom.
|
|
367
|
+
const n = indices.length;
|
|
368
|
+
if (n < 3) return [];
|
|
369
|
+
const poly = indices.map(i => pts[i]);
|
|
370
|
+
|
|
371
|
+
// find top and bottom (max y and min y; break ties by x)
|
|
372
|
+
let top = 0, bottom = 0;
|
|
373
|
+
for (let k=1;k<n;k++){
|
|
374
|
+
const p = poly[k], t = poly[top], b = poly[bottom];
|
|
375
|
+
if (p.y > t.y + EPS || (Math.abs(p.y-t.y)<=EPS && p.x < t.x)) top = k;
|
|
376
|
+
if (p.y < b.y - EPS || (Math.abs(p.y-b.y)<=EPS && p.x > b.x)) bottom = k;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// split into left/right chains walking from top to bottom along both directions
|
|
380
|
+
const nextIdx = i => (i+1)%n, prevIdx = i => (i+n-1)%n;
|
|
381
|
+
const left = []; const right = [];
|
|
382
|
+
// walk clockwise from top to bottom
|
|
383
|
+
let i = top;
|
|
384
|
+
while (i !== bottom) { i = nextIdx(i); right.push(i); }
|
|
385
|
+
// walk counter-clockwise
|
|
386
|
+
i = top;
|
|
387
|
+
while (i !== bottom) { i = prevIdx(i); left.push(i); }
|
|
388
|
+
|
|
389
|
+
const chain = new Array(n).fill(''); // 'L' or 'R'
|
|
390
|
+
chain[top] = 'L'; chain[bottom] = 'L';
|
|
391
|
+
for (const k of left) chain[k]='L';
|
|
392
|
+
for (const k of right) chain[k]='R';
|
|
393
|
+
|
|
394
|
+
// sort vertices by y desc (x asc)
|
|
395
|
+
const order = [];
|
|
396
|
+
for (let k=0;k<n;k++) order.push(k);
|
|
397
|
+
order.sort((a,b)=>{
|
|
398
|
+
const A = poly[a], B = poly[b];
|
|
399
|
+
if (Math.abs(A.y-B.y)>EPS) return B.y - A.y;
|
|
400
|
+
return A.x - B.x;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const stack = [order[0], order[1]];
|
|
404
|
+
const tris = [];
|
|
405
|
+
|
|
406
|
+
for (let s=2; s<order.length-1; s++){
|
|
407
|
+
const cur = order[s];
|
|
408
|
+
if (chain[cur] !== chain[stack[stack.length-1]]) {
|
|
409
|
+
// different chain: pop all and form fan
|
|
410
|
+
for (let k=0; k<stack.length-1; k++){
|
|
411
|
+
const a = stack[k], b = stack[k+1], c = cur;
|
|
412
|
+
// ensure correct orientation (assume original polygon orientation)
|
|
413
|
+
tris.push([indices[a], indices[b], indices[c]]);
|
|
414
|
+
}
|
|
415
|
+
stack.length = 0;
|
|
416
|
+
stack.push(order[s-1], cur);
|
|
417
|
+
} else {
|
|
418
|
+
// same chain: pop while angle is convex
|
|
419
|
+
let last = stack.pop();
|
|
420
|
+
while (stack.length > 0) {
|
|
421
|
+
const a = stack[stack.length-1], b = last, c = cur;
|
|
422
|
+
const o = orient(poly[a], poly[b], poly[c]);
|
|
423
|
+
// For CCW polygon, we keep making triangles while we have a "right turn" on left chain or "left turn" on right chain.
|
|
424
|
+
const isConvex = (chain[cur]==='L') ? (o > EPS) : (o < -EPS);
|
|
425
|
+
if (!isConvex) break;
|
|
426
|
+
tris.push([indices[a], indices[b], indices[c]]);
|
|
427
|
+
last = stack.pop();
|
|
428
|
+
}
|
|
429
|
+
stack.push(last, cur);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// connect remaining
|
|
433
|
+
const lastV = order[order.length-1];
|
|
434
|
+
for (let k=0; k<stack.length-1; k++){
|
|
435
|
+
const a = stack[k], b = stack[k+1], c = lastV;
|
|
436
|
+
tris.push([indices[a], indices[b], indices[c]]);
|
|
437
|
+
}
|
|
438
|
+
return tris;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Build monotone pieces (faces) from polygon + diagonals and triangulate them
|
|
442
|
+
function triangulateByMonotone(loop2D, diagonals) {
|
|
443
|
+
// Build graph
|
|
444
|
+
const localIdx = loop2D.map((_,i)=>i);
|
|
445
|
+
const { neighbors } = buildPiecesFromDiagonals(localIdx, diagonals);
|
|
446
|
+
|
|
447
|
+
// Convert neighbor graph into simple faces using angular sweep per vertex.
|
|
448
|
+
// We’ll enumerate half-edges (u->v), and for each not yet visited, walk the face by always taking the next edge with smallest clockwise angle.
|
|
449
|
+
const n = loop2D.length;
|
|
450
|
+
const visitedHE = new Set();
|
|
451
|
+
function heKey(u,v){return `${u}>${v}`;}
|
|
452
|
+
|
|
453
|
+
// Precompute angle-sorted neighbors around each vertex
|
|
454
|
+
const angNbrs = new Map();
|
|
455
|
+
for (let i=0;i<n;i++){
|
|
456
|
+
const p = loop2D[i];
|
|
457
|
+
const nbrs = Array.from(neighbors.get(i)||[]);
|
|
458
|
+
// sort by angle around p (atan2), clockwise from positive x
|
|
459
|
+
nbrs.sort((a,b)=>{
|
|
460
|
+
const va = {x: loop2D[a].x - p.x, y: loop2D[a].y - p.y};
|
|
461
|
+
const vb = {x: loop2D[b].x - p.x, y: loop2D[b].y - p.y};
|
|
462
|
+
const aa = Math.atan2(va.y, va.x);
|
|
463
|
+
const ab = Math.atan2(vb.y, vb.x);
|
|
464
|
+
return ab - aa; // descending = clockwise
|
|
465
|
+
});
|
|
466
|
+
angNbrs.set(i, nbrs);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const faces = [];
|
|
470
|
+
|
|
471
|
+
for (let u=0; u<n; u++){
|
|
472
|
+
const nbrs = angNbrs.get(u)||[];
|
|
473
|
+
for (const v of nbrs) {
|
|
474
|
+
const key = heKey(u,v);
|
|
475
|
+
if (visitedHE.has(key)) continue;
|
|
476
|
+
// start walking face keeping it on the left (CW next-edge rule as we sorted neighbors CW)
|
|
477
|
+
const face = [];
|
|
478
|
+
let a = u, b = v;
|
|
479
|
+
while (true){
|
|
480
|
+
visitedHE.add(heKey(a,b));
|
|
481
|
+
face.push(a);
|
|
482
|
+
// choose next edge at b: find a in its CW list, take the previous neighbor (turn right) to keep interior on left
|
|
483
|
+
const list = angNbrs.get(b)||[];
|
|
484
|
+
const k = list.indexOf(a);
|
|
485
|
+
const nextIdx = (k - 1 + list.length) % list.length;
|
|
486
|
+
const c = list[nextIdx];
|
|
487
|
+
a = b; b = c;
|
|
488
|
+
if (a === u && b === v) break;
|
|
489
|
+
if (face.length > n + diagonals.length*2 + 5) break; // safety
|
|
490
|
+
}
|
|
491
|
+
if (face.length >= 3) {
|
|
492
|
+
// Remove duplicate last if present
|
|
493
|
+
if (face[0] === face[face.length-1]) face.pop();
|
|
494
|
+
// Keep only inner faces: heuristic by signed area (same orientation as original polygon)
|
|
495
|
+
const ar = areaSigned2D(face.map(i=>loop2D[i]));
|
|
496
|
+
if (Math.abs(ar) > EPS) faces.push(face);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Triangulate each face assuming it's monotone (by construction)
|
|
502
|
+
const tris = [];
|
|
503
|
+
for (const f of faces) {
|
|
504
|
+
const t = triangulateMonotonePiece(f, loop2D);
|
|
505
|
+
for (const tri of t) tris.push(tri);
|
|
506
|
+
}
|
|
507
|
+
return tris;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ---------- End-to-end per loop ----------
|
|
511
|
+
function triangulateNonMonotoneLoop(loop3D, loopIdxMesh) {
|
|
512
|
+
const n = loop3D.length;
|
|
513
|
+
const normal = newellNormal(loop3D);
|
|
514
|
+
const { u, v } = buildLocalFrame(normal);
|
|
515
|
+
const loop2D = projectTo2D(loop3D, u, v);
|
|
516
|
+
|
|
517
|
+
// Ensure polygon is CCW in (u,v) plane; if not, reverse consistently
|
|
518
|
+
if (!isCCW(loop2D)) {
|
|
519
|
+
loop2D.reverse();
|
|
520
|
+
loopIdxMesh = loopIdxMesh.slice().reverse();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Decompose into y-monotone pieces via diagonals (indices are local 0..n-1)
|
|
524
|
+
const diagonals = monotoneDecompose(loop2D);
|
|
525
|
+
|
|
526
|
+
// Triangulate all monotone pieces; triangles are local indices - map back to mesh indices
|
|
527
|
+
const localTris = triangulateByMonotone(loop2D, diagonals);
|
|
528
|
+
const meshTris = [];
|
|
529
|
+
for (const [a,b,c] of localTris) {
|
|
530
|
+
meshTris.push(loopIdxMesh[a], loopIdxMesh[b], loopIdxMesh[c]);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return { tris: meshTris, planeNormal: normal };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ---------- Winding selection vs mesh outside ----------
|
|
537
|
+
function computeAverageBoundaryNormal(geometry, loopIdx) {
|
|
538
|
+
const nAttr = geometry.getAttribute('normal');
|
|
539
|
+
if (nAttr) {
|
|
540
|
+
const acc = new THREE.Vector3();
|
|
541
|
+
const t = new THREE.Vector3();
|
|
542
|
+
for (const i of loopIdx) {
|
|
543
|
+
t.set(nAttr.getX(i), nAttr.getY(i), nAttr.getZ(i));
|
|
544
|
+
acc.add(t);
|
|
545
|
+
}
|
|
546
|
+
if (acc.lengthSq() > EPS) return acc.normalize();
|
|
547
|
+
}
|
|
548
|
+
// fallback: Newell on loop positions
|
|
549
|
+
const pos = geometry.getAttribute('position');
|
|
550
|
+
const loop3D = loopIdx.map(i => new THREE.Vector3(pos.getX(i), pos.getY(i), pos.getZ(i)));
|
|
551
|
+
return newellNormal(loop3D);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function addTrianglesToGeometry(geometry, trisToAppend) {
|
|
555
|
+
const oldIndex = geometry.index.array;
|
|
556
|
+
const ctor = oldIndex.constructor;
|
|
557
|
+
const need32 = (oldIndex.length + trisToAppend.length) > 65535 && !(oldIndex instanceof Uint32Array);
|
|
558
|
+
const finalCtor = need32 ? Uint32Array : ctor;
|
|
559
|
+
const newIndex = new finalCtor(oldIndex.length + trisToAppend.length);
|
|
560
|
+
newIndex.set(oldIndex, 0);
|
|
561
|
+
newIndex.set(trisToAppend, oldIndex.length);
|
|
562
|
+
geometry.setIndex(new THREE.BufferAttribute(newIndex, 1));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ---------- Public API ----------
|
|
566
|
+
export function capOpenEndsNonMonotone(mesh, { recomputeNormals = true } = {}) {
|
|
567
|
+
if (!mesh || !mesh.geometry) throw new Error('capOpenEndsNonMonotone: mesh with geometry is required.');
|
|
568
|
+
const geometry = ensureIndexed(mesh.geometry);
|
|
569
|
+
mesh.geometry = geometry;
|
|
570
|
+
|
|
571
|
+
const pos = geometry.getAttribute('position');
|
|
572
|
+
const loops = collectBoundaryLoops(geometry);
|
|
573
|
+
if (loops.length === 0) return mesh;
|
|
574
|
+
|
|
575
|
+
const allCapTris = [];
|
|
576
|
+
|
|
577
|
+
for (const loopIdx of loops) {
|
|
578
|
+
// gather 3D points
|
|
579
|
+
const loop3D = loopIdx.map(i => new THREE.Vector3(pos.getX(i), pos.getY(i), pos.getZ(i)));
|
|
580
|
+
const { tris, planeNormal } = triangulateNonMonotoneLoop(loop3D, loopIdx);
|
|
581
|
+
|
|
582
|
+
if (tris.length === 0) {
|
|
583
|
+
console.warn('capOpenEndsNonMonotone: triangulation produced no triangles for a loop (possibly self-intersecting).');
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Choose winding so cap normal aligns with the "outside"
|
|
588
|
+
const avgBoundaryNormal = computeAverageBoundaryNormal(geometry, loopIdx);
|
|
589
|
+
const flip = planeNormal.dot(avgBoundaryNormal) < 0;
|
|
590
|
+
|
|
591
|
+
if (flip) {
|
|
592
|
+
for (let i=0;i<tris.length;i+=3){
|
|
593
|
+
const a = tris[i], b = tris[i+1], c = tris[i+2];
|
|
594
|
+
allCapTris.push(a, c, b);
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
allCapTris.push(...tris);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (allCapTris.length) {
|
|
602
|
+
addTrianglesToGeometry(geometry, allCapTris);
|
|
603
|
+
if (recomputeNormals) geometry.computeVertexNormals();
|
|
604
|
+
geometry.computeBoundingBox();
|
|
605
|
+
geometry.computeBoundingSphere();
|
|
606
|
+
}
|
|
607
|
+
return mesh;
|
|
608
|
+
}
|