Relocated Part, Sketch to topology.py

This commit is contained in:
Roger Maitland 2023-03-14 19:33:58 -04:00
parent 0525cd1d98
commit 7ff65f4eb0
4 changed files with 260 additions and 105 deletions

View file

@ -112,12 +112,14 @@ __all__ = [
"Matrix",
"Solid",
"Shell",
"Part",
"Plane",
"Compound",
"Location",
"Joint",
"RigidJoint",
"RevoluteJoint",
"Sketch",
"LinearJoint",
"CylindricalJoint",
"BallJoint",

View file

@ -1,35 +1,43 @@
import copy
from typing import Any, List, Union
from build123d.build_enums import *
from build123d.topology import Location, Wire, Plane, Vertex, Vector, Compound
from build123d.topology import (
Location,
Wire,
Plane,
Vertex,
Vector,
Compound,
AlgebraMixin,
)
class SkipClean:
"""Skip clean context for use in operator driven code where clean=False wouldn't work"""
# class SkipClean:
# """Skip clean context for use in operator driven code where clean=False wouldn't work"""
clean = True
# clean = True
def __enter__(self):
SkipClean.clean = False
# def __enter__(self):
# SkipClean.clean = False
def __exit__(self, exception_type, exception_value, traceback):
SkipClean.clean = True
# def __exit__(self, exception_type, exception_value, traceback):
# SkipClean.clean = True
def listify(arg: Any) -> List:
if isinstance(arg, (tuple, list)):
return list(arg)
else:
return [arg]
# def listify(arg: Any) -> List:
# if isinstance(arg, (tuple, list)):
# return list(arg)
# else:
# return [arg]
def is_algcompound(object):
return (
hasattr(object, "wrapped")
and hasattr(object, "_dim")
and hasattr(object, "_is_alg")
and object._is_alg
)
# def is_algcompound(object):
# return (
# hasattr(object, "wrapped")
# and hasattr(object, "_dim")
# and hasattr(object, "_is_alg")
# and object._is_alg
# )
class Pos(Location):
@ -52,91 +60,91 @@ class AlgLine(Compound):
self._dim = 1
class AlgebraMixin:
def _place(self, mode: Mode, *objs: Any):
# TODO error handling for non algcompound objects
# class AlgebraMixin:
# def _place(self, mode: Mode, *objs: Any):
# # TODO error handling for non algcompound objects
if not all([is_algcompound(o) for o in objs]):
raise RuntimeError(
"Non-algebraic operand(s) found in algebraic function. Are you in a context?"
)
# if not all([is_algcompound(o) for o in objs]):
# raise RuntimeError(
# "Non-algebraic operand(s) found in algebraic function. Are you in a context?"
# )
if not (objs[0]._dim == 0 or self._dim == 0 or self._dim == objs[0]._dim):
raise RuntimeError(
f"Cannot combine objects of different dimensionality: {self._dim} and {objs[0]._dim}"
)
# if not (objs[0]._dim == 0 or self._dim == 0 or self._dim == objs[0]._dim):
# raise RuntimeError(
# f"Cannot combine objects of different dimensionality: {self._dim} and {objs[0]._dim}"
# )
if self._dim == 0: # Cover addition of empty BuildPart with another object
if mode == Mode.ADD:
if len(objs) == 1:
compound = copy.deepcopy(objs[0])
else:
compound = copy.deepcopy(objs.pop()).fuse(*objs)
else:
raise RuntimeError("Can only add to an empty BuildPart object")
elif objs[0]._dim == 0: # Cover operation with empty BuildPart object
compound = self
else:
if mode == Mode.ADD:
compound = self.fuse(*objs)
# if self._dim == 0: # Cover addition of empty BuildPart with another object
# if mode == Mode.ADD:
# if len(objs) == 1:
# compound = copy.deepcopy(objs[0])
# else:
# compound = copy.deepcopy(objs.pop()).fuse(*objs)
# else:
# raise RuntimeError("Can only add to an empty BuildPart object")
# elif objs[0]._dim == 0: # Cover operation with empty BuildPart object
# compound = self
# else:
# if mode == Mode.ADD:
# compound = self.fuse(*objs)
elif self._dim == 1:
raise RuntimeError("Lines can only be added")
# elif self._dim == 1:
# raise RuntimeError("Lines can only be added")
else:
if mode == Mode.SUBTRACT:
compound = self.cut(*objs)
elif mode == Mode.INTERSECT:
compound = self.intersect(*objs)
# else:
# if mode == Mode.SUBTRACT:
# compound = self.cut(*objs)
# elif mode == Mode.INTERSECT:
# compound = self.intersect(*objs)
if SkipClean.clean:
compound = compound.clean()
# if SkipClean.clean:
# compound = compound.clean()
compound = self._wrappper_cls(compound.wrapped)
# compound = self._wrappper_cls(compound.wrapped)
return compound
# return compound
# TODO: How to use typing here
def __add__(self, other: Union[Any, List[Any]]):
return self._place(Mode.ADD, *listify(other))
# # TODO: How to use typing here
# def __add__(self, other: Union[Any, List[Any]]):
# return self._place(Mode.ADD, *listify(other))
# TODO: How to use typing here
def __sub__(self, other: Union[Any, List[Any]]):
return self._place(Mode.SUBTRACT, *listify(other))
# # TODO: How to use typing here
# def __sub__(self, other: Union[Any, List[Any]]):
# return self._place(Mode.SUBTRACT, *listify(other))
# TODO: How to use typing here
def __and__(self, other: Union[Any, List[Any]]):
return self._place(Mode.INTERSECT, *listify(other))
# # TODO: How to use typing here
# def __and__(self, other: Union[Any, List[Any]]):
# return self._place(Mode.INTERSECT, *listify(other))
def __mul__(self, loc: Location):
if self._dim == 3:
return copy.copy(self).move(loc)
else:
return self.moved(loc)
# def __mul__(self, loc: Location):
# if self._dim == 3:
# return copy.copy(self).move(loc)
# else:
# return self.moved(loc)
def __matmul__(self, obj: Union[float, Location, Plane]):
if isinstance(obj, (int, float)):
if self._dim == 1:
return Wire.make_wire(self.edges()).position_at(obj)
else:
raise TypeError("Only lines can access positions")
# def __matmul__(self, obj: Union[float, Location, Plane]):
# if isinstance(obj, (int, float)):
# if self._dim == 1:
# return Wire.make_wire(self.edges()).position_at(obj)
# else:
# raise TypeError("Only lines can access positions")
elif isinstance(obj, Location):
loc = obj
# elif isinstance(obj, Location):
# loc = obj
elif isinstance(obj, Plane):
loc = obj.to_location()
# elif isinstance(obj, Plane):
# loc = obj.to_location()
else:
raise ValueError(f"Cannot multiply with {obj}")
# else:
# raise ValueError(f"Cannot multiply with {obj}")
if self._dim == 3:
return copy.copy(self).locate(loc)
else:
return self.located(loc)
# if self._dim == 3:
# return copy.copy(self).locate(loc)
# else:
# return self.located(loc)
def __mod__(self, position):
if self._dim == 1:
return Wire.make_wire(self.edges()).tangent_at(position)
else:
raise TypeError(f"unsupported operand type(s)")
# def __mod__(self, position):
# if self._dim == 1:
# return Wire.make_wire(self.edges()).tangent_at(position)
# else:
# raise TypeError(f"unsupported operand type(s)")

View file

@ -43,14 +43,7 @@ from build123d.geometry import (
Vector,
VectorLike,
)
from build123d.topology import (
Compound,
Edge,
Face,
Shell,
Solid,
Wire,
)
from build123d.topology import Compound, Edge, Face, Shell, Solid, Wire, Part
from build123d.build_common import (
Builder,
@ -60,7 +53,7 @@ from build123d.build_common import (
validate_inputs,
)
from build123d.algebra import AlgebraMixin
# from build123d.algebra import AlgebraMixin
class BuildPart(Builder):
@ -220,6 +213,11 @@ class BuildPart(Builder):
len(new_solids),
mode,
)
if self.part:
if not isinstance(self.part, Compound):
self.part = Part(Compound.make_compound(self.part.solids()).wrapped)
else:
self.part = Part(self.part.wrapped)
post_vertices = set() if self.part is None else set(self.part.vertices())
post_edges = set() if self.part is None else set(self.part.edges())
@ -251,12 +249,12 @@ class BuildPart(Builder):
return result
class Part(Compound, AlgebraMixin):
def __init__(self, wrapped, is_alg=True):
super().__init__(wrapped)
self._is_alg = is_alg
self._dim = 3
self._wrappper_cls = Part
# class Part(Compound, AlgebraMixin):
# def __init__(self, wrapped, is_alg=True):
# super().__init__(wrapped)
# self._is_alg = is_alg
# self._dim = 3
# self._wrappper_cls = Part
class BasePartObject(Part):

View file

@ -252,6 +252,7 @@ from build123d.build_enums import (
FrameMethod,
GeomType,
Kind,
Mode,
PositionMode,
SortBy,
Transition,
@ -1096,6 +1097,7 @@ class Shape(NodeMixin):
joints: dict[str, Joint] = None,
parent: Compound = None,
children: list[Shape] = None,
is_alg: bool = False,
):
self.wrapped = downcast(obj) if obj else None
self.for_construction = False
@ -1115,6 +1117,21 @@ class Shape(NodeMixin):
# parent must be set following children as post install accesses children
self.parent = parent
if isinstance(self, Part):
self._is_alg: bool = is_alg
self._dim: int = 3
self._wrappper_cls: Shape = Part
if isinstance(self, Sketch):
self._is_alg: bool = is_alg
self._dim: int = 2
self._wrappper_cls: Shape = Sketch
if isinstance(self, LineLine):
self._is_alg: bool = is_alg
self._dim: int = 1
self._wrappper_cls: Shape = LineLine
@property
def location(self) -> Location:
"""Get this Shape's Location"""
@ -3232,6 +3249,108 @@ class Compound(Shape, Mixin3D):
return results
class AlgebraMixin:
def _place(self, mode: Mode, *objs: Any):
# TODO error handling for non algcompound objects
if not all([is_algcompound(o) for o in objs]):
raise RuntimeError(
"Non-algebraic operand(s) found in algebraic function. Are you in a context?"
)
if not (objs[0]._dim == 0 or self._dim == 0 or self._dim == objs[0]._dim):
raise RuntimeError(
f"Cannot combine objects of different dimensionality: {self._dim} and {objs[0]._dim}"
)
if self._dim == 0: # Cover addition of empty BuildPart with another object
if mode == Mode.ADD:
if len(objs) == 1:
compound = copy.deepcopy(objs[0])
else:
compound = copy.deepcopy(objs.pop()).fuse(*objs)
else:
raise RuntimeError("Can only add to an empty BuildPart object")
elif objs[0]._dim == 0: # Cover operation with empty BuildPart object
compound = self
else:
if mode == Mode.ADD:
compound = self.fuse(*objs)
elif self._dim == 1:
raise RuntimeError("Lines can only be added")
else:
if mode == Mode.SUBTRACT:
compound = self.cut(*objs)
elif mode == Mode.INTERSECT:
compound = self.intersect(*objs)
if SkipClean.clean:
compound = compound.clean()
compound = self._wrappper_cls(compound.wrapped)
return compound
# TODO: How to use typing here
def __add__(self, other: Union[Any, List[Any]]):
return self._place(Mode.ADD, *listify(other))
# TODO: How to use typing here
def __sub__(self, other: Union[Any, List[Any]]):
return self._place(Mode.SUBTRACT, *listify(other))
# TODO: How to use typing here
def __and__(self, other: Union[Any, List[Any]]):
return self._place(Mode.INTERSECT, *listify(other))
def __mul__(self, loc: Location):
if self._dim == 3:
return copy.copy(self).move(loc)
else:
return self.moved(loc)
def __matmul__(self, obj: Union[float, Location, Plane]):
if isinstance(obj, (int, float)):
if self._dim == 1:
return Wire.make_wire(self.edges()).position_at(obj)
else:
raise TypeError("Only lines can access positions")
elif isinstance(obj, Location):
loc = obj
elif isinstance(obj, Plane):
loc = obj.to_location()
else:
raise ValueError(f"Cannot multiply with {obj}")
if self._dim == 3:
return copy.copy(self).locate(loc)
else:
return self.located(loc)
def __mod__(self, position):
if self._dim == 1:
return Wire.make_wire(self.edges()).tangent_at(position)
else:
raise TypeError(f"unsupported operand type(s)")
class Part(Compound, AlgebraMixin):
pass
class Sketch(Compound, AlgebraMixin):
pass
class LineLine(Compound, AlgebraMixin):
pass
class Edge(Shape, Mixin1D):
"""A trimmed curve that represents the border of a face"""
@ -7065,3 +7184,31 @@ def sort_wires_by_build_order(wire_list: list[Wire]) -> list[list[Wire]]:
def polar(length: float, angle: float) -> tuple[float, float]:
"""Convert polar coordinates into cartesian coordinates"""
return (length * cos(radians(angle)), length * sin(radians(angle)))
def listify(arg: Any) -> List:
if isinstance(arg, (tuple, list)):
return list(arg)
else:
return [arg]
def is_algcompound(object):
return (
hasattr(object, "wrapped")
and hasattr(object, "_dim")
and hasattr(object, "_is_alg")
and object._is_alg
)
class SkipClean:
"""Skip clean context for use in operator driven code where clean=False wouldn't work"""
clean = True
def __enter__(self):
SkipClean.clean = False
def __exit__(self, exception_type, exception_value, traceback):
SkipClean.clean = True