mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Merge pull request #771 from Jojain/dev
Added thicken and new split tools
This commit is contained in:
commit
a353d3e776
2 changed files with 91 additions and 33 deletions
|
|
@ -366,6 +366,8 @@ geom_LUT_EDGE: Dict[ga.GeomAbs_CurveType, GeomType] = {
|
||||||
|
|
||||||
Shapes = Literal["Vertex", "Edge", "Wire", "Face", "Shell", "Solid", "Compound"]
|
Shapes = Literal["Vertex", "Edge", "Wire", "Face", "Shell", "Solid", "Compound"]
|
||||||
|
|
||||||
|
TrimmingTool = Union[Plane, "Shell", "Face"]
|
||||||
|
|
||||||
|
|
||||||
def tuplify(obj: Any, dim: int) -> tuple:
|
def tuplify(obj: Any, dim: int) -> tuple:
|
||||||
"""Create a size tuple"""
|
"""Create a size tuple"""
|
||||||
|
|
@ -2775,7 +2777,7 @@ class Shape(NodeMixin):
|
||||||
|
|
||||||
return ShapeList([Face(face) for face in faces])
|
return ShapeList([Face(face) for face in faces])
|
||||||
|
|
||||||
def split(self, surface: Union[Plane, Face], keep: Keep = Keep.TOP) -> Self:
|
def split(self, tool: TrimmingTool, keep: Keep = Keep.TOP) -> Self:
|
||||||
"""split
|
"""split
|
||||||
|
|
||||||
Split this shape by the provided plane or face.
|
Split this shape by the provided plane or face.
|
||||||
|
|
@ -2791,13 +2793,11 @@ class Shape(NodeMixin):
|
||||||
shape_list.Append(self.wrapped)
|
shape_list.Append(self.wrapped)
|
||||||
|
|
||||||
# Define the splitting tool
|
# Define the splitting tool
|
||||||
tool = (
|
trim_tool = (
|
||||||
Face.make_plane(surface).wrapped
|
Face.make_plane(tool).wrapped if isinstance(tool, Plane) else tool.wrapped
|
||||||
if isinstance(surface, Plane)
|
|
||||||
else surface.wrapped
|
|
||||||
)
|
)
|
||||||
tool_list = TopTools_ListOfShape()
|
tool_list = TopTools_ListOfShape()
|
||||||
tool_list.Append(tool)
|
tool_list.Append(trim_tool)
|
||||||
|
|
||||||
# Create the splitter algorithm
|
# Create the splitter algorithm
|
||||||
splitter = BRepAlgoAPI_Splitter()
|
splitter = BRepAlgoAPI_Splitter()
|
||||||
|
|
@ -2811,13 +2811,13 @@ class Shape(NodeMixin):
|
||||||
|
|
||||||
result = Compound(downcast(splitter.Shape())).unwrap(fully=False)
|
result = Compound(downcast(splitter.Shape())).unwrap(fully=False)
|
||||||
if keep != Keep.BOTH:
|
if keep != Keep.BOTH:
|
||||||
if not isinstance(surface, Plane):
|
if not isinstance(tool, Plane):
|
||||||
# Create solids from the surfaces for sorting
|
# Create solids from the surfaces for sorting
|
||||||
surface_up = surface.thicken(0.1)
|
surface_up = tool.thicken(0.1)
|
||||||
tops, bottoms = [], []
|
tops, bottoms = [], []
|
||||||
for part in result:
|
for part in result:
|
||||||
if isinstance(surface, Plane):
|
if isinstance(tool, Plane):
|
||||||
is_up = surface.to_local_coords(part).center().Z >= 0
|
is_up = tool.to_local_coords(part).center().Z >= 0
|
||||||
else:
|
else:
|
||||||
is_up = surface_up.intersect(part).volume >= TOLERANCE
|
is_up = surface_up.intersect(part).volume >= TOLERANCE
|
||||||
(tops if is_up else bottoms).append(part)
|
(tops if is_up else bottoms).append(part)
|
||||||
|
|
@ -6490,7 +6490,9 @@ class Face(Shape):
|
||||||
and 1 - abs(plane.z_dir.dot(Vector(normal))) < TOLERANCE
|
and 1 - abs(plane.z_dir.dot(Vector(normal))) < TOLERANCE
|
||||||
)
|
)
|
||||||
|
|
||||||
def thicken(self, depth: float, normal_override: VectorLike = None) -> Solid:
|
def thicken(
|
||||||
|
self, depth: float, normal_override: Optional[VectorLike] = None
|
||||||
|
) -> Solid:
|
||||||
"""Thicken Face
|
"""Thicken Face
|
||||||
|
|
||||||
Create a solid from a potentially non planar face by thickening along the normals.
|
Create a solid from a potentially non planar face by thickening along the normals.
|
||||||
|
|
@ -6520,27 +6522,7 @@ class Face(Shape):
|
||||||
if face_normal.dot(Vector(normal_override).normalized()) < 0:
|
if face_normal.dot(Vector(normal_override).normalized()) < 0:
|
||||||
adjusted_depth = -depth
|
adjusted_depth = -depth
|
||||||
|
|
||||||
solid = BRepOffset_MakeOffset()
|
return _thicken(self.wrapped, adjusted_depth)
|
||||||
solid.Initialize(
|
|
||||||
self.wrapped,
|
|
||||||
Offset=adjusted_depth,
|
|
||||||
Tol=1.0e-5,
|
|
||||||
Mode=BRepOffset_Skin,
|
|
||||||
# BRepOffset_RectoVerso - which describes the offset of a given surface shell along both
|
|
||||||
# sides of the surface but doesn't seem to work
|
|
||||||
Intersection=True,
|
|
||||||
SelfInter=False,
|
|
||||||
Join=GeomAbs_Intersection, # Could be GeomAbs_Arc,GeomAbs_Tangent,GeomAbs_Intersection
|
|
||||||
Thickening=True,
|
|
||||||
RemoveIntEdges=True,
|
|
||||||
)
|
|
||||||
solid.MakeOffsetShape()
|
|
||||||
try:
|
|
||||||
result = Solid(solid.Shape())
|
|
||||||
except StdFail_NotDone as err:
|
|
||||||
raise RuntimeError("Error applying thicken to given Face") from err
|
|
||||||
|
|
||||||
return result.clean()
|
|
||||||
|
|
||||||
def project_to_shape(
|
def project_to_shape(
|
||||||
self, target_object: Shape, direction: VectorLike, taper: float = 0
|
self, target_object: Shape, direction: VectorLike, taper: float = 0
|
||||||
|
|
@ -7006,6 +6988,22 @@ class Shell(Shape):
|
||||||
"""
|
"""
|
||||||
return cls(_make_loft(objs, False, ruled))
|
return cls(_make_loft(objs, False, ruled))
|
||||||
|
|
||||||
|
def thicken(self, depth: float) -> Solid:
|
||||||
|
"""Thicken Shell
|
||||||
|
|
||||||
|
Create a solid from a shell by thickening along the normals.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
depth (float): Amount to thicken face(s), can be positive or negative.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: Opencascade internal failures
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Solid: The resulting Solid object
|
||||||
|
"""
|
||||||
|
return _thicken(self.wrapped, depth)
|
||||||
|
|
||||||
|
|
||||||
class Solid(Mixin3D, Shape):
|
class Solid(Mixin3D, Shape):
|
||||||
"""A Solid in build123d represents a three-dimensional solid geometry
|
"""A Solid in build123d represents a three-dimensional solid geometry
|
||||||
|
|
@ -8867,6 +8865,30 @@ class Joint(ABC):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def _thicken(obj: TopoDS_Shape, depth: float):
|
||||||
|
solid = BRepOffset_MakeOffset()
|
||||||
|
solid.Initialize(
|
||||||
|
obj,
|
||||||
|
Offset=depth,
|
||||||
|
Tol=1.0e-5,
|
||||||
|
Mode=BRepOffset_Skin,
|
||||||
|
# BRepOffset_RectoVerso - which describes the offset of a given surface shell along both
|
||||||
|
# sides of the surface but doesn't seem to work
|
||||||
|
Intersection=True,
|
||||||
|
SelfInter=False,
|
||||||
|
Join=GeomAbs_Intersection, # Could be GeomAbs_Arc,GeomAbs_Tangent,GeomAbs_Intersection
|
||||||
|
Thickening=True,
|
||||||
|
RemoveIntEdges=True,
|
||||||
|
)
|
||||||
|
solid.MakeOffsetShape()
|
||||||
|
try:
|
||||||
|
result = Solid(solid.Shape())
|
||||||
|
except StdFail_NotDone as err:
|
||||||
|
raise RuntimeError("Error applying thicken to given Face") from err
|
||||||
|
|
||||||
|
return result.clean()
|
||||||
|
|
||||||
|
|
||||||
def _make_loft(
|
def _make_loft(
|
||||||
objs: Iterable[Union[Vertex, Wire]],
|
objs: Iterable[Union[Vertex, Wire]],
|
||||||
filled: bool,
|
filled: bool,
|
||||||
|
|
|
||||||
|
|
@ -3039,6 +3039,24 @@ class TestShape(DirectApiTestCase):
|
||||||
self.assertLess(s2.volume, s.volume)
|
self.assertLess(s2.volume, s.volume)
|
||||||
self.assertGreater(s2.volume, 0.0)
|
self.assertGreater(s2.volume, 0.0)
|
||||||
|
|
||||||
|
def test_split_by_non_planar_face(self):
|
||||||
|
box = Solid.make_box(1, 1, 1)
|
||||||
|
tool = Circle(1).wire()
|
||||||
|
tool_shell: Shell = Shape.extrude(tool, Vector(0, 0, 1))
|
||||||
|
split = box.split(tool_shell, keep=Keep.BOTH)
|
||||||
|
|
||||||
|
self.assertEqual(len(split.solids()), 2)
|
||||||
|
self.assertGreater(split.solids()[0].volume, split.solids()[1].volume)
|
||||||
|
|
||||||
|
def test_split_by_shell(self):
|
||||||
|
box = Solid.make_box(5, 5, 1)
|
||||||
|
tool = Wire.make_rect(4, 4)
|
||||||
|
tool_shell: Shell = Shape.extrude(tool, Vector(0, 0, 1))
|
||||||
|
split = box.split(tool_shell, keep=Keep.TOP)
|
||||||
|
inner_vol = 2 * 2
|
||||||
|
outer_vol = 5 * 5
|
||||||
|
self.assertAlmostEqual(split.volume, outer_vol - inner_vol)
|
||||||
|
|
||||||
def test_split_by_perimeter(self):
|
def test_split_by_perimeter(self):
|
||||||
# Test 0 - extract a spherical cap
|
# Test 0 - extract a spherical cap
|
||||||
target0 = Solid.make_sphere(10).rotate(Axis.Z, 90)
|
target0 = Solid.make_sphere(10).rotate(Axis.Z, 90)
|
||||||
|
|
@ -3722,6 +3740,17 @@ class TestShells(DirectApiTestCase):
|
||||||
cylinder_area = 2 * math.pi * r * h
|
cylinder_area = 2 * math.pi * r * h
|
||||||
self.assertAlmostEqual(loft.area, cylinder_area)
|
self.assertAlmostEqual(loft.area, cylinder_area)
|
||||||
|
|
||||||
|
def test_thicken(self):
|
||||||
|
rect = Wire.make_rect(10, 5)
|
||||||
|
shell: Shell = Shape.extrude(rect, Vector(0, 0, 3))
|
||||||
|
thick = shell.thicken(1)
|
||||||
|
|
||||||
|
self.assertEqual(isinstance(thick, Solid), True)
|
||||||
|
inner_vol = 3 * 10 * 5
|
||||||
|
outer_vol = 3 * 12 * 7
|
||||||
|
self.assertAlmostEqual(thick.volume, outer_vol - inner_vol)
|
||||||
|
|
||||||
|
|
||||||
class TestSolid(DirectApiTestCase):
|
class TestSolid(DirectApiTestCase):
|
||||||
def test_make_solid(self):
|
def test_make_solid(self):
|
||||||
box_faces = Solid.make_box(1, 1, 1).faces()
|
box_faces = Solid.make_box(1, 1, 1).faces()
|
||||||
|
|
@ -3862,7 +3891,14 @@ class TestSolid(DirectApiTestCase):
|
||||||
Solid.make_loft([Vertex(0, 0, 1), Vertex(0, 0, 2)])
|
Solid.make_loft([Vertex(0, 0, 1), Vertex(0, 0, 2)])
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Solid.make_loft([Vertex(0, 0, 1),Wire.make_rect(1, 1), Vertex(0, 0, 2), Vertex(0, 0, 3)])
|
Solid.make_loft(
|
||||||
|
[
|
||||||
|
Vertex(0, 0, 1),
|
||||||
|
Wire.make_rect(1, 1),
|
||||||
|
Vertex(0, 0, 2),
|
||||||
|
Vertex(0, 0, 3),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def test_extrude_until(self):
|
def test_extrude_until(self):
|
||||||
square = Face.make_rect(1, 1)
|
square = Face.make_rect(1, 1)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue