Introduced Axis Class and Sort/Filter Operators

This commit is contained in:
Roger Maitland 2022-09-27 13:39:32 -04:00
parent b37945f5ab
commit b50d6d5fe1
11 changed files with 326 additions and 163 deletions

View file

@ -29,12 +29,12 @@ import cadquery as cq
with BuildSketch() as logo_text:
Text("123d", fontsize=10, valign=Valign.BOTTOM)
font_height = logo_text.vertices().sort_by(SortBy.Y)[-1].y
font_height = (logo_text.vertices() >> Axis.Y).y
with BuildSketch() as build_text:
Text("build", fontsize=5, halign=Halign.CENTER)
build_bb = BoundingBox(build_text.sketch, mode=Mode.PRIVATE)
build_vertices = build_bb.vertices().sort_by(SortBy.X)
build_vertices = build_bb.vertices() > Axis.X
build_width = build_vertices[-1].x - build_vertices[0].x
with BuildLine() as one:
@ -50,7 +50,7 @@ with BuildPart() as three_d:
with BuildSketch():
Text("3d", fontsize=10, valign=Valign.BOTTOM)
Extrude(amount=font_height * 0.3)
logo_width = three_d.vertices().sort_by(SortBy.X)[-1].x
logo_width = (three_d.vertices() >> Axis.X).x
with BuildLine() as arrow_left:
t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))

View file

@ -60,7 +60,7 @@ with BuildPart() as single_multiple:
with BuildPart() as non_planar:
Cylinder(10, 20, rotation=(90, 0, 0), centered=(True, False, True))
Box(10, 10, 10, centered=(True, True, False), mode=Mode.INTERSECT)
Extrude(non_planar.part.faces().sort_by(SortBy.Z)[0], amount=2, mode=Mode.REPLACE)
Extrude(non_planar.part.faces() << Axis.Z, amount=2, mode=Mode.REPLACE)
# Taper Extrude and Extrude to "next" while creating a Cherry MX key cap
# See: https://www.cherrymx.de/en/dev.html

View file

@ -47,7 +47,7 @@ with BuildPart() as recessed_counter_sink:
with BuildPart() as flush_counter_sink:
with Locations((10, 10)):
Cylinder(radius=3, height=2)
with Workplanes(flush_counter_sink.part.faces().sort_by(SortBy.Z)[-1]):
with Workplanes(flush_counter_sink.part.faces() >> Axis.Z):
CounterSinkHole(radius=1, counter_sink_radius=1.5)
if "show_object" in locals():

View file

@ -8,6 +8,7 @@ desc:
This example creates a model of a double wide lego block with a
parametric length (pip_count).
*** Don't edit this file without checking the lego tutorial ***
license:
@ -26,7 +27,6 @@ license:
limitations under the License.
"""
from build123d import *
from cadquery import Plane
pip_count = 6
@ -74,7 +74,7 @@ with BuildPart() as lego:
wall_thickness,
centered=(True, True, False),
)
with Workplanes(lego.faces().sort_by(SortBy.Z)[-1]):
with Workplanes(lego.faces() >> Axis.Z):
with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):
Cylinder(
radius=pip_diameter / 2, height=pip_height, centered=(True, True, False)

View file

@ -47,13 +47,14 @@ with BuildPart() as vase:
l1 @ 0,
)
BuildFace()
Revolve(axis_origin=(0, 0, 0), axis_direction=(0, 1, 0))
Offset(openings=vase.faces().filter_by_axis(Axis.Y)[-1], amount=-1)
Revolve(axis=Axis.Y)
# Offset(openings=vase.faces().filter_by_axis(Axis.Y)[-1], amount=-1)
Offset(openings=(vase.faces() | Axis.Y) >> Axis.Y, amount=-1)
top_edges = (
vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by_type(Type.CIRCLE)
)
Fillet(*top_edges, radius=0.25)
Fillet(vase.edges().sort_by(SortBy.Y)[0], radius=0.5)
Fillet(vase.edges() << Axis.Y, radius=0.5)
if "show_object" in locals():

View file

@ -29,10 +29,11 @@ license:
limitations under the License.
"""
from __future__ import annotations
import contextvars
from itertools import product
from abc import ABC, abstractmethod
from math import radians, sqrt
from math import radians, sqrt, pi
from typing import Iterable, Union
from enum import Enum, auto
from cadquery import (
@ -104,8 +105,6 @@ Vector.X = property(_vector_x)
Vector.Y = property(_vector_y)
Vector.Z = property(_vector_z)
z_axis = (Vector(0, 0, 0), Vector(0, 0, 1))
def vertex_eq_(self: Vertex, other: Vertex) -> bool:
"""True if the distance between the two vertices is lower than their tolerance"""
@ -175,6 +174,9 @@ class Select(Enum):
ALL = auto()
LAST = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Kind(Enum):
"""Offset corner transition"""
@ -183,6 +185,9 @@ class Kind(Enum):
INTERSECTION = auto()
TANGENT = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Keep(Enum):
"""Split options"""
@ -191,6 +196,9 @@ class Keep(Enum):
BOTTOM = auto()
BOTH = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Mode(Enum):
"""Combination Mode"""
@ -201,6 +209,9 @@ class Mode(Enum):
REPLACE = auto()
PRIVATE = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Transition(Enum):
"""Sweep discontinuity handling option"""
@ -209,6 +220,9 @@ class Transition(Enum):
ROUND = auto()
TRANSFORMED = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class FontStyle(Enum):
"""Text Font Styles"""
@ -217,6 +231,9 @@ class FontStyle(Enum):
BOLD = auto()
ITALIC = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Halign(Enum):
"""Text Horizontal Alignment"""
@ -225,6 +242,9 @@ class Halign(Enum):
LEFT = auto()
RIGHT = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Valign(Enum):
"""Text Vertical Alignment"""
@ -233,6 +253,9 @@ class Valign(Enum):
TOP = auto()
BOTTOM = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Until(Enum):
"""Extrude limit"""
@ -240,27 +263,22 @@ class Until(Enum):
NEXT = auto()
LAST = auto()
class Axis(Enum):
"""One of the three dimensions"""
X = auto()
Y = auto()
Z = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class SortBy(Enum):
"""Sorting criteria"""
X = auto()
Y = auto()
Z = auto()
LENGTH = auto()
RADIUS = auto()
AREA = auto()
VOLUME = auto()
DISTANCE = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
class Type(Enum):
"""CAD object type"""
@ -282,6 +300,9 @@ class Type(Enum):
PARABOLA = auto()
OTHER = auto()
def __repr__(self):
return "<%s.%s>" % (self.__class__.__name__, self.name)
def validate_inputs(validating_class, builder_context, objects=[]):
"""Validate that objects/operations and parameters apply"""
@ -355,15 +376,164 @@ RotationLike = Union[tuple[float, float, float], Rotation]
PlaneLike = Union[str, Plane]
class Axis:
"""Axis defined by point and direction"""
@classmethod
@property
def X(self) -> Axis:
return Axis((0, 0, 0), (1, 0, 0))
@classmethod
@property
def Y(self) -> Axis:
return Axis((0, 0, 0), (0, 1, 0))
@classmethod
@property
def Z(self) -> Axis:
return Axis((0, 0, 0), (0, 0, 1))
def __init__(self, origin: VectorLike, direction: VectorLike):
self.wrapped = gp_Ax1(
Vector(origin).toPnt(), gp_Dir(*Vector(direction).normalized().toTuple())
)
self.position = Vector(
self.wrapped.Location().X(),
self.wrapped.Location().Y(),
self.wrapped.Location().Z(),
)
self.direction = Vector(
self.wrapped.Direction().X(),
self.wrapped.Direction().Y(),
self.wrapped.Direction().Z(),
)
@classmethod
def from_occt(cls, axis: gp_Ax1) -> Axis:
"""Create an Axis instance from the occt object"""
position = (
axis.Location().X(),
axis.Location().Y(),
axis.Location().Z(),
)
direction = (
axis.Direction().X(),
axis.Direction().Y(),
axis.Direction().Z(),
)
return Axis(position, direction)
def __repr__(self) -> str:
return f"({self.position.toTuple()},{self.direction.toTuple()})"
def __str__(self) -> str:
return f"Axis: ({self.position.toTuple()},{self.direction.toTuple()})"
def copy(self) -> Axis:
"""Return copy of self"""
# Doesn't support sub-classing
return Axis(self.position, self.direction)
def to_location(self) -> Location:
"""Return self as Location"""
return Location(Plane(origin=self.position, normal=self.direction))
def to_plane(self) -> Plane:
"""Return self as Plane"""
return Plane(origin=self.position, normal=self.direction)
def is_coaxial(
self,
other: Axis,
angular_tolerance: float = 1e-5,
linear_tolerance: float = 1e-5,
) -> bool:
"""are axes coaxial
True if the angle between self and other is lower or equal to angular_tolerance and
the distance between self and other is lower or equal to linear_tolerance.
Args:
other (Axis): axis to compare to
angular_tolerance (float, optional): max angular deviation. Defaults to 1e-5.
linear_tolerance (float, optional): max linear deviation. Defaults to 1e-5.
Returns:
bool: axes are coaxial
"""
return self.wrapped.IsCoaxial(
other.wrapped, angular_tolerance * (pi / 180), linear_tolerance
)
def is_normal(self, other: Axis, angular_tolerance: float = 1e-5) -> bool:
"""are axes normal
Returns True if the direction of this and another axis are normal to each other. That is,
if the angle between the two axes is equal to 90° within the angular_tolerance.
Args:
other (Axis): axis to compare to
angular_tolerance (float, optional): max angular deviation. Defaults to 1e-5.
Returns:
bool: axes are normal
"""
return self.wrapped.IsNormal(other.wrapped, angular_tolerance * (pi / 180))
def is_opposite(self, other: Axis, angular_tolerance: float = 1e-5) -> bool:
"""are axes opposite
Returns True if the direction of this and another axis are parallel with opposite orientation.
That is, if the angle between the two axes is equal to 180° within the angular_tolerance.
Args:
other (Axis): axis to compare to
angular_tolerance (float, optional): max angular deviation. Defaults to 1e-5.
Returns:
bool: axes are opposite
"""
return self.wrapped.IsOpposite(other.wrapped, angular_tolerance * (pi / 180))
def is_parallel(self, other: Axis, angular_tolerance: float = 1e-5) -> bool:
"""are axes parallel
Returns True if the direction of this and another axis are parallel with same
orientation or opposite orientation. That is, if the angle between the two axes is
equal to 0° or 180° within the angular_tolerance.
Args:
other (Axis): axis to compare to
angular_tolerance (float, optional): max angular deviation. Defaults to 1e-5.
Returns:
bool: axes are parallel
"""
return self.wrapped.IsParallel(other.wrapped, angular_tolerance * (pi / 180))
def angle_between(self, other: Axis) -> float:
"""calculate angle between axes
Computes the angular value, in degrees, between the direction of self and other
between 0° and 360°.
Args:
other (Axis): axis to compare to
Returns:
float: angle between axes
"""
return self.wrapped.Angle(other.wrapped) * 180 / pi
def reversed(self) -> Axis:
"""Return a copy of self with the direction reversed"""
return Axis.from_occt(self.wrapped.Reversed())
class ShapeList(list):
"""Subclass of list with custom filter and sort methods appropriate to CAD"""
axis_map = {
Axis.X: ((1, 0, 0), (-1, 0, 0)),
Axis.Y: ((0, 1, 0), (0, -1, 0)),
Axis.Z: ((0, 0, 1), (0, 0, -1)),
}
def __init_subclass__(cls) -> None:
super().__init_subclass__()
@ -388,40 +558,26 @@ class ShapeList(list):
lambda o: isinstance(o, Edge) and o.geomType() == "LINE", self
)
result = []
result = list(
filter(
lambda o: (
o.normalAt(None) - Vector(*ShapeList.axis_map[axis][0])
).Length
<= tolerance
or (o.normalAt(None) - Vector(*ShapeList.axis_map[axis][1])).Length
<= tolerance,
lambda o: axis.is_parallel(
Axis(o.Center(), o.normalAt(None)), tolerance
),
planar_faces,
)
)
result.extend(
list(
filter(
lambda o: (
o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][0])
).Length
<= tolerance
or (o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][1])).Length
<= tolerance,
lambda o: axis.is_parallel(
Axis(o.positionAt(0), o.tangentAt(0)), tolerance
),
linear_edges,
)
)
)
if axis == Axis.X:
result = sorted(result, key=lambda obj: obj.Center().x)
elif axis == Axis.Y:
result = sorted(result, key=lambda obj: obj.Center().y)
elif axis == Axis.Z:
result = sorted(result, key=lambda obj: obj.Center().z)
return ShapeList(result)
return ShapeList(result).sort_by(axis)
def filter_by_position(
self,
@ -444,38 +600,25 @@ class ShapeList(list):
Returns:
ShapeList: filtered object list
"""
if axis == Axis.X:
if inclusive == (True, True):
result = filter(lambda o: min <= o.Center().x <= max, self)
elif inclusive == (True, False):
result = filter(lambda o: min <= o.Center().x < max, self)
elif inclusive == (False, True):
result = filter(lambda o: min < o.Center().x <= max, self)
elif inclusive == (False, False):
result = filter(lambda o: min < o.Center().x < max, self)
result = sorted(result, key=lambda obj: obj.Center().x)
elif axis == Axis.Y:
if inclusive == (True, True):
result = filter(lambda o: min <= o.Center().y <= max, self)
elif inclusive == (True, False):
result = filter(lambda o: min <= o.Center().y < max, self)
elif inclusive == (False, True):
result = filter(lambda o: min < o.Center().y <= max, self)
elif inclusive == (False, False):
result = filter(lambda o: min < o.Center().y < max, self)
result = sorted(result, key=lambda obj: obj.Center().y)
elif axis == Axis.Z:
if inclusive == (True, True):
result = filter(lambda o: min <= o.Center().z <= max, self)
elif inclusive == (True, False):
result = filter(lambda o: min <= o.Center().z < max, self)
elif inclusive == (False, True):
result = filter(lambda o: min < o.Center().z <= max, self)
elif inclusive == (False, False):
result = filter(lambda o: min < o.Center().z < max, self)
result = sorted(result, key=lambda obj: obj.Center().z)
if inclusive == (True, True):
objects = filter(
lambda o: min <= axis.to_plane().toLocalCoords(o).Center().z <= max,
self,
)
elif inclusive == (True, False):
objects = filter(
lambda o: min <= axis.to_plane().toLocalCoords(o).Center().z < max, self
)
elif inclusive == (False, True):
objects = filter(
lambda o: min < axis.to_plane().toLocalCoords(o).Center().z <= max, self
)
elif inclusive == (False, False):
objects = filter(
lambda o: min < axis.to_plane().toLocalCoords(o).Center().z < max, self
)
return ShapeList(result)
return ShapeList(objects).sort_by(axis)
def filter_by_type(
self,
@ -495,7 +638,7 @@ class ShapeList(list):
result = filter(lambda o: o.geomType() == type.name, self)
return ShapeList(result)
def sort_by(self, sort_by: SortBy = SortBy.Z, reverse: bool = False):
def sort_by(self, sort_by: Union[Axis, SortBy] = Axis.Z, reverse: bool = False):
"""sort by
Sort objects by provided criteria. Note that not all sort_by criteria apply to all
@ -508,57 +651,73 @@ class ShapeList(list):
Returns:
ShapeList: sorted list of objects
"""
if sort_by == SortBy.X:
if isinstance(sort_by, Axis):
objects = sorted(
self,
key=lambda obj: obj.Center().x,
reverse=reverse,
)
elif sort_by == SortBy.Y:
objects = sorted(
self,
key=lambda obj: obj.Center().y,
reverse=reverse,
)
elif sort_by == SortBy.Z:
objects = sorted(
self,
key=lambda obj: obj.Center().z,
reverse=reverse,
)
elif sort_by == SortBy.LENGTH:
objects = sorted(
self,
key=lambda obj: obj.Length(),
reverse=reverse,
)
elif sort_by == SortBy.RADIUS:
objects = sorted(
self,
key=lambda obj: obj.radius(),
reverse=reverse,
)
elif sort_by == SortBy.DISTANCE:
objects = sorted(
self,
key=lambda obj: obj.Center().Length,
reverse=reverse,
)
elif sort_by == SortBy.AREA:
objects = sorted(
self,
key=lambda obj: obj.Area(),
reverse=reverse,
)
elif sort_by == SortBy.VOLUME:
objects = sorted(
self,
key=lambda obj: obj.Volume(),
key=lambda o: sort_by.to_plane().toLocalCoords(o).Center().z,
reverse=reverse,
)
elif isinstance(sort_by, SortBy):
if sort_by == SortBy.LENGTH:
objects = sorted(
self,
key=lambda obj: obj.Length(),
reverse=reverse,
)
elif sort_by == SortBy.RADIUS:
objects = sorted(
self,
key=lambda obj: obj.radius(),
reverse=reverse,
)
elif sort_by == SortBy.DISTANCE:
objects = sorted(
self,
key=lambda obj: obj.Center().Length,
reverse=reverse,
)
elif sort_by == SortBy.AREA:
objects = sorted(
self,
key=lambda obj: obj.Area(),
reverse=reverse,
)
elif sort_by == SortBy.VOLUME:
objects = sorted(
self,
key=lambda obj: obj.Volume(),
reverse=reverse,
)
else:
raise ValueError(f"Sort by {type(sort_by)} unsupported")
return ShapeList(objects)
def __gt__(self, sort_by: Union[Axis, SortBy] = Axis.Z):
"""Sort operator"""
return self.sort_by(sort_by)
def __lt__(self, sort_by: Union[Axis, SortBy] = Axis.Z):
"""Reverse sort operator"""
return self.sort_by(sort_by, reverse=True)
def __rshift__(self, sort_by: Union[Axis, SortBy] = Axis.Z):
"""Sort and select largest element operator"""
return self.sort_by(sort_by)[-1]
def __lshift__(self, sort_by: Union[Axis, SortBy] = Axis.Z):
"""Sort and select smallest element operator"""
return self.sort_by(sort_by)[0]
def __or__(self, axis: Axis = Axis.Z):
"""Filter by axis operator"""
return self.filter_by_axis(axis)
def __mod__(self, type: Type):
"""Filter by type operator"""
return self.filter_by_type(type)
def _vertices(self: Shape) -> ShapeList[Vertex]:
"""Return ShapeList of Vertex in self"""

View file

@ -623,8 +623,7 @@ class Revolve(Compound):
Args:
profiles (Face, optional): sequence of 2D profile to revolve.
axis_origin (VectorLike, optional): axis start in local coordinates. Defaults to (0, 0, 0).
axis_direction (VectorLike, optional): axis direction. Defaults to (0, 1, 0).
axis (Axis): axis of rotation.
revolution_arc (float, optional): angular size of revolution. Defaults to 360.0.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
@ -635,8 +634,7 @@ class Revolve(Compound):
def __init__(
self,
*profiles: Face,
axis_origin: VectorLike,
axis_direction: VectorLike,
axis: Axis,
revolution_arc: float = 360.0,
mode: Mode = Mode.ADD,
):
@ -653,27 +651,23 @@ class Revolve(Compound):
profiles = context.pending_faces
context.pending_faces = []
axis_origin = Vector(axis_origin)
axis_direction = Vector(axis_direction)
self.profiles = profiles
self.axis_origin = axis_origin
self.axis_direction = axis_direction
self.axis = axis
self.revolution_arc = revolution_arc
self.mode = mode
new_solids = []
for profile in profiles:
# axis_origin must be on the same plane as profile
# axis origin must be on the same plane as profile
face_occt_pln = gp_Pln(
profile.Center().toPnt(), profile.normalAt(profile.Center()).toDir()
)
if not face_occt_pln.Contains(axis_origin.toPnt(), 1e-5):
if not face_occt_pln.Contains(axis.position.toPnt(), 1e-5):
raise ValueError(
"axis_origin must be on the same plane as the face to revolve"
"axis origin must be on the same plane as the face to revolve"
)
if not face_occt_pln.Contains(
gp_Lin(axis_origin.toPnt(), axis_direction.toDir()), 1e-5, 1e-5
gp_Lin(axis.position.toPnt(), axis.direction.toDir()), 1e-5, 1e-5
):
raise ValueError(
"axis must be in the same plane as the face to revolve"
@ -682,8 +676,8 @@ class Revolve(Compound):
new_solid = Solid.revolve(
profile,
angle,
axis_origin,
axis_origin + axis_direction,
axis.position,
axis.position + axis.direction,
)
new_solids.extend(
[

View file

@ -300,7 +300,7 @@ class Circle(Compound):
0 if centered[0] else radius,
0 if centered[1] else radius,
)
face = Face.makeFromWires(Wire.makeCircle(radius, *z_axis)).moved(
face = Face.makeFromWires(Wire.makeCircle(radius, (0, 0, 0), (0, 0, 1))).moved(
Location(center_offset)
)
new_faces = [
@ -381,7 +381,9 @@ class Polygon(Compound):
context: BuildSketch = BuildSketch._get_context()
validate_inputs(self, context)
poly_pts = [Vector(p) for p in pts]
face = Face.makeFromWires(Wire.makePolygon(poly_pts)).rotate(*z_axis, rotation)
face = Face.makeFromWires(Wire.makePolygon(poly_pts)).rotate(
(0, 0, 0), (0, 0, 1), rotation
)
bounding_box = face.BoundingBox()
center_offset = Vector(
0 if centered[0] else bounding_box.xlen / 2,
@ -420,7 +422,7 @@ class Rectangle(Compound):
context: BuildSketch = BuildSketch._get_context()
validate_inputs(self, context)
face = Face.makePlane(height, width).rotate(*z_axis, rotation)
face = Face.makePlane(height, width).rotate((0, 0, 0), (0, 0, 1), rotation)
bounding_box = face.BoundingBox()
center_offset = Vector(
0 if centered[0] else bounding_box.xlen / 2,
@ -466,7 +468,9 @@ class RegularPolygon(Compound):
)
for i in range(side_count + 1)
]
face = Face.makeFromWires(Wire.makePolygon(pts)).rotate(*z_axis, rotation)
face = Face.makeFromWires(Wire.makePolygon(pts)).rotate(
(0, 0, 0), (0, 0, 1), rotation
)
bounding_box = face.BoundingBox()
center_offset = Vector(
0 if centered[0] else bounding_box.xlen / 2,
@ -509,7 +513,9 @@ class SlotArc(Compound):
if isinstance(arc, Edge):
raise ValueError("Bug - Edges aren't supported by offset")
# arc_wire = arc if isinstance(arc, Wire) else Wire.assembleEdges([arc])
face = Face.makeFromWires(arc.offset2D(height / 2)[0]).rotate(*z_axis, rotation)
face = Face.makeFromWires(arc.offset2D(height / 2)[0]).rotate(
(0, 0, 0), (0, 0, 1), rotation
)
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
@ -553,7 +559,7 @@ class SlotCenterPoint(Compound):
Edge.makeLine(center_v, center_v - half_line),
]
)[0].offset2D(height / 2)[0]
).rotate(*z_axis, rotation)
).rotate((0, 0, 0), (0, 0, 1), rotation)
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
@ -591,7 +597,7 @@ class SlotCenterToCenter(Compound):
Edge.makeLine(Vector(), Vector(+center_separation / 2, 0, 0)),
]
).offset2D(height / 2)[0]
).rotate(*z_axis, rotation)
).rotate((0, 0, 0), (0, 0, 1), rotation)
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
@ -628,7 +634,7 @@ class SlotOverall(Compound):
Edge.makeLine(Vector(), Vector(+width / 2 - height / 2, 0, 0)),
]
).offset2D(height / 2)[0]
).rotate(*z_axis, rotation)
).rotate((0, 0, 0), (0, 0, 1), rotation)
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
@ -741,7 +747,9 @@ class Trapezoid(Compound):
)
)
pts.append(pts[0])
face = Face.makeFromWires(Wire.makePolygon(pts)).rotate(*z_axis, rotation)
face = Face.makeFromWires(Wire.makePolygon(pts)).rotate(
(0, 0, 0), (0, 0, 1), rotation
)
bounding_box = face.BoundingBox()
center_offset = Vector(
0 if centered[0] else bounding_box.xlen / 2,

View file

@ -58,9 +58,10 @@ class TestProperties(unittest.TestCase):
self.assertTupleAlmostEquals((v.x, v.y, v.z), (1, 2, 3), 5)
def test_vector_properties(self):
v = Vector(1,2,3)
v = Vector(1, 2, 3)
self.assertTupleAlmostEquals((v.X, v.Y, v.Z), (1, 2, 3), 5)
class TestRotation(unittest.TestCase):
"""Test the Rotation derived class of Location"""
@ -209,24 +210,24 @@ class TestShapeList(unittest.TestCase):
self.assertEqual(edges[0].radius(), 0.5)
self.assertEqual(edges[-1].radius(), 1)
with self.subTest(sort_by=SortBy.X):
with self.subTest(sort_by="X"):
with BuildPart() as test:
Box(1, 1, 1)
edges = test.edges().sort_by(SortBy.X)
edges = test.edges() > Axis.X
self.assertEqual(edges[0].Center().x, -0.5)
self.assertEqual(edges[-1].Center().x, 0.5)
with self.subTest(sort_by=SortBy.Y):
with self.subTest(sort_by="Y"):
with BuildPart() as test:
Box(1, 1, 1)
edges = test.edges().sort_by(SortBy.Y)
edges = test.edges() > Axis.Y
self.assertEqual(edges[0].Center().y, -0.5)
self.assertEqual(edges[-1].Center().y, 0.5)
with self.subTest(sort_by=SortBy.Z):
with self.subTest(sort_by="Z"):
with BuildPart() as test:
Box(1, 1, 1)
edges = test.edges().sort_by(SortBy.Z)
edges = test.edges() > Axis.Z
self.assertEqual(edges[0].Center().z, -0.5)
self.assertEqual(edges[-1].Center().z, 0.5)

View file

@ -160,7 +160,7 @@ class TestOffset(unittest.TestCase):
Box(10, 10, 10)
Offset(
amount=-1,
openings=test.faces().sort_by()[0],
openings=test.faces() >> Axis.Z,
kind=Kind.INTERSECTION,
)
self.assertAlmostEqual(test.part.Volume(), 10**3 - 8**2 * 9, 5)
@ -173,7 +173,7 @@ class BoundingBoxTests(unittest.TestCase):
Circle(10)
with BuildSketch(mode=Mode.PRIVATE) as bb:
BoundingBox(*mickey.faces())
ears = bb.vertices().sort_by(SortBy.Y)[:-2]
ears = (bb.vertices() > Axis.Y)[:-2]
with Locations(*ears):
Circle(7)
self.assertAlmostEqual(mickey.sketch.Area(), 586.1521145312807, 5)

View file

@ -296,7 +296,7 @@ class TestRevolve(unittest.TestCase):
l1 @ 0,
)
BuildFace()
Revolve(axis_origin=(0, 0, 0), axis_direction=(0, 1, 0))
Revolve(axis=Axis.Y)
self.assertLess(test.part.Volume(), 22**2 * pi * 50, 5)
self.assertGreater(test.part.Volume(), 144 * pi * 50, 5)
@ -309,7 +309,7 @@ class TestRevolve(unittest.TestCase):
l3 = Line(l2 @ 1, (20, 0))
l4 = Line(l3 @ 1, l1 @ 0)
BuildFace()
Revolve(axis_origin=(0, 0, 0), axis_direction=(1, 0, 0))
Revolve(axis=Axis.X)
self.assertLess(test.part.Volume(), 244 * pi * 20, 5)
self.assertGreater(test.part.Volume(), 100 * pi * 20, 5)
@ -318,14 +318,14 @@ class TestRevolve(unittest.TestCase):
with BuildSketch():
Rectangle(1, 1, centered=(False, False))
with self.assertRaises(ValueError):
Revolve(axis_origin=(1, 1, 1), axis_direction=(0, 1, 0))
Revolve(axis=Axis((1, 1, 1), (0, 1, 0)))
def test_invalid_axis_direction(self):
with BuildPart():
with BuildSketch():
Rectangle(1, 1, centered=(False, False))
with self.assertRaises(ValueError):
Revolve(axis_origin=(0, 0, 0), axis_direction=(0, 0, 1))
Revolve(axis=Axis.Z)
class TestSection(unittest.TestCase):