Completed docstrings

This commit is contained in:
Roger Maitland 2022-07-11 20:50:36 -04:00
parent da093c25d6
commit 9b6831ee95
2 changed files with 163 additions and 174 deletions

View file

@ -14,55 +14,28 @@ f1.wrapped.TShape() == f2.wrapped.TShape() <=== TRUE
Thanks. Playing around a bit more, it seems like translate() makes the underlying TShapes unequal, but Shape.moved() preserves TShape. This returns true, which could be useful: x1 = cq.Workplane().box(3,4,5) x2 = cq.Workplane(x1.findSolid().moved(cq.Location(cq.Vector(1,2,3),cq.Vector(4,5,6),7))) f1 = x1.faces(">Z").val() f2 = x2.faces(">Z").val() f1.wrapped.TShape() == f2.wrapped.TShape() <=== TRUE
"""
import logging
from functools import partial
from math import pi, sin, cos, radians, sqrt
from pstats import SortKey
from typing import Union, Iterable, Sequence, Callable
import builtins
from math import radians
from typing import Union
from enum import Enum, auto
import cadquery as cq
from cadquery.hull import find_hull
from cadquery import (
Edge,
Face,
Wire,
Vector,
Shape,
Location,
Vertex,
Compound,
Solid,
Plane,
)
from cadquery.occ_impl.shapes import VectorLike, Real
from OCP.gp import gp_Vec, gp_Pnt, gp_Ax1, gp_Dir, gp_Trsf
import cq_warehouse.extensions
class Rotation(Location):
def __init__(self, about_x: float = 0, about_y: float = 0, about_z: float = 0):
self.about_x = about_x
self.about_y = about_y
self.about_z = about_z
# Compute rotation matrix.
rx = gp_Trsf()
rx.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), radians(about_x))
ry = gp_Trsf()
ry.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 1, 0)), radians(about_y))
rz = gp_Trsf()
rz.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), radians(about_z))
super().__init__(Location(rx * ry * rz).wrapped)
RotationLike = Union[tuple[float, float, float], Rotation]
z_axis = (Vector(0, 0, 0), Vector(0, 0, 1))
_context_stack = []
#
# Operators
#
def __matmul__custom(e: Union[Edge, Wire], p: float):
return e.positionAt(p)
@ -75,81 +48,12 @@ Edge.__matmul__ = __matmul__custom
Edge.__mod__ = __mod__custom
Wire.__matmul__ = __matmul__custom
Wire.__mod__ = __mod__custom
line = Edge.makeLine(Vector(0, 0, 0), Vector(10, 0, 0))
# print(f"position of line at 1/2: {line @ 0.5=}")
# print(f"tangent of line at 1/2: {line % 0.5=}")
context_stack = []
def by_x(obj: Shape) -> float:
return obj.Center().x
def _by_x_shape(self) -> float:
return self.Center().x
Shape.by_x = _by_x_shape
def by_y(obj: Shape) -> float:
return obj.Center().y
def _by_y_shape(self) -> float:
return self.Center().y
Shape.by_y = _by_y_shape
def by_z(obj: Shape) -> float:
return obj.Center().z
def _by_z_shape(self) -> float:
return self.Center().z
Shape.by_z = _by_z_shape
def by_length(obj: Union[Edge, Wire]) -> float:
return obj.Length()
def _by_length_edge_or_wire(self) -> float:
return self.Length()
Edge.by_length = _by_length_edge_or_wire
Wire.by_length = _by_length_edge_or_wire
def by_radius(obj: Union[Edge, Wire]) -> float:
return obj.radius()
def _by_radius_edge_or_wire(self) -> float:
return self.radius()
Edge.by_radius = _by_radius_edge_or_wire
Wire.by_radius = _by_radius_edge_or_wire
def by_area(obj: cq.Shape) -> float:
return obj.Area()
def _by_area_shape(self) -> float:
return self.Area()
Shape.by_area = _by_area_shape
#
# ENUMs
#
class Select(Enum):
ALL = auto()
LAST = auto()
@ -232,24 +136,6 @@ class BuildAssembly:
pass
def _null(self):
return self
Solid.null = _null
Compound.null = _null
def pts_to_locations(*pts: Union[Vector, Location]) -> list[Location]:
if pts:
locations = [
pt if isinstance(pt, Location) else Location(Vector(pt)) for pt in pts
]
else:
locations = [Location(Vector())]
return locations
class SortBy(Enum):
X = auto()
Y = auto()
@ -261,6 +147,28 @@ class SortBy(Enum):
DISTANCE = auto()
#
# DirectAPI Classes
#
class Rotation(Location):
def __init__(self, about_x: float = 0, about_y: float = 0, about_z: float = 0):
self.about_x = about_x
self.about_y = about_y
self.about_z = about_z
# Compute rotation matrix.
rx = gp_Trsf()
rx.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), radians(about_x))
ry = gp_Trsf()
ry.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 1, 0)), radians(about_y))
rz = gp_Trsf()
rz.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), radians(about_z))
super().__init__(Location(rx * ry * rz).wrapped)
RotationLike = Union[tuple[float, float, float], Rotation]
class ShapeList(list):
axis_map = {
Axis.X: ((1, 0, 0), (-1, 0, 0)),

View file

@ -6,13 +6,11 @@ TODO:
"""
from math import radians, tan
from typing import Union
import cadquery as cq
from cadquery import (
Edge,
Face,
Wire,
Vector,
Shape,
Location,
Vertex,
Compound,
@ -23,34 +21,43 @@ from cadquery.occ_impl.shapes import VectorLike
import cq_warehouse.extensions
from build123d_common import *
logging.basicConfig(
filename="build123D.log",
encoding="utf-8",
level=logging.DEBUG,
# level=logging.CRITICAL,
format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] - %(message)s",
)
# logging.basicConfig(
# filename="build123D.log",
# encoding="utf-8",
# level=logging.DEBUG,
# # level=logging.CRITICAL,
# format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] - %(message)s",
# )
class BuildPart:
"""BuildPart
Create 3D parts (objects with the property of volume) from sketches or 3D objects.
Args:
mode (Mode, optional): combination mode. Defaults to Mode.ADDITION.
workplane (Plane, optional): initial plane to work on. Defaults to Plane.named("XY").
"""
@property
def workplane_count(self) -> int:
"""Number of active workplanes"""
return len(self.workplanes)
@property
def pending_faces_count(self) -> int:
"""Number of pending faces"""
return len(self.pending_faces.values())
@property
def pending_edges_count(self) -> int:
"""Number of pending edges"""
return len(self.pending_edges.values())
@property
def pending_solids_count(self) -> int:
return len(self.pending_solids.values())
@property
def pending_location_count(self) -> int:
"""Number of current locations"""
return len(self.locations)
def __init__(
@ -60,11 +67,9 @@ class BuildPart:
):
self.part: Compound = None
self.workplanes: list[Plane] = [workplane]
self.locations: list[Location] = [Location(workplane.origin)]
self.pending_faces: dict[int : list[Face]] = {0: []}
self.pending_edges: dict[int : list[Edge]] = {0: []}
self.pending_solids: dict[int : list[Solid]] = {0: []}
self.locations: list[Location] = [Location(workplane.origin)]
self.last_operation: dict[CqObject : list[Shape]] = {}
self.mode = mode
self.last_vertices = []
self.last_edges = []
@ -72,30 +77,57 @@ class BuildPart:
self.last_solids = []
def __enter__(self):
"""Upon entering BuildPart, add current BuildPart instance to context stack"""
context_stack.append(self)
return self
def __exit__(self, exception_type, exception_value, traceback):
pass
"""Upon exiting BuildPart - do nothing"""
def workplane(self, *workplanes: Plane, replace=True):
"""Create Workplane(s)
Add a sequence of planes as workplanes possibly replacing existing workplanes.
Args:
workplanes (Plane): a sequence of planes to add as workplanes
replace (bool, optional): replace existing workplanes. Defaults to True.
"""
if replace:
self.workplanes = []
# self.locations: list[Location] = {0: []}
for plane in workplanes:
self.workplanes.append(plane)
# self.locations[len(self.workplanes) - 1] = [Location()]
def vertices(self, select: Select = Select.ALL) -> VertexList[Vertex]:
"""Return Vertices from Part
Return either all or the vertices created during the last operation.
Args:
select (Select, optional): Vertex selector. Defaults to Select.ALL.
Returns:
VertexList[Vertex]: Vertices extracted
"""
vertex_list = []
if select == Select.ALL:
for e in self.part.Edges():
vertex_list.extend(e.Vertices())
for edge in self.part.Edges():
vertex_list.extend(edge.Vertices())
elif select == Select.LAST:
vertex_list = self.last_vertices
return VertexList(set(vertex_list))
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
"""Return Edges from Part
Return either all or the edges created during the last operation.
Args:
select (Select, optional): Edge selector. Defaults to Select.ALL.
Returns:
ShapeList[Edge]: Edges extracted
"""
if select == Select.ALL:
edge_list = self.part.Edges()
elif select == Select.LAST:
@ -103,6 +135,16 @@ class BuildPart:
return ShapeList(edge_list)
def faces(self, select: Select = Select.ALL) -> ShapeList[Face]:
"""Return Faces from Part
Return either all or the faces created during the last operation.
Args:
select (Select, optional): Face selector. Defaults to Select.ALL.
Returns:
ShapeList[Face]: Faces extracted
"""
if select == Select.ALL:
face_list = self.part.Faces()
elif select == Select.LAST:
@ -110,6 +152,16 @@ class BuildPart:
return ShapeList(face_list)
def solids(self, select: Select = Select.ALL) -> ShapeList[Solid]:
"""Return Solids from Part
Return either all or the solids created during the last operation.
Args:
select (Select, optional): Solid selector. Defaults to Select.ALL.
Returns:
ShapeList[Solid]: Solids extracted
"""
if select == Select.ALL:
solid_list = self.part.Solids()
elif select == Select.LAST:
@ -118,9 +170,12 @@ class BuildPart:
@staticmethod
def get_context() -> "BuildPart":
"""Return the current BuildPart instance. Used by Object and Operation
classes to refer to the current context."""
return context_stack[-1]
def add_to_pending(self, *objects: Union[Edge, Face]):
"""Add objects to BuildPart pending lists"""
for obj in objects:
for i, workplane in enumerate(self.workplanes):
for loc in self.locations:
@ -141,6 +196,25 @@ class BuildPart:
*objects: Union[Edge, Wire, Face, Solid, Compound],
mode: Mode = Mode.ADDITION,
):
"""Add objects to BuildPart instance
Core method to interface with BuildPart instance. Input sequence of objects is
parsed into lists of edges, faces, and solids. Edges and faces are added to pending
lists. Solids are combined with current part.
Each operation generates a list of vertices, edges, faces, and solids that have
changed during this operation. These lists are only guaranteed to be valid up until
the next operation as subsequent operations can element these objects.
Args:
objects (Union[Edge, Wire, Face, Solid, Compound]): sequence of objects to add
mode (Mode, optional): combination mode. Defaults to Mode.ADDITION.
Raises:
ValueError: Nothing to subtract from
ValueError: Nothing to intersect with
ValueError: Invalid mode
"""
if context_stack and mode != Mode.PRIVATE:
# Sort the provided objects into edges, faces and solids
new_faces = [obj for obj in objects if isinstance(obj, Face)]
@ -192,6 +266,7 @@ class BuildPart:
self.add_to_pending(*new_faces)
def get_and_clear_locations(self) -> list:
"""Return position and planes from current points and workplanes and clear locations."""
position_planes = []
for workplane in self.workplanes:
position_planes.extend(
@ -200,14 +275,13 @@ class BuildPart:
for location in self.locations
]
)
# Clear used locations
self.locations = [Location(Vector())]
return position_planes
"""
Operations
"""
#
# Operations
#
class ChamferPart(Compound):
@ -426,7 +500,6 @@ class Loft(Solid):
class PushPointsToPart:
def __init__(self, *pts: Union[VectorLike, Location]):
"""Part Operation: Push Points
Push the sequence of tuples, Vectors or Locations to builder internal structure,
@ -435,6 +508,8 @@ class PushPointsToPart:
Args:
pts (Union[VectorLike, Location]): sequence of points
"""
def __init__(self, *pts: Union[VectorLike, Location]):
new_locations = [
pt if isinstance(pt, Location) else Location(Vector(pt)) for pt in pts
]
@ -546,7 +621,8 @@ class Sweep(Compound):
multisection (bool, optional): sweep multiple on path. Defaults to False.
make_solid (bool, optional): create solid instead of face. Defaults to True.
is_frenet (bool, optional): use freenet algorithm. Defaults to False.
transition (Transition, optional): discontinuity handling option. Defaults to Transition.RIGHT.
transition (Transition, optional): discontinuity handling option.
Defaults to Transition.RIGHT.
normal (VectorLike, optional): fixed normal. Defaults to None.
binormal (Union[Edge, Wire], optional): guide rotation along path. Defaults to None.
mode (Mode, optional): combination. Defaults to Mode.ADDITION.
@ -572,7 +648,7 @@ class Sweep(Compound):
binormal_mode = binormal
new_solids = []
for i, workplane in enumerate(BuildPart.get_context().workplanes):
for i in range(BuildPart.get_context().workplane_count):
if not multisection:
for face in BuildPart.get_context().pending_faces[i]:
new_solids.append(
@ -620,9 +696,9 @@ class WorkplanesFromFaces:
BuildPart.get_context().workplane(*new_planes, replace=replace)
"""
Objects
"""
#
# Objects
#
class AddToPart(Compound):
@ -681,7 +757,8 @@ class Box(Compound):
width (float): box size
height (float): box size
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes. Defaults to (True, True, True).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
mode (Mode, optional): combine mode. Defaults to Mode.ADDITION.
"""
@ -722,7 +799,8 @@ class Cone(Compound):
height (float): cone size
arc_size (float, optional): angular size of cone. Defaults to 360.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes. Defaults to (True, True, True).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
mode (Mode, optional): combine mode. Defaults to Mode.ADDITION.
"""
@ -768,7 +846,8 @@ class Cylinder(Compound):
height (float): cylinder size
arc_size (float, optional): angular size of cone. Defaults to 360.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes. Defaults to (True, True, True).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
mode (Mode, optional): combine mode. Defaults to Mode.ADDITION.
"""
@ -809,7 +888,8 @@ class Sphere(Compound):
arc_size2 (float, optional): angular size of sphere. Defaults to 90.
arc_size3 (float, optional): angular size of sphere. Defaults to 360.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes. Defaults to (True, True, True).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
mode (Mode, optional): combine mode. Defaults to Mode.ADDITION.
"""
@ -852,7 +932,8 @@ class Torus(Compound):
major_arc_size (float, optional): angular size or torus. Defaults to 0.
minor_arc_size (float, optional): angular size or torus. Defaults to 360.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes. Defaults to (True, True, True).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
mode (Mode, optional): combine mode. Defaults to Mode.ADDITION.
"""