Add->add, restored context logging and validation

This commit is contained in:
Roger Maitland 2023-03-23 15:32:15 -04:00
parent cb05ee97b6
commit 92979fc29c
21 changed files with 210 additions and 239 deletions

View file

@ -60,7 +60,7 @@ if "show" in locals():
b = Box(1, 2, 3) b = Box(1, 2, 3)
with BuildPart() as bp: with BuildPart() as bp:
Add(b) add(b)
if "show" in locals(): if "show" in locals():
show(bp) show(bp)
@ -70,7 +70,7 @@ if "show" in locals():
b = Box(1, 2, 3) + Cylinder(0.75, 2.5) b = Box(1, 2, 3) + Cylinder(0.75, 2.5)
with BuildPart() as bp: with BuildPart() as bp:
Add(b) add(b)
Cylinder(0.4, 6, mode=Mode.SUBTRACT) Cylinder(0.4, 6, mode=Mode.SUBTRACT)
c = bp.part - Plane.YZ * Cylinder(0.2, 6) c = bp.part - Plane.YZ * Cylinder(0.2, 6)

View file

@ -59,7 +59,7 @@ if "show" in locals():
b = Rectangle(1, 2) + Circle(0.75) b = Rectangle(1, 2) + Circle(0.75)
with BuildSketch() as sk: with BuildSketch() as sk:
Add(b) add(b)
Circle(0.1, mode=Mode.SUBTRACT) Circle(0.1, mode=Mode.SUBTRACT)
c = sk.sketch - Pos(0, 0.5) * Circle(0.2) c = sk.sketch - Pos(0, 0.5) * Circle(0.2)

View file

@ -75,9 +75,9 @@ with BuildLine() as extension_lines:
(logo_width, -ext_line_length - font_height * 0.1), (logo_width, -ext_line_length - font_height * 0.1),
) )
with Locations(l1 @ 0.5): with Locations(l1 @ 0.5):
Add(*arrow_left.line) add(*arrow_left.line)
with Locations(l2 @ 0.5): with Locations(l2 @ 0.5):
Add(*arrow_left.line, rotation=180.0) add(*arrow_left.line, rotation=180.0)
Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0)) Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))
Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0)) Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))
@ -87,12 +87,12 @@ with BuildSketch() as build:
(l1 @ 0.5 + l2 @ 0.5) / 2 (l1 @ 0.5 + l2 @ 0.5) / 2
- Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0) - Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)
): ):
Add(build_text.sketch) add(build_text.sketch)
# add the customizable text to the build text sketch # add the customizable text to the build text sketch
with Locations( with Locations(
(l1 @ 1 + l2 @ 1) / 2 - Vector((cust_vertices[-1].X + cust_vertices[0].X), 1.4) (l1 @ 1 + l2 @ 1) / 2 - Vector((cust_vertices[-1].X + cust_vertices[0].X), 1.4)
): ):
Add(cust_text.sketch) add(cust_text.sketch)
cmpd = Compound.make_compound( cmpd = Compound.make_compound(
[three_d.part, two.sketch, one.line, build.sketch, extension_lines.line] [three_d.part, two.sketch, one.line, build.sketch, extension_lines.line]

View file

@ -64,9 +64,9 @@ with BuildLine() as extension_lines:
(logo_width, -ext_line_length - font_height * 0.1), (logo_width, -ext_line_length - font_height * 0.1),
) )
with Locations(l1 @ 0.5): with Locations(l1 @ 0.5):
Add(*arrow_left.line) add(*arrow_left.line)
with Locations(l2 @ 0.5): with Locations(l2 @ 0.5):
Add(*arrow_left.line, rotation=180.0) add(*arrow_left.line, rotation=180.0)
Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0)) Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))
Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0)) Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))
@ -76,7 +76,7 @@ with BuildSketch() as build:
(l1 @ 0.5 + l2 @ 0.5) / 2 (l1 @ 0.5 + l2 @ 0.5) / 2
- Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0) - Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)
): ):
Add(build_text.sketch) add(build_text.sketch)
# logo = Assembly(None, name="logo") # logo = Assembly(None, name="logo")
# logo.add(one.wires()[0], name="one") # logo.add(one.wires()[0], name="one")

View file

