From 97088d6f3c0656c6d9984a06b045facbca0501df Mon Sep 17 00:00:00 2001 From: gumyr Date: Sat, 13 Jan 2024 11:06:09 -0500 Subject: [PATCH] Fix import of invalid triangles Issue #472 --- src/build123d/mesher.py | 15 +++++++++++---- tests/cyl_w_rect_hole.stl | Bin 0 -> 39084 bytes tests/test_mesher.py | 11 +++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/cyl_w_rect_hole.stl diff --git a/src/build123d/mesher.py b/src/build123d/mesher.py index 6e4d7b35..ce3e2402 100644 --- a/src/build123d/mesher.py +++ b/src/build123d/mesher.py @@ -82,6 +82,7 @@ # pylint: disable=no-name-in-module, import-error import copy import ctypes +import itertools import math import os import sys @@ -96,9 +97,12 @@ BRepBuilderAPI_MakeSolid, BRepBuilderAPI_Sewing, ) +from OCP.BRepGProp import BRepGProp from OCP.BRepMesh import BRepMesh_IncrementalMesh from OCP.gp import gp_Pnt import OCP.TopAbs as ta +from OCP.GProp import GProp_GProps +from OCP.ShapeFix import ShapeFix_Shape from OCP.TopAbs import TopAbs_ShapeEnum from OCP.TopExp import TopExp_Explorer from OCP.TopoDS import TopoDS_Compound @@ -106,8 +110,8 @@ from py_lib3mf import Lib3MF from build123d.build_enums import MeshType, Unit -from build123d.geometry import Color, TOLERANCE -from build123d.topology import Compound, Shape, Shell, Solid, downcast +from build123d.geometry import Color, TOLERANCE, Vector +from build123d.topology import Compound, Face, Shape, Shell, Solid, downcast class Mesher: @@ -455,8 +459,11 @@ def _get_shape(self, mesh_3mf: Lib3MF.MeshObject) -> Shape: # Create the triangular face using the polygon polygon_builder = BRepBuilderAPI_MakePolygon(*ocp_vertices, Close=True) face_builder = BRepBuilderAPI_MakeFace(polygon_builder.Wire()) - # Add new Face to Shell - shell_builder.Add(face_builder.Face()) + facet = face_builder.Face() + facet_properties = GProp_GProps() + BRepGProp.SurfaceProperties_s(facet, facet_properties) + if facet_properties.Mass() != 0: # Area==0 is an invalid facet + shell_builder.Add(facet) # Create the Shell(s) - if the object has voids there will be multiple shell_builder.Perform() diff --git a/tests/cyl_w_rect_hole.stl b/tests/cyl_w_rect_hole.stl new file mode 100644 index 0000000000000000000000000000000000000000..11d198ff1527b85542040df519f707442987acc8 GIT binary patch literal 39084 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 HmOJr(6?%bH literal 0 HcmV?d00001 diff --git a/tests/test_mesher.py b/tests/test_mesher.py index 49bc42a7..f195ac3a 100644 --- a/tests/test_mesher.py +++ b/tests/test_mesher.py @@ -190,5 +190,16 @@ def test_hollow_import(self): self.assertTrue(stl[0].is_valid()) +class TestImportDegenerateTriangles(unittest.TestCase): + def test_degenerate_import(self): + """Some STLs may contain 'triangles' where all three points are on a line""" + importer = Mesher() + stl = importer.read("cyl_w_rect_hole.stl")[0] + self.assertEqual(type(stl), Solid) + self.assertTrue(stl.is_manifold) + self.assertTrue(stl.is_valid()) + self.assertEqual(sum(f.area == 0 for f in stl.faces()), 0) + + if __name__ == "__main__": unittest.main()