Skip to content

Commit

Permalink
Fixed typing in operations_part.py
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jan 11, 2025
1 parent 8a94a9f commit 152aedf
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 55 deletions.
14 changes: 9 additions & 5 deletions src/build123d/build_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ def _is_point(obj):
T = TypeVar("T", Any, list[Any])


def flatten_sequence(*obj: T) -> list[Any]:
def flatten_sequence(*obj: T) -> ShapeList[Any]:
"""Convert a sequence of object potentially containing iterables into a flat list"""

flat_list = []
flat_list = ShapeList()
for item in obj:
# Note: an Iterable can't be used here as it will match with Vector & Vertex
# and break them into a list of floats.
Expand Down Expand Up @@ -316,10 +316,14 @@ def _add_to_pending(self, *objects: Edge | Face, face_plane: Plane | None = None
"""Integrate a sequence of objects into existing builder object"""
return NotImplementedError # pragma: no cover

T = TypeVar("T", bound="Builder")

@classmethod
def _get_context(
cls, caller: Builder | Shape | Joint | str | None = None, log: bool = True
) -> Builder | None:
cls: Type[T],
caller: Builder | Shape | Joint | str | None = None,
log: bool = True,
) -> T | None:
"""Return the instance of the current builder"""
result = cls._current.get(None)
context_name = "None" if result is None else type(result).__name__
Expand Down Expand Up @@ -818,7 +822,7 @@ def __getattr__(self, name):


def validate_inputs(
context: Builder, validating_class, objects: Iterable[Shape] | None = None
context: Builder | None, validating_class, objects: Iterable[Shape] | None = None
):
"""A function to wrap the method when used outside of a Builder context"""
if context is None:
Expand Down
120 changes: 70 additions & 50 deletions src/build123d/operations_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"""

from __future__ import annotations
from typing import Union
from typing import cast

from collections.abc import Iterable
from build123d.build_enums import Mode, Until, Kind, Side
Expand Down Expand Up @@ -56,11 +56,11 @@


def extrude(
to_extrude: Face | Sketch = None,
amount: float = None,
dir: VectorLike = None, # pylint: disable=redefined-builtin
until: Until = None,
target: Compound | Solid = None,
to_extrude: Face | Sketch | None = None,
amount: float | None = None,
dir: VectorLike | None = None, # pylint: disable=redefined-builtin
until: Until | None = None,
target: Compound | Solid | None = None,
both: bool = False,
taper: float = 0.0,
clean: bool = True,
Expand Down Expand Up @@ -89,7 +89,7 @@ def extrude(
Part: extruded object
"""
# pylint: disable=too-many-locals, too-many-branches
context: BuildPart = BuildPart._get_context("extrude")
context: BuildPart | None = BuildPart._get_context("extrude")
validate_inputs(context, "extrude", to_extrude)

to_extrude_faces: list[Face]
Expand Down Expand Up @@ -130,12 +130,6 @@ def extrude(
if len(face_planes) != len(to_extrude_faces):
raise ValueError("dir must be provided when extruding non-planar faces")

if until is not None:
if target is None and context is None:
raise ValueError("A target object must be provided")
if target is None:
target = context.part

logger.info(
"%d face(s) to extrude on %d face plane(s)",
len(to_extrude_faces),
Expand All @@ -144,7 +138,7 @@ def extrude(

for face, plane in zip(to_extrude_faces, face_planes):
for direction in [1, -1] if both else [1]:
if amount:
if amount is not None:
if taper == 0:
new_solids.append(
Solid.extrude(
Expand All @@ -162,10 +156,19 @@ def extrude(
)

else:
if until is None:
raise ValueError("Either amount or until must be provided")
if target is None:
if context is None:
raise ValueError("A target object must be provided")
target_object = context.part
else:
target_object = target

new_solids.append(
Solid.extrude_until(
section=face,
target_object=target,
target_object=target_object,
direction=plane.z_dir * direction,
until=until,
)
Expand All @@ -186,7 +189,7 @@ def extrude(


def loft(
sections: Face | Sketch | Iterable[Vertex | Face | Sketch] = None,
sections: Face | Sketch | Iterable[Vertex | Face | Sketch] | None = None,
ruled: bool = False,
clean: bool = True,
mode: Mode = Mode.ADD,
Expand All @@ -203,7 +206,7 @@ def loft(
clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""
context: BuildPart = BuildPart._get_context("loft")
context: BuildPart | None = BuildPart._get_context("loft")

section_list = flatten_sequence(sections)
validate_inputs(context, "loft", section_list)
Expand Down Expand Up @@ -235,10 +238,11 @@ def loft(
elif isinstance(s, Face):
loft_wires.append(s.outer_wire())
elif isinstance(s, Sketch):
loft_wires.append(s.face().outer_wire())
loft_wires.extend([f.outer_wire() for f in s.faces()])
elif all(isinstance(s, Vertex) for s in section_list):
raise ValueError(
"At least one face/sketch is required if vertices are the first, last, or first and last elements"
"At least one face/sketch is required if vertices are the first, last, "
"or first and last elements"
)

new_solid = Solid.make_loft(loft_wires, ruled)
Expand All @@ -262,7 +266,7 @@ def loft(
def make_brake_formed(
thickness: float,
station_widths: float | Iterable[float],
line: Edge | Wire | Curve = None,
line: Edge | Wire | Curve | None = None,
side: Side = Side.LEFT,
kind: Kind = Kind.ARC,
clean: bool = True,
Expand Down Expand Up @@ -298,7 +302,7 @@ def make_brake_formed(
Part: sheet metal part
"""
# pylint: disable=too-many-locals, too-many-branches
context: BuildPart = BuildPart._get_context("make_brake_formed")
context: BuildPart | None = BuildPart._get_context("make_brake_formed")
validate_inputs(context, "make_brake_formed")

if line is not None:
Expand All @@ -321,8 +325,16 @@ def make_brake_formed(
raise ValueError("line not suitable - probably straight") from exc

# Make edge pairs
station_edges = ShapeList()
station_edges: ShapeList[Edge] = ShapeList()
line_vertices = line.vertices()

if isinstance(station_widths, (float, int)):
station_widths_list = [station_widths] * len(line_vertices)
elif isinstance(station_widths, Iterable):
station_widths_list = list(station_widths)
else:
raise TypeError("station_widths must be either a single number or an iterable")

for vertex in line_vertices:
others = offset_vertices.sort_by_distance(Vector(vertex.X, vertex.Y, vertex.Z))
for other in others[1:]:
Expand All @@ -333,27 +345,25 @@ def make_brake_formed(
break
station_edges = station_edges.sort_by(line)

if isinstance(station_widths, (float, int)):
station_widths = [station_widths] * len(line_vertices)
if len(station_widths) != len(line_vertices):
if len(station_widths_list) != len(line_vertices):
raise ValueError(
f"widths must either be a single number or an iterable with "
f"a length of the # vertices in line ({len(line_vertices)})"
)
station_faces = [
Face.extrude(obj=e, direction=plane.z_dir * w)
for e, w in zip(station_edges, station_widths)
for e, w in zip(station_edges, station_widths_list)
]
sweep_paths = line.edges().sort_by(line)
sections = []
sections: list[Solid] = []
for i in range(len(station_faces) - 1):
sections.append(
Solid.sweep_multi(
[station_faces[i], station_faces[i + 1]], path=sweep_paths[i]
)
)
if len(sections) > 1:
new_solid = sections.pop().fuse(*sections)
new_solid = cast(Part, Part.fuse(*sections))
else:
new_solid = sections[0]

Expand Down Expand Up @@ -391,7 +401,7 @@ def project_workplane(
Returns:
Plane: workplane aligned for projection
"""
context: BuildPart = BuildPart._get_context("project_workplane")
context: BuildPart | None = BuildPart._get_context("project_workplane")

if context is not None and not isinstance(context, BuildPart):
raise RuntimeError(
Expand Down Expand Up @@ -422,7 +432,7 @@ def project_workplane(


def revolve(
profiles: Face | Iterable[Face] = None,
profiles: Face | Iterable[Face] | None = None,
axis: Axis = Axis.Z,
revolution_arc: float = 360.0,
clean: bool = True,
Expand All @@ -444,7 +454,7 @@ def revolve(
Raises:
ValueError: Invalid axis of revolution
"""
context: BuildPart = BuildPart._get_context("revolve")
context: BuildPart | None = BuildPart._get_context("revolve")

profile_list = flatten_sequence(profiles)

Expand All @@ -458,16 +468,13 @@ def revolve(
if all([s is None for s in profile_list]):
if context is None or (context is not None and not context.pending_faces):
raise ValueError("No profiles provided")
profile_list = context.pending_faces
profile_faces = context.pending_faces
context.pending_faces = []
context.pending_face_planes = []
else:
p_list = []
for profile in profile_list:
p_list.extend(profile.faces())
profile_list = p_list
profile_faces = profile_list.faces()

new_solids = [Solid.revolve(profile, angle, axis) for profile in profile_list]
new_solids = [Solid.revolve(profile, angle, axis) for profile in profile_faces]

new_solid = Compound(new_solids)
if context is not None:
Expand All @@ -479,7 +486,7 @@ def revolve(


def section(
obj: Part = None,
obj: Part | None = None,
section_by: Plane | Iterable[Plane] = Plane.XZ,
height: float = 0.0,
clean: bool = True,
Expand All @@ -497,13 +504,17 @@ def section(
clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination mode. Defaults to Mode.INTERSECT.
"""
context: BuildPart = BuildPart._get_context("section")
context: BuildPart | None = BuildPart._get_context("section")
validate_inputs(context, "section", None)

if context is not None and obj is None:
max_size = context.part.bounding_box(optimal=False).diagonal
if obj is not None:
to_section = obj
elif context is not None:
to_section = context.part
else:
max_size = obj.bounding_box(optimal=False).diagonal
raise ValueError("No object to section")

max_size = to_section.bounding_box(optimal=False).diagonal

if section_by is not None:
section_planes = (
Expand All @@ -528,7 +539,13 @@ def section(
else:
raise ValueError("obj must be provided")

new_objects = [obj.intersect(plane) for plane in planes]
new_objects: list[Face | Shell] = []
for plane in planes:
intersection = to_section.intersect(plane)
if isinstance(intersection, ShapeList):
new_objects.extend(intersection)
elif intersection is not None:
new_objects.append(intersection)

if context is not None:
context._add_to_context(
Expand All @@ -542,9 +559,9 @@ def section(


def thicken(
to_thicken: Face | Sketch = None,
amount: float = None,
normal_override: VectorLike = None,
to_thicken: Face | Sketch | None = None,
amount: float | None = None,
normal_override: VectorLike | None = None,
both: bool = False,
clean: bool = True,
mode: Mode = Mode.ADD,
Expand All @@ -555,7 +572,7 @@ def thicken(
Args:
to_thicken (Union[Face, Sketch], optional): object to thicken. Defaults to None.
amount (float, optional): distance to extrude, sign controls direction. Defaults to None.
amount (float): distance to extrude, sign controls direction.
normal_override (Vector, optional): The normal_override vector can be used to
indicate which way is 'up', potentially flipping the face normal direction
such that many faces with different normals all go in the same direction
Expand All @@ -571,11 +588,14 @@ def thicken(
Returns:
Part: extruded object
"""
context: BuildPart = BuildPart._get_context("thicken")
context: BuildPart | None = BuildPart._get_context("thicken")
validate_inputs(context, "thicken", to_thicken)

to_thicken_faces: list[Face]

if amount is None:
raise ValueError("An amount must be provided")

if to_thicken is None:
if context is not None and context.pending_faces:
# Get pending faces and face planes
Expand Down Expand Up @@ -603,15 +623,15 @@ def thicken(
for direction in [1, -1] if both else [1]:
new_solids.append(
Solid.thicken(
face, depth=amount, normal_override=face_normal * direction
face, depth=amount, normal_override=Vector(face_normal) * direction
)
)

if context is not None:
context._add_to_context(*new_solids, clean=clean, mode=mode)
else:
if len(new_solids) > 1:
new_solids = [new_solids.pop().fuse(*new_solids)]
new_solids = [cast(Part, Part.fuse(*new_solids))]
if clean:
new_solids = [solid.clean() for solid in new_solids]

Expand Down

0 comments on commit 152aedf

Please sign in to comment.