diff --git a/build123d_common.py b/build123d_common.py index 6584e95..9879fa2 100644 --- a/build123d_common.py +++ b/build123d_common.py @@ -160,10 +160,12 @@ class Kind(Enum): INTERSECTION = auto() TANGENT = auto() + class Keep(Enum): TOP = auto() BOTTOM = auto() + class FilterBy(Enum): LAST_OPERATION = auto() @@ -270,13 +272,19 @@ class ShapeList(list): return super().__init_subclass__() def filter_by_normal(self, axis: Axis): - return ShapeList( - filter( - lambda o: o.normalAt(o.Center()) == Vector(*ShapeList.axis_map[axis][0]) - or o.normalAt(o.Center()) == Vector(*ShapeList.axis_map[axis][1]), - self, - ) + result = filter( + lambda o: o.normalAt(o.Center()) == Vector(*ShapeList.axis_map[axis][0]) + or o.normalAt(o.Center()) == Vector(*ShapeList.axis_map[axis][1]), + self, ) + if axis == Axis.X: + result = sorted(result, key=lambda obj: obj.Center().x) + elif axis == Axis.Y: + result = sorted(result, key=lambda obj: obj.Center().y) + elif axis == Axis.Z: + result = sorted(result, key=lambda obj: obj.Center().z) + + return ShapeList(result) def filter_by_position( self, diff --git a/build_part.py b/build_part.py index 3092f81..03e4722 100644 --- a/build_part.py +++ b/build_part.py @@ -1,11 +1,10 @@ """ TODO: - add TwistExtrude, ProjectText -- add centered to each object +- add centered to wedge """ -from math import pi, sin, cos, radians, sqrt, tan -from typing import Union, Iterable, Callable -from enum import Enum, auto +from math import radians, tan +from typing import Union import cadquery as cq from cadquery import ( Edge, @@ -19,7 +18,7 @@ from cadquery import ( Solid, Plane, ) -from cadquery.occ_impl.shapes import VectorLike, Real +from cadquery.occ_impl.shapes import VectorLike import cq_warehouse.extensions from build123d_common import * @@ -540,12 +539,20 @@ class Box(Compound): width: float, height: float, rotation: RotationLike = (0, 0, 0), + centered: tuple[bool, bool, bool] = (True, True, True), mode: Mode = Mode.ADDITION, ): rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation position_planes = BuildPart.get_context().get_and_clear_locations() + center_offset = Vector( + -length / 2 if centered[0] else 0, + -width / 2 if centered[1] else 0, + -height / 2 if centered[2] else 0, + ) new_solids = [ - Solid.makeBox(length, width, height, pos, plane.zDir).moved(rotate) + Solid.makeBox(length, width, height, pos + center_offset, plane.zDir).moved( + rotate + ) for pos, plane in position_planes ] BuildPart.get_context().add_to_context(*new_solids, mode=mode) @@ -560,16 +567,22 @@ class Cone(Compound): height: float, angle: float = 360, rotation: RotationLike = (0, 0, 0), + centered: tuple[bool, bool, bool] = (True, True, True), mode: Mode = Mode.ADDITION, ): rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation position_planes = BuildPart.get_context().get_and_clear_locations() + center_offset = Vector( + 0 if centered[0] else max(bottom_radius, top_radius), + 0 if centered[1] else max(bottom_radius, top_radius), + -height / 2 if centered[2] else 0, + ) new_solids = [ Solid.makeCone( bottom_radius, top_radius, height, - pos, + pos + center_offset, plane.zDir, angle, ).moved(rotate) @@ -586,12 +599,20 @@ class Cylinder(Compound): height: float, angle: float = 360, rotation: RotationLike = (0, 0, 0), + centered: tuple[bool, bool, bool] = (True, True, True), mode: Mode = Mode.ADDITION, ): rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation position_planes = BuildPart.get_context().get_and_clear_locations() + center_offset = Vector( + 0 if centered[0] else radius, + 0 if centered[1] else radius, + -height / 2 if centered[2] else 0, + ) new_solids = [ - Solid.makeCylinder(radius, height, pos, plane.zDir, angle).moved(rotate) + Solid.makeCylinder( + radius, height, pos + center_offset, plane.zDir, angle + ).moved(rotate) for pos, plane in position_planes ] BuildPart.get_context().add_to_context(*new_solids, mode=mode) @@ -606,14 +627,20 @@ class Sphere(Compound): angle2: float = 90, angle3: float = 360, rotation: RotationLike = (0, 0, 0), + centered: tuple[bool, bool, bool] = (True, True, True), mode: Mode = Mode.ADDITION, ): rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation position_planes = BuildPart.get_context().get_and_clear_locations() + center_offset = Vector( + 0 if centered[0] else radius, + 0 if centered[1] else radius, + 0 if centered[2] else radius, + ) new_solids = [ - Solid.makeSphere(radius, pos, plane.zDir, angle1, angle2, angle3).moved( - rotate - ) + Solid.makeSphere( + radius, pos + center_offset, plane.zDir, angle1, angle2, angle3 + ).moved(rotate) for pos, plane in position_planes ] BuildPart.get_context().add_to_context(*new_solids, mode=mode) @@ -628,13 +655,24 @@ class Torus(Compound): angle1: float = 0, angle2: float = 360, rotation: RotationLike = (0, 0, 0), + centered: tuple[bool, bool, bool] = (True, True, True), mode: Mode = Mode.ADDITION, ): rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation position_planes = BuildPart.get_context().get_and_clear_locations() + center_offset = Vector( + 0 if centered[0] else major_radius, + 0 if centered[1] else major_radius, + 0 if centered[2] else minor_radius, + ) new_solids = [ Solid.makeTorus( - major_radius, minor_radius, pos, plane.zDir, angle1, angle2 + major_radius, + minor_radius, + pos + center_offset, + plane.zDir, + angle1, + angle2, ).moved(rotate) for pos, plane in position_planes ] diff --git a/build_part_test.py b/build_part_test.py index 16ad2af..c0ff5ef 100644 --- a/build_part_test.py +++ b/build_part_test.py @@ -5,37 +5,36 @@ TODO: from build_part import * from build_sketch import * -# with BuildPart(workplane=Plane.named("XZ")) as rail: -# with BuildSketch() as din: -# PushPointsToSketch((0, 0.5)) -# Rectangle(35, 1) -# PushPointsToSketch((0, 7.5 / 2)) -# Rectangle(27, 7.5) -# PushPointsToSketch((0, 6.5 / 2)) -# Rectangle(25, 6.5, mode=Mode.SUBTRACTION) -# inside_vertices = ( -# din.vertices() -# .filter_by_position(Axis.Y, 0.0, 7.5, inclusive=(False, False)) -# .filter_by_position(Axis.X, -17.5, 17.5, inclusive=(False, False)) -# ) -# FilletSketch(*inside_vertices, radius=0.8) -# outside_vertices = list( -# filter( -# lambda v: (v.Y == 0.0 or v.Y == 7.5) and -17.5 < v.X < 17.5, -# din.vertices(), -# ) -# ) -# FilletSketch(*outside_vertices, radius=1.8) -# Extrude(1000) -# top = rail.faces().filter_by_normal(Axis.Z).sort_by(SortBy.Z)[-1] -# WorkplanesFromFaces(top, replace=True) -# with BuildSketch() as slots: -# RectangularArrayToSketch(0, 25, 1, 39) -# SlotOverall(15, 6.2, rotation=90) -# slot_holes = Extrude(-20, mode=Mode.SUBTRACTION) +with BuildPart(workplane=Plane.named("XZ")) as rail: + with BuildSketch() as din: + PushPointsToSketch((0, 0.5)) + Rectangle(35, 1) + PushPointsToSketch((0, 7.5 / 2)) + Rectangle(27, 7.5) + PushPointsToSketch((0, 6.5 / 2)) + Rectangle(25, 6.5, mode=Mode.SUBTRACTION) + inside_vertices = ( + din.vertices() + .filter_by_position(Axis.Y, 0.0, 7.5, inclusive=(False, False)) + .filter_by_position(Axis.X, -17.5, 17.5, inclusive=(False, False)) + ) + FilletSketch(*inside_vertices, radius=0.8) + outside_vertices = list( + filter( + lambda v: (v.Y == 0.0 or v.Y == 7.5) and -17.5 < v.X < 17.5, + din.vertices(), + ) + ) + FilletSketch(*outside_vertices, radius=1.8) + Extrude(1000) + top = rail.faces().filter_by_normal(Axis.Z)[-1] + WorkplanesFromFaces(top, replace=True) + with BuildSketch() as slots: + RectangularArrayToSketch(0, 25, 1, 39) + SlotOverall(15, 6.2, rotation=90) + slot_holes = Extrude(-20, mode=Mode.SUBTRACTION) with BuildPart() as cube: - PushPointsToPart((-5, -5, -5)) Box(10, 10, 10, rotation=(10, 20, 30)) WorkplanesFromFaces(*cube.faces(), replace=True) with BuildSketch() as pipe: @@ -73,7 +72,7 @@ with BuildPart() as hole: CounterBoreHole(2, 4, 1) if "show_object" in locals(): - # show_object(rail.part, name="rail") + show_object(rail.part, name="rail") show_object(cube.part, name="cube") # show_object(s.part, name="sphere") # show_object(c.part, name="cone")