mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Refactored topology.py ready to split into multiple modules
This commit is contained in:
parent
36a89eafad
commit
d1de2a6da1
14 changed files with 1831 additions and 1743 deletions
|
|
@ -66,6 +66,7 @@ from build123d.geometry import (
|
|||
Location,
|
||||
LocationEncoder,
|
||||
Matrix,
|
||||
Plane,
|
||||
Pos,
|
||||
Rot,
|
||||
Rotation,
|
||||
|
|
@ -78,7 +79,6 @@ from build123d.topology import (
|
|||
Compound,
|
||||
Edge,
|
||||
Face,
|
||||
Plane,
|
||||
Shape,
|
||||
ShapeList,
|
||||
Shell,
|
||||
|
|
@ -419,10 +419,10 @@ class TestBoundBox(DirectApiTestCase):
|
|||
|
||||
# Test creation of a bounding box from a shape - note the low accuracy comparison
|
||||
# as the box is a little larger than the shape
|
||||
bb1 = BoundBox._from_topo_ds(Solid.make_cylinder(1, 1).wrapped, optimal=False)
|
||||
bb1 = BoundBox.from_topo_ds(Solid.make_cylinder(1, 1).wrapped, optimal=False)
|
||||
self.assertVectorAlmostEquals(bb1.size, (2, 2, 1), 1)
|
||||
|
||||
bb2 = BoundBox._from_topo_ds(
|
||||
bb2 = BoundBox.from_topo_ds(
|
||||
Solid.make_cylinder(0.5, 0.5).translate((0, 0, 0.1)).wrapped, optimal=False
|
||||
)
|
||||
self.assertTrue(bb2.is_inside(bb1))
|
||||
|
|
@ -459,11 +459,11 @@ class TestBoundBox(DirectApiTestCase):
|
|||
class TestCadObjects(DirectApiTestCase):
|
||||
def _make_circle(self):
|
||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 2.0)
|
||||
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||
return Edge.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||
|
||||
def _make_ellipse(self):
|
||||
ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 4.0, 2.0)
|
||||
return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge())
|
||||
return Edge.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge())
|
||||
|
||||
def test_edge_wrapper_center(self):
|
||||
e = self._make_circle()
|
||||
|
|
@ -608,7 +608,7 @@ class TestCadObjects(DirectApiTestCase):
|
|||
self.assertVectorAlmostEquals(e2.center(CenterOf.MASS), (1.0, 2.0, 4.0), 3)
|
||||
|
||||
def test_vertices(self):
|
||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 1, 0)).Edge())
|
||||
e = Edge.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 1, 0)).Edge())
|
||||
self.assertEqual(2, len(e.vertices()))
|
||||
|
||||
def test_edge_wrapper_radius(self):
|
||||
|
|
@ -849,8 +849,8 @@ class TestCompound(DirectApiTestCase):
|
|||
# N.B. b and bb overlap but still add to Compound volume
|
||||
|
||||
def test_constructor(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Compound(bob="fred")
|
||||
with self.assertRaises(TypeError):
|
||||
Compound(foo="bar")
|
||||
|
||||
def test_len(self):
|
||||
self.assertEqual(len(Compound()), 0)
|
||||
|
|
@ -1139,7 +1139,7 @@ class TestEdge(DirectApiTestCase):
|
|||
self.assertAlmostEqual((e2 @ 0.1).X, -(e2r @ 0.1).X, 5)
|
||||
|
||||
def test_init(self):
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(TypeError):
|
||||
Edge(direction=(1, 0, 0))
|
||||
|
||||
|
||||
|
|
@ -1354,11 +1354,11 @@ class TestFace(DirectApiTestCase):
|
|||
for y in range(11)
|
||||
]
|
||||
surface = Face.make_surface_from_array_of_points(pnts)
|
||||
solid = surface.thicken(1)
|
||||
solid = Solid.thicken(surface, 1)
|
||||
self.assertAlmostEqual(solid.volume, 101.59, 2)
|
||||
|
||||
square = Face.make_rect(10, 10)
|
||||
bbox = square.thicken(1, normal_override=(0, 0, -1)).bounding_box()
|
||||
bbox = Solid.thicken(square, 1, normal_override=(0, 0, -1)).bounding_box()
|
||||
self.assertVectorAlmostEquals(bbox.min, (-5, -5, -1), 5)
|
||||
self.assertVectorAlmostEquals(bbox.max, (5, 5, 0), 5)
|
||||
|
||||
|
|
@ -1596,11 +1596,11 @@ class TestFunctions(unittest.TestCase):
|
|||
# unwrap fully
|
||||
c0 = Compound([b1])
|
||||
c1 = Compound([c0])
|
||||
result = Shape.cast(unwrap_topods_compound(c1.wrapped, True))
|
||||
result = Compound.cast(unwrap_topods_compound(c1.wrapped, True))
|
||||
self.assertTrue(isinstance(result, Solid))
|
||||
|
||||
# unwrap not fully
|
||||
result = Shape.cast(unwrap_topods_compound(c1.wrapped, False))
|
||||
result = Compound.cast(unwrap_topods_compound(c1.wrapped, False))
|
||||
self.assertTrue(isinstance(result, Compound))
|
||||
|
||||
|
||||
|
|
@ -1632,18 +1632,18 @@ class TestImportExport(DirectApiTestCase):
|
|||
self.assertVectorAlmostEquals(stl_box.position, (0, 0, 0), 5)
|
||||
|
||||
|
||||
class TestJupyter(DirectApiTestCase):
|
||||
def test_repr_javascript(self):
|
||||
shape = Solid.make_box(1, 1, 1)
|
||||
# class TestJupyter(DirectApiTestCase):
|
||||
# def test_repr_javascript(self):
|
||||
# shape = Solid.make_box(1, 1, 1)
|
||||
|
||||
# Test no exception on rendering to js
|
||||
js1 = shape._repr_javascript_()
|
||||
# # Test no exception on rendering to js
|
||||
# js1 = shape._repr_javascript_()
|
||||
|
||||
assert "function render" in js1
|
||||
# assert "function render" in js1
|
||||
|
||||
def test_display_error(self):
|
||||
with self.assertRaises(AttributeError):
|
||||
display(Vector())
|
||||
# def test_display_error(self):
|
||||
# with self.assertRaises(AttributeError):
|
||||
# display(Vector())
|
||||
|
||||
|
||||
class TestLocation(DirectApiTestCase):
|
||||
|
|
@ -1930,6 +1930,19 @@ class TestLocation(DirectApiTestCase):
|
|||
self.assertTrue(isinstance(i, Vertex))
|
||||
self.assertVectorAlmostEquals(Vector(i), (0.5, 0.5, 0.5), 5)
|
||||
|
||||
e1 = Edge.make_line((0, -1), (2, 1))
|
||||
e2 = Edge.make_line((0, 1), (2, -1))
|
||||
e3 = Edge.make_line((0, 0), (2, 0))
|
||||
|
||||
i = e1.intersect(e2, e3)
|
||||
self.assertTrue(isinstance(i, Vertex))
|
||||
self.assertVectorAlmostEquals(i, (1, 0, 0), 5)
|
||||
|
||||
e4 = Edge.make_line((1, -1), (1, 1))
|
||||
e5 = Edge.make_line((2, -1), (2, 1))
|
||||
i = e3.intersect(e4, e5)
|
||||
self.assertIsNone(i)
|
||||
|
||||
|
||||
class TestMatrix(DirectApiTestCase):
|
||||
def test_matrix_creation_and_access(self):
|
||||
|
|
@ -2946,6 +2959,7 @@ class TestProjection(DirectApiTestCase):
|
|||
projected_text = sphere.project_faces(
|
||||
faces=Compound.make_text("dog", font_size=14),
|
||||
path=arch_path,
|
||||
start=0.01, # avoid a character spanning the sphere edge
|
||||
)
|
||||
self.assertEqual(len(projected_text.solids()), 0)
|
||||
self.assertEqual(len(projected_text.faces()), 3)
|
||||
|
|
@ -3051,9 +3065,9 @@ class TestShape(DirectApiTestCase):
|
|||
def test_split(self):
|
||||
shape = Box(1, 1, 1) - Pos((0, 0, -0.25)) * Box(1, 0.5, 0.5)
|
||||
split_shape = shape.split(Plane.XY, keep=Keep.BOTTOM)
|
||||
self.assertEqual(len(split_shape.solids()), 2)
|
||||
self.assertAlmostEqual(split_shape.volume, 0.25, 5)
|
||||
self.assertTrue(isinstance(split_shape, Compound))
|
||||
self.assertTrue(isinstance(split_shape, list))
|
||||
self.assertEqual(len(split_shape), 2)
|
||||
self.assertAlmostEqual(split_shape[0].volume + split_shape[1].volume, 0.25, 5)
|
||||
split_shape = shape.split(Plane.XY, keep=Keep.TOP)
|
||||
self.assertEqual(len(split_shape.solids()), 1)
|
||||
self.assertTrue(isinstance(split_shape, Solid))
|
||||
|
|
@ -3068,16 +3082,17 @@ class TestShape(DirectApiTestCase):
|
|||
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)
|
||||
tool_shell: Shell = Shell.extrude(tool, Vector(0, 0, 1))
|
||||
top, bottom = box.split(tool_shell, keep=Keep.BOTH)
|
||||
|
||||
self.assertEqual(len(split.solids()), 2)
|
||||
self.assertGreater(split.solids()[0].volume, split.solids()[1].volume)
|
||||
self.assertFalse(top is None)
|
||||
self.assertFalse(bottom is None)
|
||||
self.assertGreater(top.volume, bottom.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))
|
||||
tool_shell: Shell = Shell.extrude(tool, Vector(0, 0, 1))
|
||||
split = box.split(tool_shell, keep=Keep.TOP)
|
||||
inner_vol = 2 * 2
|
||||
outer_vol = 5 * 5
|
||||
|
|
@ -3097,41 +3112,28 @@ class TestShape(DirectApiTestCase):
|
|||
ring_projected = ring.project_to_shape(target0, (0, 0, -1))[0]
|
||||
ring_outerwire = ring_projected.outer_wire()
|
||||
inside1, outside1 = target0.split_by_perimeter(ring_outerwire, Keep.BOTH)
|
||||
if isinstance(inside1, list):
|
||||
inside1 = Compound(inside1)
|
||||
if isinstance(outside1, list):
|
||||
outside1 = Compound(outside1)
|
||||
self.assertLess(inside1.area, outside1.area)
|
||||
self.assertEqual(len(outside1.faces()), 2)
|
||||
|
||||
# Test 2 - extract multiple faces
|
||||
with BuildPart() as cross:
|
||||
with BuildSketch(Pos(Z=-5) * Rot(Z=-45)) as skt:
|
||||
Rectangle(5, 1, align=Align.MIN)
|
||||
Rectangle(1, 5, align=Align.MIN)
|
||||
fillet(skt.vertices(), 0.3)
|
||||
extrude(amount=10)
|
||||
target2 = cross.part
|
||||
target2 = Box(1, 10, 10)
|
||||
square = Face.make_rect(3, 3, Plane((12, 0, 0), z_dir=(1, 0, 0)))
|
||||
square_projected = square.project_to_shape(cross.part, (-1, 0, 0))[0]
|
||||
projected_edges = square_projected.edges().sort_by(SortBy.DISTANCE)[2:]
|
||||
projected_perimeter = Wire(projected_edges)
|
||||
inside2 = target2.split_by_perimeter(projected_perimeter, Keep.INSIDE)
|
||||
self.assertTrue(isinstance(inside2, Shell))
|
||||
|
||||
# Test 3 - Invalid, wire on shape edge
|
||||
target3 = Solid.make_cylinder(5, 10, Plane((0, 0, -5)))
|
||||
square_projected = square.project_to_shape(target3, (-1, 0, 0))[0].unwrap(
|
||||
fully=True
|
||||
square_projected = square.project_to_shape(target2, (-1, 0, 0))[0]
|
||||
outside2 = target2.split_by_perimeter(
|
||||
square_projected.outer_wire(), Keep.OUTSIDE
|
||||
)
|
||||
project_perimeter = square_projected.outer_wire()
|
||||
inside3 = target3.split_by_perimeter(project_perimeter, Keep.INSIDE)
|
||||
self.assertIsNone(inside3)
|
||||
outside3 = target3.split_by_perimeter(project_perimeter, Keep.OUTSIDE)
|
||||
self.assertAlmostEqual(outside3.area, target3.shell().area, 5)
|
||||
self.assertTrue(isinstance(outside2, Shell))
|
||||
|
||||
# Test 4 - invalid inputs
|
||||
with self.assertRaises(ValueError):
|
||||
_, _ = target2.split_by_perimeter(projected_perimeter.edges()[0], Keep.BOTH)
|
||||
_, _ = target2.split_by_perimeter(Edge.make_line((0, 0), (1, 0)), Keep.BOTH)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
_, _ = target3.split_by_perimeter(projected_perimeter, Keep.TOP)
|
||||
_, _ = target2.split_by_perimeter(Edge.make_circle(1), Keep.TOP)
|
||||
|
||||
def test_distance(self):
|
||||
sphere1 = Solid.make_sphere(1, Plane((-5, 0, 0)))
|
||||
|
|
@ -3332,7 +3334,7 @@ class TestShape(DirectApiTestCase):
|
|||
s = Solid.make_sphere(1).solid()
|
||||
self.assertTrue(isinstance(s, Solid))
|
||||
with self.assertWarns(UserWarning):
|
||||
Solid.make_sphere(1).split(Plane.XY, keep=Keep.BOTH).solid()
|
||||
Compound(Solid.make_sphere(1).split(Plane.XY, keep=Keep.BOTH)).solid()
|
||||
|
||||
def test_manifold(self):
|
||||
self.assertTrue(Solid.make_box(1, 1, 1).is_manifold)
|
||||
|
|
@ -3498,6 +3500,17 @@ class TestShapeList(DirectApiTestCase):
|
|||
self.assertEqual(len(box.faces().group_by(SortBy.AREA)[0]), 2)
|
||||
self.assertEqual(len(box.faces().group_by(SortBy.AREA)[1]), 4)
|
||||
|
||||
line = Edge.make_line((0, 0, 0), (1, 1, 2))
|
||||
vertices_by_line = box.vertices().group_by(line)
|
||||
self.assertEqual(len(vertices_by_line[0]), 1)
|
||||
self.assertEqual(len(vertices_by_line[1]), 2)
|
||||
self.assertEqual(len(vertices_by_line[2]), 1)
|
||||
self.assertEqual(len(vertices_by_line[3]), 1)
|
||||
self.assertEqual(len(vertices_by_line[4]), 2)
|
||||
self.assertEqual(len(vertices_by_line[5]), 1)
|
||||
self.assertVectorAlmostEquals(vertices_by_line[0][0], (0, 0, 0), 5)
|
||||
self.assertVectorAlmostEquals(vertices_by_line[-1][0], (1, 1, 2), 5)
|
||||
|
||||
with BuildPart() as boxes:
|
||||
with GridLocations(10, 10, 3, 3):
|
||||
Box(1, 1, 1)
|
||||
|
|
@ -3549,34 +3562,34 @@ class TestShapeList(DirectApiTestCase):
|
|||
def test_group_by_str_repr(self):
|
||||
nonagon = RegularPolygon(5, 9)
|
||||
|
||||
expected = [
|
||||
"[[<build123d.topology.Edge at 0x1277f6e1cd0>],",
|
||||
" [<build123d.topology.Edge at 0x1277f6e1c10>,",
|
||||
" <build123d.topology.Edge at 0x1277fd8a090>],",
|
||||
" [<build123d.topology.Edge at 0x1277f75d690>,",
|
||||
" <build123d.topology.Edge at 0x127760d9310>],",
|
||||
" [<build123d.topology.Edge at 0x12777261f90>,",
|
||||
" <build123d.topology.Edge at 0x1277f6bd2d0>],",
|
||||
" [<build123d.topology.Edge at 0x1276fbb0590>,",
|
||||
" <build123d.topology.Edge at 0x1277fec6d90>]]",
|
||||
]
|
||||
# TODO: re-enable this test once the topology refactor complete
|
||||
# expected = [
|
||||
# "[[<build123d.topology.one_d.Edge at 0x1277f6e1cd0>],",
|
||||
# " [<build123d.topology.one_d.Edge at 0x1277f6e1c10>,",
|
||||
# " <build123d.topology.one_d.Edge at 0x1277fd8a090>],",
|
||||
# " [<build123d.topology.one_d.Edge at 0x1277f75d690>,",
|
||||
# " <build123d.topology.one_d.Edge at 0x127760d9310>],",
|
||||
# " [<build123d.topology.one_d.Edge at 0x12777261f90>,",
|
||||
# " <build123d.topology.one_d.Edge at 0x1277f6bd2d0>],",
|
||||
# " [<build123d.topology.one_d.Edge at 0x1276fbb0590>,",
|
||||
# " <build123d.topology.one_d.Edge at 0x1277fec6d90>]]",
|
||||
# ]
|
||||
# self.assertDunderStrEqual(str(nonagon.edges().group_by(Axis.X)), expected)
|
||||
|
||||
self.assertDunderStrEqual(str(nonagon.edges().group_by(Axis.X)), expected)
|
||||
|
||||
expected_repr = (
|
||||
"[[<build123d.topology.Edge object at 0x000001277FEC6D90>],"
|
||||
" [<build123d.topology.Edge object at 0x000001277F6BCC10>,"
|
||||
" <build123d.topology.Edge object at 0x000001277EC3D5D0>],"
|
||||
" [<build123d.topology.Edge object at 0x000001277F6BEA90>,"
|
||||
" <build123d.topology.Edge object at 0x000001276FCB2310>],"
|
||||
" [<build123d.topology.Edge object at 0x000001277F6D10D0>,"
|
||||
" <build123d.topology.Edge object at 0x000001276FBAAD10>],"
|
||||
" [<build123d.topology.Edge object at 0x000001277FC86F90>,"
|
||||
" <build123d.topology.Edge object at 0x000001277F6E1CD0>]]"
|
||||
)
|
||||
self.assertDunderReprEqual(
|
||||
repr(nonagon.edges().group_by(Axis.X)), expected_repr
|
||||
)
|
||||
# expected_repr = (
|
||||
# "[[<build123d.topology.one_d.Edge object at 0x000001277FEC6D90>],"
|
||||
# " [<build123d.topology.one_d.Edge object at 0x000001277F6BCC10>,"
|
||||
# " <build123d.topology.one_d.Edge object at 0x000001277EC3D5D0>],"
|
||||
# " [<build123d.topology.one_d.Edge object at 0x000001277F6BEA90>,"
|
||||
# " <build123d.topology.one_d.Edge object at 0x000001276FCB2310>],"
|
||||
# " [<build123d.topology.one_d.Edge object at 0x000001277F6D10D0>,"
|
||||
# " <build123d.topology.one_d.Edge object at 0x000001276FBAAD10>],"
|
||||
# " [<build123d.topology.one_d.Edge object at 0x000001277FC86F90>,"
|
||||
# " <build123d.topology.one_d.Edge object at 0x000001277F6E1CD0>]]"
|
||||
# )
|
||||
# self.assertDunderReprEqual(
|
||||
# repr(nonagon.edges().group_by(Axis.X)), expected_repr
|
||||
# )
|
||||
|
||||
f = io.StringIO()
|
||||
p = pretty.PrettyPrinter(f)
|
||||
|
|
@ -3732,8 +3745,8 @@ class TestShells(DirectApiTestCase):
|
|||
self.assertAlmostEqual(nm_shell.volume, 0, 5)
|
||||
|
||||
def test_constructor(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Shell(bob="fred")
|
||||
with self.assertRaises(TypeError):
|
||||
Shell(foo="bar")
|
||||
|
||||
x_section = Rot(90) * Spline((0, -5), (-3, -2), (-2, 0), (-3, 2), (0, 5))
|
||||
surface = sweep(x_section, Circle(5).wire())
|
||||
|
|
@ -3760,8 +3773,8 @@ class TestShells(DirectApiTestCase):
|
|||
self.assertEqual(len(sweep_e_w.faces()), 2)
|
||||
self.assertEqual(len(sweep_w_e.faces()), 2)
|
||||
self.assertEqual(len(sweep_c2_c1.faces()), 2)
|
||||
self.assertEqual(len(sweep_w_w.faces()), 4)
|
||||
self.assertEqual(len(sweep_c2_c2.faces()), 4)
|
||||
self.assertEqual(len(sweep_w_w.faces()), 3) # 3 with clean, 4 without
|
||||
self.assertEqual(len(sweep_c2_c2.faces()), 3) # 3 with clean, 4 without
|
||||
|
||||
def test_make_loft(self):
|
||||
r = 3
|
||||
|
|
@ -3775,8 +3788,8 @@ class TestShells(DirectApiTestCase):
|
|||
|
||||
def test_thicken(self):
|
||||
rect = Wire.make_rect(10, 5)
|
||||
shell: Shell = Shape.extrude(rect, Vector(0, 0, 3))
|
||||
thick = shell.thicken(1)
|
||||
shell: Shell = Shell.extrude(rect, Vector(0, 0, 3))
|
||||
thick = Solid.thicken(shell, 1)
|
||||
|
||||
self.assertEqual(isinstance(thick, Solid), True)
|
||||
inner_vol = 3 * 10 * 5
|
||||
|
|
@ -3953,8 +3966,8 @@ class TestSolid(DirectApiTestCase):
|
|||
self.assertAlmostEqual(swept.volume, 5 * (1 - 0.1**2), 5)
|
||||
|
||||
def test_constructor(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Solid(bob="fred")
|
||||
with self.assertRaises(TypeError):
|
||||
Solid(foo="bar")
|
||||
|
||||
|
||||
class TestVector(DirectApiTestCase):
|
||||
|
|
@ -4273,7 +4286,7 @@ class TestVertex(DirectApiTestCase):
|
|||
test_vertex - [1, 2, 3]
|
||||
|
||||
def test_vertex_str(self):
|
||||
self.assertEqual(str(Vertex(0, 0, 0)), "Vertex: (0.0, 0.0, 0.0)")
|
||||
self.assertEqual(str(Vertex(0, 0, 0)), "Vertex(0.0, 0.0, 0.0)")
|
||||
|
||||
def test_vertex_to_vector(self):
|
||||
self.assertIsInstance(Vector(Vertex(0, 0, 0)), Vector)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue