mirror of
https://github.com/gumyr/build123d.git
synced 2026-03-01 11:11:45 -08:00
Most unittest complete
This commit is contained in:
parent
c661df3472
commit
dcdad16075
2 changed files with 353 additions and 74 deletions
|
|
@ -85,12 +85,18 @@ class BuildPart(Builder):
|
|||
@property
|
||||
def pending_faces_count(self) -> int:
|
||||
"""Number of pending faces"""
|
||||
return len(self.pending_faces.values())
|
||||
count = 0
|
||||
for face_list in self.pending_faces.values():
|
||||
count += len(face_list)
|
||||
return count
|
||||
|
||||
@property
|
||||
def pending_edges_count(self) -> int:
|
||||
"""Number of pending edges"""
|
||||
return len(self.pending_edges.values())
|
||||
count = 0
|
||||
for edge_list in self.pending_edges.values():
|
||||
count += len(edge_list)
|
||||
return count
|
||||
|
||||
@property
|
||||
def pending_location_count(self) -> int:
|
||||
|
|
@ -161,7 +167,7 @@ class BuildPart(Builder):
|
|||
if select == Select.ALL:
|
||||
face_list = self.part.Faces()
|
||||
elif select == Select.LAST:
|
||||
face_list = self.last_edges
|
||||
face_list = self.last_faces
|
||||
return ShapeList(face_list)
|
||||
|
||||
def solids(self, select: Select = Select.ALL) -> ShapeList[Solid]:
|
||||
|
|
@ -280,16 +286,14 @@ class BuildPart(Builder):
|
|||
self.part = self.part.fuse(*new_solids).clean()
|
||||
elif mode == Mode.SUBTRACT:
|
||||
if self.part is None:
|
||||
raise ValueError("Nothing to subtract from")
|
||||
raise RuntimeError("Nothing to subtract from")
|
||||
self.part = self.part.cut(*new_solids).clean()
|
||||
elif mode == Mode.INTERSECT:
|
||||
if self.part is None:
|
||||
raise ValueError("Nothing to intersect with")
|
||||
raise RuntimeError("Nothing to intersect with")
|
||||
self.part = self.part.intersect(*new_solids).clean()
|
||||
elif mode == Mode.REPLACE:
|
||||
self.part = Compound.makeCompound(new_solids).clean()
|
||||
else:
|
||||
raise ValueError(f"Invalid mode: {mode}")
|
||||
|
||||
post_vertices = set() if self.part is None else set(self.part.Vertices())
|
||||
post_edges = set() if self.part is None else set(self.part.Edges())
|
||||
|
|
@ -335,12 +339,12 @@ class CounterBoreHole(Compound):
|
|||
depth: float = None,
|
||||
mode: Mode = Mode.SUBTRACT,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
hole_depth = (
|
||||
BuildPart._get_context().part.BoundingBox().DiagonalLength
|
||||
if depth is None
|
||||
else depth
|
||||
context.part.BoundingBox().DiagonalLength if depth is None else depth
|
||||
)
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
new_solids = [
|
||||
Solid.makeCylinder(
|
||||
radius, hole_depth, loc.position(), plane.zDir * -1.0
|
||||
|
|
@ -354,7 +358,7 @@ class CounterBoreHole(Compound):
|
|||
)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -379,13 +383,13 @@ class CounterSinkHole(Compound):
|
|||
counter_sink_angle: float = 82, # Common tip angle
|
||||
mode: Mode = Mode.SUBTRACT,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
hole_depth = (
|
||||
BuildPart._get_context().part.BoundingBox().DiagonalLength
|
||||
if depth is None
|
||||
else depth
|
||||
context.part.BoundingBox().DiagonalLength if depth is None else depth
|
||||
)
|
||||
cone_height = counter_sink_radius / tan(radians(counter_sink_angle / 2.0))
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
new_solids = [
|
||||
Solid.makeCylinder(
|
||||
radius, hole_depth, loc.position(), plane.zDir * -1.0
|
||||
|
|
@ -394,13 +398,13 @@ class CounterSinkHole(Compound):
|
|||
counter_sink_radius,
|
||||
0.0,
|
||||
cone_height,
|
||||
loc,
|
||||
loc.position(),
|
||||
plane.zDir * -1.0,
|
||||
)
|
||||
)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -424,12 +428,14 @@ class Extrude(Compound):
|
|||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
new_solids: list[Solid] = []
|
||||
for plane_index, faces in BuildPart._get_context().pending_faces.items():
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
for plane_index, faces in context.pending_faces.items():
|
||||
for face in faces:
|
||||
new_solids.append(
|
||||
Solid.extrudeLinear(
|
||||
face,
|
||||
BuildPart._get_context().workplanes[plane_index].zDir * until,
|
||||
context.workplanes[plane_index].zDir * until,
|
||||
0,
|
||||
)
|
||||
)
|
||||
|
|
@ -437,15 +443,13 @@ class Extrude(Compound):
|
|||
new_solids.append(
|
||||
Solid.extrudeLinear(
|
||||
face,
|
||||
BuildPart._get_context().workplanes[plane_index].zDir
|
||||
* until
|
||||
* -1.0,
|
||||
context.workplanes[plane_index].zDir * until * -1.0,
|
||||
0,
|
||||
)
|
||||
)
|
||||
|
||||
BuildPart._get_context().pending_faces = {0: []}
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context.pending_faces = {0: []}
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -466,19 +470,19 @@ class Hole(Compound):
|
|||
depth: float = None,
|
||||
mode: Mode = Mode.SUBTRACT,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
hole_depth = (
|
||||
BuildPart._get_context().part.BoundingBox().DiagonalLength
|
||||
if depth is None
|
||||
else depth
|
||||
context.part.BoundingBox().DiagonalLength if depth is None else depth
|
||||
)
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
new_solids = [
|
||||
Solid.makeCylinder(
|
||||
radius, hole_depth, loc.position(), plane.zDir * -1.0, 360
|
||||
)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -494,14 +498,16 @@ class Loft(Solid):
|
|||
|
||||
def __init__(self, ruled: bool = False, mode: Mode = Mode.ADD):
|
||||
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
loft_wires = []
|
||||
for i in range(len(BuildPart._get_context().workplanes)):
|
||||
for face in BuildPart._get_context().pending_faces[i]:
|
||||
for i in range(len(context.workplanes)):
|
||||
for face in context.pending_faces[i]:
|
||||
loft_wires.append(face.outerWire())
|
||||
new_solid = Solid.makeLoft(loft_wires, ruled)
|
||||
|
||||
BuildPart._get_context().pending_faces = {0: []}
|
||||
BuildPart._get_context()._add_to_context(new_solid, mode=mode)
|
||||
context.pending_faces = {0: []}
|
||||
context._add_to_context(new_solid, mode=mode)
|
||||
super().__init__(new_solid.wrapped)
|
||||
|
||||
|
||||
|
|
@ -524,13 +530,16 @@ class Revolve(Compound):
|
|||
axis_end: VectorLike = None,
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
# Make sure we account for users specifying angles larger than 360 degrees, and
|
||||
# for OCCT not assuming that a 0 degree revolve means a 360 degree revolve
|
||||
angle = revolution_arc % 360.0
|
||||
angle = 360.0 if angle == 0 else angle
|
||||
|
||||
new_solids = []
|
||||
for i, workplane in enumerate(BuildPart._get_context().workplanes):
|
||||
for i, workplane in enumerate(context.workplanes):
|
||||
axis = []
|
||||
if axis_start is None:
|
||||
axis.append(workplane.fromLocalCoords(Vector(0, 0, 0)))
|
||||
|
|
@ -542,31 +551,52 @@ class Revolve(Compound):
|
|||
else:
|
||||
axis.append(workplane.fromLocalCoords(Vector(axis_end)))
|
||||
|
||||
for face in BuildPart._get_context().pending_faces[i]:
|
||||
for face in context.pending_faces[i]:
|
||||
new_solids.append(Solid.revolve(face, angle, *axis))
|
||||
|
||||
BuildPart._get_context().pending_faces = {0: []}
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context.pending_faces = {0: []}
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
class Section(Compound):
|
||||
"""Part Operation: Section
|
||||
|
||||
Slices current part at the given height from current workplane(s).
|
||||
Slices current part at the given height by section_by or current workplane(s).
|
||||
|
||||
Args:
|
||||
section_by (Plane, optional): sequence of planes to section object.
|
||||
Defaults to None.
|
||||
height (float, optional): workplane offset. Defaults to 0.0.
|
||||
mode (Mode, optional): combination mode. Defaults to Mode.INTERSECT.
|
||||
"""
|
||||
|
||||
def __init__(self, height: float = 0.0, mode: Mode = Mode.INTERSECT):
|
||||
def __init__(
|
||||
self,
|
||||
*section_by: Plane,
|
||||
height: float = 0.0,
|
||||
mode: Mode = Mode.INTERSECT,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
max_size = context.part.BoundingBox().DiagonalLength
|
||||
|
||||
section_planes = section_by if section_by else context.workplanes
|
||||
section_planes = (
|
||||
section_planes if isinstance(section_planes, Iterable) else [section_planes]
|
||||
)
|
||||
|
||||
planes = [
|
||||
Face.makePlane(basePnt=plane.origin + plane.zDir * height, dir=plane.zDir)
|
||||
for plane in BuildPart._get_context().workplanes
|
||||
Face.makePlane(
|
||||
2 * max_size,
|
||||
2 * max_size,
|
||||
basePnt=plane.origin + plane.zDir * height,
|
||||
dir=plane.zDir,
|
||||
)
|
||||
for plane in section_planes
|
||||
]
|
||||
|
||||
BuildPart._get_context()._add_to_context(planes, mode=mode)
|
||||
context._add_to_context(*planes, mode=mode)
|
||||
super().__init__(Compound.makeCompound(planes).wrapped)
|
||||
|
||||
|
||||
|
|
@ -589,10 +619,10 @@ class Shell(Compound):
|
|||
kind: Kind = Kind.ARC,
|
||||
mode: Mode = Mode.REPLACE,
|
||||
):
|
||||
new_part = BuildPart._get_context().part.shell(
|
||||
faces, thickness, kind=kind.name.lower()
|
||||
)
|
||||
BuildPart._get_context()._add_to_context(new_part, mode=mode)
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
new_part = context.part.shell(faces, thickness, kind=kind.name.lower())
|
||||
context._add_to_context(new_part, mode=mode)
|
||||
super().__init__(new_part.wrapped)
|
||||
|
||||
|
||||
|
|
@ -613,7 +643,9 @@ class Split(Compound):
|
|||
keep: Keep = Keep.TOP,
|
||||
mode: Mode = Mode.INTERSECT,
|
||||
):
|
||||
max_size = BuildPart._get_context().BoundingBox().DiagonalLength
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
max_size = context.part.BoundingBox().DiagonalLength
|
||||
|
||||
def build_cutter(keep: Keep) -> Solid:
|
||||
cutter_center = (
|
||||
|
|
@ -634,8 +666,8 @@ class Split(Compound):
|
|||
else:
|
||||
cutters.append(build_cutter(keep))
|
||||
|
||||
BuildPart._get_context()._add_to_context(*cutters, mode=mode)
|
||||
super().__init__(BuildPart._get_context().part.wrapped)
|
||||
context._add_to_context(*cutters, mode=mode)
|
||||
super().__init__(context.part.wrapped)
|
||||
|
||||
|
||||
class Sweep(Compound):
|
||||
|
|
@ -666,6 +698,8 @@ class Sweep(Compound):
|
|||
binormal: Union[Edge, Wire] = None,
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
path_wire = Wire.assembleEdges([path]) if isinstance(path, Edge) else path
|
||||
if binormal is None:
|
||||
binormal_mode = Vector(normal)
|
||||
|
|
@ -675,9 +709,9 @@ class Sweep(Compound):
|
|||
binormal_mode = binormal
|
||||
|
||||
new_solids = []
|
||||
for i in range(BuildPart._get_context().workplane_count):
|
||||
for i in range(context.workplane_count):
|
||||
if not multisection:
|
||||
for face in BuildPart._get_context().pending_faces[i]:
|
||||
for face in context.pending_faces[i]:
|
||||
new_solids.append(
|
||||
Solid.sweep(
|
||||
face,
|
||||
|
|
@ -689,18 +723,15 @@ class Sweep(Compound):
|
|||
)
|
||||
)
|
||||
else:
|
||||
sections = [
|
||||
face.outerWire()
|
||||
for face in BuildPart._get_context().pending_faces[i]
|
||||
]
|
||||
sections = [face.outerWire() for face in context.pending_faces[i]]
|
||||
new_solids.append(
|
||||
Solid.sweep_multi(
|
||||
sections, path_wire, make_solid, is_frenet, binormal_mode
|
||||
)
|
||||
)
|
||||
|
||||
BuildPart._get_context().pending_faces = {0: []}
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context.pending_faces = {0: []}
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -767,8 +798,10 @@ class Box(Compound):
|
|||
centered: tuple[bool, bool, bool] = (True, True, True),
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
center_offset = Vector(
|
||||
-length / 2 if centered[0] else 0,
|
||||
-width / 2 if centered[1] else 0,
|
||||
|
|
@ -784,7 +817,7 @@ class Box(Compound):
|
|||
).moved(rotate)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -814,8 +847,10 @@ class Cone(Compound):
|
|||
centered: tuple[bool, bool, bool] = (True, True, True),
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
center_offset = Vector(
|
||||
0 if centered[0] else max(bottom_radius, top_radius),
|
||||
0 if centered[1] else max(bottom_radius, top_radius),
|
||||
|
|
@ -832,7 +867,7 @@ class Cone(Compound):
|
|||
).moved(rotate)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -860,8 +895,10 @@ class Cylinder(Compound):
|
|||
centered: tuple[bool, bool, bool] = (True, True, True),
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
center_offset = Vector(
|
||||
0 if centered[0] else radius,
|
||||
0 if centered[1] else radius,
|
||||
|
|
@ -877,7 +914,7 @@ class Cylinder(Compound):
|
|||
).moved(rotate)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -907,8 +944,10 @@ class Sphere(Compound):
|
|||
centered: tuple[bool, bool, bool] = (True, True, True),
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
center_offset = Vector(
|
||||
0 if centered[0] else radius,
|
||||
0 if centered[1] else radius,
|
||||
|
|
@ -925,7 +964,7 @@ class Sphere(Compound):
|
|||
).moved(rotate)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -956,8 +995,10 @@ class Torus(Compound):
|
|||
centered: tuple[bool, bool, bool] = (True, True, True),
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
center_offset = Vector(
|
||||
0 if centered[0] else major_radius,
|
||||
0 if centered[1] else major_radius,
|
||||
|
|
@ -974,7 +1015,7 @@ class Torus(Compound):
|
|||
).moved(rotate)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
||||
|
||||
|
|
@ -1007,13 +1048,15 @@ class Wedge(Compound):
|
|||
rotation: RotationLike = (0, 0, 0),
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildPart = BuildPart._get_context()
|
||||
|
||||
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
|
||||
location_planes = BuildPart._get_context()._get_and_clear_locations()
|
||||
location_planes = context._get_and_clear_locations()
|
||||
new_solids = [
|
||||
Solid.makeWedge(
|
||||
dx, dy, dz, xmin, zmin, xmax, zmax, loc.position(), plane.zDir
|
||||
).moved(rotate)
|
||||
for loc, plane in location_planes
|
||||
]
|
||||
BuildPart._get_context()._add_to_context(*new_solids, mode=mode)
|
||||
context._add_to_context(*new_solids, mode=mode)
|
||||
super().__init__(Compound.makeCompound(new_solids).wrapped)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ license:
|
|||
|
||||
"""
|
||||
import unittest
|
||||
from math import pi, sin
|
||||
from build123d import *
|
||||
from cadquery import Compound, Plane, Vector
|
||||
|
||||
|
||||
def _assertTupleAlmostEquals(self, expected, actual, places, msg=None):
|
||||
|
|
@ -47,7 +49,7 @@ class BuildPartTests(unittest.TestCase):
|
|||
Box(10, 10, 10)
|
||||
self.assertEqual(len(test.vertices()), 8)
|
||||
Box(5, 5, 20, centered=(True, True, False))
|
||||
self.assertEqual(len(test.vertices(Select.LAST)), 8)
|
||||
self.assertEqual(len(test.vertices(Select.LAST)), 8)
|
||||
|
||||
def test_select_edges(self):
|
||||
"""Test edges()"""
|
||||
|
|
@ -55,7 +57,7 @@ class BuildPartTests(unittest.TestCase):
|
|||
Box(10, 10, 10)
|
||||
self.assertEqual(len(test.edges()), 12)
|
||||
Box(5, 5, 20, centered=(True, True, False))
|
||||
self.assertEqual(len(test.edges(Select.LAST)), 12)
|
||||
self.assertEqual(len(test.edges(Select.LAST)), 12)
|
||||
|
||||
def test_select_faces(self):
|
||||
"""Test faces()"""
|
||||
|
|
@ -66,8 +68,242 @@ class BuildPartTests(unittest.TestCase):
|
|||
with BuildSketch():
|
||||
Rectangle(5, 5)
|
||||
Extrude(5)
|
||||
self.assertEqual(len(test.faces()), 11)
|
||||
self.assertEqual(len(test.faces(Select.LAST)), 6)
|
||||
self.assertEqual(len(test.faces()), 11)
|
||||
self.assertEqual(len(test.faces(Select.LAST)), 6)
|
||||
|
||||
def test_select_solids(self):
|
||||
"""Test faces()"""
|
||||
with BuildPart() as test:
|
||||
for i in [5, 10]:
|
||||
PushPoints((3 * i, 0, 0))
|
||||
Box(10, 10, i)
|
||||
Box(20, 5, 5)
|
||||
self.assertEqual(len(test.solids()), 2)
|
||||
self.assertEqual(len(test.solids(Select.LAST)), 1)
|
||||
|
||||
def test_mode_add_multiple(self):
|
||||
with BuildPart() as test:
|
||||
PolarArray(30, 0, 360, 5)
|
||||
Box(20, 20, 20)
|
||||
self.assertAlmostEqual(len(test.solids()), 5)
|
||||
|
||||
def test_mode_subtract(self):
|
||||
with BuildPart() as test:
|
||||
Box(20, 20, 20)
|
||||
Sphere(10, mode=Mode.SUBTRACT)
|
||||
self.assertTrue(isinstance(test._obj, Compound))
|
||||
self.assertAlmostEqual(test.part.Volume(), 8000 - (4000 / 3) * pi, 5)
|
||||
|
||||
def test_mode_intersect(self):
|
||||
"""Note that a negative volume is created"""
|
||||
with BuildPart() as test:
|
||||
Box(20, 20, 20)
|
||||
Sphere(10, mode=Mode.INTERSECT)
|
||||
self.assertAlmostEqual(abs(test.part.Volume()), (4000 / 3) * pi, 5)
|
||||
|
||||
def test_mode_replace(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
Sphere(10, mode=Mode.REPLACE)
|
||||
self.assertAlmostEqual(test.part.Volume(), (4000 / 3) * pi, 5)
|
||||
|
||||
def test_add_pending_faces(self):
|
||||
with BuildPart() as test:
|
||||
Box(100, 100, 100)
|
||||
WorkplanesFromFaces(*test.faces())
|
||||
with BuildSketch():
|
||||
PolarArray(10, 0, 360, 5)
|
||||
Circle(2)
|
||||
self.assertEqual(test.workplane_count, 6)
|
||||
self.assertEqual(test.pending_faces_count, 30)
|
||||
|
||||
def test_add_pending_edges(self):
|
||||
with BuildPart() as test:
|
||||
Box(100, 100, 100)
|
||||
WorkplanesFromFaces(*test.faces())
|
||||
with BuildLine():
|
||||
CenterArc((0, 0), 5, 0, 180)
|
||||
self.assertEqual(test.pending_edges_count, 6)
|
||||
|
||||
def test_add_pending_location_count(self):
|
||||
with BuildPart() as test:
|
||||
PolarArray(30, 0, 360, 5)
|
||||
self.assertEqual(test.pending_location_count, 5)
|
||||
|
||||
|
||||
class BuildPartExceptions(unittest.TestCase):
|
||||
"""Test exception handling"""
|
||||
|
||||
def test_invalid_subtract(self):
|
||||
with self.assertRaises(RuntimeError):
|
||||
with BuildPart():
|
||||
Sphere(10, mode=Mode.SUBTRACT)
|
||||
|
||||
def test_invalid_intersect(self):
|
||||
with self.assertRaises(RuntimeError):
|
||||
with BuildPart():
|
||||
Sphere(10, mode=Mode.INTERSECT)
|
||||
|
||||
|
||||
class TestCounterBoreHole(unittest.TestCase):
|
||||
def test_fixed_depth(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
PushPoints(test.faces().filter_by_axis(Axis.Z)[-1].Center())
|
||||
CounterBoreHole(2, 3, 1, 5)
|
||||
self.assertAlmostEqual(test.part.Volume(), 1000 - 4 * 4 * pi - 9 * pi, 5)
|
||||
|
||||
def test_through_hole(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
PushPoints(test.faces().filter_by_axis(Axis.Z)[-1].Center())
|
||||
CounterBoreHole(2, 3, 1)
|
||||
self.assertAlmostEqual(test.part.Volume(), 1000 - 4 * 9 * pi - 9 * pi, 5)
|
||||
|
||||
|
||||
class TestCounterSinkHole(unittest.TestCase):
|
||||
def test_fixed_depth(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
PushPoints(test.faces().filter_by_axis(Axis.Z)[-1].Center())
|
||||
CounterSinkHole(2, 4, 5)
|
||||
self.assertLess(test.part.Volume(), 1000, 5)
|
||||
self.assertGreater(test.part.Volume(), 1000 - 16 * 5 * pi, 5)
|
||||
|
||||
def test_through_hole(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
PushPoints(test.faces().filter_by_axis(Axis.Z)[-1].Center())
|
||||
CounterSinkHole(2, 4)
|
||||
self.assertLess(test.part.Volume(), 1000, 5)
|
||||
self.assertGreater(test.part.Volume(), 1000 - 16 * 10 * pi, 5)
|
||||
|
||||
|
||||
class TestExtrude(unittest.TestCase):
|
||||
def test_extrude_both(self):
|
||||
with BuildPart() as test:
|
||||
with BuildSketch():
|
||||
Rectangle(5, 5)
|
||||
Extrude(2.5, both=True)
|
||||
self.assertAlmostEqual(test.part.Volume(), 125, 5)
|
||||
|
||||
|
||||
class TestHole(unittest.TestCase):
|
||||
def test_fixed_depth(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
PushPoints(test.faces().filter_by_axis(Axis.Z)[-1].Center())
|
||||
Hole(2, 5)
|
||||
self.assertAlmostEqual(test.part.Volume(), 1000 - 4 * 5 * pi, 5)
|
||||
|
||||
def test_through_hole(self):
|
||||
with BuildPart() as test:
|
||||
Box(10, 10, 10)
|
||||
PushPoints(test.faces().filter_by_axis(Axis.Z)[-1].Center())
|
||||
Hole(2)
|
||||
self.assertAlmostEqual(test.part.Volume(), 1000 - 4 * 10 * pi, 5)
|
||||
|
||||
|
||||
class TestLoft(unittest.TestCase):
|
||||
def test_simple_loft(self):
|
||||
with BuildPart() as test:
|
||||
slice_count = 10
|
||||
for i in range(slice_count + 1):
|
||||
Workplanes(Plane(origin=(0, 0, i * 3), normal=(0, 0, 1)))
|
||||
with BuildSketch() as slice:
|
||||
Circle(10 * sin(i * pi / slice_count) + 5)
|
||||
Loft()
|
||||
self.assertLess(test.part.Volume(), 225 * pi * 30, 5)
|
||||
self.assertGreater(test.part.Volume(), 25 * pi * 30, 5)
|
||||
|
||||
|
||||
class TestRevolve(unittest.TestCase):
|
||||
def test_simple_revolve(self):
|
||||
with BuildPart() as test:
|
||||
with BuildSketch():
|
||||
with BuildLine():
|
||||
l1 = Line((0, 0), (12, 0))
|
||||
l2 = RadiusArc(l1 @ 1, (15, 20), 50)
|
||||
l3 = Spline(
|
||||
l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1))
|
||||
)
|
||||
l4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)
|
||||
l5 = Spline(
|
||||
l4 @ 1,
|
||||
l4 @ 1 + Vector(2.5, 2.5),
|
||||
l4 @ 1 + Vector(0, 5),
|
||||
tangents=(l4 % 1, (-1, 0)),
|
||||
)
|
||||
Polyline(
|
||||
l5 @ 1,
|
||||
l5 @ 1 + Vector(0, 1),
|
||||
(0, (l5 @ 1).y + 1),
|
||||
l1 @ 0,
|
||||
)
|
||||
BuildFace()
|
||||
Revolve()
|
||||
self.assertLess(test.part.Volume(), 22**2 * pi * 50, 5)
|
||||
self.assertGreater(test.part.Volume(), 144 * pi * 50, 5)
|
||||
|
||||
def test_revolve_with_axis(self):
|
||||
with BuildPart() as test:
|
||||
with BuildSketch():
|
||||
with BuildLine():
|
||||
l1 = Line((0, 0), (0, 12))
|
||||
l2 = RadiusArc(l1 @ 1, (20, 10), 50)
|
||||
l3 = Line(l2 @ 1, (20, 0))
|
||||
l4 = Line(l3 @ 1, l1 @ 0)
|
||||
BuildFace()
|
||||
Revolve(axis_start=(0, 0, 0), axis_end=(1, 0, 0))
|
||||
self.assertLess(test.part.Volume(), 244 * pi * 20, 5)
|
||||
self.assertGreater(test.part.Volume(), 100 * pi * 20, 5)
|
||||
|
||||
|
||||
class TestSection(unittest.TestCase):
|
||||
def test_circle(self):
|
||||
with BuildPart() as test:
|
||||
Sphere(10)
|
||||
Section()
|
||||
self.assertAlmostEqual(test.faces()[-1].Area(), 100 * pi, 5)
|
||||
|
||||
# def test_custom_plane(self):
|
||||
# with BuildPart() as test:
|
||||
# Sphere(10)
|
||||
# Section(Plane.named("XZ"))
|
||||
# self.assertAlmostEqual(
|
||||
# test.faces().filter_by_axis(Axis.Y)[-1].Area(), 100 * pi, 5
|
||||
# )
|
||||
|
||||
|
||||
class TestShell(unittest.TestCase):
|
||||
def test_box_shell(self):
|
||||
with BuildPart() as test:
|
||||
Cylinder(10, 10)
|
||||
Shell(thickness=1, kind=Kind.INTERSECTION)
|
||||
self.assertAlmostEqual(
|
||||
test.part.Volume(), 11**2 * pi * 12 - 10**2 * pi * 10, 5
|
||||
)
|
||||
|
||||
|
||||
class TestSplit(unittest.TestCase):
|
||||
def test_split(self):
|
||||
with BuildPart() as test:
|
||||
Sphere(10)
|
||||
Split(keep=Keep.TOP)
|
||||
self.assertAlmostEqual(test.part.Volume(), (2 / 3) * 1000 * pi, 5)
|
||||
|
||||
def test_split_both(self):
|
||||
with BuildPart() as test:
|
||||
Sphere(10)
|
||||
Split(keep=Keep.BOTH)
|
||||
self.assertEqual(len(test.solids()), 2)
|
||||
|
||||
|
||||
class TestTorus(unittest.TestCase):
|
||||
def test_simple_torus(self):
|
||||
with BuildPart() as test:
|
||||
Torus(100, 10)
|
||||
self.assertAlmostEqual(test.part.Volume(), pi * 100 * 2 * pi * 100, 5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue