From 833b138ed5a539f59b66bbbf9ee0da95d2c8dcde Mon Sep 17 00:00:00 2001 From: Roger Maitland Date: Mon, 25 Jul 2022 10:10:47 -0400 Subject: [PATCH] Added generic operations --- src/build123d/__init__.py | 20 +- .../{build123d_common.py => build_common.py} | 0 ...generic_operations.py => build_generic.py} | 185 +++++++++++++++++- src/build123d/build_line.py | 2 +- src/build123d/build_part.py | 2 +- src/build123d/build_sketch.py | 2 +- 6 files changed, 194 insertions(+), 17 deletions(-) rename src/build123d/{build123d_common.py => build_common.py} (100%) rename src/build123d/{build_generic_operations.py => build_generic.py} (58%) diff --git a/src/build123d/__init__.py b/src/build123d/__init__.py index f505970..76fa999 100644 --- a/src/build123d/__init__.py +++ b/src/build123d/__init__.py @@ -1,8 +1,8 @@ -from .build123d_common import * +from .build_common import * from .build_line import * from .build_sketch import * from .build_part import * -from .build_generic_operations import * +from .build_generic import * __all__ = [ "Select", @@ -25,8 +25,12 @@ __all__ = [ "BoundingBox", "Chamfer", "Fillet", + "HexArray", + "Mirror", + "PolarArray", + "PushPoints", + "RectangularArray", "BuildLine", - "MirrorToLine", "CenterArc", "Helix", "Line", @@ -45,9 +49,6 @@ __all__ = [ "FilletPart", "Hole", "Loft", - "PolarArrayToPart", - "PushPointsToPart", - "RectangularArrayToPart", "Revolve", "Section", "Shell", @@ -63,18 +64,11 @@ __all__ = [ "Torus", "Wedge", "BuildSketch", - "BoundingBoxSketch", "BuildFace", "BuildHull", "ChamferSketch", "FilletSketch", - "HexArrayToSketch", - "MirrorToSketch", "Offset", - "PolarArrayToSketch", - "PushPointsToSketch", - "RectangularArrayToSketch", - "AddToSketch", "Circle", "Ellipse", "Polygon", diff --git a/src/build123d/build123d_common.py b/src/build123d/build_common.py similarity index 100% rename from src/build123d/build123d_common.py rename to src/build123d/build_common.py diff --git a/src/build123d/build_generic_operations.py b/src/build123d/build_generic.py similarity index 58% rename from src/build123d/build_generic_operations.py rename to src/build123d/build_generic.py index 63b2fa5..6d499e7 100644 --- a/src/build123d/build_generic_operations.py +++ b/src/build123d/build_generic.py @@ -1,8 +1,9 @@ -from build123d.build123d_common import * +from build123d.build_common import * from build123d.build_line import BuildLine from build123d.build_sketch import BuildSketch from build123d.build_part import BuildPart from cadquery import Shape, Vertex +from cadquery.occ_impl.shapes import VectorLike class Add(Compound): @@ -214,3 +215,185 @@ class Fillet(Compound): raise RuntimeError( f"Fillet does not support builder {current_context.__class__.__name__}" ) + + +class HexArray: + """Generic Operation: Hex Array + + Creates a hexagon array of points and pushes them to Part or Sketch locations. + + Args: + diagonal: tip to tip size of hexagon ( must be > 0) + xCount: number of points ( > 0 ) + yCount: number of points ( > 0 ) + center: If True, the array will be centered around the workplane center. + If False, the lower corner will be on the reference point and the array will + extend in the positive x and y directions. Can also use a 2-tuple to specify + centering along each axis. + + Raises: + ValueError: Spacing and count must be > 0 + """ + + def __init__( + self, + diagonal: float, + xCount: int, + yCount: int, + center: Union[bool, tuple[bool, bool]] = True, + ): + xSpacing = 3 * diagonal / 4 + ySpacing = diagonal * sqrt(3) / 2 + if xSpacing <= 0 or ySpacing <= 0 or xCount < 1 or yCount < 1: + raise ValueError("Spacing and count must be > 0 ") + + if isinstance(center, bool): + center = (center, center) + + lpoints = [] # coordinates relative to bottom left point + for x in range(0, xCount, 2): + for y in range(yCount): + lpoints.append(Vector(xSpacing * x, ySpacing * y + ySpacing / 2)) + for x in range(1, xCount, 2): + for y in range(yCount): + lpoints.append(Vector(xSpacing * x, ySpacing * y + ySpacing)) + + # shift points down and left relative to origin if requested + offset = Vector() + if center[0]: + offset += Vector(-xSpacing * (xCount - 1) * 0.5, 0) + if center[1]: + offset += Vector(0, -ySpacing * (yCount - 1) * 0.5) + lpoints = [x + offset for x in lpoints] + + # convert to locations + new_locations = [Location(pt) for pt in lpoints] + + Builder._get_context().locations = new_locations + + +class Mirror(Compound): + """Generic Operation: Mirror + + Add the mirror of the provided sequence of faces about the given axis to Line or Sketch. + + Args: + objects (Union[Edge, Face,Compound]): sequence of edges or faces to mirror + axis (Axis, optional): axis to mirror about. Defaults to Axis.X. + mode (Mode, optional): combination mode. Defaults to Mode.ADD. + """ + + def __init__( + self, + *objects: Union[Edge, Face, Compound], + axis: Axis = Axis.X, + mode: Mode = Mode.ADD, + ): + new_edges = [obj for obj in objects if isinstance(obj, Edge)] + new_faces = [obj for obj in objects if isinstance(obj, Face)] + for compound in filter(lambda o: isinstance(o, Compound), objects): + new_faces.extend(compound.Faces()) + + mirrored_edges = Plane.named("XY").mirrorInPlane(new_edges, axis=axis.name) + mirrored_faces = Plane.named("XY").mirrorInPlane(new_faces, axis=axis.name) + + current_context = Builder._get_context() + if isinstance(current_context, BuildLine): + current_context._add_to_context(*mirrored_edges, mode=mode) + elif isinstance(current_context, BuildLine): + current_context._add_to_context(*mirrored_edges, mode=mode) + current_context._add_to_context(*mirrored_faces, mode=mode) + else: + raise RuntimeError( + f"Mirror does not support builder {current_context.__class__.__name__}" + ) + super().__init__(Compound.makeCompound(mirrored_edges + mirrored_faces).wrapped) + + +class PolarArray: + """Generic Operation: Polar Array + + Push a polar array of locations to Part or Sketch + + Args: + radius (float): array radius + start_angle (float): angle to first point from +ve X axis + stop_angle (float): angle to last point from +ve X axis + count (int): Number of points to push + rotate (bool, optional): Align locations with arc tangents. Defaults to True. + + Raises: + ValueError: Count must be greater than or equal to 1 + """ + + def __init__( + self, + radius: float, + start_angle: float, + stop_angle: float, + count: int, + rotate: bool = True, + ): + if count < 1: + raise ValueError(f"At least 1 elements required, requested {count}") + + angle_step = (stop_angle - start_angle) / count + + # Note: rotate==False==0 so the location orientation doesn't change + new_locations = [ + Location( + Vector(radius, 0).rotateZ(start_angle + angle_step * i), + Vector(0, 0, 1), + rotate * angle_step * i, + ) + for i in range(count) + ] + + Builder._get_context().locations = new_locations + + +class PushPoints: + """Generic Operation: Push Points + + Push sequence of locations to Part or Sketch + + Args: + pts (Union[VectorLike, Location]): sequence of points to push + """ + + def __init__(self, *pts: Union[VectorLike, Location]): + new_locations = [ + Location(Vector(pt)) if not isinstance(pt, Location) else pt for pt in pts + ] + Builder._get_context().locations = new_locations + + +class RectangularArray: + """Generic Operation: Rectangular Array + + Push a rectangular array of locations to Part or Sketch + + Args: + x_spacing (float): horizontal spacing + y_spacing (float): vertical spacing + x_count (int): number of horizontal points + y_count (int): number of vertical points + + Raises: + ValueError: Either x or y count must be greater than or equal to one. + """ + + def __init__(self, x_spacing: float, y_spacing: float, x_count: int, y_count: int): + if x_count < 1 or y_count < 1: + raise ValueError( + f"At least 1 elements required, requested {x_count}, {y_count}" + ) + + new_locations = [] + offset = Vector((x_count - 1) * x_spacing, (y_count - 1) * y_spacing) * 0.5 + for i, j in product(range(x_count), range(y_count)): + new_locations.append( + Location(Vector(i * x_spacing, j * y_spacing) - offset) + ) + + Builder._get_context().locations = new_locations diff --git a/src/build123d/build_line.py b/src/build123d/build_line.py index 609b15c..860d4e9 100644 --- a/src/build123d/build_line.py +++ b/src/build123d/build_line.py @@ -48,7 +48,7 @@ import cq_warehouse.extensions # from .build123d_common import * # from .build_sketch import BuildSketch # from .build_part import BuildPart -from build123d.build123d_common import * +from build123d.build_common import * class BuildLine(Builder): diff --git a/src/build123d/build_part.py b/src/build123d/build_part.py index 149da6a..a209396 100644 --- a/src/build123d/build_part.py +++ b/src/build123d/build_part.py @@ -60,7 +60,7 @@ from cadquery import ( from cadquery.occ_impl.shapes import VectorLike import cq_warehouse.extensions -from build123d.build123d_common import * +from build123d.build_common import * class BuildPart(Builder): diff --git a/src/build123d/build_sketch.py b/src/build123d/build_sketch.py index d97c935..47de986 100644 --- a/src/build123d/build_sketch.py +++ b/src/build123d/build_sketch.py @@ -60,7 +60,7 @@ from cadquery import Edge, Face, Wire, Vector, Shape, Location, Vertex, Compound from cadquery.occ_impl.shapes import VectorLike import cq_warehouse.extensions -from build123d.build123d_common import * +from build123d.build_common import * class BuildSketch(Builder):