Merge branch 'gumyr:dev' into publishPyPI

This commit is contained in:
jdegenstein 2023-11-30 11:07:52 -06:00 committed by GitHub
commit 2fcaecb673
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 289 additions and 82 deletions

View file

@ -103,6 +103,30 @@ G = 1
KG = 1000 * G
LB = 453.59237 * G
T = TypeVar("T")
def flatten_sequence(*obj: T) -> list[T]:
"""Convert a sequence of object potentially containing iterables into a flat list"""
def is_point(obj):
"""Identify points as tuples of numbers"""
return isinstance(obj, tuple) and all(
isinstance(item, (int, float)) for item in obj
)
flat_list = []
for item in obj:
# Note: an Iterable can't be used here as it will match with Vector & Vertex
# and break them into a list of floats.
if isinstance(item, (list, tuple, filter, set)) and not is_point(item):
flat_list.extend(item)
else:
flat_list.append(item)
return flat_list
operations_apply_to = {
"add": ["BuildPart", "BuildSketch", "BuildLine"],
"bounding_box": ["BuildPart", "BuildSketch", "BuildLine"],
@ -942,16 +966,28 @@ class Locations(LocationList):
Creates a context of locations for Part or Sketch
Args:
pts (Union[VectorLike, Vertex, Location]): sequence of points to push
pts (Union[VectorLike, Vertex, Location, Face, Plane, Axis] or iterable of same):
sequence of points to push
Attributes:
local_locations (list{Location}): locations relative to workplane
"""
def __init__(self, *pts: Union[VectorLike, Vertex, Location, Face, Plane, Axis]):
def __init__(
self,
*pts: Union[
VectorLike,
Vertex,
Location,
Face,
Plane,
Axis,
Iterable[VectorLike, Vertex, Location, Face, Plane, Axis],
],
):
local_locations = []
for point in pts:
for point in flatten_sequence(*pts):
if isinstance(point, Location):
local_locations.append(point)
elif isinstance(point, Vector):
@ -1108,6 +1144,7 @@ class WorkplaneList:
@staticmethod
def _convert_to_planes(objs: Iterable[Union[Face, Plane, Location]]) -> list[Plane]:
"""Translate objects to planes"""
objs = flatten_sequence(*objs)
planes = []
for obj in objs:
if isinstance(obj, Plane):
@ -1186,7 +1223,6 @@ class WorkplaneList:
return result
T = TypeVar("T")
P = ParamSpec("P")

View file

@ -83,6 +83,8 @@ class BuildLine(Builder):
):
self.line: Curve = None
super().__init__(workplane, mode=mode)
if len(self.workplanes) > 1:
raise ValueError("BuildLine only accepts one workplane")
def __exit__(self, exception_type, exception_value, traceback):
"""Upon exiting restore context and send object to parent"""

View file

@ -31,7 +31,7 @@ import copy
from math import copysign, cos, radians, sin, sqrt
from typing import Iterable, Union
from build123d.build_common import WorkplaneList, validate_inputs
from build123d.build_common import WorkplaneList, flatten_sequence, validate_inputs
from build123d.build_enums import AngularDirection, GeomType, LengthMode, Mode
from build123d.build_line import BuildLine
from build123d.geometry import Axis, Plane, Vector, VectorLike
@ -90,6 +90,7 @@ class Bezier(BaseLineObject):
context: BuildLine = BuildLine._get_context(self)
validate_inputs(context, self)
cntl_pnts = flatten_sequence(*cntl_pnts)
polls = WorkplaneList.localize(*cntl_pnts)
curve = Edge.make_bezier(*polls, weights=weights)
@ -353,7 +354,7 @@ class FilletPolyline(BaseLineObject):
are filleted to a given radius.
Args:
pts (VectorLike): sequence of three or more points
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of three or more points
radius (float): radius of filleted corners
close (bool, optional): close by generating an extra Edge. Defaults to False.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
@ -367,7 +368,7 @@ class FilletPolyline(BaseLineObject):
def __init__(
self,
*pts: VectorLike,
*pts: Union[VectorLike, Iterable[VectorLike]],
radius: float,
close: bool = False,
mode: Mode = Mode.ADD,
@ -375,6 +376,8 @@ class FilletPolyline(BaseLineObject):
context: BuildLine = BuildLine._get_context(self)
validate_inputs(context, self)
pts = flatten_sequence(*pts)
if len(pts) < 3:
raise ValueError("filletpolyline requires three or more pts")
if radius <= 0:
@ -506,7 +509,7 @@ class Line(BaseLineObject):
Add a straight line defined by two end points.
Args:
pts (VectorLike): sequence of two points
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of two points
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
Raises:
@ -515,7 +518,10 @@ class Line(BaseLineObject):
_applies_to = [BuildLine._tag]
def __init__(self, *pts: VectorLike, mode: Mode = Mode.ADD):
def __init__(
self, *pts: Union[VectorLike, Iterable[VectorLike]], mode: Mode = Mode.ADD
):
pts = flatten_sequence(*pts)
if len(pts) != 2:
raise ValueError("Line requires two pts")
@ -637,7 +643,7 @@ class Polyline(BaseLineObject):
Add a sequence of straight lines defined by successive point pairs.
Args:
pts (VectorLike): sequence of three or more points
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of three or more points
close (bool, optional): close by generating an extra Edge. Defaults to False.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
@ -647,10 +653,16 @@ class Polyline(BaseLineObject):
_applies_to = [BuildLine._tag]
def __init__(self, *pts: VectorLike, close: bool = False, mode: Mode = Mode.ADD):
def __init__(
self,
*pts: Union[VectorLike, Iterable[VectorLike]],
close: bool = False,
mode: Mode = Mode.ADD,
):
context: BuildLine = BuildLine._get_context(self)
validate_inputs(context, self)
pts = flatten_sequence(*pts)
if len(pts) < 3:
raise ValueError("polyline requires three or more pts")
@ -766,7 +778,7 @@ class Spline(BaseLineObject):
Add a spline through the provided points optionally constrained by tangents.
Args:
pts (VectorLike): sequence of two or more points
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of two or more points
tangents (Iterable[VectorLike], optional): tangents at end points. Defaults to None.
tangent_scalars (Iterable[float], optional): change shape by amplifying tangent.
Defaults to None.
@ -778,12 +790,13 @@ class Spline(BaseLineObject):
def __init__(
self,
*pts: VectorLike,
*pts: Union[VectorLike, Iterable[VectorLike]],
tangents: Iterable[VectorLike] = None,
tangent_scalars: Iterable[float] = None,
periodic: bool = False,
mode: Mode = Mode.ADD,
):
pts = flatten_sequence(*pts)
context: BuildLine = BuildLine._get_context(self)
validate_inputs(context, self)
@ -821,7 +834,7 @@ class TangentArc(BaseLineObject):
Add an arc defined by two points and a tangent.
Args:
pts (VectorLike): sequence of two points
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of two points
tangent (VectorLike): tangent to constrain arc
tangent_from_first (bool, optional): apply tangent to first point. Note, applying
tangent to end point will flip the orientation of the arc. Defaults to True.
@ -835,11 +848,12 @@ class TangentArc(BaseLineObject):
def __init__(
self,
*pts: VectorLike,
*pts: Union[VectorLike, Iterable[VectorLike]],
tangent: VectorLike,
tangent_from_first: bool = True,
mode: Mode = Mode.ADD,
):
pts = flatten_sequence(*pts)
context: BuildLine = BuildLine._get_context(self)
validate_inputs(context, self)
@ -862,7 +876,7 @@ class ThreePointArc(BaseLineObject):
Add an arc generated by three points.
Args:
pts (VectorLike): sequence of three points
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of three points
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
Raises:
@ -871,10 +885,13 @@ class ThreePointArc(BaseLineObject):
_applies_to = [BuildLine._tag]
def __init__(self, *pts: VectorLike, mode: Mode = Mode.ADD):
def __init__(
self, *pts: Union[VectorLike, Iterable[VectorLike]], mode: Mode = Mode.ADD
):
context: BuildLine = BuildLine._get_context(self)
validate_inputs(context, self)
pts = flatten_sequence(*pts)
if len(pts) != 3:
raise ValueError("ThreePointArc requires three points")
points = WorkplaneList.localize(*pts)

View file

@ -28,9 +28,9 @@ license:
from __future__ import annotations
from math import cos, pi, radians, sin, tan
from typing import Union
from typing import Iterable, Union
from build123d.build_common import LocationList, validate_inputs
from build123d.build_common import LocationList, flatten_sequence, validate_inputs
from build123d.build_enums import Align, FontStyle, Mode
from build123d.build_sketch import BuildSketch
from build123d.geometry import Axis, Location, Rotation, Vector, VectorLike
@ -155,7 +155,8 @@ class Polygon(BaseSketchObject):
Add polygon(s) defined by given sequence of points to sketch.
Args:
pts (VectorLike): sequence of points defining the vertices of polygon
pts (Union[VectorLike, Iterable[VectorLike]]): sequence of points defining the
vertices of polygon
rotation (float, optional): angles to rotate objects. Defaults to 0.
align (Union[Align, tuple[Align, Align]], optional): align min, center, or max of object.
Defaults to (Align.CENTER, Align.CENTER).
@ -166,7 +167,7 @@ class Polygon(BaseSketchObject):
def __init__(
self,
*pts: VectorLike,
*pts: Union[VectorLike, Iterable[VectorLike]],
rotation: float = 0,
align: Union[Align, tuple[Align, Align]] = (Align.CENTER, Align.CENTER),
mode: Mode = Mode.ADD,
@ -174,6 +175,7 @@ class Polygon(BaseSketchObject):
context = BuildSketch._get_context(self)
validate_inputs(context, self)
pts = flatten_sequence(*pts)
self.pts = pts
self.align = tuplify(align, 2)
@ -495,7 +497,7 @@ class SlotOverall(BaseSketchObject):
).offset_2d(height / 2)
)
else:
face = Circle(width/2, mode=mode).face()
face = Circle(width / 2, mode=mode).face()
super().__init__(face, rotation, align, mode)
@ -518,6 +520,7 @@ class Text(BaseSketchObject):
rotation (float, optional): angles to rotate objects. Defaults to 0.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
# pylint: disable=too-many-instance-attributes
_applies_to = [BuildSketch._tag]

View file

@ -31,7 +31,13 @@ import logging
from math import radians, tan
from typing import Union, Iterable
from build123d.build_common import Builder, LocationList, WorkplaneList, validate_inputs
from build123d.build_common import (
Builder,
LocationList,
WorkplaneList,
flatten_sequence,
validate_inputs,
)
from build123d.build_enums import Keep, Kind, Mode, Side, Transition
from build123d.build_line import BuildLine
from build123d.build_part import BuildPart
@ -206,9 +212,8 @@ def bounding_box(
raise ValueError("objects must be provided")
object_list = [context._obj]
else:
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "bounding_box", object_list)
if all([obj._dim == 2 for obj in object_list]):
@ -300,9 +305,7 @@ def chamfer(
):
raise ValueError("No objects provided")
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "chamfer", object_list)
@ -397,9 +400,8 @@ def fillet(
):
raise ValueError("No objects provided")
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "fillet", object_list)
if context is not None:
target = context._obj
@ -495,9 +497,8 @@ def mirror(
raise ValueError("objects must be provided")
object_list = [context._obj]
else:
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "mirror", object_list)
mirrored = [copy.deepcopy(o).mirror(about) for o in object_list]
@ -562,9 +563,8 @@ def offset(
raise ValueError("objects must be provided")
object_list = [context._obj]
else:
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "offset", object_list)
edges: list[Edge] = []
@ -701,9 +701,7 @@ def project(
else:
workplane = context.exit_workplanes[0]
else:
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
# The size of the object determines the size of the target projection screen
# as the screen is normal to the direction of parallel projection
@ -826,9 +824,8 @@ def scale(
raise ValueError("objects must be provided")
object_list = [context._obj]
else:
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "scale", object_list)
if isinstance(by, (int, float)):
@ -907,9 +904,8 @@ def split(
raise ValueError("objects must be provided")
object_list = [context._obj]
else:
object_list = (
[*objects] if isinstance(objects, (list, tuple, filter)) else [objects]
)
object_list = flatten_sequence(objects)
validate_inputs(context, "split", object_list)
new_objects = []

View file

@ -44,13 +44,18 @@ from build123d.topology import (
Vertex,
)
from build123d.build_common import logger, WorkplaneList, validate_inputs
from build123d.build_common import (
logger,
WorkplaneList,
flatten_sequence,
validate_inputs,
)
def extrude(
to_extrude: Union[Face, Sketch] = None,
amount: float = None,
dir: VectorLike = None, # pylint: disable=redefined-builtin
dir: VectorLike = None, # pylint: disable=redefined-builtin
until: Until = None,
target: Union[Compound, Solid] = None,
both: bool = False,
@ -175,7 +180,7 @@ def extrude(
def loft(
sections: Union[Face, Iterable[Face]] = None,
sections: Union[Face, Sketch, Iterable[Union[Vertex, Face, Sketch]]] = None,
ruled: bool = False,
clean: bool = True,
mode: Mode = Mode.ADD,
@ -185,17 +190,16 @@ def loft(
Loft the pending sketches/faces, across all workplanes, into a solid.
Args:
sections (Face): slices to loft into object. If not provided, pending_faces
will be used.
sections (Vertex, Face, Sketch): slices to loft into object. If not provided, pending_faces
will be used. If vertices are to be used, a vertex can be the first, last, or
first and last elements.
ruled (bool, optional): discontiguous layer tangents. Defaults to False.
clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
context: BuildPart = BuildPart._get_context("loft")
section_list = (
[*sections] if isinstance(sections, (list, tuple, filter)) else [sections]
)
section_list = flatten_sequence(sections)
validate_inputs(context, "loft", section_list)
if all([s is None for s in section_list]):
@ -205,9 +209,32 @@ def loft(
context.pending_faces = []
context.pending_face_planes = []
else:
loft_wires = [
face.outer_wire() for section in section_list for face in section.faces()
]
if all(isinstance(s, (Face, Sketch)) for s in section_list):
loft_wires = [
face.outer_wire()
for section in section_list
for face in section.faces()
]
elif any(isinstance(s, Vertex) for s in section_list) and any(
isinstance(s, (Face, Sketch)) for s in section_list
):
if any(isinstance(s, Vertex) for s in section_list[1:-1]):
raise ValueError(
"Vertices must be the first, last, or first and last elements"
)
loft_wires = []
for s in section_list:
if isinstance(s, Vertex):
loft_wires.append(s)
elif isinstance(s, Face):
loft_wires.append(s.outer_wire())
elif isinstance(s, Sketch):
loft_wires.append(s.face().outer_wire())
elif all(isinstance(s, Vertex) for s in section_list):
raise ValueError(
"At least one face/sketch is required if vertices are the first, last, or first and last elements"
)
new_solid = Solid.make_loft(loft_wires, ruled)
# Try to recover an invalid loft
@ -411,9 +438,8 @@ def revolve(
"""
context: BuildPart = BuildPart._get_context("revolve")
profile_list = (
[*profiles] if isinstance(profiles, (list, tuple, filter)) else [profiles]
)
profile_list = flatten_sequence(profiles)
validate_inputs(context, "revolve", profile_list)
# Make sure we account for users specifying angles larger than 360 degrees, and

View file

@ -30,7 +30,7 @@ from __future__ import annotations
from typing import Iterable, Union
from build123d.build_enums import Mode
from build123d.topology import Compound, Curve, Edge, Face, ShapeList, Wire, Sketch
from build123d.build_common import validate_inputs
from build123d.build_common import flatten_sequence, validate_inputs
from build123d.build_sketch import BuildSketch
@ -49,7 +49,7 @@ def make_face(
context: BuildSketch = BuildSketch._get_context("make_face")
if edges is not None:
outer_edges = [*edges] if isinstance(edges, (list, tuple, filter)) else [edges]
outer_edges = flatten_sequence(edges)
elif context is not None:
outer_edges = context.pending_edges
else:
@ -84,7 +84,7 @@ def make_hull(
context: BuildSketch = BuildSketch._get_context("make_hull")
if edges is not None:
hull_edges = [*edges] if isinstance(edges, (list, tuple, filter)) else [edges]
hull_edges = flatten_sequence(edges)
elif context is not None:
hull_edges = context.pending_edges
if context.sketch_local is not None:
@ -131,7 +131,7 @@ def trace(
context: BuildSketch = BuildSketch._get_context("trace")
if lines is not None:
trace_lines = [*lines] if isinstance(lines, (list, tuple, filter)) else [lines]
trace_lines = flatten_sequence(lines)
trace_edges = [e for l in trace_lines for e in l.edges()]
elif context is not None:
trace_edges = context.pending_edges

View file

@ -1390,6 +1390,7 @@ class Shape(NodeMixin):
topo_parent (Shape): assembly parent of this object
"""
# pylint: disable=too-many-instance-attributes, too-many-public-methods
_dim = None
@ -1763,7 +1764,7 @@ class Shape(NodeMixin):
try:
upgrader.Build()
self.wrapped = downcast(upgrader.Shape())
except: # pylint: disable=bare-except
except: # pylint: disable=bare-except
warnings.warn(f"Unable to clean {self}")
return self
@ -3256,6 +3257,7 @@ class ShapePredicate(Protocol):
class ShapeList(list[T]):
"""Subclass of list with custom filter and sort methods appropriate to CAD"""
# pylint: disable=too-many-public-methods
@property
@ -4244,6 +4246,7 @@ class Curve(Compound):
class Edge(Mixin1D, Shape):
"""A trimmed curve that represents the border of a face"""
# pylint: disable=too-many-public-methods
_dim = 1
@ -4999,6 +5002,7 @@ class Edge(Mixin1D, Shape):
class Face(Shape):
"""a bounded surface that represents part of the boundary of a solid"""
# pylint: disable=too-many-public-methods
_dim = 2
@ -5935,13 +5939,15 @@ class Solid(Mixin3D, Shape):
)
@classmethod
def make_loft(cls, wires: list[Wire], ruled: bool = False) -> Solid:
def make_loft(
cls, objs: Iterable[Union[Vertex, Wire]], ruled: bool = False
) -> Solid:
"""make loft
Makes a loft from a list of wires.
Makes a loft from a list of wires and vertices, where vertices can be the first, last, or first and last elements.
Args:
wires (list[Wire]): section perimeters
objs (list[Vertex, Wire]): wire perimeters or vertices
ruled (bool, optional): stepped or smooth. Defaults to False (smooth).
Raises:
@ -5950,13 +5956,18 @@ class Solid(Mixin3D, Shape):
Returns:
Solid: Lofted object
"""
if len(objs) < 2:
raise ValueError("More than one wire, or a wire and a vertex is required")
# the True flag requests building a solid instead of a shell.
if len(wires) < 2:
raise ValueError("More than one wire is required")
loft_builder = BRepOffsetAPI_ThruSections(True, ruled)
for wire in wires:
loft_builder.AddWire(wire.wrapped)
for obj in objs:
if isinstance(obj, Vertex):
loft_builder.AddVertex(obj.wrapped)
elif isinstance(obj, Wire):
loft_builder.AddWire(obj.wrapped)
loft_builder.Build()
@ -6274,7 +6285,7 @@ class Solid(Mixin3D, Shape):
.solids()
.sort_by(direction_axis)[0]
)
except: # pylint: disable=bare-except
except: # pylint: disable=bare-except
warnings.warn("clipping error - extrusion may be incorrect")
else:
extrusion_parts = [extrusion.intersect(target_object)]
@ -6285,7 +6296,7 @@ class Solid(Mixin3D, Shape):
.solids()
.sort_by(direction_axis)[0]
)
except: # pylint: disable=bare-except
except: # pylint: disable=bare-except
warnings.warn("clipping error - extrusion may be incorrect")
extrusion = Shape.fuse(*extrusion_parts)

View file

@ -28,7 +28,7 @@ license:
import unittest
from math import pi
from build123d import *
from build123d import Builder, WorkplaneList, LocationList
from build123d import WorkplaneList, flatten_sequence
def _assertTupleAlmostEquals(self, expected, actual, places, msg=None):
@ -40,6 +40,38 @@ def _assertTupleAlmostEquals(self, expected, actual, places, msg=None):
unittest.TestCase.assertTupleAlmostEquals = _assertTupleAlmostEquals
class TestFlattenSequence(unittest.TestCase):
"""Test the flatten_sequence helper function"""
def test_single_object(self):
self.assertListEqual(flatten_sequence("a"), ["a"])
def test_sequence(self):
self.assertListEqual(flatten_sequence("a", "b", "c"), ["a", "b", "c"])
def test_list(self):
self.assertListEqual(flatten_sequence(["a", "b", "c"]), ["a", "b", "c"])
def test_list_sequence(self):
self.assertListEqual(
flatten_sequence(["a", "b", "c"], "d"), ["a", "b", "c", "d"]
)
def test_sequence_tuple(self):
self.assertListEqual(
flatten_sequence("a", ("b", "c", "d"), "e"), ["a", "b", "c", "d", "e"]
)
def test_points(self):
self.assertListEqual(
flatten_sequence("a", (1, 2, 3), "e"), ["a", (1, 2, 3), "e"]
)
self.assertListEqual(
flatten_sequence("a", (1.0, 2.0, 3.0), "e"), ["a", (1.0, 2.0, 3.0), "e"]
)
class TestBuilder(unittest.TestCase):
"""Test the Builder base class"""
@ -123,6 +155,18 @@ class TestBuilder(unittest.TestCase):
with self.assertWarns(UserWarning):
p.solid()
def test_workplanes_as_list(self):
with BuildPart() as p:
Box(1, 1, 1)
with BuildSketch(p.faces() >> Axis.Z):
Rectangle(0.25, 0.25)
extrude(amount=0.25)
self.assertAlmostEqual(p.part.volume, 1**3 + 0.25**3, 5)
with self.assertRaises(ValueError):
with BuildLine([Plane.XY, Plane.XZ]):
Line((0, 0), (1, 1))
class TestBuilderExit(unittest.TestCase):
def test_multiple(self):
@ -306,6 +350,22 @@ class TestLocations(unittest.TestCase):
self.assertTupleAlmostEquals(grid.min.to_tuple(), (-5, -15, 0), 5)
self.assertTupleAlmostEquals(grid.max.to_tuple(), (5, 15, 0), 5)
def test_mixed_sequence_list(self):
locs = Locations((0, 1), [(2, 3), (4, 5)], (6, 7))
self.assertEqual(len(locs.locations), 4)
self.assertTupleAlmostEquals(
locs.locations[0].position.to_tuple(), (0, 1, 0), 5
)
self.assertTupleAlmostEquals(
locs.locations[1].position.to_tuple(), (2, 3, 0), 5
)
self.assertTupleAlmostEquals(
locs.locations[2].position.to_tuple(), (4, 5, 0), 5
)
self.assertTupleAlmostEquals(
locs.locations[3].position.to_tuple(), (6, 7, 0), 5
)
class TestProperties(unittest.TestCase):
def test_vector_properties(self):
@ -609,9 +669,6 @@ class TestValidateInputs(unittest.TestCase):
with BuildPart() as p:
Box(1, 1, 1)
fillet(4, radius=1)
self.assertEqual(
"fillet doesn't accept int, did you intend <keyword>=4?", str(rte.exception)
)
class TestVectorExtensions(unittest.TestCase):
@ -695,20 +752,20 @@ class TestWorkplaneStorage(unittest.TestCase):
class TestContextAwareSelectors(unittest.TestCase):
def test_context_aware_selectors(self):
with BuildPart() as p:
Box(1,1,1)
Box(1, 1, 1)
self.assertEqual(solids(), p.solids())
self.assertEqual(faces(), p.faces())
self.assertEqual(wires(), p.wires())
self.assertEqual(edges(), p.edges())
self.assertEqual(vertices(), p.vertices())
with BuildSketch() as p:
Rectangle(1,1)
Rectangle(1, 1)
self.assertEqual(faces(), p.faces())
self.assertEqual(wires(), p.wires())
self.assertEqual(edges(), p.edges())
self.assertEqual(vertices(), p.vertices())
with BuildLine() as p:
Line((0,0), (1,0))
Line((0, 0), (1, 0))
self.assertEqual(edges(), p.edges())
self.assertEqual(vertices(), p.vertices())
with BuildSketch() as p:

View file

@ -280,6 +280,21 @@ class BuildLineTests(unittest.TestCase):
self.assertEqual(len(test.edges()), 4)
self.assertAlmostEqual(test.wires()[0].length, 4)
def test_polyline_with_list(self):
"""Test edge generation and close"""
with BuildLine() as test:
Polyline((0, 0), [(1, 0), (1, 1)], (0, 1), close=True)
self.assertAlmostEqual(
(test.edges()[0] @ 0 - test.edges()[-1] @ 1).length, 0, 5
)
self.assertEqual(len(test.edges()), 4)
self.assertAlmostEqual(test.wires()[0].length, 4)
def test_line_with_list(self):
"""Test line with a list of points"""
l = Line([(0, 0), (10, 0)])
self.assertAlmostEqual(l.length, 10, 5)
def test_wires_select_last(self):
with BuildLine() as test:
Line((0, 0), (0, 1))

View file

@ -325,6 +325,50 @@ class TestLoft(unittest.TestCase):
self.assertLess(test.part.volume, 225 * pi * 30, 5)
self.assertGreater(test.part.volume, 25 * pi * 30, 5)
def test_loft_vertex(self):
with BuildPart() as test:
v1 = Vertex(0, 0, 3)
with BuildSketch() as s:
Rectangle(1, 1)
loft(sections=[s.sketch, v1], ruled=True)
self.assertAlmostEqual(test.part.volume, 1, 5)
def test_loft_vertices(self):
with BuildPart() as test:
v1 = Vertex(0, 0, 3)
v2 = Vertex(0, 0, -3)
with BuildSketch() as s:
Rectangle(1, 1)
loft(sections=[v2, s.sketch, v1], ruled=True)
self.assertAlmostEqual(test.part.volume, 2, 5)
def test_loft_vertex_face(self):
v1 = Vertex(0, 0, 3)
r = Rectangle(1, 1)
test = loft(sections=[r.face(), v1], ruled=True)
self.assertAlmostEqual(test.volume, 1, 5)
def test_loft_no_sections_assert(self):
with BuildPart() as test:
with self.assertRaises(ValueError):
loft(sections=[None])
def test_loft_all_vertices_assert(self):
with BuildPart() as test:
v1 = Vertex(0, 0, -1)
v2 = Vertex(0, 0, 2)
with self.assertRaises(ValueError):
loft(sections=[v1, v2])
def test_loft_vertex_middle_assert(self):
with BuildPart() as test:
v1 = Vertex(0, 0, -1)
v2 = Vertex(0, 0, 2)
with BuildSketch() as s:
Circle(1)
with self.assertRaises(ValueError):
loft(sections=[v1, v2, s.sketch])
class TestRevolve(unittest.TestCase):
def test_simple_revolve(self):