mirror of
https://github.com/gumyr/build123d.git
synced 2026-02-04 22:40:41 -08:00
Relocated Part, Sketch to topology.py
This commit is contained in:
parent
0525cd1d98
commit
7ff65f4eb0
4 changed files with 260 additions and 105 deletions
|
|
@ -112,12 +112,14 @@ __all__ = [
|
|||
"Matrix",
|
||||
"Solid",
|
||||
"Shell",
|
||||
"Part",
|
||||
"Plane",
|
||||
"Compound",
|
||||
"Location",
|
||||
"Joint",
|
||||
"RigidJoint",
|
||||
"RevoluteJoint",
|
||||
"Sketch",
|
||||
"LinearJoint",
|
||||
"CylindricalJoint",
|
||||
"BallJoint",
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue