From cf5f140d023e52a163438746b1dd7d48425d37d5 Mon Sep 17 00:00:00 2001 From: Roger Maitland Date: Fri, 15 Jul 2022 11:19:44 -0400 Subject: [PATCH] Vase example, Workplanes, new filters --- examples/din_rail.py | 2 +- examples/pillow_block.py | 2 +- examples/vase.py | 67 ++++++++++++++++++++++++++++++ src/build123d/build123d_common.py | 69 ++++++++++++++++++++++++++++--- src/build123d/build_part.py | 15 +++++++ src/build123d/build_sketch.py | 6 ++- 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 examples/vase.py diff --git a/examples/din_rail.py b/examples/din_rail.py index 0d9d618..cb0de6c 100644 --- a/examples/din_rail.py +++ b/examples/din_rail.py @@ -65,7 +65,7 @@ with BuildPart(workplane=Plane.named("XZ")) as rail: ) FilletSketch(*outside_vertices, radius=fillet + thickness) Extrude(rail_length) - WorkplanesFromFaces(rail.faces().filter_by_normal(Axis.Z)[-1], replace=True) + WorkplanesFromFaces(rail.faces().filter_by_axis(Axis.Z)[-1], replace=True) with BuildSketch() as slots: RectangularArrayToSketch(0, slot_pitch, 1, rail_length // slot_pitch - 1) SlotOverall(slot_length, slot_width, rotation=90) diff --git a/examples/pillow_block.py b/examples/pillow_block.py index b5085e4..cb63979 100644 --- a/examples/pillow_block.py +++ b/examples/pillow_block.py @@ -39,7 +39,7 @@ with BuildPart() as pillow_block: Rectangle(width, height) FilletSketch(*plan.vertices(), radius=5) Extrude(thickness) - WorkplanesFromFaces(pillow_block.faces().filter_by_normal(Axis.Z)[-1]) + WorkplanesFromFaces(pillow_block.faces().filter_by_axis(Axis.Z)[-1]) CounterBoreHole(bearing_axle_radius, bearing_radius, bearing_thickness) RectangularArrayToPart(width - 2 * padding, height - 2 * padding, 2, 2) CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height) diff --git a/examples/vase.py b/examples/vase.py new file mode 100644 index 0000000..828a4da --- /dev/null +++ b/examples/vase.py @@ -0,0 +1,67 @@ +""" + +name: vase.py +by: Gumyr +date: July 15th 2022 + +desc: + + This example demonstrates revolving a sketch, shelling and selecting edges + by position range and type for fillets. + +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. +""" +from cadquery import Vector +from build123d.build123d_common import * +from build123d.build_line import * +from build123d.build_sketch import * +from build123d.build_part import * + +with BuildPart() as vase: + with BuildSketch() as profile: + with BuildLine() as outline: + l1 = Line((0, 0), (12, 0)) + l2 = RadiusArc(l1 @ 1, (15, 20), 50) + l3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1))) + l4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5) + l5 = Spline( + l4 @ 1, + l4 @ 1 + Vector(2.5, 2.5), + l4 @ 1 + Vector(0, 5), + tangents=(l4 % 1, (-1, 0)), + ) + Polyline( + l5 @ 1, + l5 @ 1 + Vector(0, 1), + (0, (l5 @ 1).y + 1), + l1 @ 0, + ) + BuildFace() + Revolve() + Shell(vase.faces().filter_by_axis(Axis.Y)[-1], thickness=-1) + top_edges = ( + vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by_type(Type.CIRCLE) + ) + # debug(top_edges) + FilletPart(*top_edges, radius=0.25) + FilletPart(vase.edges().sort_by(SortBy.Y)[0], radius=0.5) + + +if "show_object" in locals(): + # show_object(outline.line, name="outline") + # show_object(profile.sketch, name="profile") + show_object(vase.part, name="vase") diff --git a/src/build123d/build123d_common.py b/src/build123d/build123d_common.py index f5fe4a1..257065b 100644 --- a/src/build123d/build123d_common.py +++ b/src/build123d/build123d_common.py @@ -32,7 +32,7 @@ license: from math import radians from typing import Union from enum import Enum, auto -from cadquery import Edge, Wire, Vector, Location +from cadquery import Edge, Wire, Vector, Location, Face from OCP.gp import gp_Pnt, gp_Ax1, gp_Dir, gp_Trsf import cq_warehouse.extensions @@ -139,6 +139,25 @@ class SortBy(Enum): DISTANCE = auto() +class Type(Enum): + PLANE = auto() + CYLINDER = auto() + CONE = auto() + SPHERE = auto() + TORUS = auto() + BEZIER = auto() + BSPLINE = auto() + REVOLUTION = auto() + EXTRUSION = auto() + OFFSET = auto() + LINE = auto() + CIRCLE = auto() + ELLIPSE = auto() + HYPERBOLA = auto() + PARABOLA = auto() + OTHER = auto() + + # # DirectAPI Classes # @@ -171,11 +190,42 @@ class ShapeList(list): def __init_subclass__(cls) -> None: return super().__init_subclass__() - def filter_by_normal(self, axis: Axis): - 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, + def filter_by_axis(self, axis: Axis, tolerance=1e-5): + + planar_faces = filter( + lambda o: isinstance(o, Face) and o.geomType() == "PLANE", self + ) + linear_edges = filter( + lambda o: isinstance(o, Edge) and o.geomType() == "LINE", self + ) + + result = [] + + result = list( + filter( + lambda o: ( + o.normalAt(None) - Vector(*ShapeList.axis_map[axis][0]) + ).Length + <= tolerance + or (o.normalAt(None) - Vector(*ShapeList.axis_map[axis][1])).Length + <= tolerance, + planar_faces, + ) + ) + result.extend( + list( + filter( + lambda o: ( + o.tangentAt(None) - Vector(*ShapeList.axis_map[axis][0]) + ).Length + <= tolerance + or ( + o.tangentAt(o.Center()) - Vector(*ShapeList.axis_map[axis][1]) + ).Length + <= tolerance, + linear_edges, + ) + ) ) if axis == Axis.X: result = sorted(result, key=lambda obj: obj.Center().x) @@ -223,6 +273,13 @@ class ShapeList(list): return ShapeList(result) + def filter_by_type( + self, + type: Type, + ): + result = filter(lambda o: o.geomType() == type.name, self) + return ShapeList(result) + def sort_by(self, sort_by: SortBy = SortBy.Z, reverse: bool = False): if sort_by == SortBy.X: diff --git a/src/build123d/build_part.py b/src/build123d/build_part.py index 93149a0..463265d 100644 --- a/src/build123d/build_part.py +++ b/src/build123d/build_part.py @@ -827,6 +827,21 @@ class Sweep(Compound): super().__init__(Compound.makeCompound(new_solids).wrapped) +class Workplanes: + """Part Operation: Workplanes + + Create workplanes from the given sequence of planes, optionally replacing existing + workplanes. + + Args: + planes (Plane): sequence of planes to use as workplanes. + replace (bool, optional): replace existing workplanes. Defaults to True. + """ + + def __init__(self, *planes: Plane, replace=True): + BuildPart._get_context()._workplane(*planes, replace=replace) + + class WorkplanesFromFaces: """Part Operation: Workplanes from Faces diff --git a/src/build123d/build_sketch.py b/src/build123d/build_sketch.py index 8af50d2..c675aea 100644 --- a/src/build123d/build_sketch.py +++ b/src/build123d/build_sketch.py @@ -277,7 +277,8 @@ class BuildFace: """ def __init__(self, *edges: Edge, mode: Mode = Mode.ADD): - pending_face = Face.makeFromWires(Wire.combine(edges)[0]) + outer_edges = edges if edges else BuildSketch._get_context().pending_edges + pending_face = Face.makeFromWires(Wire.combine(outer_edges)[0]) BuildSketch._add_to_context(pending_face, mode) BuildSketch._get_context().pending_edges = [] @@ -293,7 +294,8 @@ class BuildHull: """ def __init__(self, *edges: Edge, mode: Mode = Mode.ADD): - pending_face = find_hull(edges) + hull_edges = edges if edges else BuildSketch._get_context().pending_edges + pending_face = find_hull(hull_edges) BuildSketch._add_to_context(pending_face, mode) BuildSketch._get_context().pending_edges = []