@ -39,7 +39,7 @@ with BuildSketch() as minute_indicator:
with BuildSketch() as clock_face: with BuildSketch() as clock_face:
Circle(clock_radius) Circle(clock_radius)
with PolarLocations(0, 60): with PolarLocations(0, 60):
Add(minute_indicator.sketch, mode=Mode.SUBTRACT) add(minute_indicator.sketch, mode=Mode.SUBTRACT)
with PolarLocations(clock_radius * 0.875, 12): with PolarLocations(clock_radius * 0.875, 12):
SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT) SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)
for hour in range(1, 13): for hour in range(1, 13):

View file

@ -151,7 +151,7 @@ with BuildPart() as box_builder:
box = box_builder.part box = box_builder.part
with BuildPart() as lid_builder: with BuildPart() as lid_builder:
Add(box_plan.sketch) add(box_plan.sketch)
extrude(amount=pocket_t / 2 + bottom_t) extrude(amount=pocket_t / 2 + bottom_t)
with BuildSketch() as pocket: with BuildSketch() as pocket:
offset(box_plan.sketch, amount=-(wall_t - lip_t), mode=Mode.ADD) offset(box_plan.sketch, amount=-(wall_t - lip_t), mode=Mode.ADD)

View file

@ -120,7 +120,7 @@ with BuildPart() as box_builder:
fillet(*plan.vertices(), radius=card_width / 15) fillet(*plan.vertices(), radius=card_width / 15)
extrude(amount=wall / 2) extrude(amount=wall / 2)
with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as walls: with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as walls:
Add(plan.sketch) add(plan.sketch)
offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT) offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)
extrude(amount=deck / 2) extrude(amount=deck / 2)
with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as inset_walls: with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as inset_walls:
@ -130,11 +130,11 @@ with BuildPart() as box_builder:
with BuildPart() as lid_builder: with BuildPart() as lid_builder:
with BuildSketch() as outset_walls: with BuildSketch() as outset_walls:
Add(plan.sketch) add(plan.sketch)
offset(plan.sketch, amount=-(wall - gap) / 2, mode=Mode.SUBTRACT) offset(plan.sketch, amount=-(wall - gap) / 2, mode=Mode.SUBTRACT)
extrude(amount=deck / 2) extrude(amount=deck / 2)
with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as top: with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as top:
Add(plan.sketch) add(plan.sketch)
extrude(amount=wall / 2) extrude(amount=wall / 2)
with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]): with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]):
holes = GridLocations( holes = GridLocations(

View file

@ -3,10 +3,9 @@ from build123d.build_common import *
from build123d.build_line import * from build123d.build_line import *
from build123d.build_sketch import * from build123d.build_sketch import *
from build123d.build_part import * from build123d.build_part import *
from build123d.build_generic import *
from build123d.geometry import * from build123d.geometry import *
from build123d.topology import * from build123d.topology import *
from build123d.build_enums import ApproxOption from build123d.build_enums import *
from build123d.importers import * from build123d.importers import *
from build123d.operations_generic import * from build123d.operations_generic import *
from build123d.operations_part import * from build123d.operations_part import *
@ -41,7 +40,6 @@ __all__ = [
"Unit", "Unit",
"Until", "Until",
# Builders, # Builders,
"Add",
"HexLocations", "HexLocations",
"PolarLocations", "PolarLocations",
"Locations", "Locations",
@ -129,6 +127,7 @@ __all__ = [
# Other functions # Other functions
"polar", "polar",
# Operations # Operations
"add",
"bounding_box", "bounding_box",
"chamfer", "chamfer",
"extrude", "extrude",

View file

@ -41,10 +41,13 @@ from build123d.build_enums import Align, Mode, Select
from build123d.geometry import Axis, Location, Plane, Vector, VectorLike from build123d.geometry import Axis, Location, Plane, Vector, VectorLike
from build123d.topology import ( from build123d.topology import (
Compound, Compound,
Curve,
Edge, Edge,
Face, Face,
Part,
Shape, Shape,
ShapeList, ShapeList,
Sketch,
Solid, Solid,
Vertex, Vertex,
Wire, Wire,
@ -75,6 +78,25 @@ FT = 12 * IN
THOU = IN / 1000 THOU = IN / 1000
operations_apply_to = {
"add": ["BuildPart", "BuildSketch", "BuildLine"],
"bounding_box": ["BuildPart", "BuildSketch", "BuildLine"],
"chamfer": ["BuildPart", "BuildSketch"],
"extrude": ["BuildPart"],
"fillet": ["BuildPart", "BuildSketch"],
"loft": ["BuildPart"],
"make_face": ["BuildSketch"],
"make_hull": ["BuildSketch"],
"mirror": ["BuildPart", "BuildSketch", "BuildLine"],
"offset": ["BuildPart", "BuildSketch", "BuildLine"],
"revolve": ["BuildPart"],
"scale": ["BuildPart", "BuildSketch", "BuildLine"],
"section": ["BuildPart"],
"split": ["BuildPart", "BuildSketch", "BuildLine"],
"sweep": ["BuildPart"],
}
class Builder(ABC): class Builder(ABC):
"""Builder """Builder
@ -194,33 +216,19 @@ class Builder(ABC):
return NotImplementedError # pragma: no cover return NotImplementedError # pragma: no cover
@classmethod @classmethod
def _get_context(cls, caller=None) -> Self: def _get_context(cls, caller: Union[Builder, str] = None, log: bool = True) -> Self:
"""Return the instance of the current builder""" """Return the instance of the current builder"""
result = cls._current.get(None) result = cls._current.get(None)
context_name = "None" if result is None else type(result).__name__
current_builder = inspect.currentframe().f_back.f_locals.get("self", None) if log:
if isinstance(caller, (Part, Sketch, Curve, Wire)):
if not isinstance(current_builder, Builder): caller_name = caller.__class__.__name__
current_builder = None elif isinstance(caller, str):
caller_name = caller
if not isinstance(caller, Builder): else:
caller = None caller_name = "None"
logger.info("%s context requested by %s", context_name, caller_name)
# print(f"{result=}, {current_builder=}, {caller=}")
if current_builder is None:
logger.info("Context requested by None")
else:
logger.info(
"Context requested by %s",
type(current_builder).__name__,
)
if caller is not None and result is None:
if hasattr(caller, "_applies_to"):
raise RuntimeError(
f"No valid context found, use one of {caller._applies_to}"
)
raise RuntimeError("No valid context found-common")
return result return result
@ -322,14 +330,23 @@ class Builder(ABC):
objects = [objects] objects = [objects]
if ( if (
validating_class is not None isinstance(validating_class, (Part, Sketch, Curve, Wire))
and not self._tag() in validating_class._applies_to and self._tag() not in validating_class._applies_to
): ):
raise RuntimeError( raise RuntimeError(
f"{self.__class__.__name__} doesn't have a " f"{self.__class__.__name__} doesn't have a "
f"{validating_class.__class__.__name__} object or operation " f"{validating_class.__class__.__name__} object or operation "
f"({validating_class.__class__.__name__} applies to {validating_class._applies_to})" f"({validating_class.__class__.__name__} applies to {validating_class._applies_to})"
) )
elif (
isinstance(validating_class, str)
and self.__class__.__name__ not in operations_apply_to[validating_class]
):
raise RuntimeError(
f"{self.__class__.__name__} doesn't have a "
f"{validating_class} object or operation "
f"({validating_class} applies to {operations_apply_to[validating_class]})"
)
# Check for valid object inputs # Check for valid object inputs
for obj in objects: for obj in objects:
if obj is None: if obj is None:

View file

@ -1,144 +0,0 @@
"""
BuildGeneric
name: build_generic.py
by: Gumyr
date: July 12th 2022
desc:
This python module is a library of generic classes used by other
build123d builders.
license:
Copyright 2022 Gumyr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import logging
from typing import Union
from build123d.build_common import Builder, LocationList, WorkplaneList, validate_inputs
from build123d.build_enums import Mode
from build123d.build_line import BuildLine
from build123d.build_part import BuildPart
from build123d.build_sketch import BuildSketch
from build123d.geometry import Axis, Rotation, RotationLike
from build123d.topology import Compound, Edge, Face, Solid, Wire
logging.getLogger("build123d").addHandler(logging.NullHandler())
logger = logging.getLogger("build123d")
#
# Objects
#
class Add(Compound):
"""Generic Object: Add Object to Part or Sketch
Add an object to the builder.
if BuildPart:
Edges and Wires are added to pending_edges. Compounds of Face are added to
pending_faces. Solids or Compounds of Solid are combined into the part.
elif BuildSketch:
Edges and Wires are added to pending_edges. Compounds of Face are added to sketch.
elif BuildLine:
Edges and Wires are added to line.
Args:
objects (Union[Edge, Wire, Face, Solid, Compound]): sequence of objects to add
rotation (Union[float, RotationLike], optional): rotation angle for sketch,
rotation about each axis for part. Defaults to None.
mode (Mode, optional): combine mode. Defaults to Mode.ADD.
"""
_applies_to = [BuildPart._tag(), BuildSketch._tag(), BuildLine._tag()]
def __init__(
self,
*objects: Union[Edge, Wire, Face, Solid, Compound],
rotation: Union[float, RotationLike] = None,
mode: Mode = Mode.ADD,
):
context: Builder = Builder._get_context(self)
if context is None:
raise RuntimeError("Add must have an active builder context")
validate_inputs(context, self, objects)
if isinstance(context, BuildPart):
if rotation is None:
rotation = Rotation(0, 0, 0)
elif isinstance(rotation, float):
raise ValueError("Float values of rotation are not valid for BuildPart")
elif isinstance(rotation, tuple):
rotation = Rotation(*rotation)
objects = [obj.moved(rotation) for obj in objects]
new_edges = [obj for obj in objects if isinstance(obj, Edge)]
new_wires = [obj for obj in objects if isinstance(obj, Wire)]
new_faces = [obj for obj in objects if isinstance(obj, Face)]
new_solids = [obj for obj in objects if isinstance(obj, Solid)]
for compound in filter(lambda o: isinstance(o, Compound), objects):
new_edges.extend(compound.get_type(Edge))
new_wires.extend(compound.get_type(Wire))
new_faces.extend(compound.get_type(Face))
new_solids.extend(compound.get_type(Solid))
for new_wire in new_wires:
new_edges.extend(new_wire.edges())
# Add the pending Edges in one group
if not LocationList._get_context():
raise RuntimeError("There is no active Locations context")
located_edges = [
edge.moved(location)
for edge in new_edges
for location in LocationList._get_context().locations
]
context._add_to_pending(*located_edges)
new_objects = located_edges
# Add to pending Faces batched by workplane
for workplane in WorkplaneList._get_context().workplanes:
faces_per_workplane = []
for location in LocationList._get_context().locations:
for face in new_faces:
faces_per_workplane.append(face.moved(location))
context._add_to_pending(*faces_per_workplane, face_plane=workplane)
new_objects.extend(faces_per_workplane)
# Add to context Solids
located_solids = [
solid.moved(location)
for solid in new_solids
for location in LocationList._get_context().locations
]
context._add_to_context(*located_solids, mode=mode)
new_objects.extend(located_solids)
elif isinstance(context, (BuildLine, BuildSketch)):
rotation_angle = rotation if isinstance(rotation, (int, float)) else 0.0
new_objects = []
for obj in objects:
new_objects.extend(
[
obj.rotate(Axis.Z, rotation_angle).moved(location)
for location in LocationList._get_context().local_locations
]
)
context._add_to_context(*new_objects, mode=mode)
super().__init__(Compound.make_compound(new_objects).wrapped)

View file

@ -63,7 +63,7 @@ class BuildLine(Builder):
@staticmethod @staticmethod
def _tag() -> str: def _tag() -> str:
return BuildLine return "BuildLine"
@property @property
def _obj(self): def _obj(self):

View file

@ -52,7 +52,7 @@ class BuildPart(Builder):
@staticmethod @staticmethod
def _tag() -> str: def _tag() -> str:
return BuildPart return "BuildPart"
@property @property
def _obj(self): def _obj(self):

View file

@ -55,7 +55,7 @@ class BaseLineObject(Wire):
curve: Union[Edge, Wire], curve: Union[Edge, Wire],
mode: Mode = Mode.ADD, mode: Mode = Mode.ADD,
): ):
context: BuildLine = BuildLine._get_context(self) context: BuildLine = BuildLine._get_context(self, log=False)
if context is not None: if context is not None:
context._add_to_context(*curve.edges(), mode=mode) context._add_to_context(*curve.edges(), mode=mode)

View file

@ -72,7 +72,7 @@ class BasePartObject(Part):
align_offset.append(-bbox.max.to_tuple()[i]) align_offset.append(-bbox.max.to_tuple()[i])
solid.move(Location(Vector(*align_offset))) solid.move(Location(Vector(*align_offset)))
context: BuildPart = BuildPart._get_context(self) context: BuildPart = BuildPart._get_context(self, log=False)
if context is None: if context is None:
new_solids = [solid] new_solids = [solid]

View file

@ -73,7 +73,7 @@ class BaseSketchObject(Sketch):
align_offset.append(-bbox.max.to_tuple()[i]) align_offset.append(-bbox.max.to_tuple()[i])
obj.move(Location(Vector(*align_offset))) obj.move(Location(Vector(*align_offset)))
context: BuildSketch = BuildSketch._get_context(self) context: BuildSketch = BuildSketch._get_context(self, log=False)
if context is None: if context is None:
new_faces = obj.faces() new_faces = obj.faces()

View file

@ -29,11 +29,19 @@ license:
import copy import copy
import logging import logging
from typing import Union from typing import Union
from build123d.build_enums import Mode, Kind, Keep
from build123d.build_common import Builder, LocationList, WorkplaneList, validate_inputs
from build123d.build_enums import Keep, Kind, Mode
from build123d.build_line import BuildLine
from build123d.build_part import BuildPart
from build123d.build_sketch import BuildSketch
from build123d.geometry import ( from build123d.geometry import (
Axis,
Location, Location,
Matrix, Matrix,
Plane, Plane,
Rotation,
RotationLike,
Vector, Vector,
) )
from build123d.topology import ( from build123d.topology import (
@ -41,9 +49,9 @@ from build123d.topology import (
Curve, Curve,
Edge, Edge,
Face, Face,
Matrix,
Part, Part,
Plane, Plane,
Matrix,
Shape, Shape,
Sketch, Sketch,
Solid, Solid,
@ -51,15 +59,101 @@ from build123d.topology import (
Wire, Wire,
) )
from build123d.build_common import Builder, validate_inputs
logging.getLogger("build123d").addHandler(logging.NullHandler()) logging.getLogger("build123d").addHandler(logging.NullHandler())
logger = logging.getLogger("build123d") logger = logging.getLogger("build123d")
# def add(
# Operations *objects: Union[Edge, Wire, Face, Solid, Compound],
# rotation: Union[float, RotationLike] = None,
mode: Mode = Mode.ADD,
) -> Compound:
"""Generic Object: Add Object to Part or Sketch
Add an object to a builder.
if BuildPart:
Edges and Wires are added to pending_edges. Compounds of Face are added to
pending_faces. Solids or Compounds of Solid are combined into the part.
elif BuildSketch:
Edges and Wires are added to pending_edges. Compounds of Face are added to sketch.
elif BuildLine:
Edges and Wires are added to line.
Args:
objects (Union[Edge, Wire, Face, Solid, Compound]): sequence of objects to add
rotation (Union[float, RotationLike], optional): rotation angle for sketch,
rotation about each axis for part. Defaults to None.
mode (Mode, optional): combine mode. Defaults to Mode.ADD.
"""
context: Builder = Builder._get_context(None)
if context is None:
raise RuntimeError("Add must have an active builder context")
validate_inputs(context, None, objects)
if isinstance(context, BuildPart):
if rotation is None:
rotation = Rotation(0, 0, 0)
elif isinstance(rotation, float):
raise ValueError("Float values of rotation are not valid for BuildPart")
elif isinstance(rotation, tuple):
rotation = Rotation(*rotation)
objects = [obj.moved(rotation) for obj in objects]
new_edges = [obj for obj in objects if isinstance(obj, Edge)]
new_wires = [obj for obj in objects if isinstance(obj, Wire)]
new_faces = [obj for obj in objects if isinstance(obj, Face)]
new_solids = [obj for obj in objects if isinstance(obj, Solid)]
for compound in filter(lambda o: isinstance(o, Compound), objects):
new_edges.extend(compound.get_type(Edge))
new_wires.extend(compound.get_type(Wire))
new_faces.extend(compound.get_type(Face))
new_solids.extend(compound.get_type(Solid))
for new_wire in new_wires:
new_edges.extend(new_wire.edges())
# Add the pending Edges in one group
if not LocationList._get_context():
raise RuntimeError("There is no active Locations context")
located_edges = [
edge.moved(location)
for edge in new_edges
for location in LocationList._get_context().locations
]
context._add_to_pending(*located_edges)
new_objects = located_edges
# Add to pending Faces batched by workplane
for workplane in WorkplaneList._get_context().workplanes:
faces_per_workplane = []
for location in LocationList._get_context().locations:
for face in new_faces:
faces_per_workplane.append(face.moved(location))
context._add_to_pending(*faces_per_workplane, face_plane=workplane)
new_objects.extend(faces_per_workplane)
# Add to context Solids
located_solids = [
solid.moved(location)
for solid in new_solids
for location in LocationList._get_context().locations
]
context._add_to_context(*located_solids, mode=mode)
new_objects.extend(located_solids)
elif isinstance(context, (BuildLine, BuildSketch)):
rotation_angle = rotation if isinstance(rotation, (int, float)) else 0.0
new_objects = []
for obj in objects:
new_objects.extend(
[
obj.rotate(Axis.Z, rotation_angle).moved(location)
for location in LocationList._get_context().local_locations
]
)
context._add_to_context(*new_objects, mode=mode)
return Compound.make_compound(new_objects)
def bounding_box( def bounding_box(
@ -76,8 +170,8 @@ def bounding_box(
objects (Shape): sequence of objects objects (Shape): sequence of objects
mode (Mode, optional): combination mode. Defaults to Mode.ADD. mode (Mode, optional): combination mode. Defaults to Mode.ADD.
""" """
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("bounding_box")
validate_inputs(context, None, objects) validate_inputs(context, "bounding_box", objects)
if (not objects and context is None) or ( if (not objects and context is None) or (
not objects and context is not None and not context._obj not objects and context is not None and not context._obj
@ -141,12 +235,13 @@ def chamfer(
target (Union[Face, Sketch, Solid, Part], optional): object to chamfer. Defaults to None. target (Union[Face, Sketch, Solid, Part], optional): object to chamfer. Defaults to None.
Raises: Raises:
ValueError: no objects provided
ValueError: objects must be Edges ValueError: objects must be Edges
ValueError: objects must be Vertices ValueError: objects must be Vertices
ValueError: missing target object ValueError: missing target object
""" """
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("chamfer")
validate_inputs(context, None, objects) validate_inputs(context, "chamfer", objects)
if (not objects and context is None) or ( if (not objects and context is None) or (
not objects and context is not None and not context._obj not objects and context is not None and not context._obj
@ -204,8 +299,8 @@ def fillet(
ValueError: objects must be Vertices ValueError: objects must be Vertices
ValueError: missing target object ValueError: missing target object
""" """
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("fillet")
validate_inputs(context, None, objects) validate_inputs(context, "fillet", objects)
if (not objects and context is None) or ( if (not objects and context is None) or (
not objects and context is not None and not context._obj not objects and context is not None and not context._obj
@ -262,8 +357,8 @@ def mirror(
Raises: Raises:
ValueError: missing objects ValueError: missing objects
""" """
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("mirror")
validate_inputs(context, None, objects) validate_inputs(context, "mirror", objects)
if not objects: if not objects:
if context is None: if context is None:
@ -314,8 +409,8 @@ def offset(
ValueError: missing objects ValueError: missing objects
ValueError: Invalid object type ValueError: Invalid object type
""" """
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("offset")
validate_inputs(context, None, objects) validate_inputs(context, "offset", objects)
if not objects: if not objects:
if context is None: if context is None:
@ -404,8 +499,8 @@ def scale(
Raises: Raises:
ValueError: missing objects ValueError: missing objects
""" """
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("scale")
validate_inputs(context, None, objects) validate_inputs(context, "scale", objects)
if not objects: if not objects:
if context is None: if context is None:
@ -491,8 +586,8 @@ def split(
) )
) )
context: Builder = Builder._get_context(None) context: Builder = Builder._get_context("split")
validate_inputs(context, None, objects) validate_inputs(context, "split", objects)
if not objects: if not objects:
if context is None: if context is None:

View file

@ -89,8 +89,8 @@ def extrude(
Returns: Returns:
Part: extruded object Part: extruded object
""" """
context: BuildPart = BuildPart._get_context(None) context: BuildPart = BuildPart._get_context("extrude")
validate_inputs(context, None, to_extrude) validate_inputs(context, "extrude", to_extrude)
to_extrude_faces: list[Face] to_extrude_faces: list[Face]
@ -173,7 +173,8 @@ def loft(
clean (bool, optional): Remove extraneous internal structure. Defaults to True. clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination mode. Defaults to Mode.ADD. mode (Mode, optional): combination mode. Defaults to Mode.ADD.
""" """
context: BuildPart = BuildPart._get_context(None) context: BuildPart = BuildPart._get_context("loft")
validate_inputs(context, "loft", sections)
if not sections: if not sections:
loft_wires = [face.outer_wire() for face in context.pending_faces] loft_wires = [face.outer_wire() for face in context.pending_faces]
@ -222,7 +223,8 @@ def revolve(
Raises: Raises:
ValueError: Invalid axis of revolution ValueError: Invalid axis of revolution
""" """
context: BuildPart = BuildPart._get_context(None) context: BuildPart = BuildPart._get_context("revolve")
validate_inputs(context, "revolve", profiles)
# Make sure we account for users specifying angles larger than 360 degrees, and # Make sure we account for users specifying angles larger than 360 degrees, and
# for OCCT not assuming that a 0 degree revolve means a 360 degree revolve # for OCCT not assuming that a 0 degree revolve means a 360 degree revolve
@ -279,7 +281,8 @@ def section(
clean (bool, optional): Remove extraneous internal structure. Defaults to True. clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination mode. Defaults to Mode.INTERSECT. mode (Mode, optional): combination mode. Defaults to Mode.INTERSECT.
""" """
context: BuildPart = BuildPart._get_context(None) context: BuildPart = BuildPart._get_context("section")
validate_inputs(context, "section", None)
if context is not None and obj is None: if context is not None and obj is None:
max_size = context.part.bounding_box().diagonal max_size = context.part.bounding_box().diagonal
@ -341,7 +344,8 @@ def sweep(
clean (bool, optional): Remove extraneous internal structure. Defaults to True. clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination. Defaults to Mode.ADD. mode (Mode, optional): combination. Defaults to Mode.ADD.
""" """
context: BuildPart = BuildPart._get_context(None) context: BuildPart = BuildPart._get_context("sweep")
validate_inputs(context, "sweep", sections)
if path is None: if path is None:
path_wire = context.pending_edges_as_wire path_wire = context.pending_edges_as_wire

View file

@ -42,8 +42,8 @@ def make_face(*edges: Edge, mode: Mode = Mode.ADD) -> Sketch:
edges (Edge): sequence of perimeter edges edges (Edge): sequence of perimeter edges
mode (Mode, optional): combination mode. Defaults to Mode.ADD. mode (Mode, optional): combination mode. Defaults to Mode.ADD.
""" """
context: BuildSketch = BuildSketch._get_context(None) context: BuildSketch = BuildSketch._get_context("make_face")
validate_inputs(context, None, edges) validate_inputs(context, "make_face", edges)
if edges: if edges:
outer_edges = list(edges) outer_edges = list(edges)
@ -71,8 +71,8 @@ def make_hull(*edges: Edge, mode: Mode = Mode.ADD) -> Sketch:
pending and sketch edges. pending and sketch edges.
mode (Mode, optional): combination mode. Defaults to Mode.ADD. mode (Mode, optional): combination mode. Defaults to Mode.ADD.
""" """
context: BuildSketch = BuildSketch._get_context(None) context: BuildSketch = BuildSketch._get_context("make_hull")
validate_inputs(context, None, edges) validate_inputs(context, "make_hull", edges)
if edges: if edges:
hull_edges = list(edges) hull_edges = list(edges)

View file

@ -381,7 +381,7 @@ class TestValidateInputs(unittest.TestCase):
with BuildPart() as p: with BuildPart() as p:
Box(1, 1, 1) Box(1, 1, 1)
with BuildSketch(): with BuildSketch():
Add(p) add(p)
def test_no_sequence(self): def test_no_sequence(self):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):

View file

@ -67,28 +67,28 @@ class AddTests(unittest.TestCase):
def test_add_to_line(self): def test_add_to_line(self):
# Add Edge # Add Edge
with BuildLine() as test: with BuildLine() as test:
Add(Edge.make_line((0, 0, 0), (1, 1, 1))) add(Edge.make_line((0, 0, 0), (1, 1, 1)))
self.assertTupleAlmostEquals((test.wires()[0] @ 1).to_tuple(), (1, 1, 1), 5) self.assertTupleAlmostEquals((test.wires()[0] @ 1).to_tuple(), (1, 1, 1), 5)
# Add Wire # Add Wire
with BuildLine() as wire: with BuildLine() as wire:
Polyline((0, 0, 0), (1, 1, 1), (2, 0, 0), (3, 1, 1)) Polyline((0, 0, 0), (1, 1, 1), (2, 0, 0), (3, 1, 1))
with BuildLine() as test: with BuildLine() as test:
Add(wire.wires()[0]) add(wire.wires()[0])
self.assertEqual(len(test.line.edges()), 3) self.assertEqual(len(test.line.edges()), 3)
def test_add_to_sketch(self): def test_add_to_sketch(self):
with BuildSketch() as test: with BuildSketch() as test:
Add(Face.make_rect(10, 10)) add(Face.make_rect(10, 10))
self.assertAlmostEqual(test.sketch.area, 100, 5) self.assertAlmostEqual(test.sketch.area, 100, 5)
def test_add_to_part(self): def test_add_to_part(self):
# Add Solid # Add Solid
with BuildPart() as test: with BuildPart() as test:
Add(Solid.make_box(10, 10, 10)) add(Solid.make_box(10, 10, 10))
self.assertAlmostEqual(test.part.volume, 1000, 5) self.assertAlmostEqual(test.part.volume, 1000, 5)
# Add Compound # Add Compound
with BuildPart() as test: with BuildPart() as test:
Add( add(
Compound.make_compound( Compound.make_compound(
[ [
Solid.make_box(10, 10, 10), Solid.make_box(10, 10, 10),
@ -101,17 +101,17 @@ class AddTests(unittest.TestCase):
with BuildLine() as wire: with BuildLine() as wire:
Polyline((0, 0, 0), (1, 1, 1), (2, 0, 0), (3, 1, 1)) Polyline((0, 0, 0), (1, 1, 1), (2, 0, 0), (3, 1, 1))
with BuildPart() as test: with BuildPart() as test:
Add(wire.wires()[0]) add(wire.wires()[0])
self.assertEqual(len(test.pending_edges), 3) self.assertEqual(len(test.pending_edges), 3)
def test_errors(self): def test_errors(self):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
Add(Edge.make_line((0, 0, 0), (1, 1, 1))) add(Edge.make_line((0, 0, 0), (1, 1, 1)))
def test_unsupported_builder(self): def test_unsupported_builder(self):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
with _TestBuilder(): with _TestBuilder():
Add(Edge.make_line((0, 0, 0), (1, 1, 1))) add(Edge.make_line((0, 0, 0), (1, 1, 1)))
def test_local_global_locations(self): def test_local_global_locations(self):
"""Check that add is using a local location list""" """Check that add is using a local location list"""
@ -124,7 +124,7 @@ class AddTests(unittest.TestCase):
extrude(amount=10) extrude(amount=10)
topf = mainp.faces().sort_by(Axis.Z)[-1] topf = mainp.faces().sort_by(Axis.Z)[-1]
with BuildSketch(topf): with BuildSketch(topf):
Add(vertwalls.sketch) add(vertwalls.sketch)
extrude(amount=15) extrude(amount=15)
self.assertEqual(len(mainp.solids()), 1) self.assertEqual(len(mainp.solids()), 1)
@ -135,7 +135,7 @@ class AddTests(unittest.TestCase):
] ]
with BuildPart(Plane.XY, Plane.YZ) as multiple: with BuildPart(Plane.XY, Plane.YZ) as multiple:
with Locations((1, 1), (-1, -1)) as locs: with Locations((1, 1), (-1, -1)) as locs:
Add(*faces) add(*faces)
self.assertEqual(len(multiple.pending_faces), 16) self.assertEqual(len(multiple.pending_faces), 16)
@ -283,7 +283,7 @@ class ChamferTests(unittest.TestCase):
self.assertAlmostEqual(test.sketch.area, 200 - 4 * 0.5, 5) self.assertAlmostEqual(test.sketch.area, 200 - 4 * 0.5, 5)
def test_errors(self): def test_errors(self):
with self.assertRaises(ValueError): with self.assertRaises(RuntimeError):
with BuildLine(): with BuildLine():
chamfer(length=1) chamfer(length=1)
@ -321,7 +321,7 @@ class FilletTests(unittest.TestCase):
self.assertAlmostEqual(test.sketch.area, 200 - 4 + pi, 5) self.assertAlmostEqual(test.sketch.area, 200 - 4 + pi, 5)
def test_errors(self): def test_errors(self):
with self.assertRaises(ValueError): with self.assertRaises(RuntimeError):
with BuildLine(): with BuildLine():
fillet(radius=1) fillet(radius=1)

View file

@ -120,11 +120,11 @@ class TestBuildOnPlanes(unittest.TestCase):
def test_not_coplanar(self): def test_not_coplanar(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
with BuildSketch() as error: with BuildSketch() as error:
Add(Face.make_rect(1, 1, Plane.XY.offset(1))) add(Face.make_rect(1, 1, Plane.XY.offset(1)))
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
with BuildSketch() as error: with BuildSketch() as error:
Add(Face.make_rect(1, 1, Plane.XZ)) add(Face.make_rect(1, 1, Plane.XZ))
def test_changing_geometry(self): def test_changing_geometry(self):
with BuildSketch() as s: with BuildSketch() as s: