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,1120 @@
|
|
|
1
|
+
// threadGeometry.js
|
|
2
|
+
// Unified thread geometry helper for multiple standards in a single ES6 class.
|
|
3
|
+
import { Solid } from "./BetterSolid.js";
|
|
4
|
+
import { Manifold, THREE } from "./SolidShared.js";
|
|
5
|
+
|
|
6
|
+
export const ThreadStandard = {
|
|
7
|
+
ISO_METRIC: "ISO_METRIC", // 60° V, ISO 68-1 style basic metric
|
|
8
|
+
UNIFIED: "UNIFIED", // 60° V, UNC/UNF/UNEF style basic form
|
|
9
|
+
ACME: "ACME", // 29° Acme, flat crest/root
|
|
10
|
+
STUB_ACME: "STUB_ACME", // 29° Stub Acme
|
|
11
|
+
TRAPEZOIDAL_METRIC: "TRAPEZOIDAL_METRIC", // 30° metric trapezoidal (Tr)
|
|
12
|
+
WHITWORTH: "WHITWORTH", // 55° Whitworth, rounded crest/root
|
|
13
|
+
NPT: "NPT", // 60° NPT tapered pipe thread
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
17
|
+
const EPS = 1e-9;
|
|
18
|
+
|
|
19
|
+
const computeInternalOverlap = (thread) => {
|
|
20
|
+
const pitch = Math.abs(thread?.pitch || 0);
|
|
21
|
+
const depth = Math.abs(thread?.effectiveThreadDepth || 0);
|
|
22
|
+
const base = pitch > 0 ? pitch * 0.05 : 0.02;
|
|
23
|
+
const cap = depth > 0 ? depth * 0.25 : base;
|
|
24
|
+
const minVal = pitch > 0 ? pitch * 0.01 : 0.01;
|
|
25
|
+
return Math.max(EPS * 10, Math.min(cap, base) || minVal);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const safeDelete = (obj) => {
|
|
29
|
+
try {
|
|
30
|
+
if (obj && typeof obj.delete === "function") obj.delete();
|
|
31
|
+
} catch {
|
|
32
|
+
/* ignore */
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const vecFrom = (v, fallback = [0, 0, 0]) => {
|
|
37
|
+
if (v && typeof v.x === "number") return new THREE.Vector3(v.x, v.y, v.z);
|
|
38
|
+
if (Array.isArray(v)) {
|
|
39
|
+
return new THREE.Vector3(Number(v[0]) || 0, Number(v[1]) || 0, Number(v[2]) || 0);
|
|
40
|
+
}
|
|
41
|
+
return new THREE.Vector3(Number(fallback[0]) || 0, Number(fallback[1]) || 0, Number(fallback[2]) || 0);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const buildPlacementMatrix = ({ axis, axisDirection, xDirection, origin } = {}) => {
|
|
45
|
+
const axisVec = vecFrom(axis || axisDirection || [0, 1, 0]);
|
|
46
|
+
if (axisVec.lengthSq() < EPS) axisVec.set(0, 1, 0);
|
|
47
|
+
axisVec.normalize();
|
|
48
|
+
|
|
49
|
+
let xVec = xDirection ? vecFrom(xDirection) : new THREE.Vector3(1, 0, 0);
|
|
50
|
+
xVec.addScaledVector(axisVec, -xVec.dot(axisVec));
|
|
51
|
+
if (xVec.lengthSq() < EPS) {
|
|
52
|
+
const fallback = Math.abs(axisVec.y) < 0.9 ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(1, 0, 0);
|
|
53
|
+
xVec.crossVectors(fallback, axisVec);
|
|
54
|
+
if (xVec.lengthSq() < EPS) xVec.set(1, 0, 0);
|
|
55
|
+
}
|
|
56
|
+
xVec.normalize();
|
|
57
|
+
const yVec = new THREE.Vector3().crossVectors(axisVec, xVec).normalize();
|
|
58
|
+
const m = new THREE.Matrix4().makeBasis(xVec, yVec, axisVec);
|
|
59
|
+
m.setPosition(vecFrom(origin || [0, 0, 0]));
|
|
60
|
+
return m;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const manifoldToSolid = (manifold, name = "Thread", faceName = "THREAD", idToFaceName = null) => {
|
|
64
|
+
if (!manifold) return null;
|
|
65
|
+
const solid = new Solid();
|
|
66
|
+
solid.name = name;
|
|
67
|
+
|
|
68
|
+
const faceIdMap = (() => {
|
|
69
|
+
if (idToFaceName instanceof Map) return new Map(idToFaceName);
|
|
70
|
+
if (idToFaceName && typeof idToFaceName === "object") return new Map(Object.entries(idToFaceName));
|
|
71
|
+
return new Map();
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
const mesh = manifold.getMesh();
|
|
75
|
+
let addedCount = 0;
|
|
76
|
+
let failedCount = 0;
|
|
77
|
+
try {
|
|
78
|
+
const vp = mesh.vertProperties;
|
|
79
|
+
const tv = mesh.triVerts;
|
|
80
|
+
const triCount = Math.floor(tv.length / 3);
|
|
81
|
+
const faceIDs = mesh.faceID && mesh.faceID.length === triCount ? mesh.faceID : null;
|
|
82
|
+
|
|
83
|
+
if (faceIdMap.size === 0) {
|
|
84
|
+
if (faceIDs) {
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
for (let t = 0; t < triCount; t++) {
|
|
87
|
+
const fid = faceIDs[t] >>> 0;
|
|
88
|
+
if (seen.has(fid)) continue;
|
|
89
|
+
seen.add(fid);
|
|
90
|
+
faceIdMap.set(fid, faceName || `FACE_${fid}`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
faceIdMap.set(0, faceName || "FACE_0");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('[ThreadGeometry] manifoldToSolid: converting', triCount, 'triangles to solid with', faceIdMap.size, 'face labels');
|
|
98
|
+
for (let t = 0; t < triCount; t++) {
|
|
99
|
+
const i0 = tv[3 * t + 0] >>> 0;
|
|
100
|
+
const i1 = tv[3 * t + 1] >>> 0;
|
|
101
|
+
const i2 = tv[3 * t + 2] >>> 0;
|
|
102
|
+
const p0 = [vp[i0 * 3 + 0], vp[i0 * 3 + 1], vp[i0 * 3 + 2]];
|
|
103
|
+
const p1 = [vp[i1 * 3 + 0], vp[i1 * 3 + 1], vp[i1 * 3 + 2]];
|
|
104
|
+
const p2 = [vp[i2 * 3 + 0], vp[i2 * 3 + 1], vp[i2 * 3 + 2]];
|
|
105
|
+
|
|
106
|
+
const fid = faceIDs ? (faceIDs[t] >>> 0) : 0;
|
|
107
|
+
let triFaceName = faceIdMap.get(fid);
|
|
108
|
+
if (!triFaceName) {
|
|
109
|
+
const fallbackName = faceIdMap.size === 0 ? (faceName || `FACE_${fid}`) : `FACE_${fid}`;
|
|
110
|
+
triFaceName = fallbackName;
|
|
111
|
+
faceIdMap.set(fid, triFaceName);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
solid.addTriangle(triFaceName, p0, p1, p2);
|
|
116
|
+
addedCount++;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
failedCount++;
|
|
119
|
+
if (failedCount <= 3) {
|
|
120
|
+
console.warn('[ThreadGeometry] Failed to add triangle', t, ':', err.message, { p0, p1, p2, face: triFaceName });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log('[ThreadGeometry] manifoldToSolid: added', addedCount, 'triangles, failed', failedCount);
|
|
125
|
+
console.log('[ThreadGeometry] manifoldToSolid: solid internals:', {
|
|
126
|
+
triVertsLength: solid._triVerts?.length,
|
|
127
|
+
triIDsLength: solid._triIDs?.length,
|
|
128
|
+
vertPropertiesLength: solid._vertProperties?.length,
|
|
129
|
+
faceCount: solid._faceNameToID?.size,
|
|
130
|
+
});
|
|
131
|
+
// Force manifoldization to catch any issues early
|
|
132
|
+
try {
|
|
133
|
+
solid._manifoldize();
|
|
134
|
+
console.log('[ThreadGeometry] manifoldToSolid: manifoldization successful');
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.warn('[ThreadGeometry] manifoldToSolid: manifoldization failed:', err);
|
|
137
|
+
}
|
|
138
|
+
} finally {
|
|
139
|
+
safeDelete(mesh);
|
|
140
|
+
safeDelete(manifold);
|
|
141
|
+
}
|
|
142
|
+
return solid;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const buildThreadProfilePolygon = (thread, radialOffset = 0) => {
|
|
146
|
+
const external = thread.isExternal === true;
|
|
147
|
+
let crestR = Math.max(EPS, (thread.crestRadius || 0) + radialOffset);
|
|
148
|
+
let rootR = Math.max(EPS, (thread.rootRadius || 0) + radialOffset);
|
|
149
|
+
const minGap = Math.max(Math.abs(thread.effectiveThreadDepth || EPS * 10), EPS * 10);
|
|
150
|
+
if (external) {
|
|
151
|
+
if (crestR <= rootR + EPS * 10) crestR = rootR + minGap;
|
|
152
|
+
} else {
|
|
153
|
+
if (rootR <= crestR + EPS * 10) rootR = crestR + minGap;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Extrude-with-twist creates a helix by:
|
|
157
|
+
// 1. Taking a 2D profile in the XY plane
|
|
158
|
+
// 2. Extruding it along Z while rotating it
|
|
159
|
+
// The profile should be positioned at the thread radius and shaped like the tooth cross-section
|
|
160
|
+
|
|
161
|
+
const pitch = thread.pitch || 1;
|
|
162
|
+
const halfPitch = pitch / 2;
|
|
163
|
+
|
|
164
|
+
let crestRad = crestR;
|
|
165
|
+
if (!external) {
|
|
166
|
+
const overlap = computeInternalOverlap(thread);
|
|
167
|
+
const targetCrest = crestR - overlap;
|
|
168
|
+
crestRad = Math.max(EPS, Math.min(targetCrest, rootR - EPS));
|
|
169
|
+
console.log('[ThreadGeometry] Applying internal overlap:', { overlap, crestR, crestRad, rootR });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log('[ThreadGeometry] Profile polygon params:', {
|
|
173
|
+
external,
|
|
174
|
+
crestR,
|
|
175
|
+
crestRad,
|
|
176
|
+
rootR,
|
|
177
|
+
pitch,
|
|
178
|
+
halfPitch,
|
|
179
|
+
effectiveThreadDepth: thread.effectiveThreadDepth,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Create a trapezoidal profile representing the thread tooth cross-section
|
|
183
|
+
// Profile format: [axial_position, radius]
|
|
184
|
+
// This represents the shape as seen from the side (radial cross-section)
|
|
185
|
+
// The procedural helix generator will sweep this profile around the cylinder
|
|
186
|
+
|
|
187
|
+
// Trapezoidal profile: two parallel edges at crestRad and rootR (parallel to cylinder axis)
|
|
188
|
+
// connected by sloped flanks
|
|
189
|
+
const pts = [
|
|
190
|
+
[-halfPitch, crestRad], // Bottom: inner surface
|
|
191
|
+
[-halfPitch * 0.3, rootR], // Bottom flank to outer surface
|
|
192
|
+
[halfPitch * 0.3, rootR], // Top flank at outer surface
|
|
193
|
+
[halfPitch, crestRad], // Top: inner surface
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
console.log('[ThreadGeometry] Profile polygon points [axial, radius]:', pts);
|
|
197
|
+
return pts; // Return as-is, no need for CCW check in this format
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const applyPlacement = (solid, opts = {}) => {
|
|
201
|
+
if (!solid) return solid;
|
|
202
|
+
const { transform } = opts;
|
|
203
|
+
if (transform && typeof transform.isMatrix4 === "boolean" && transform.isMatrix4) {
|
|
204
|
+
solid.bakeTransform(transform);
|
|
205
|
+
return solid;
|
|
206
|
+
}
|
|
207
|
+
if (transform && (transform.position || transform.rotationEuler || transform.scale || transform.t || transform.rDeg)) {
|
|
208
|
+
solid.bakeTRS({
|
|
209
|
+
t: transform.position || transform.t,
|
|
210
|
+
rDeg: transform.rotationEuler || transform.rDeg,
|
|
211
|
+
s: transform.scale || transform.s,
|
|
212
|
+
});
|
|
213
|
+
return solid;
|
|
214
|
+
}
|
|
215
|
+
const placement = buildPlacementMatrix({
|
|
216
|
+
axis: opts.axis || opts.axisDirection,
|
|
217
|
+
xDirection: opts.xDirection,
|
|
218
|
+
origin: opts.origin,
|
|
219
|
+
});
|
|
220
|
+
solid.bakeTransform(placement);
|
|
221
|
+
return solid;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* ThreadGeometry:
|
|
226
|
+
* - Handles multiple basic thread standards.
|
|
227
|
+
* - Provides diameters, radii, fundamental heights, helix angles, and simple parametric helpers.
|
|
228
|
+
*
|
|
229
|
+
* All results are "basic profile" only – no tolerances/allowances.
|
|
230
|
+
* Units: whatever you pass in (mm for metric, inches for inch threads).
|
|
231
|
+
*/
|
|
232
|
+
export class ThreadGeometry {
|
|
233
|
+
constructor(options) {
|
|
234
|
+
const {
|
|
235
|
+
standard = ThreadStandard.ISO_METRIC,
|
|
236
|
+
nominalDiameter, // diameter at reference plane (major for external)
|
|
237
|
+
pitch, // thread pitch P
|
|
238
|
+
tpi, // alternative for inch systems: threads per inch
|
|
239
|
+
isExternal = true,
|
|
240
|
+
starts = 1,
|
|
241
|
+
// For NPT taper direction: +1 grows diameter with +z, -1 shrinks
|
|
242
|
+
taperDirection = 1,
|
|
243
|
+
} = options || {};
|
|
244
|
+
|
|
245
|
+
if (!nominalDiameter || nominalDiameter <= 0) {
|
|
246
|
+
throw new Error("nominalDiameter must be a positive number.");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let P = pitch;
|
|
250
|
+
if (!P) {
|
|
251
|
+
if ((standard === ThreadStandard.UNIFIED || standard === ThreadStandard.ACME ||
|
|
252
|
+
standard === ThreadStandard.STUB_ACME || standard === ThreadStandard.NPT) &&
|
|
253
|
+
tpi && tpi > 0) {
|
|
254
|
+
P = 1 / tpi;
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error("You must provide pitch (and/or tpi for inch-based standards).");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (P <= 0) {
|
|
260
|
+
throw new Error("pitch (or 1/tpi) must be a positive number.");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.standard = standard;
|
|
264
|
+
this.nominalDiameter = nominalDiameter;
|
|
265
|
+
this.pitch = P;
|
|
266
|
+
this.isExternal = isExternal;
|
|
267
|
+
this.starts = starts || 1;
|
|
268
|
+
this.taperDirection = taperDirection >= 0 ? 1 : -1;
|
|
269
|
+
|
|
270
|
+
// Derived / standard-specific profile params
|
|
271
|
+
const profile = this._computeStandardProfile(standard, P);
|
|
272
|
+
|
|
273
|
+
// Fundamental profile params
|
|
274
|
+
this.flankAngleDeg = profile.flankAngleDeg;
|
|
275
|
+
this.flankAngleRad = profile.flankAngleRad;
|
|
276
|
+
this.halfAngleRad = profile.halfAngleRad;
|
|
277
|
+
this.fundamentalTriangleHeight = profile.H;
|
|
278
|
+
this.effectiveThreadDepth = profile.threadDepth;
|
|
279
|
+
this.crestTruncation = profile.crestTruncation;
|
|
280
|
+
this.rootTruncation = profile.rootTruncation;
|
|
281
|
+
this.roundingRadius = profile.roundingRadius;
|
|
282
|
+
this.roundingHeight = profile.roundingHeight;
|
|
283
|
+
this.isTapered = profile.isTapered;
|
|
284
|
+
this.taperPerLengthOnDiameter = profile.taperPerLengthOnDiameter; // e.g. 1/16 for NPT
|
|
285
|
+
this.taperHalfAngle = profile.taperHalfAngle;
|
|
286
|
+
|
|
287
|
+
// Units hint
|
|
288
|
+
this.units = (standard === ThreadStandard.ISO_METRIC ||
|
|
289
|
+
standard === ThreadStandard.TRAPEZOIDAL_METRIC)
|
|
290
|
+
? "mm"
|
|
291
|
+
: "inch";
|
|
292
|
+
|
|
293
|
+
// Thread thickness at pitch
|
|
294
|
+
// (for symmetric threads this is still P/2 at the pitch line)
|
|
295
|
+
this.threadThicknessAtPitch = P / 2;
|
|
296
|
+
|
|
297
|
+
// Lead & helix angles (base / reference at nominal diameters)
|
|
298
|
+
this.lead = P * this.starts;
|
|
299
|
+
|
|
300
|
+
// For non-tapered: major diameter is just nominal
|
|
301
|
+
// For tapered: treat nominalDiameter as major diameter at z = 0 "gauge" plane
|
|
302
|
+
this.majorDiameter = nominalDiameter;
|
|
303
|
+
|
|
304
|
+
// Basic diameters at reference plane
|
|
305
|
+
const threadDepth = this.effectiveThreadDepth;
|
|
306
|
+
|
|
307
|
+
if (!profile.usesCustomDiameterFormulas) {
|
|
308
|
+
// Symmetric profiles: minor = major - 2*depth, pitch = (major+minor)/2
|
|
309
|
+
this.minorDiameter = this.majorDiameter - 2 * threadDepth;
|
|
310
|
+
this.pitchDiameter = (this.majorDiameter + this.minorDiameter) / 2;
|
|
311
|
+
} else {
|
|
312
|
+
// Whitworth uses pre-derived depth; we still use the same relation
|
|
313
|
+
// unless a different form is needed (here we keep it simple).
|
|
314
|
+
this.minorDiameter = this.majorDiameter - 2 * threadDepth;
|
|
315
|
+
this.pitchDiameter = (this.majorDiameter + this.minorDiameter) / 2;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Radii at reference plane
|
|
319
|
+
this.majorRadius = this.majorDiameter / 2;
|
|
320
|
+
this.minorRadius = this.minorDiameter / 2;
|
|
321
|
+
this.pitchRadius = this.pitchDiameter / 2;
|
|
322
|
+
|
|
323
|
+
// Crest/root interpretation for internal vs external
|
|
324
|
+
this.crestDiameter = isExternal ? this.majorDiameter : this.minorDiameter;
|
|
325
|
+
this.rootDiameter = isExternal ? this.minorDiameter : this.majorDiameter;
|
|
326
|
+
this.crestRadius = this.crestDiameter / 2;
|
|
327
|
+
this.rootRadius = this.rootDiameter / 2;
|
|
328
|
+
|
|
329
|
+
// Helix angles at reference plane
|
|
330
|
+
this.helixAngleAtPitchDiameter = Math.atan(this.lead / (Math.PI * this.pitchDiameter));
|
|
331
|
+
this.helixAngleAtMajorDiameter = Math.atan(this.lead / (Math.PI * this.majorDiameter));
|
|
332
|
+
this.helixAngleAtMinorDiameter = Math.atan(this.lead / (Math.PI * this.minorDiameter));
|
|
333
|
+
|
|
334
|
+
// Radial offsets useful for building 2D profiles around pitch line (cylindrical assumption)
|
|
335
|
+
this.profile = {
|
|
336
|
+
flankAngleRad: this.flankAngleRad,
|
|
337
|
+
halfAngleRad: this.halfAngleRad,
|
|
338
|
+
radialOffsetPitchToCrest: this.crestRadius - this.pitchRadius,
|
|
339
|
+
radialOffsetPitchToRoot: this.rootRadius - this.pitchRadius,
|
|
340
|
+
roundingRadius: this.roundingRadius,
|
|
341
|
+
roundingHeight: this.roundingHeight,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Standard-specific basic profile data.
|
|
347
|
+
* Returns: {
|
|
348
|
+
* flankAngleDeg, flankAngleRad, halfAngleRad,
|
|
349
|
+
* H, threadDepth,
|
|
350
|
+
* crestTruncation, rootTruncation,
|
|
351
|
+
* roundingRadius, roundingHeight,
|
|
352
|
+
* isTapered, taperPerLengthOnDiameter, taperHalfAngle,
|
|
353
|
+
* usesCustomDiameterFormulas
|
|
354
|
+
* }
|
|
355
|
+
*/
|
|
356
|
+
_computeStandardProfile(standard, P) {
|
|
357
|
+
switch (standard) {
|
|
358
|
+
case ThreadStandard.ISO_METRIC:
|
|
359
|
+
case ThreadStandard.UNIFIED: {
|
|
360
|
+
// 60° V-thread, ISO/UTS basic form with truncation:
|
|
361
|
+
// H_sharp = P / (2 * tan(30°))
|
|
362
|
+
const halfAngleRad = 30 * DEG_TO_RAD;
|
|
363
|
+
const flankAngleDeg = 60;
|
|
364
|
+
const flankAngleRad = flankAngleDeg * DEG_TO_RAD;
|
|
365
|
+
const H = (P / 2) / Math.tan(halfAngleRad); // fundamental sharp V height
|
|
366
|
+
const threadDepth = (5 / 8) * H; // radial depth
|
|
367
|
+
const crestTruncation = H / 8;
|
|
368
|
+
const rootTruncation = H / 4;
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
flankAngleDeg,
|
|
372
|
+
flankAngleRad,
|
|
373
|
+
halfAngleRad,
|
|
374
|
+
H,
|
|
375
|
+
threadDepth,
|
|
376
|
+
crestTruncation,
|
|
377
|
+
rootTruncation,
|
|
378
|
+
roundingRadius: 0,
|
|
379
|
+
roundingHeight: 0,
|
|
380
|
+
isTapered: false,
|
|
381
|
+
taperPerLengthOnDiameter: 0,
|
|
382
|
+
taperHalfAngle: 0,
|
|
383
|
+
usesCustomDiameterFormulas: false,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
case ThreadStandard.ACME: {
|
|
388
|
+
// 29° Acme thread:
|
|
389
|
+
// basic height ≈ 0.5 * P, flat crest/root.
|
|
390
|
+
const flankAngleDeg = 29;
|
|
391
|
+
const flankAngleRad = flankAngleDeg * DEG_TO_RAD;
|
|
392
|
+
const halfAngleRad = flankAngleRad / 2;
|
|
393
|
+
const H = 0.5 * P; // basic thread height
|
|
394
|
+
const threadDepth = H; // crest to root (radial)
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
flankAngleDeg,
|
|
398
|
+
flankAngleRad,
|
|
399
|
+
halfAngleRad,
|
|
400
|
+
H,
|
|
401
|
+
threadDepth,
|
|
402
|
+
crestTruncation: 0, // flats, not sharp-V truncation
|
|
403
|
+
rootTruncation: 0,
|
|
404
|
+
roundingRadius: 0,
|
|
405
|
+
roundingHeight: 0,
|
|
406
|
+
isTapered: false,
|
|
407
|
+
taperPerLengthOnDiameter: 0,
|
|
408
|
+
taperHalfAngle: 0,
|
|
409
|
+
usesCustomDiameterFormulas: false,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
case ThreadStandard.STUB_ACME: {
|
|
414
|
+
// Stub Acme: same 29° angle, reduced height.
|
|
415
|
+
// Common basic height ≈ 0.3 * P (simplified).
|
|
416
|
+
const flankAngleDeg = 29;
|
|
417
|
+
const flankAngleRad = flankAngleDeg * DEG_TO_RAD;
|
|
418
|
+
const halfAngleRad = flankAngleRad / 2;
|
|
419
|
+
const H = 0.3 * P;
|
|
420
|
+
const threadDepth = H;
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
flankAngleDeg,
|
|
424
|
+
flankAngleRad,
|
|
425
|
+
halfAngleRad,
|
|
426
|
+
H,
|
|
427
|
+
threadDepth,
|
|
428
|
+
crestTruncation: 0,
|
|
429
|
+
rootTruncation: 0,
|
|
430
|
+
roundingRadius: 0,
|
|
431
|
+
roundingHeight: 0,
|
|
432
|
+
isTapered: false,
|
|
433
|
+
taperPerLengthOnDiameter: 0,
|
|
434
|
+
taperHalfAngle: 0,
|
|
435
|
+
usesCustomDiameterFormulas: false,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
case ThreadStandard.TRAPEZOIDAL_METRIC: {
|
|
440
|
+
// Metric trapezoidal (Tr), 30° included angle.
|
|
441
|
+
// Basic height often treated ~0.5 * P, similar to Acme but 30°.
|
|
442
|
+
const flankAngleDeg = 30;
|
|
443
|
+
const flankAngleRad = flankAngleDeg * DEG_TO_RAD;
|
|
444
|
+
const halfAngleRad = flankAngleRad / 2;
|
|
445
|
+
const H = 0.5 * P;
|
|
446
|
+
const threadDepth = H;
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
flankAngleDeg,
|
|
450
|
+
flankAngleRad,
|
|
451
|
+
halfAngleRad,
|
|
452
|
+
H,
|
|
453
|
+
threadDepth,
|
|
454
|
+
crestTruncation: 0,
|
|
455
|
+
rootTruncation: 0,
|
|
456
|
+
roundingRadius: 0,
|
|
457
|
+
roundingHeight: 0,
|
|
458
|
+
isTapered: false,
|
|
459
|
+
taperPerLengthOnDiameter: 0,
|
|
460
|
+
taperHalfAngle: 0,
|
|
461
|
+
usesCustomDiameterFormulas: false,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
case ThreadStandard.WHITWORTH: {
|
|
466
|
+
// Whitworth:
|
|
467
|
+
// included angle = 55°
|
|
468
|
+
// fundamental triangle height H ≈ 0.96049106 * P
|
|
469
|
+
// actual depth h ≈ 0.64032738 * P
|
|
470
|
+
// crest/root rounding radius r ≈ 0.13732908 * P
|
|
471
|
+
// rounding height e ≈ 0.073917569 * P
|
|
472
|
+
const flankAngleDeg = 55;
|
|
473
|
+
const flankAngleRad = flankAngleDeg * DEG_TO_RAD;
|
|
474
|
+
const halfAngleRad = flankAngleRad / 2;
|
|
475
|
+
|
|
476
|
+
const H = 0.96049106 * P;
|
|
477
|
+
const threadDepth = 0.64032738 * P;
|
|
478
|
+
const roundingRadius = 0.13732908 * P;
|
|
479
|
+
const roundingHeight = 0.073917569 * P;
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
flankAngleDeg,
|
|
483
|
+
flankAngleRad,
|
|
484
|
+
halfAngleRad,
|
|
485
|
+
H,
|
|
486
|
+
threadDepth,
|
|
487
|
+
crestTruncation: 0,
|
|
488
|
+
rootTruncation: 0,
|
|
489
|
+
roundingRadius,
|
|
490
|
+
roundingHeight,
|
|
491
|
+
isTapered: false,
|
|
492
|
+
taperPerLengthOnDiameter: 0,
|
|
493
|
+
taperHalfAngle: 0,
|
|
494
|
+
usesCustomDiameterFormulas: true,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
case ThreadStandard.NPT: {
|
|
499
|
+
// NPT:
|
|
500
|
+
// 60° profile, 1 in 16 taper on DIAMETER.
|
|
501
|
+
// We reuse the 60° V basic form, but mark it as tapered.
|
|
502
|
+
const halfAngleRad = 30 * DEG_TO_RAD;
|
|
503
|
+
const flankAngleDeg = 60;
|
|
504
|
+
const flankAngleRad = flankAngleDeg * DEG_TO_RAD;
|
|
505
|
+
const H = (P / 2) / Math.tan(halfAngleRad);
|
|
506
|
+
const threadDepth = (5 / 8) * H; // reuse V-thread truncation
|
|
507
|
+
|
|
508
|
+
const taperPerLengthOnDiameter = 1 / 16;
|
|
509
|
+
const taperHalfAngle = Math.atan((taperPerLengthOnDiameter / 2));
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
flankAngleDeg,
|
|
513
|
+
flankAngleRad,
|
|
514
|
+
halfAngleRad,
|
|
515
|
+
H,
|
|
516
|
+
threadDepth,
|
|
517
|
+
crestTruncation: H / 8,
|
|
518
|
+
rootTruncation: H / 4,
|
|
519
|
+
roundingRadius: 0,
|
|
520
|
+
roundingHeight: 0,
|
|
521
|
+
isTapered: true,
|
|
522
|
+
taperPerLengthOnDiameter,
|
|
523
|
+
taperHalfAngle,
|
|
524
|
+
usesCustomDiameterFormulas: false,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
default:
|
|
529
|
+
throw new Error(`Unsupported thread standard: ${standard}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Parametric helix at pitch radius, assuming a cylindrical base.
|
|
535
|
+
* For tapered threads, use diametersAtZ() and sweep along a conical surface.
|
|
536
|
+
*
|
|
537
|
+
* x(t) = r * cos(t)
|
|
538
|
+
* y(t) = r * sin(t)
|
|
539
|
+
* z(t) = (lead / (2π)) * t
|
|
540
|
+
*/
|
|
541
|
+
helixAtPitchRadius(t) {
|
|
542
|
+
return {
|
|
543
|
+
x: this.pitchRadius * Math.cos(t),
|
|
544
|
+
y: this.pitchRadius * Math.sin(t),
|
|
545
|
+
z: (this.lead / (2 * Math.PI)) * t,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Build a BREP.Solid representing this thread.
|
|
551
|
+
* @param {object} [options]
|
|
552
|
+
* @param {number} options.length Overall thread length (along local +Z before placement)
|
|
553
|
+
* @param {'symbolic'|'modeled'} [options.mode='symbolic'] Choose simplified cylinder/cone or detailed helical form
|
|
554
|
+
* @param {boolean} [options.modeled] Shortcut for mode='modeled'
|
|
555
|
+
* @param {number} [options.radialOffset=0] Radial offset/clearance applied to crest/root
|
|
556
|
+
* @param {number} [options.resolution=64] Circular resolution for symbolic/core geometry
|
|
557
|
+
* @param {number} [options.segmentsPerTurn=12] Vertical divisions per revolution for modeled threads
|
|
558
|
+
* @param {boolean} [options.includeCore=true] Include the core cylinder/cone (set false to emit ridges only)
|
|
559
|
+
* @param {'crest'|'root'|'pitch'} [options.symbolicRadius='crest'] Which diameter to use for symbolic threads
|
|
560
|
+
* @param {string} [options.name='Thread'] Solid name
|
|
561
|
+
* @param {string} [options.faceName='THREAD'] Face label
|
|
562
|
+
* @param {THREE.Matrix4|object} [options.transform] Optional transform or {position,rotationEuler,scale}
|
|
563
|
+
* @param {THREE.Vector3|number[]} [options.axis=[0,1,0]] Target axis direction (placement)
|
|
564
|
+
* @param {THREE.Vector3|number[]} [options.xDirection=[1,0,0]] Tangential reference direction
|
|
565
|
+
* @param {THREE.Vector3|number[]} [options.origin=[0,0,0]] Placement origin
|
|
566
|
+
*/
|
|
567
|
+
toSolid(options = {}) {
|
|
568
|
+
const length = Number(options.length ?? options.height ?? options.threadLength ?? 0);
|
|
569
|
+
if (!Number.isFinite(length) || length <= 0) {
|
|
570
|
+
throw new Error("ThreadGeometry.toSolid requires a positive length.");
|
|
571
|
+
}
|
|
572
|
+
const modeRaw = String(options.mode || "").toLowerCase();
|
|
573
|
+
const modeled = options.modeled === true || modeRaw === "modeled" || modeRaw === "helical" || modeRaw === "detailed";
|
|
574
|
+
const solid = modeled ? this._buildModeledSolid(length, options) : this._buildSymbolicSolid(length, options);
|
|
575
|
+
const faceName = options.faceName || "THREAD";
|
|
576
|
+
const baseMetadata = {
|
|
577
|
+
thread: {
|
|
578
|
+
standard: this.standard,
|
|
579
|
+
nominalDiameter: this.nominalDiameter,
|
|
580
|
+
pitch: this.pitch,
|
|
581
|
+
isExternal: this.isExternal,
|
|
582
|
+
starts: this.starts,
|
|
583
|
+
isTapered: this.isTapered,
|
|
584
|
+
modeled,
|
|
585
|
+
length,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const applyThreadMetadata = (target, extra = {}) => {
|
|
590
|
+
try {
|
|
591
|
+
solid.setFaceMetadata(target, { ...baseMetadata, ...extra });
|
|
592
|
+
} catch {
|
|
593
|
+
/* best-effort */
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
applyThreadMetadata(faceName);
|
|
599
|
+
|
|
600
|
+
const segments = Array.isArray(solid.threadFaceNames?.segments) ? solid.threadFaceNames.segments : [];
|
|
601
|
+
const caps = solid.threadFaceNames?.caps || null;
|
|
602
|
+
|
|
603
|
+
for (const seg of segments) {
|
|
604
|
+
if (!seg?.name) continue;
|
|
605
|
+
applyThreadMetadata(seg.name, {
|
|
606
|
+
threadFaceRole: seg.role || "edge",
|
|
607
|
+
profileEdgeIndex: seg.edgeIndex ?? null,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (caps?.start?.name) {
|
|
612
|
+
applyThreadMetadata(caps.start.name, {
|
|
613
|
+
threadFaceRole: caps.start.role || "start_cap",
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
if (caps?.end?.name) {
|
|
617
|
+
applyThreadMetadata(caps.end.name, {
|
|
618
|
+
threadFaceRole: caps.end.role || "end_cap",
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
} catch {
|
|
622
|
+
/* metadata best-effort */
|
|
623
|
+
}
|
|
624
|
+
return solid;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
_buildSymbolicSolid(length, options = {}) {
|
|
628
|
+
const name = options.name || "Thread";
|
|
629
|
+
const faceName = options.faceName || "THREAD";
|
|
630
|
+
const radialOffset = Number(options.radialOffset ?? options.clearance ?? 0);
|
|
631
|
+
const res = Math.max(8, Math.floor(Number(options.resolution) || 64));
|
|
632
|
+
const radiusMode = String(options.symbolicRadius || options.symbolicMode || "crest").toLowerCase();
|
|
633
|
+
|
|
634
|
+
const diamAt = (z) => this.diametersAtZ(z);
|
|
635
|
+
const d0 = diamAt(0);
|
|
636
|
+
const d1 = diamAt(length);
|
|
637
|
+
|
|
638
|
+
// For symbolic internal threads, drill to minor diameter and show dashed major diameter rings
|
|
639
|
+
const pickRadius = (diam) => {
|
|
640
|
+
switch (radiusMode) {
|
|
641
|
+
case "root":
|
|
642
|
+
return (this.isExternal ? diam.minor : diam.major) * 0.5;
|
|
643
|
+
case "pitch":
|
|
644
|
+
return diam.pitch * 0.5;
|
|
645
|
+
case "crest":
|
|
646
|
+
default:
|
|
647
|
+
return (this.isExternal ? diam.major : diam.minor) * 0.5;
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// Hole radius: use minor diameter for internal threads regardless of symbolicRadius
|
|
652
|
+
const holeRadius = (diam) => {
|
|
653
|
+
if (!this.isExternal) return (diam.minor || 0) * 0.5;
|
|
654
|
+
return pickRadius(diam);
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
let r0 = Math.max(EPS, holeRadius(d0) + radialOffset);
|
|
658
|
+
let r1 = Math.max(EPS, holeRadius(d1) + radialOffset);
|
|
659
|
+
if (!this.isExternal) {
|
|
660
|
+
const overlap = computeInternalOverlap(this);
|
|
661
|
+
r0 = Math.max(EPS, r0 - overlap);
|
|
662
|
+
r1 = Math.max(EPS, r1 - overlap);
|
|
663
|
+
console.log('[ThreadGeometry] Applying internal overlap to symbolic thread:', { overlap, r0, r1, radialOffset });
|
|
664
|
+
}
|
|
665
|
+
const manifold = Manifold.cylinder(length, r0, r1, res, false);
|
|
666
|
+
const solid = manifoldToSolid(manifold, name, faceName);
|
|
667
|
+
|
|
668
|
+
// Add centerline through the symbolic thread (matches minor diameter cylinder axis)
|
|
669
|
+
solid.addAuxEdge(`${faceName}:CENTERLINE`, [
|
|
670
|
+
[0, 0, 0],
|
|
671
|
+
[0, 0, length],
|
|
672
|
+
], { materialKey: 'OVERLAY' });
|
|
673
|
+
|
|
674
|
+
if (!this.isExternal) {
|
|
675
|
+
// Rings are attached to the same z planes as the minor cylinder (start/end)
|
|
676
|
+
const majorR0 = Math.max(EPS, d0.major * 0.5 + radialOffset);
|
|
677
|
+
const majorR1 = Math.max(EPS, d1.major * 0.5 + radialOffset);
|
|
678
|
+
const edgeRes = Math.max(24, res);
|
|
679
|
+
const makeCircle = (r, z) => {
|
|
680
|
+
const pts = [];
|
|
681
|
+
for (let i = 0; i <= edgeRes; i++) {
|
|
682
|
+
const a = (i / edgeRes) * Math.PI * 2;
|
|
683
|
+
pts.push([r * Math.cos(a), r * Math.sin(a), z]);
|
|
684
|
+
}
|
|
685
|
+
return pts;
|
|
686
|
+
};
|
|
687
|
+
// Use the exact start/end planes of the minor-diameter cylinder: z=0 and z=length
|
|
688
|
+
solid.addAuxEdge(`${faceName}:MAJOR_RING_START`, makeCircle(majorR0, 0), { closedLoop: true, materialKey: 'THREAD_SYMBOLIC_MAJOR' });
|
|
689
|
+
solid.addAuxEdge(`${faceName}:MAJOR_RING_END`, makeCircle(majorR1, length), { closedLoop: true, materialKey: 'THREAD_SYMBOLIC_MAJOR' });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
applyPlacement(solid, options);
|
|
693
|
+
return solid;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
_buildModeledSolid(length, options = {}) {
|
|
697
|
+
const name = options.name || "Thread";
|
|
698
|
+
const faceName = options.faceName || "THREAD";
|
|
699
|
+
const radialOffset = Number(options.radialOffset ?? options.clearance ?? 0);
|
|
700
|
+
const includeCore = options.includeCore !== false;
|
|
701
|
+
const res = Math.max(8, Math.floor(Number(options.resolution) || 64));
|
|
702
|
+
const segmentsPerTurn = Math.max(4, Math.floor(Number(options.segmentsPerTurn ?? options.divisionsPerTurn ?? 12)));
|
|
703
|
+
const turns = Math.max(EPS, length / Math.max(this.lead, EPS));
|
|
704
|
+
const nDiv = Math.max(1, Math.round(turns * segmentsPerTurn));
|
|
705
|
+
|
|
706
|
+
console.log('[ThreadGeometry] Building modeled solid procedurally with triangles:', {
|
|
707
|
+
length,
|
|
708
|
+
turns,
|
|
709
|
+
nDiv,
|
|
710
|
+
segmentsPerTurn,
|
|
711
|
+
lead: this.lead,
|
|
712
|
+
pitch: this.pitch,
|
|
713
|
+
isExternal: this.isExternal,
|
|
714
|
+
majorDiameter: this.majorDiameter,
|
|
715
|
+
minorDiameter: this.minorDiameter,
|
|
716
|
+
effectiveThreadDepth: this.effectiveThreadDepth,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// Get the 2D profile polygon (trapezoidal cross-section of thread tooth)
|
|
720
|
+
const profilePts = buildThreadProfilePolygon(this, radialOffset);
|
|
721
|
+
console.log('[ThreadGeometry] Thread profile (side view):', profilePts);
|
|
722
|
+
console.log('[ThreadGeometry] Profile point 0:', profilePts[0]);
|
|
723
|
+
|
|
724
|
+
// Build thread geometry procedurally by sweeping profile along helix
|
|
725
|
+
const threadSolid = new Solid({ name, faceName });
|
|
726
|
+
|
|
727
|
+
// Profile has points in [axial_offset, radius] format
|
|
728
|
+
// We'll sweep this profile around the cylinder axis along a helical path
|
|
729
|
+
const numProfilePts = profilePts.length;
|
|
730
|
+
|
|
731
|
+
// Give each profile edge its own face label so swept surfaces stay distinct
|
|
732
|
+
const segmentDescriptors = numProfilePts === 4
|
|
733
|
+
? [
|
|
734
|
+
{ name: `${faceName}:FLANK_A`, role: "flank", edgeIndex: 0 },
|
|
735
|
+
{ name: `${faceName}:ROOT`, role: "root", edgeIndex: 1 },
|
|
736
|
+
{ name: `${faceName}:FLANK_B`, role: "flank", edgeIndex: 2 },
|
|
737
|
+
{ name: `${faceName}:CREST`, role: "crest", edgeIndex: 3 },
|
|
738
|
+
]
|
|
739
|
+
: profilePts.map((_, idx) => ({
|
|
740
|
+
name: `${faceName}:EDGE_${idx}`,
|
|
741
|
+
role: "edge",
|
|
742
|
+
edgeIndex: idx,
|
|
743
|
+
}));
|
|
744
|
+
|
|
745
|
+
const capDescriptors = {
|
|
746
|
+
start: { name: `${faceName}:CAP_START`, role: "start_cap" },
|
|
747
|
+
end: { name: `${faceName}:CAP_END`, role: "end_cap" },
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
// Store profile vertices at each step for end capping
|
|
751
|
+
const profileRings = [];
|
|
752
|
+
|
|
753
|
+
for (let i = 0; i <= nDiv; i++) { // Note: <= to include final position
|
|
754
|
+
const t = i / nDiv;
|
|
755
|
+
const angle = t * 360 * turns; // degrees
|
|
756
|
+
const z = t * length;
|
|
757
|
+
const rad = angle * Math.PI / 180;
|
|
758
|
+
|
|
759
|
+
// Transform all profile points to 3D at this angle
|
|
760
|
+
const ring = [];
|
|
761
|
+
for (let j = 0; j < numProfilePts; j++) {
|
|
762
|
+
const [axial, r] = profilePts[j];
|
|
763
|
+
ring.push([
|
|
764
|
+
r * Math.cos(rad),
|
|
765
|
+
r * Math.sin(rad),
|
|
766
|
+
z + axial
|
|
767
|
+
]);
|
|
768
|
+
}
|
|
769
|
+
profileRings.push(ring);
|
|
770
|
+
|
|
771
|
+
// Create quads between this ring and previous ring
|
|
772
|
+
if (i > 0) {
|
|
773
|
+
const prevRing = profileRings[i - 1];
|
|
774
|
+
const currRing = profileRings[i];
|
|
775
|
+
|
|
776
|
+
for (let j = 0; j < numProfilePts; j++) {
|
|
777
|
+
const j_next = (j + 1) % numProfilePts;
|
|
778
|
+
|
|
779
|
+
const p0 = prevRing[j];
|
|
780
|
+
const p1 = prevRing[j_next];
|
|
781
|
+
const p2 = currRing[j];
|
|
782
|
+
const p3 = currRing[j_next];
|
|
783
|
+
const quadFaceName = segmentDescriptors[j]?.name || `${faceName}:EDGE_${j}`;
|
|
784
|
+
|
|
785
|
+
// Create two triangles for this quad (CCW winding)
|
|
786
|
+
threadSolid.addTriangle(quadFaceName, p0, p1, p2);
|
|
787
|
+
threadSolid.addTriangle(quadFaceName, p1, p3, p2);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Add end caps to close the geometry
|
|
793
|
+
// The profile is a quadrilateral, so we need to triangulate it properly
|
|
794
|
+
// Start cap (at i=0) - fan triangulation from first vertex
|
|
795
|
+
const startRing = profileRings[0];
|
|
796
|
+
for (let j = 1; j < numProfilePts - 1; j++) {
|
|
797
|
+
threadSolid.addTriangle(capDescriptors.start.name, startRing[0], startRing[j + 1], startRing[j]);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// End cap (at i=nDiv) - fan triangulation from first vertex (opposite winding)
|
|
801
|
+
const endRing = profileRings[nDiv];
|
|
802
|
+
for (let j = 1; j < numProfilePts - 1; j++) {
|
|
803
|
+
threadSolid.addTriangle(capDescriptors.end.name, endRing[0], endRing[j], endRing[j + 1]);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const numTriangles = threadSolid._triVerts ? threadSolid._triVerts.length / 3 : 0;
|
|
807
|
+
console.log('[ThreadGeometry] Generated', numTriangles, 'triangles procedurally (including end caps)');
|
|
808
|
+
|
|
809
|
+
const faceIdToName = new Map(threadSolid._idToFaceName);
|
|
810
|
+
const threadFaceNames = { segments: segmentDescriptors, caps: capDescriptors };
|
|
811
|
+
|
|
812
|
+
let manifold = threadSolid._manifoldize();
|
|
813
|
+
if (!manifold) {
|
|
814
|
+
console.error('[ThreadGeometry] Failed to manifoldize procedurally generated thread');
|
|
815
|
+
return threadSolid;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (this.isTapered && Math.abs(this.taperPerLengthOnDiameter) > EPS) {
|
|
819
|
+
const radialDeltaPerLen = 0.5 * this.taperPerLengthOnDiameter * this.taperDirection;
|
|
820
|
+
const warped = manifold.warp((vert) => {
|
|
821
|
+
const z = vert[2];
|
|
822
|
+
const deltaR = radialDeltaPerLen * z;
|
|
823
|
+
const r = Math.hypot(vert[0], vert[1]);
|
|
824
|
+
if (r > EPS) {
|
|
825
|
+
const s = (r + deltaR) / r;
|
|
826
|
+
vert[0] *= s;
|
|
827
|
+
vert[1] *= s;
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
safeDelete(manifold);
|
|
831
|
+
manifold = warped;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (this.starts && this.starts > 1) {
|
|
835
|
+
const base = manifold;
|
|
836
|
+
let combined = base;
|
|
837
|
+
for (let k = 1; k < this.starts; k++) {
|
|
838
|
+
const angle = (360 * k) / this.starts;
|
|
839
|
+
const rotated = base.rotate(0, 0, angle);
|
|
840
|
+
const next = combined.add(rotated);
|
|
841
|
+
safeDelete(rotated);
|
|
842
|
+
if (combined !== base) safeDelete(combined);
|
|
843
|
+
combined = next;
|
|
844
|
+
}
|
|
845
|
+
if (combined !== base) safeDelete(base);
|
|
846
|
+
manifold = combined;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (includeCore) {
|
|
850
|
+
const d0 = this.diametersAtZ(0);
|
|
851
|
+
const d1 = this.diametersAtZ(length);
|
|
852
|
+
// For internal threads, the core needs to be slightly smaller than crest radius
|
|
853
|
+
// to avoid overlapping geometry. Use 95% of the minor diameter.
|
|
854
|
+
const coreR0 = Math.max(
|
|
855
|
+
EPS,
|
|
856
|
+
(d0.minor * 0.5 + radialOffset) * 0.95,
|
|
857
|
+
);
|
|
858
|
+
const coreR1 = Math.max(
|
|
859
|
+
EPS,
|
|
860
|
+
(d1.minor * 0.5 + radialOffset) * 0.95,
|
|
861
|
+
);
|
|
862
|
+
console.log('[ThreadGeometry] Adding core cylinder:', {
|
|
863
|
+
isExternal: this.isExternal,
|
|
864
|
+
coreR0,
|
|
865
|
+
coreR1,
|
|
866
|
+
minorDiameter0: d0.minor,
|
|
867
|
+
majorDiameter0: d0.major,
|
|
868
|
+
crestRadius: this.crestRadius,
|
|
869
|
+
});
|
|
870
|
+
try {
|
|
871
|
+
const core = Manifold.cylinder(length, coreR0, coreR1, res, false);
|
|
872
|
+
const merged = manifold.add(core);
|
|
873
|
+
safeDelete(core);
|
|
874
|
+
safeDelete(manifold);
|
|
875
|
+
manifold = merged;
|
|
876
|
+
console.log('[ThreadGeometry] Core added successfully');
|
|
877
|
+
} catch (err) {
|
|
878
|
+
console.warn('[ThreadGeometry] Failed to add core, continuing with thread ridges only:', err.message);
|
|
879
|
+
// Continue with just the thread ridges if core addition fails
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const solid = manifoldToSolid(manifold, name, faceName, faceIdToName);
|
|
884
|
+
applyPlacement(solid, options);
|
|
885
|
+
solid.threadFaceNames = threadFaceNames;
|
|
886
|
+
return solid;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* For tapered threads (e.g. NPT), get diameters at axial position z.
|
|
891
|
+
* z > 0 moves in +taperDirection along axis.
|
|
892
|
+
*/
|
|
893
|
+
diametersAtZ(z) {
|
|
894
|
+
if (!this.isTapered || z === 0) {
|
|
895
|
+
return {
|
|
896
|
+
major: this.majorDiameter,
|
|
897
|
+
pitch: this.pitchDiameter,
|
|
898
|
+
minor: this.minorDiameter,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const deltaD = this.taperPerLengthOnDiameter * z * this.taperDirection;
|
|
902
|
+
return {
|
|
903
|
+
major: this.majorDiameter + deltaD,
|
|
904
|
+
pitch: this.pitchDiameter + deltaD,
|
|
905
|
+
minor: this.minorDiameter + deltaD,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* ISO Metric: from designation "M10x1.5"
|
|
911
|
+
*/
|
|
912
|
+
static fromMetricDesignation(designation, opts = {}) {
|
|
913
|
+
if (typeof designation !== "string") {
|
|
914
|
+
throw new Error("designation must be a string like 'M10x1.5'.");
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const match = designation
|
|
918
|
+
.trim()
|
|
919
|
+
.toUpperCase()
|
|
920
|
+
.match(/^M(\d+(?:\.\d+)?)[X×](\d+(?:\.\d+)?)$/);
|
|
921
|
+
|
|
922
|
+
if (!match) {
|
|
923
|
+
throw new Error("Invalid metric designation. Expected format like 'M10x1.5'.");
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const nominalDiameter = parseFloat(match[1]);
|
|
927
|
+
const pitch = parseFloat(match[2]);
|
|
928
|
+
|
|
929
|
+
return new ThreadGeometry({
|
|
930
|
+
standard: ThreadStandard.ISO_METRIC,
|
|
931
|
+
nominalDiameter,
|
|
932
|
+
pitch,
|
|
933
|
+
...opts,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Metric trapezoidal: from designation "Tr60x9" (single start only).
|
|
939
|
+
*/
|
|
940
|
+
static fromTrapezoidalDesignation(designation, opts = {}) {
|
|
941
|
+
if (typeof designation !== "string") {
|
|
942
|
+
throw new Error("designation must be a string like 'Tr60x9'.");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const match = designation
|
|
946
|
+
.trim()
|
|
947
|
+
.toUpperCase()
|
|
948
|
+
.match(/^TR(\d+(?:\.\d+)?)[X×](\d+(?:\.\d+)?)$/);
|
|
949
|
+
|
|
950
|
+
if (!match) {
|
|
951
|
+
throw new Error("Invalid trapezoidal designation. Expected format like 'Tr60x9'.");
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const nominalDiameter = parseFloat(match[1]);
|
|
955
|
+
const pitch = parseFloat(match[2]);
|
|
956
|
+
|
|
957
|
+
return new ThreadGeometry({
|
|
958
|
+
standard: ThreadStandard.TRAPEZOIDAL_METRIC,
|
|
959
|
+
nominalDiameter,
|
|
960
|
+
pitch,
|
|
961
|
+
...opts,
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Unified (UNC/UNF/UNEF) – inch, from diameter and TPI.
|
|
967
|
+
*/
|
|
968
|
+
static fromUnified(nominalDiameterInch, tpi, opts = {}) {
|
|
969
|
+
if (!nominalDiameterInch || nominalDiameterInch <= 0) {
|
|
970
|
+
throw new Error("nominalDiameterInch must be a positive number.");
|
|
971
|
+
}
|
|
972
|
+
if (!tpi || tpi <= 0) {
|
|
973
|
+
throw new Error("tpi must be a positive number.");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return new ThreadGeometry({
|
|
977
|
+
standard: ThreadStandard.UNIFIED,
|
|
978
|
+
nominalDiameter: nominalDiameterInch,
|
|
979
|
+
tpi,
|
|
980
|
+
...opts,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* General Acme: inch, from diameter and TPI.
|
|
986
|
+
*/
|
|
987
|
+
static fromAcme(nominalDiameterInch, tpi, opts = {}) {
|
|
988
|
+
if (!nominalDiameterInch || nominalDiameterInch <= 0) {
|
|
989
|
+
throw new Error("nominalDiameterInch must be a positive number.");
|
|
990
|
+
}
|
|
991
|
+
if (!tpi || tpi <= 0) {
|
|
992
|
+
throw new Error("tpi must be a positive number.");
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return new ThreadGeometry({
|
|
996
|
+
standard: ThreadStandard.ACME,
|
|
997
|
+
nominalDiameter: nominalDiameterInch,
|
|
998
|
+
tpi,
|
|
999
|
+
...opts,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Stub Acme: inch, from diameter and TPI.
|
|
1005
|
+
*/
|
|
1006
|
+
static fromStubAcme(nominalDiameterInch, tpi, opts = {}) {
|
|
1007
|
+
if (!nominalDiameterInch || nominalDiameterInch <= 0) {
|
|
1008
|
+
throw new Error("nominalDiameterInch must be a positive number.");
|
|
1009
|
+
}
|
|
1010
|
+
if (!tpi || tpi <= 0) {
|
|
1011
|
+
throw new Error("tpi must be a positive number.");
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return new ThreadGeometry({
|
|
1015
|
+
standard: ThreadStandard.STUB_ACME,
|
|
1016
|
+
nominalDiameter: nominalDiameterInch,
|
|
1017
|
+
tpi,
|
|
1018
|
+
...opts,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Whitworth: inch, from diameter and pitch (or TPI via 1/tpi).
|
|
1024
|
+
*/
|
|
1025
|
+
static fromWhitworth(nominalDiameterInch, pitchOrTpi, opts = {}) {
|
|
1026
|
+
if (!nominalDiameterInch || nominalDiameterInch <= 0) {
|
|
1027
|
+
throw new Error("nominalDiameterInch must be a positive number.");
|
|
1028
|
+
}
|
|
1029
|
+
if (!pitchOrTpi || pitchOrTpi <= 0) {
|
|
1030
|
+
throw new Error("pitchOrTpi must be a positive number.");
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
let pitch = pitchOrTpi;
|
|
1034
|
+
if (pitchOrTpi < 1) {
|
|
1035
|
+
// assume given as pitch already
|
|
1036
|
+
pitch = pitchOrTpi;
|
|
1037
|
+
} else {
|
|
1038
|
+
// if user gives TPI, treat as TPI
|
|
1039
|
+
pitch = 1 / pitchOrTpi;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return new ThreadGeometry({
|
|
1043
|
+
standard: ThreadStandard.WHITWORTH,
|
|
1044
|
+
nominalDiameter: nominalDiameterInch,
|
|
1045
|
+
pitch,
|
|
1046
|
+
...opts,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* NPT: inch, approximate basic geometry from a reference diameter + TPI.
|
|
1052
|
+
* nominalDiameterInch here should be the major diameter at z = 0 plane
|
|
1053
|
+
* that you want to treat as your modeling reference.
|
|
1054
|
+
*/
|
|
1055
|
+
static fromNPT(nominalDiameterInch, tpi, opts = {}) {
|
|
1056
|
+
if (!nominalDiameterInch || nominalDiameterInch <= 0) {
|
|
1057
|
+
throw new Error("nominalDiameterInch must be a positive number.");
|
|
1058
|
+
}
|
|
1059
|
+
if (!tpi || tpi <= 0) {
|
|
1060
|
+
throw new Error("tpi must be a positive number.");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return new ThreadGeometry({
|
|
1064
|
+
standard: ThreadStandard.NPT,
|
|
1065
|
+
nominalDiameter: nominalDiameterInch,
|
|
1066
|
+
tpi,
|
|
1067
|
+
...opts,
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Plain JSON snapshot of all geometric data.
|
|
1073
|
+
*/
|
|
1074
|
+
toObject() {
|
|
1075
|
+
return {
|
|
1076
|
+
standard: this.standard,
|
|
1077
|
+
nominalDiameter: this.nominalDiameter,
|
|
1078
|
+
pitch: this.pitch,
|
|
1079
|
+
isExternal: this.isExternal,
|
|
1080
|
+
starts: this.starts,
|
|
1081
|
+
units: this.units,
|
|
1082
|
+
|
|
1083
|
+
flankAngleDeg: this.flankAngleDeg,
|
|
1084
|
+
flankAngleRad: this.flankAngleRad,
|
|
1085
|
+
halfAngleRad: this.halfAngleRad,
|
|
1086
|
+
|
|
1087
|
+
fundamentalTriangleHeight: this.fundamentalTriangleHeight,
|
|
1088
|
+
effectiveThreadDepth: this.effectiveThreadDepth,
|
|
1089
|
+
crestTruncation: this.crestTruncation,
|
|
1090
|
+
rootTruncation: this.rootTruncation,
|
|
1091
|
+
roundingRadius: this.roundingRadius,
|
|
1092
|
+
roundingHeight: this.roundingHeight,
|
|
1093
|
+
|
|
1094
|
+
majorDiameter: this.majorDiameter,
|
|
1095
|
+
pitchDiameter: this.pitchDiameter,
|
|
1096
|
+
minorDiameter: this.minorDiameter,
|
|
1097
|
+
|
|
1098
|
+
majorRadius: this.majorRadius,
|
|
1099
|
+
pitchRadius: this.pitchRadius,
|
|
1100
|
+
minorRadius: this.minorRadius,
|
|
1101
|
+
|
|
1102
|
+
crestDiameter: this.crestDiameter,
|
|
1103
|
+
rootDiameter: this.rootDiameter,
|
|
1104
|
+
crestRadius: this.crestRadius,
|
|
1105
|
+
rootRadius: this.rootRadius,
|
|
1106
|
+
|
|
1107
|
+
lead: this.lead,
|
|
1108
|
+
helixAngleAtPitchDiameter: this.helixAngleAtPitchDiameter,
|
|
1109
|
+
helixAngleAtMajorDiameter: this.helixAngleAtMajorDiameter,
|
|
1110
|
+
helixAngleAtMinorDiameter: this.helixAngleAtMinorDiameter,
|
|
1111
|
+
|
|
1112
|
+
isTapered: this.isTapered,
|
|
1113
|
+
taperPerLengthOnDiameter: this.taperPerLengthOnDiameter,
|
|
1114
|
+
taperHalfAngle: this.taperHalfAngle,
|
|
1115
|
+
taperDirection: this.taperDirection,
|
|
1116
|
+
|
|
1117
|
+
profile: { ...this.profile },
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
}
|