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"]
|
||||
|
||||
TrimmingTool = Union[Plane, "Shell", "Face"]
|
||||
|
||||
|
||||
def tuplify(obj: Any, dim: int) -> tuple:
|
||||
"""Create a size tuple"""
|
||||
|
|
@ -2775,7 +2777,7 @@ class Shape(NodeMixin):
|
|||
|
||||
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 this shape by the provided plane or face.
|
||||
|
|
@ -2791,13 +2793,11 @@ class Shape(NodeMixin):
|
|||
shape_list.Append(self.wrapped)
|
||||
|
||||
# Define the splitting tool
|
||||
tool = (
|
||||
Face.make_plane(surface).wrapped
|
||||
if isinstance(surface, Plane)
|
||||
else surface.wrapped
|
||||
trim_tool = (
|
||||
Face.make_plane(tool).wrapped if isinstance(tool, Plane) else tool.wrapped
|
||||
)
|
||||
tool_list = TopTools_ListOfShape()
|
||||
tool_list.Append(tool)
|
||||
tool_list.Append(trim_tool)
|
||||
|
||||
# Create the splitter algorithm
|
||||
splitter = BRepAlgoAPI_Splitter()
|
||||
|
|
@ -2811,13 +2811,13 @@ class Shape(NodeMixin):
|
|||
|
||||
result = Compound(downcast(splitter.Shape())).unwrap(fully=False)
|
||||
if keep != Keep.BOTH:
|
||||
if not isinstance(surface, Plane):
|
||||
if not isinstance(tool, Plane):
|
||||
# Create solids from the surfaces for sorting
|
||||
surface_up = surface.thicken(0.1)
|
||||
surface_up = tool.thicken(0.1)
|
||||
tops, bottoms = [], []
|
||||
for part in result:
|
||||
if isinstance(surface, Plane):
|
||||
is_up = surface.to_local_coords(part).center().Z >= 0
|
||||
if isinstance(tool, Plane):
|
||||
is_up = tool.to_local_coords(part).center().Z >= 0
|
||||
else:
|
||||
is_up = surface_up.intersect(part).volume >= TOLERANCE
|
||||
(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
|
||||
)
|
||||
|
||||
def thicken(self, depth: float, normal_override: VectorLike = None) -> Solid:
|
||||
def thicken(
|
||||
self, depth: float, normal_override: Optional[VectorLike] = None
|
||||
) -> Solid:
|
||||
"""Thicken Face
|
||||
|
||||
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:
|
||||
adjusted_depth = -depth
|
||||
|
||||
solid = BRepOffset_MakeOffset()
|
||||
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()
|
||||
return _thicken(self.wrapped, adjusted_depth)
|
||||
|
||||
def project_to_shape(
|
||||
self, target_object: Shape, direction: VectorLike, taper: float = 0
|
||||
|
|
@ -7006,6 +6988,22 @@ class Shell(Shape):
|
|||
"""
|
||||
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):
|
||||
"""A Solid in build123d represents a three-dimensional solid geometry
|
||||
|
|
@ -8867,6 +8865,30 @@ class Joint(ABC):
|
|||
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(
|
||||
objs: Iterable[Union[Vertex, Wire]],
|
||||
filled: bool,
|
||||
|
|
|
|||
|
|
@ -3039,6 +3039,24 @@ class TestShape(DirectApiTestCase):
|
|||
self.assertLess(s2.volume, s.volume)
|
||||
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):
|
||||
# Test 0 - extract a spherical cap
|
||||
target0 = Solid.make_sphere(10).rotate(Axis.Z, 90)
|
||||
|
|
@ -3722,6 +3740,17 @@ class TestShells(DirectApiTestCase):
|
|||
cylinder_area = 2 * math.pi * r * h
|
||||
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):
|
||||
def test_make_solid(self):
|
||||
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)])
|
||||
|
||||
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):
|
||||
square = Face.make_rect(1, 1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue