mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Introduced Axis Class and Sort/Filter Operators
This commit is contained in:
parent
b37945f5ab
commit
b50d6d5fe1
11 changed files with 326 additions and 163 deletions
|
|
@ -29,12 +29,12 @@ import cadquery as cq
|
||||||
|
|
||||||
with BuildSketch() as logo_text:
|
with BuildSketch() as logo_text:
|
||||||
Text("123d", fontsize=10, valign=Valign.BOTTOM)
|
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:
|
with BuildSketch() as build_text:
|
||||||
Text("build", fontsize=5, halign=Halign.CENTER)
|
Text("build", fontsize=5, halign=Halign.CENTER)
|
||||||
build_bb = BoundingBox(build_text.sketch, mode=Mode.PRIVATE)
|
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
|
build_width = build_vertices[-1].x - build_vertices[0].x
|
||||||
|
|
||||||
with BuildLine() as one:
|
with BuildLine() as one:
|
||||||
|
|
@ -50,7 +50,7 @@ with BuildPart() as three_d:
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
Text("3d", fontsize=10, valign=Valign.BOTTOM)
|
Text("3d", fontsize=10, valign=Valign.BOTTOM)
|
||||||
Extrude(amount=font_height * 0.3)
|
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:
|
with BuildLine() as arrow_left:
|
||||||
t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))
|
t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ with BuildPart() as single_multiple:
|
||||||
with BuildPart() as non_planar:
|
with BuildPart() as non_planar:
|
||||||
Cylinder(10, 20, rotation=(90, 0, 0), centered=(True, False, True))
|
Cylinder(10, 20, rotation=(90, 0, 0), centered=(True, False, True))
|
||||||
Box(10, 10, 10, centered=(True, True, False), mode=Mode.INTERSECT)
|
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
|
# Taper Extrude and Extrude to "next" while creating a Cherry MX key cap
|
||||||
# See: https://www.cherrymx.de/en/dev.html
|
# See: https://www.cherrymx.de/en/dev.html
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ with BuildPart() as recessed_counter_sink:
|
||||||
with BuildPart() as flush_counter_sink:
|
with BuildPart() as flush_counter_sink:
|
||||||
with Locations((10, 10)):
|
with Locations((10, 10)):
|
||||||
Cylinder(radius=3, height=2)
|
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)
|
CounterSinkHole(radius=1, counter_sink_radius=1.5)
|
||||||
|
|
||||||
if "show_object" in locals():
|
if "show_object" in locals():
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ desc:
|
||||||
|
|
||||||
This example creates a model of a double wide lego block with a
|
This example creates a model of a double wide lego block with a
|
||||||
parametric length (pip_count).
|
parametric length (pip_count).
|
||||||
|
*** Don't edit this file without checking the lego tutorial ***
|
||||||
|
|
||||||
license:
|
license:
|
||||||
|
|
||||||
|
|
@ -26,7 +27,6 @@ license:
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
from build123d import *
|
from build123d import *
|
||||||
from cadquery import Plane
|
|
||||||
|
|
||||||
pip_count = 6
|
pip_count = 6
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ with BuildPart() as lego:
|
||||||
wall_thickness,
|
wall_thickness,
|
||||||
centered=(True, True, False),
|
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):
|
with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):
|
||||||
Cylinder(
|
Cylinder(
|
||||||
radius=pip_diameter / 2, height=pip_height, centered=(True, True, False)
|
radius=pip_diameter / 2, height=pip_height, centered=(True, True, False)
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,14 @@ with BuildPart() as vase:
|
||||||
l1 @ 0,
|
l1 @ 0,
|
||||||
)
|
)
|
||||||
BuildFace()
|
BuildFace()
|
||||||
Revolve(axis_origin=(0, 0, 0), axis_direction=(0, 1, 0))
|
Revolve(axis=Axis.Y)
|
||||||
Offset(openings=vase.faces().filter_by_axis(Axis.Y)[-1], amount=-1)
|
# Offset(openings=vase.faces().filter_by_axis(Axis.Y)[-1], amount=-1)
|
||||||
|
Offset(openings=(vase.faces() | Axis.Y) >> Axis.Y, amount=-1)
|
||||||
top_edges = (
|
top_edges = (
|
||||||
vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by_type(Type.CIRCLE)
|
vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by_type(Type.CIRCLE)
|
||||||
)
|
)
|
||||||
Fillet(*top_edges, radius=0.25)
|
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():
|
if "show_object" in locals():
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,11 @@ license:
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
import contextvars
|
import contextvars
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from math import radians, sqrt
|
from math import radians, sqrt, pi
|
||||||
from typing import Iterable, Union
|
from typing import Iterable, Union
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from cadquery import (
|
from cadquery import (
|
||||||
|
|
@ -104,8 +105,6 @@ Vector.X = property(_vector_x)
|
||||||
Vector.Y = property(_vector_y)
|
Vector.Y = property(_vector_y)
|
||||||
Vector.Z = property(_vector_z)
|
Vector.Z = property(_vector_z)
|
||||||
|
|
||||||
z_axis = (Vector(0, 0, 0), Vector(0, 0, 1))
|
|
||||||
|
|
||||||
|
|
||||||
def vertex_eq_(self: Vertex, other: Vertex) -> bool:
|
def vertex_eq_(self: Vertex, other: Vertex) -> bool:
|
||||||
"""True if the distance between the two vertices is lower than their tolerance"""
|
"""True if the distance between the two vertices is lower than their tolerance"""
|
||||||
|
|
@ -175,6 +174,9 @@ class Select(Enum):
|
||||||
ALL = auto()
|
ALL = auto()
|
||||||
LAST = auto()
|
LAST = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Kind(Enum):
|
class Kind(Enum):
|
||||||
"""Offset corner transition"""
|
"""Offset corner transition"""
|
||||||
|
|
@ -183,6 +185,9 @@ class Kind(Enum):
|
||||||
INTERSECTION = auto()
|
INTERSECTION = auto()
|
||||||
TANGENT = auto()
|
TANGENT = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Keep(Enum):
|
class Keep(Enum):
|
||||||
"""Split options"""
|
"""Split options"""
|
||||||
|
|
@ -191,6 +196,9 @@ class Keep(Enum):
|
||||||
BOTTOM = auto()
|
BOTTOM = auto()
|
||||||
BOTH = auto()
|
BOTH = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Mode(Enum):
|
class Mode(Enum):
|
||||||
"""Combination Mode"""
|
"""Combination Mode"""
|
||||||
|
|
@ -201,6 +209,9 @@ class Mode(Enum):
|
||||||
REPLACE = auto()
|
REPLACE = auto()
|
||||||
PRIVATE = auto()
|
PRIVATE = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Transition(Enum):
|
class Transition(Enum):
|
||||||
"""Sweep discontinuity handling option"""
|
"""Sweep discontinuity handling option"""
|
||||||
|
|
@ -209,6 +220,9 @@ class Transition(Enum):
|
||||||
ROUND = auto()
|
ROUND = auto()
|
||||||
TRANSFORMED = auto()
|
TRANSFORMED = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class FontStyle(Enum):
|
class FontStyle(Enum):
|
||||||
"""Text Font Styles"""
|
"""Text Font Styles"""
|
||||||
|
|
@ -217,6 +231,9 @@ class FontStyle(Enum):
|
||||||
BOLD = auto()
|
BOLD = auto()
|
||||||
ITALIC = auto()
|
ITALIC = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Halign(Enum):
|
class Halign(Enum):
|
||||||
"""Text Horizontal Alignment"""
|
"""Text Horizontal Alignment"""
|
||||||
|
|
@ -225,6 +242,9 @@ class Halign(Enum):
|
||||||
LEFT = auto()
|
LEFT = auto()
|
||||||
RIGHT = auto()
|
RIGHT = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Valign(Enum):
|
class Valign(Enum):
|
||||||
"""Text Vertical Alignment"""
|
"""Text Vertical Alignment"""
|
||||||
|
|
@ -233,6 +253,9 @@ class Valign(Enum):
|
||||||
TOP = auto()
|
TOP = auto()
|
||||||
BOTTOM = auto()
|
BOTTOM = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Until(Enum):
|
class Until(Enum):
|
||||||
"""Extrude limit"""
|
"""Extrude limit"""
|
||||||
|
|
@ -240,27 +263,22 @@ class Until(Enum):
|
||||||
NEXT = auto()
|
NEXT = auto()
|
||||||
LAST = auto()
|
LAST = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
class Axis(Enum):
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
"""One of the three dimensions"""
|
|
||||||
|
|
||||||
X = auto()
|
|
||||||
Y = auto()
|
|
||||||
Z = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class SortBy(Enum):
|
class SortBy(Enum):
|
||||||
"""Sorting criteria"""
|
"""Sorting criteria"""
|
||||||
|
|
||||||
X = auto()
|
|
||||||
Y = auto()
|
|
||||||
Z = auto()
|
|
||||||
LENGTH = auto()
|
LENGTH = auto()
|
||||||
RADIUS = auto()
|
RADIUS = auto()
|
||||||
AREA = auto()
|
AREA = auto()
|
||||||
VOLUME = auto()
|
VOLUME = auto()
|
||||||
DISTANCE = auto()
|
DISTANCE = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
class Type(Enum):
|
class Type(Enum):
|
||||||
"""CAD object type"""
|
"""CAD object type"""
|
||||||
|
|
@ -282,6 +300,9 @@ class Type(Enum):
|
||||||
PARABOLA = auto()
|
PARABOLA = auto()
|
||||||
OTHER = auto()
|
OTHER = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s.%s>" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
|
|
||||||
def validate_inputs(validating_class, builder_context, objects=[]):
|
def validate_inputs(validating_class, builder_context, objects=[]):
|
||||||
"""Validate that objects/operations and parameters apply"""
|
"""Validate that objects/operations and parameters apply"""
|
||||||
|
|
@ -355,15 +376,164 @@ RotationLike = Union[tuple[float, float, float], Rotation]
|
||||||
PlaneLike = Union[str, Plane]
|
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):
|
class ShapeList(list):
|
||||||
"""Subclass of list with custom filter and sort methods appropriate to CAD"""
|
"""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:
|
def __init_subclass__(cls) -> None:
|
||||||
super().__init_subclass__()
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
|
@ -388,40 +558,26 @@ class ShapeList(list):
|
||||||
lambda o: isinstance(o, Edge) and o.geomType() == "LINE", self
|
lambda o: isinstance(o, Edge) and o.geomType() == "LINE", self
|
||||||
)
|
)
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
result = list(
|
result = list(
|
||||||
filter(
|
filter(
|
||||||
lambda o: (
|
lambda o: axis.is_parallel(
|
||||||
o.normalAt(None) - Vector(*ShapeList.axis_map[axis][0])
|
Axis(o.Center(), o.normalAt(None)), tolerance
|
||||||
).Length
|
),
|
||||||
<= tolerance
|
|
||||||
or (o.normalAt(None) - Vector(*ShapeList.axis_map[axis][1])).Length
|
|
||||||
<= tolerance,
|
|
||||||
planar_faces,
|
planar_faces,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.extend(
|
result.extend(
|
||||||
list(
|
list(
|
||||||
filter(
|
filter(
|
||||||
lambda o: (
|
lambda o: axis.is_parallel(
|
||||||
o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][0])
|
Axis(o.positionAt(0), o.tangentAt(0)), tolerance
|
||||||
).Length
|
),
|
||||||
<= tolerance
|
|
||||||
or (o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][1])).Length
|
|
||||||
<= tolerance,
|
|
||||||
linear_edges,
|
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(
|
def filter_by_position(
|
||||||
self,
|
self,
|
||||||
|
|
@ -444,38 +600,25 @@ class ShapeList(list):
|
||||||
Returns:
|
Returns:
|
||||||
ShapeList: filtered object list
|
ShapeList: filtered object list
|
||||||
"""
|
"""
|
||||||
if axis == Axis.X:
|
if inclusive == (True, True):
|
||||||
if inclusive == (True, True):
|
objects = filter(
|
||||||
result = filter(lambda o: min <= o.Center().x <= max, self)
|
lambda o: min <= axis.to_plane().toLocalCoords(o).Center().z <= max,
|
||||||
elif inclusive == (True, False):
|
self,
|
||||||
result = filter(lambda o: min <= o.Center().x < max, self)
|
)
|
||||||
elif inclusive == (False, True):
|
elif inclusive == (True, False):
|
||||||
result = filter(lambda o: min < o.Center().x <= max, self)
|
objects = filter(
|
||||||
elif inclusive == (False, False):
|
lambda o: min <= axis.to_plane().toLocalCoords(o).Center().z < max, self
|
||||||
result = filter(lambda o: min < o.Center().x < max, self)
|
)
|
||||||
result = sorted(result, key=lambda obj: obj.Center().x)
|
elif inclusive == (False, True):
|
||||||
elif axis == Axis.Y:
|
objects = filter(
|
||||||
if inclusive == (True, True):
|
lambda o: min < axis.to_plane().toLocalCoords(o).Center().z <= max, self
|
||||||
result = filter(lambda o: min <= o.Center().y <= max, self)
|
)
|
||||||
elif inclusive == (True, False):
|
elif inclusive == (False, False):
|
||||||
result = filter(lambda o: min <= o.Center().y < max, self)
|
objects = filter(
|
||||||
elif inclusive == (False, True):
|
lambda o: min < axis.to_plane().toLocalCoords(o).Center().z < max, self
|
||||||
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)
|
|
||||||
|
|
||||||
return ShapeList(result)
|
return ShapeList(objects).sort_by(axis)
|
||||||
|
|
||||||
def filter_by_type(
|
def filter_by_type(
|
||||||
self,
|
self,
|
||||||
|
|
@ -495,7 +638,7 @@ class ShapeList(list):
|
||||||
result = filter(lambda o: o.geomType() == type.name, self)
|
result = filter(lambda o: o.geomType() == type.name, self)
|
||||||
return ShapeList(result)
|
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 by
|
||||||
|
|
||||||
Sort objects by provided criteria. Note that not all sort_by criteria apply to all
|
Sort objects by provided criteria. Note that not all sort_by criteria apply to all
|
||||||
|
|
@ -508,57 +651,73 @@ class ShapeList(list):
|
||||||
Returns:
|
Returns:
|
||||||
ShapeList: sorted list of objects
|
ShapeList: sorted list of objects
|
||||||
"""
|
"""
|
||||||
if sort_by == SortBy.X:
|
if isinstance(sort_by, Axis):
|
||||||
objects = sorted(
|
objects = sorted(
|
||||||
self,
|
self,
|
||||||
key=lambda obj: obj.Center().x,
|
key=lambda o: sort_by.to_plane().toLocalCoords(o).Center().z,
|
||||||
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(),
|
|
||||||
reverse=reverse,
|
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)
|
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]:
|
def _vertices(self: Shape) -> ShapeList[Vertex]:
|
||||||
"""Return ShapeList of Vertex in self"""
|
"""Return ShapeList of Vertex in self"""
|
||||||
|
|
|
||||||
|
|
@ -623,8 +623,7 @@ class Revolve(Compound):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
profiles (Face, optional): sequence of 2D profile to revolve.
|
profiles (Face, optional): sequence of 2D profile to revolve.
|
||||||
axis_origin (VectorLike, optional): axis start in local coordinates. Defaults to (0, 0, 0).
|
axis (Axis): axis of rotation.
|
||||||
axis_direction (VectorLike, optional): axis direction. Defaults to (0, 1, 0).
|
|
||||||
revolution_arc (float, optional): angular size of revolution. Defaults to 360.0.
|
revolution_arc (float, optional): angular size of revolution. Defaults to 360.0.
|
||||||
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
|
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
|
||||||
|
|
||||||
|
|
@ -635,8 +634,7 @@ class Revolve(Compound):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*profiles: Face,
|
*profiles: Face,
|
||||||
axis_origin: VectorLike,
|
axis: Axis,
|
||||||
axis_direction: VectorLike,
|
|
||||||
revolution_arc: float = 360.0,
|
revolution_arc: float = 360.0,
|
||||||
mode: Mode = Mode.ADD,
|
mode: Mode = Mode.ADD,
|
||||||
):
|
):
|
||||||
|
|
@ -653,27 +651,23 @@ class Revolve(Compound):
|
||||||
profiles = context.pending_faces
|
profiles = context.pending_faces
|
||||||
context.pending_faces = []
|
context.pending_faces = []
|
||||||
|
|
||||||
axis_origin = Vector(axis_origin)
|
|
||||||
axis_direction = Vector(axis_direction)
|
|
||||||
|
|
||||||
self.profiles = profiles
|
self.profiles = profiles
|
||||||
self.axis_origin = axis_origin
|
self.axis = axis
|
||||||
self.axis_direction = axis_direction
|
|
||||||
self.revolution_arc = revolution_arc
|
self.revolution_arc = revolution_arc
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
|
||||||
new_solids = []
|
new_solids = []
|
||||||
for profile in profiles:
|
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(
|
face_occt_pln = gp_Pln(
|
||||||
profile.Center().toPnt(), profile.normalAt(profile.Center()).toDir()
|
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(
|
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(
|
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(
|
raise ValueError(
|
||||||
"axis must be in the same plane as the face to revolve"
|
"axis must be in the same plane as the face to revolve"
|
||||||
|
|
@ -682,8 +676,8 @@ class Revolve(Compound):
|
||||||
new_solid = Solid.revolve(
|
new_solid = Solid.revolve(
|
||||||
profile,
|
profile,
|
||||||
angle,
|
angle,
|
||||||
axis_origin,
|
axis.position,
|
||||||
axis_origin + axis_direction,
|
axis.position + axis.direction,
|
||||||
)
|
)
|
||||||
new_solids.extend(
|
new_solids.extend(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,7 @@ class Circle(Compound):
|
||||||
0 if centered[0] else radius,
|
0 if centered[0] else radius,
|
||||||
0 if centered[1] 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)
|
Location(center_offset)
|
||||||
)
|
)
|
||||||
new_faces = [
|
new_faces = [
|
||||||
|
|
@ -381,7 +381,9 @@ class Polygon(Compound):
|
||||||
context: BuildSketch = BuildSketch._get_context()
|
context: BuildSketch = BuildSketch._get_context()
|
||||||
validate_inputs(self, context)
|
validate_inputs(self, context)
|
||||||
poly_pts = [Vector(p) for p in pts]
|
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()
|
bounding_box = face.BoundingBox()
|
||||||
center_offset = Vector(
|
center_offset = Vector(
|
||||||
0 if centered[0] else bounding_box.xlen / 2,
|
0 if centered[0] else bounding_box.xlen / 2,
|
||||||
|
|
@ -420,7 +422,7 @@ class Rectangle(Compound):
|
||||||
context: BuildSketch = BuildSketch._get_context()
|
context: BuildSketch = BuildSketch._get_context()
|
||||||
validate_inputs(self, 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()
|
bounding_box = face.BoundingBox()
|
||||||
center_offset = Vector(
|
center_offset = Vector(
|
||||||
0 if centered[0] else bounding_box.xlen / 2,
|
0 if centered[0] else bounding_box.xlen / 2,
|
||||||
|
|
@ -466,7 +468,9 @@ class RegularPolygon(Compound):
|
||||||
)
|
)
|
||||||
for i in range(side_count + 1)
|
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()
|
bounding_box = face.BoundingBox()
|
||||||
center_offset = Vector(
|
center_offset = Vector(
|
||||||
0 if centered[0] else bounding_box.xlen / 2,
|
0 if centered[0] else bounding_box.xlen / 2,
|
||||||
|
|
@ -509,7 +513,9 @@ class SlotArc(Compound):
|
||||||
if isinstance(arc, Edge):
|
if isinstance(arc, Edge):
|
||||||
raise ValueError("Bug - Edges aren't supported by offset")
|
raise ValueError("Bug - Edges aren't supported by offset")
|
||||||
# arc_wire = arc if isinstance(arc, Wire) else Wire.assembleEdges([arc])
|
# 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 = [
|
new_faces = [
|
||||||
face.moved(location) for location in LocationList._get_context().locations
|
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),
|
Edge.makeLine(center_v, center_v - half_line),
|
||||||
]
|
]
|
||||||
)[0].offset2D(height / 2)[0]
|
)[0].offset2D(height / 2)[0]
|
||||||
).rotate(*z_axis, rotation)
|
).rotate((0, 0, 0), (0, 0, 1), rotation)
|
||||||
new_faces = [
|
new_faces = [
|
||||||
face.moved(location) for location in LocationList._get_context().locations
|
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)),
|
Edge.makeLine(Vector(), Vector(+center_separation / 2, 0, 0)),
|
||||||
]
|
]
|
||||||
).offset2D(height / 2)[0]
|
).offset2D(height / 2)[0]
|
||||||
).rotate(*z_axis, rotation)
|
).rotate((0, 0, 0), (0, 0, 1), rotation)
|
||||||
new_faces = [
|
new_faces = [
|
||||||
face.moved(location) for location in LocationList._get_context().locations
|
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)),
|
Edge.makeLine(Vector(), Vector(+width / 2 - height / 2, 0, 0)),
|
||||||
]
|
]
|
||||||
).offset2D(height / 2)[0]
|
).offset2D(height / 2)[0]
|
||||||
).rotate(*z_axis, rotation)
|
).rotate((0, 0, 0), (0, 0, 1), rotation)
|
||||||
new_faces = [
|
new_faces = [
|
||||||
face.moved(location) for location in LocationList._get_context().locations
|
face.moved(location) for location in LocationList._get_context().locations
|
||||||
]
|
]
|
||||||
|
|
@ -741,7 +747,9 @@ class Trapezoid(Compound):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pts.append(pts[0])
|
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()
|
bounding_box = face.BoundingBox()
|
||||||
center_offset = Vector(
|
center_offset = Vector(
|
||||||
0 if centered[0] else bounding_box.xlen / 2,
|
0 if centered[0] else bounding_box.xlen / 2,
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,10 @@ class TestProperties(unittest.TestCase):
|
||||||
self.assertTupleAlmostEquals((v.x, v.y, v.z), (1, 2, 3), 5)
|
self.assertTupleAlmostEquals((v.x, v.y, v.z), (1, 2, 3), 5)
|
||||||
|
|
||||||
def test_vector_properties(self):
|
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)
|
self.assertTupleAlmostEquals((v.X, v.Y, v.Z), (1, 2, 3), 5)
|
||||||
|
|
||||||
|
|
||||||
class TestRotation(unittest.TestCase):
|
class TestRotation(unittest.TestCase):
|
||||||
"""Test the Rotation derived class of Location"""
|
"""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[0].radius(), 0.5)
|
||||||
self.assertEqual(edges[-1].radius(), 1)
|
self.assertEqual(edges[-1].radius(), 1)
|
||||||
|
|
||||||
with self.subTest(sort_by=SortBy.X):
|
with self.subTest(sort_by="X"):
|
||||||
with BuildPart() as test:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
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[0].Center().x, -0.5)
|
||||||
self.assertEqual(edges[-1].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:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
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[0].Center().y, -0.5)
|
||||||
self.assertEqual(edges[-1].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:
|
with BuildPart() as test:
|
||||||
Box(1, 1, 1)
|
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[0].Center().z, -0.5)
|
||||||
self.assertEqual(edges[-1].Center().z, 0.5)
|
self.assertEqual(edges[-1].Center().z, 0.5)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ class TestOffset(unittest.TestCase):
|
||||||
Box(10, 10, 10)
|
Box(10, 10, 10)
|
||||||
Offset(
|
Offset(
|
||||||
amount=-1,
|
amount=-1,
|
||||||
openings=test.faces().sort_by()[0],
|
openings=test.faces() >> Axis.Z,
|
||||||
kind=Kind.INTERSECTION,
|
kind=Kind.INTERSECTION,
|
||||||
)
|
)
|
||||||
self.assertAlmostEqual(test.part.Volume(), 10**3 - 8**2 * 9, 5)
|
self.assertAlmostEqual(test.part.Volume(), 10**3 - 8**2 * 9, 5)
|
||||||
|
|
@ -173,7 +173,7 @@ class BoundingBoxTests(unittest.TestCase):
|
||||||
Circle(10)
|
Circle(10)
|
||||||
with BuildSketch(mode=Mode.PRIVATE) as bb:
|
with BuildSketch(mode=Mode.PRIVATE) as bb:
|
||||||
BoundingBox(*mickey.faces())
|
BoundingBox(*mickey.faces())
|
||||||
ears = bb.vertices().sort_by(SortBy.Y)[:-2]
|
ears = (bb.vertices() > Axis.Y)[:-2]
|
||||||
with Locations(*ears):
|
with Locations(*ears):
|
||||||
Circle(7)
|
Circle(7)
|
||||||
self.assertAlmostEqual(mickey.sketch.Area(), 586.1521145312807, 5)
|
self.assertAlmostEqual(mickey.sketch.Area(), 586.1521145312807, 5)
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@ class TestRevolve(unittest.TestCase):
|
||||||
l1 @ 0,
|
l1 @ 0,
|
||||||
)
|
)
|
||||||
BuildFace()
|
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.assertLess(test.part.Volume(), 22**2 * pi * 50, 5)
|
||||||
self.assertGreater(test.part.Volume(), 144 * 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))
|
l3 = Line(l2 @ 1, (20, 0))
|
||||||
l4 = Line(l3 @ 1, l1 @ 0)
|
l4 = Line(l3 @ 1, l1 @ 0)
|
||||||
BuildFace()
|
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.assertLess(test.part.Volume(), 244 * pi * 20, 5)
|
||||||
self.assertGreater(test.part.Volume(), 100 * pi * 20, 5)
|
self.assertGreater(test.part.Volume(), 100 * pi * 20, 5)
|
||||||
|
|
||||||
|
|
@ -318,14 +318,14 @@ class TestRevolve(unittest.TestCase):
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
Rectangle(1, 1, centered=(False, False))
|
Rectangle(1, 1, centered=(False, False))
|
||||||
with self.assertRaises(ValueError):
|
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):
|
def test_invalid_axis_direction(self):
|
||||||
with BuildPart():
|
with BuildPart():
|
||||||
with BuildSketch():
|
with BuildSketch():
|
||||||
Rectangle(1, 1, centered=(False, False))
|
Rectangle(1, 1, centered=(False, False))
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Revolve(axis_origin=(0, 0, 0), axis_direction=(0, 0, 1))
|
Revolve(axis=Axis.Z)
|
||||||
|
|
||||||
|
|
||||||
class TestSection(unittest.TestCase):
|
class TestSection(unittest.TestCase):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue