diff --git a/examples/algpart.py b/examples/algpart.py deleted file mode 100644 index f4beef6..0000000 --- a/examples/algpart.py +++ /dev/null @@ -1,81 +0,0 @@ -from build123d import * - -try: - from ocp_vscode import show_object, show -except: - ... -# %% - -with BuildPart() as bp: - x = Box(1, 2, 1) - -if "show" in locals(): - show(bp) - -# %% - -b = Box(1, 2, 3) -c = Cylinder(0.1, 4) -d = b - [ - Plane.XZ * Pos(y=1) * c, - Plane.XZ * Pos(y=-1) * c, - Plane.YZ * c, -] - -if "show" in locals(): - show(d, transparent=True) - -# %% - -with BuildPart() as bp: - Box(1, 2, 3) - with Locations(Plane.XZ): - with Locations(Location((0, 1, 0)), Location((0, -1, 0))): - Cylinder(0.1, 4, mode=Mode.SUBTRACT) - with Locations(Plane.YZ): - Cylinder(0.1, 4, mode=Mode.SUBTRACT) - -if "show" in locals(): - show(bp) - -# %% - -with BuildPart() as bp2: - Box(1, 2, 3) + Box(0.5, 0.5, 4) - -if "show" in locals(): - show(bp2) - -# %% - -b = Box(1, 2, 3) -with BuildPart() as bp: - b - -# bp.part is None -if "show" in locals(): - show(bp) - -# %% - -b = Box(1, 2, 3) -with BuildPart() as bp: - add(b) - -if "show" in locals(): - show(bp) - -# %% - -b = Box(1, 2, 3) + Cylinder(0.75, 2.5) - -with BuildPart() as bp: - add(b) - Cylinder(0.4, 6, mode=Mode.SUBTRACT) - -c = bp.part - Plane.YZ * Cylinder(0.2, 6) - -if "show" in locals(): - show(c) - -# %% diff --git a/examples/algsketch.py b/examples/algsketch.py deleted file mode 100644 index 8e0faba..0000000 --- a/examples/algsketch.py +++ /dev/null @@ -1,111 +0,0 @@ -from build123d import * - -try: - from ocp_vscode import show_object, show -except: - ... - -# %% - -with BuildSketch() as sk: - x = Rectangle(1, 2) - -if "show" in locals(): - show(sk) - -# %% - -b = Rectangle(1, 2) -c = Circle(0.1) -d = b - [ - Pos(y=0.6) * c, - Pos(y=-0.6) * c, -] - -if "show" in locals(): - show(d, transparent=True) - -# %% - -with BuildSketch() as sk: - Rectangle(1, 2) - with Locations(Location((0, 0.6, 0)), Location((0, -0.6, 0))): - Circle(0.1, mode=Mode.SUBTRACT) - Circle(0.1, mode=Mode.SUBTRACT) - -if "show" in locals(): - show(sk) - -# %% - -with BuildSketch() as sk2: - Rectangle(1, 2) + Rectangle(0.5, 5) - -if "show" in locals(): - show(sk2) - -# %% - -b = Rectangle(1, 2) -with BuildSketch() as sk: - b - -# bp.part is None -if "show" in locals(): - show(sk) - -# %% - -b = Rectangle(1, 2) + Circle(0.75) - -with BuildSketch() as sk: - add(b) - Circle(0.1, mode=Mode.SUBTRACT) - -c = sk.sketch - Pos(0, 0.5) * Circle(0.2) - -if "show" in locals(): - show(c) - -# %% - -b = Rectangle(1, 2) -c = Circle(0.1) -d = Plane.XZ * b - [ - Plane.XZ * Pos(y=0.6) * c, - Plane.XZ * Pos(y=-0.6) * c, - Plane.XZ * c, -] - -if "show" in locals(): - show(d, transparent=True) - -# %% - -b = Plane.XZ * Rectangle(1, 2) -c = Plane.XZ * Circle(0.1) -d = b - [ - Pos(z=0.6) * c, # note c is on XZ plane, but this is relative - Pos(z=-0.6) * c, # to XY plane, i.e. c @ Plane.XY * Pos(z=0.6) - c, -] - -if "show" in locals(): - show(d, axes=True, axes0=True) - - -0 # %% - -b = Plane.ZX * Pos(1, 2, 3) * Rectangle(1, 2) -c = Plane.ZX * Pos(1, 2, 3) * Circle(0.1) -d = b - [ - Pos(x=0.6) * c, - Pos(x=-0.6) * c, - c, -] - -if "show" in locals(): - show(d, axes=True, axes0=True) - - -# %% diff --git a/examples/build123d_logo_algebra.py b/examples/build123d_logo_algebra.py index 7db7560..8fda593 100644 --- a/examples/build123d_logo_algebra.py +++ b/examples/build123d_logo_algebra.py @@ -1,7 +1,7 @@ from build123d import * logo_text = Text("123d", font_size=10, align=(Align.MIN, Align.MIN)) -font_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y +font_height = logo_text.vertices().sort_by(Axis.Y).last.Y build_text = Text("build", font_size=5, align=(Align.CENTER, Align.CENTER)) build_bb = build_text.bounding_box() @@ -14,7 +14,7 @@ two = Pos(font_height * 0.35, 0) * Text("2", font_size=10, align=(Align.MIN, Ali three_d = Text("3d", font_size=10, align=(Align.MIN, Align.MIN)) three_d = Pos(font_height * 1.1, 0) * extrude(three_d, amount=font_height * 0.3) -logo_width = three_d.vertices().sort_by(Axis.X)[-1].X +logo_width = three_d.vertices().sort_by(Axis.X).last.X t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0)) arrow_left = t1 + mirror(t1, about=Plane.XZ) @@ -37,9 +37,7 @@ extension_lines += Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0)) p1 = Pos((l1 @ 0.5 + l2 @ 0.5) / 2 - Vector((build_bb.max.X + build_bb.min.X) / 2, 0)) build = p1 * build_text +cmpd = Compound.make_compound([three_d, two, one, build, extension_lines]) + if "show_object" in locals(): - show_object(one.wrapped, name="one") - show_object(two.wrapped, name="two") - show_object(three_d.wrapped, name="three_d") - show_object(extension_lines.wrapped, name="extension_lines") - show_object(build.wrapped, name="build") + show_object(cmpd, name="compound") diff --git a/examples/canadian_flag_algebra.py b/examples/canadian_flag_algebra.py new file mode 100644 index 0000000..0dbb4c6 --- /dev/null +++ b/examples/canadian_flag_algebra.py @@ -0,0 +1,89 @@ +from math import sin, cos, pi +from build123d import * + +# Canadian Flags have a 2:1 aspect ratio +height = 50 +width = 2 * height +wave_amplitude = 3 + + +def surface(amplitude, u, v): + """Calculate the surface displacement of the flag at a given position""" + return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin( + 1.1 * pi * v + ) + + +# Note that the surface to project on must be a little larger than the faces +# being projected onto it to create valid projected faces +the_wind = Face.make_surface_from_array_of_points( + [ + [ + Vector( + width * (v * 1.1 / 40 - 0.05), + height * (u * 1.2 / 40 - 0.1), + height * surface(wave_amplitude, u / 40, v / 40) / 2, + ) + for u in range(41) + ] + for v in range(41) + ] +) + +field_planar = Plane.XY.offset(10) * Rectangle( + width / 4, height, align=(Align.MIN, Align.MIN) +) +west_field_planar = field_planar.faces()[0] +east_field_planar = mirror(west_field_planar, about=Plane.YZ.offset(width / 2)) + +l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569)) +l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125)) +r1 = RadiusArc(l1 @ 1, l2 @ 0, 0.0271) +l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071)) +r2 = TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1) +l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188)) +r3 = SagittaArc(l3 @ 1, l4 @ 0, 0.003) +l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835)) +r4 = ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0) +l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752)) +s = Spline( + l5 @ 1, + l6 @ 0, + tangents=(l5 % 1, l6 % 0), + tangent_scalars=(2, 2), +) +l7 = Line((0.0692, 0.7808), (0.0000, 0.9167)) +r5 = TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1) + +outline = l1 + [l2, r1, l3, r2, l4, r3, l5, r4, l6, s, l7, r5] +outline += mirror(outline, about=Plane.YZ) + +maple_leaf_planar = make_face(outline) + +center_field_planar = ( + Rectangle(1, 1, align=(Align.CENTER, Align.MIN)) - maple_leaf_planar +) + + +def scale_move(obj): + return Plane((width / 2, 0, 10)) * scale(obj, by=height) + + +def project(obj): + return obj.faces()[0].project_to_shape(the_wind, (0, 0, -1))[0] + + +maple_leaf_planar = scale_move(maple_leaf_planar) +center_field_planar = scale_move(center_field_planar) + +west_field = project(west_field_planar) +east_field = project(east_field_planar) +center_field = project(center_field_planar) +maple_leaf = project(maple_leaf_planar) + + +if "show_object" in locals(): + show_object(west_field, name="west", options={"color": (255, 0, 0)}) + show_object(east_field, name="east", options={"color": (255, 0, 0)}) + show_object(center_field, name="center", options={"color": (255, 255, 255)}) + show_object(maple_leaf, name="maple", options={"color": (255, 0, 0)}) diff --git a/examples/custom_sketch_objects_algebra.py b/examples/custom_sketch_objects_algebra.py index f9b63f7..f364a89 100644 --- a/examples/custom_sketch_objects_algebra.py +++ b/examples/custom_sketch_objects_algebra.py @@ -90,9 +90,9 @@ lip_t = wall_t / 2 - lid_gap / 2 # Lip thickness box_plan = RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15) box = extrude(box_plan, amount=bottom_t + pocket_t / 2) base_top = box.faces().sort_by(Axis.Z).last -walls = Plane(base_top) * Offset(box_plan, amount=-lip_t) +walls = Plane(base_top) * offset(box_plan, amount=-lip_t) box += extrude(walls, amount=pocket_t / 2) -top = Plane.XY.offset(wall_t / 2) * Offset(box_plan, amount=-wall_t) +top = Plane.XY.offset(wall_t / 2) * offset(box_plan, amount=-wall_t) box -= extrude(top, amount=pocket_t) diff --git a/examples/din_rail_algebra.py b/examples/din_rail_algebra.py index 36f9df2..fa8bea3 100644 --- a/examples/din_rail_algebra.py +++ b/examples/din_rail_algebra.py @@ -38,7 +38,7 @@ rail = extrude(din, rail_length) plane = Plane(rail.faces().sort_by(Axis.Y).last) slot_faces = [ - (plane * loc * SlotOverall(slot_length, slot_width)).faces()[0] + (plane * loc * Rot(0, 0, 90) * SlotOverall(slot_length, slot_width)).faces()[0] for loc in GridLocations(0, slot_pitch, 1, rail_length // slot_pitch - 1) ] diff --git a/examples/extrude_algebra.py b/examples/extrude_algebra.py new file mode 100644 index 0000000..4e373f6 --- /dev/null +++ b/examples/extrude_algebra.py @@ -0,0 +1,82 @@ +from build123d import * + +# Extrude pending face by amount +simple = extrude(Text("O", font_size=10), amount=5) + +# Extrude pending face in both directions by amount +both = extrude(Text("O", font_size=10), amount=5, both=True) + +# Extrude multiple pending faces on multiple faces +multiple = Box(10, 10, 10) +faces = [ + Plane(face) * loc * Text("Ω", font_size=3) + for face in multiple.faces() + for loc in GridLocations(5, 5, 2, 2) +] +multiple += [extrude(face, amount=1) for face in faces] + +# Non-planar surface +non_planar = Rot(90, 0, 0) * Cylinder( + 10, 20, align=(Align.CENTER, Align.MIN, Align.CENTER) +) +non_planar &= Box(10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.MIN)) +non_planar = extrude(non_planar.faces().sort_by(Axis.Z).first, amount=2) + +rad, rev = 3, 25 + +# Extrude last +circle = Pos(0, rev) * Circle(rad) +ex26_target = revolve(circle, axis=Axis.X, revolution_arc=90) +ex26_target = ex26_target + mirror(ex26_target, about=Plane.XZ) + +rect = Rectangle(rad, rev) + +ex26 = extrude(rect, until=Until.LAST, target_object=ex26_target) + +# Extrude next +circle = Pos(0, rev) * Circle(rad) +ex27 = revolve(circle, axis=Axis.X, revolution_arc=90) + +circle2 = Plane.XZ * Pos(0, rev) * Circle(rad) +ex27 += revolve(circle2, axis=Axis.X, revolution_arc=150) +rect = Plane.XY.offset(-60) * Rectangle(rad, rev + 25) +extrusion27 = extrude(rect, until=Until.NEXT, target_object=ex27, mode=Mode.ADD) + + +# Extrude next both +ex28 = Rot(0, 90, 0) * Torus(25, 5) +rect = Rectangle(rad, rev) +extrusion28 = extrude(rect, until=Until.NEXT, target_object=ex28, both=True) + +if "show_object" in locals(): + show_object(simple.translate((-15, 0, 0)), name="simple pending extrude") + show_object(both.translate((20, 10, 0)), name="simple both") + show_object(multiple.translate((0, -20, 0)), name="multiple pending extrude") + show_object(non_planar.translate((20, -10, 0)), name="non planar") + show_object( + ex26_target.translate((-40, 0, 0)), + name="extrude until last target", + options={"alpha": 0.8}, + ) + show_object( + ex26.translate((-40, 0, 0)), + name="extrude until last", + ) + show_object( + ex27.rotate(Axis.Z, 90).translate((0, 50, 0)), + name="extrude until next target", + options={"alpha": 0.8}, + ) + show_object( + extrusion27.rotate(Axis.Z, 90).translate((0, 50, 0)), + name="extrude until next", + ) + show_object( + ex28.rotate(Axis.Z, -90).translate((0, -50, 0)), + name="extrude until next both target", + options={"alpha": 0.8}, + ) + show_object( + extrusion28.rotate(Axis.Z, -90).translate((0, -50, 0)), + name="extrude until next both", + ) diff --git a/examples/heat_exchanger_algebra.py b/examples/heat_exchanger_algebra.py new file mode 100644 index 0000000..4f3d59b --- /dev/null +++ b/examples/heat_exchanger_algebra.py @@ -0,0 +1,61 @@ +from build123d import * + +exchanger_diameter = 10 * CM +exchanger_length = 30 * CM +plate_thickness = 5 * MM +# 149 tubes +tube_diameter = 5 * MM +tube_spacing = 2 * MM +tube_wall_thickness = 0.5 * MM +tube_extension = 3 * MM +bundle_diameter = exchanger_diameter - 2 * tube_diameter +fillet_radius = tube_spacing / 3 +assert tube_extension > fillet_radius + +# Build the heat exchanger +tube_locations = [ + l + for l in HexLocations( + apothem=(tube_diameter + tube_spacing) / 2, + x_count=exchanger_diameter // tube_diameter, + y_count=exchanger_diameter // tube_diameter, + ) + if l.position.length < bundle_diameter / 2 +] + +tube_plan = Sketch() + [ + loc + * ( + Circle(radius=tube_diameter / 2) + - Circle(radius=tube_diameter / 2 - tube_wall_thickness) + ) + for loc in tube_locations +] + +heat_exchanger = extrude(tube_plan, exchanger_length / 2) + +plate_plane = Plane( + origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness), + z_dir=(0, 0, 1), +) +plate = Circle(radius=exchanger_diameter / 2) - [ + loc * Circle(radius=tube_diameter / 2 - tube_wall_thickness) + for loc in tube_locations +] +heat_exchanger += extrude(plate_plane * plate, plate_thickness) +edges = ( + heat_exchanger.edges() + .filter_by(GeomType.CIRCLE) + .group_by(SortBy.RADIUS)[1] + .group_by()[2] +) +half_volume_before_fillet = heat_exchanger.volume +heat_exchanger = fillet(*edges, radius=fillet_radius, target=heat_exchanger) +half_volume_after_fillet = heat_exchanger.volume +heat_exchanger += mirror(heat_exchanger, about=Plane.XY) + +fillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet) +assert abs(fillet_volume - 469.88331045553787) < 1e-3 + +if "show_object" in locals(): + show_object(heat_exchanger.wrapped) diff --git a/examples/joints_algebra.py b/examples/joints_algebra.py new file mode 100644 index 0000000..9b34afb --- /dev/null +++ b/examples/joints_algebra.py @@ -0,0 +1,140 @@ +from build123d import * + + +class JointBox(Part): + """A filleted box with joints + + A box of the given dimensions with all of the edges filleted. + + Args: + length (float): box length + width (float): box width + height (float): box height + radius (float): edge radius + taper (float): vertical taper in degrees + """ + + def __init__( + self, + length: float, + width: float, + height: float, + radius: float = 0.0, + taper: float = 0.0, + ): + # Create the object + obj = extrude(Rectangle(length, width), amount=height, taper=taper) + if radius != 0.0: + obj = fillet(*obj.edges(), radius=radius, target=obj) + obj -= Rot(0, 90, 0) * Cylinder(width / 4, length) + # Initialize the Part class with the new OCCT object + super().__init__(obj.wrapped) + + +# +# Base Object +# +# base = JointBox(10, 10, 10) +# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1))) +# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1), (1, 0, 0), 5)) +loc = Location(Vector(1, 1, 1), (1, 1, 1), 30) +base = loc * JointBox(10, 10, 10, taper=3) + +base_top_edges = base.edges().filter_by(loc.x_axis).group_by(loc.z_axis)[-1] +# +# Rigid Joint +# +fixed_arm = JointBox(1, 1, 5, 0.2) +j1 = RigidJoint( + "side", base, Plane(base.faces().sort_by(loc.x_axis).last).to_location() +) +j2 = RigidJoint( + "top", fixed_arm, (-Plane(fixed_arm.faces().sort_by().last)).to_location() +) +base.joints["side"].connect_to(fixed_arm.joints["top"]) +# or +# j1.connect_to(j2) + +# +# Hinge +# +hinge_arm = JointBox(2, 1, 10, taper=1) +swing_arm_hinge_edge = ( + hinge_arm.edges() + .group_by(SortBy.LENGTH)[-1] + .sort_by(Axis.X)[-2:] + .sort_by(Axis.Y)[0] +) +swing_arm_hinge_axis = swing_arm_hinge_edge.to_axis() +base_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1] +base_hinge_axis = base_corner_edge.to_axis() +j3 = RevoluteJoint("hinge", base, axis=base_hinge_axis, angular_range=(0, 180)) +j4 = RigidJoint("corner", hinge_arm, swing_arm_hinge_axis.to_location()) +base.joints["hinge"].connect_to(hinge_arm.joints["corner"], angle=90) + + +# +# Slider +# +slider_arm = JointBox(4, 1, 2, 0.2) +s1 = LinearJoint( + "slide", + base, + axis=Edge.make_mid_way(*base_top_edges, 0.67).to_axis(), + linear_range=(0, base_top_edges[0].length), +) +s2 = RigidJoint("slide", slider_arm, Location(Vector(0, 0, 0))) +base.joints["slide"].connect_to(slider_arm.joints["slide"], position=8) +# s1.connect_to(s2,8) + +# +# Cylindrical +# +hole_axis = Axis( + base.faces().sort_by(Axis.Y)[0].center(), + -base.faces().sort_by(Axis.Y)[0].normal_at(), +) +screw_arm = JointBox(1, 1, 10, 0.49) +j5 = CylindricalJoint("hole", base, hole_axis, linear_range=(-10, 10)) +j6 = RigidJoint("screw", screw_arm, screw_arm.faces().sort_by(Axis.Z)[-1].location) +j5.connect_to(j6, position=-1, angle=90) + +# +# PinSlotJoint +# +j7 = LinearJoint( + "slot", + base, + axis=Edge.make_mid_way(*base_top_edges, 0.33).to_axis(), + linear_range=(0, base_top_edges[0].length), +) +pin_arm = JointBox(2, 1, 2) +j8 = RevoluteJoint("pin", pin_arm, axis=Axis.Z, angular_range=(0, 360)) +j7.connect_to(j8, position=6, angle=60) + +# +# BallJoint +# +j9 = BallJoint("socket", base, Plane(base.faces().sort_by(Axis.X)[0]).to_location()) +ball = JointBox(2, 2, 2, 0.99) +j10 = RigidJoint("ball", ball, Location(Vector(0, 0, 1))) +j9.connect_to(j10, angles=(10, 20, 30)) + +if "show_object" in locals(): + show_object(base, name="base", options={"alpha": 0.8}) + show_object(base.joints["side"].symbol, name="side joint") + show_object(base.joints["hinge"].symbol, name="hinge joint") + show_object(base.joints["slide"].symbol, name="slot joint") + show_object(base.joints["slot"].symbol, name="pin slot joint") + show_object(base.joints["hole"].symbol, name="hole") + show_object(base.joints["socket"].symbol, name="socket joint") + show_object(hinge_arm.joints["corner"].symbol, name="hinge_arm joint") + show_object(fixed_arm, name="fixed_arm", options={"alpha": 0.6}) + show_object(fixed_arm.joints["top"].symbol, name="fixed_arm joint") + show_object(hinge_arm, name="hinge_arm", options={"alpha": 0.6}) + show_object(slider_arm, name="slider_arm", options={"alpha": 0.6}) + show_object(pin_arm, name="pin_arm", options={"alpha": 0.6}) + show_object(slider_arm.joints["slide"].symbol, name="slider attachment") + show_object(pin_arm.joints["pin"].symbol, name="pin axis") + show_object(screw_arm, name="screw_arm") + show_object(ball, name="ball", options={"alpha": 0.6}) diff --git a/examples/key_cap_algebra.py b/examples/key_cap_algebra.py new file mode 100644 index 0000000..df8d94f --- /dev/null +++ b/examples/key_cap_algebra.py @@ -0,0 +1,42 @@ +from build123d import * + +# Taper Extrude and Extrude to "next" while creating a Cherry MX key cap +# See: https://www.cherrymx.de/en/dev.html + +plan = Rectangle(18 * MM, 18 * MM) +key_cap = extrude(plan, amount=10 * MM, taper=15) + +# Create a dished top +key_cap -= Location((0, -3 * MM, 47 * MM), (90, 0, 0)) * Sphere(40 * MM) + +# Fillet all the edges except the bottom +key_cap = fillet( + *key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)), + radius=1 * MM, + target=key_cap, +) + +# Hollow out the key by subtracting a scaled version +key_cap -= scale(key_cap, by=(0.925, 0.925, 0.85)) + + +# Add supporting ribs while leaving room for switch activation +ribs = Rectangle(17.5 * MM, 0.5 * MM) +ribs += Rectangle(0.5 * MM, 17.5 * MM) +ribs += Circle(radius=5.51 * MM / 2) + +# Extrude the mount and ribs to the key cap underside +key_cap += extrude(Pos(0, 0, 4 * MM) * ribs, until=Until.NEXT, target_object=key_cap) + + +# Find the face on the bottom of the ribs to build onto +rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0] + +# Add the switch socket +socket = Circle(radius=5.5 * MM / 2) +socket -= Rectangle(4.1 * MM, 1.17 * MM) +socket -= Rectangle(1.17 * MM, 4.1 * MM) +key_cap += extrude(Plane(rib_bottom) * socket, amount=3.5 * MM) + +if "show_object" in locals(): + show_object(key_cap, name="key cap", options={"alpha": 0.7}) diff --git a/examples/mixed_algebra_context.py b/examples/mixed_algebra_context.py new file mode 100644 index 0000000..b308e10 --- /dev/null +++ b/examples/mixed_algebra_context.py @@ -0,0 +1,36 @@ +from build123d import * + +# Mix context and algebra api for parts + +b = Box(1, 2, 3) + Cylinder(0.75, 2.5) + +with BuildPart() as bp: + add(b) + Cylinder(0.4, 6, mode=Mode.SUBTRACT) + +c = bp.part - Plane.YZ * Cylinder(0.2, 6) + +# Mix context and algebra api for sketches + +r = Rectangle(1, 2) + Circle(0.75) + +with BuildSketch() as bs: + add(r) + Circle(0.4, mode=Mode.SUBTRACT) + +d = bs.sketch - Pos(0, 1) * Circle(0.2) + +# Mix context and algebra api for sketches + +l1 = Line((-1, 0), (1, 1)) + Line((1, 1), (2, 4)) + +with BuildLine() as bl: + add(l1) + Line((2, 4), (-1, 1)) + +e = bl.line + ThreePointArc((-1, 0), (-1.5, 0.5), (-1, 1)) + +if "show_object" in locals(): + show_object(Pos(0, -2, 0) * c, "part") + show_object(Pos(0, 2, 0) * d, "sketch") + show_object(Pos(0, 0, 2) * e, "curve") diff --git a/examples/pegboard_j_hook_algebra.py b/examples/pegboard_j_hook_algebra.py new file mode 100644 index 0000000..8069ba8 --- /dev/null +++ b/examples/pegboard_j_hook_algebra.py @@ -0,0 +1,48 @@ +from build123d import * + +pegd = 6.35 + 0.1 # mm ~0.25inch +c2c = 25.4 # mm 1.0inch +arcd = 7.2 +both = 10 +topx = 6 +midx = 8 +maind = 0.82 * pegd +midd = 1.0 * pegd +hookd = 23 +hookx = 10 +splitz = maind / 2 - 0.1 +topangs = 70 + +l1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0)) +l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs) +l3 = PolarLine( + start=l2 @ 1, + length=topx, + direction=l2 % 1, +) +l4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs) +l5 = PolarLine( + start=l4 @ 1, + length=topx, + direction=l4 % 1, +) +l6 = JernArc(start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170) +l7 = PolarLine( + start=l6 @ 1, + length=hookx, + direction=l6 % 1, +) +sprof = l1 + (l2, l3, l4, l5, l6, l7) +wire = Wire.make_wire(sprof.edges()) # TODO sprof.wires() fails +mainp = sweep(Plane.YZ * Circle(radius=maind / 2), path=wire) + +stub = Line((0, 0), (0, midx + maind / 2)) +mainp += sweep(Plane.XZ * Circle(radius=midd / 2), path=stub) + + +# splits help keep the object 3d printable by reducing overhang +mainp = split(mainp, bisect_by=Plane(origin=(0, 0, -splitz))) +mainp = split(mainp, bisect_by=Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM) + +if "show_object" in locals(): + show_object(mainp) diff --git a/examples/playing_cards_algebra.py b/examples/playing_cards_algebra.py new file mode 100644 index 0000000..05aa30c --- /dev/null +++ b/examples/playing_cards_algebra.py @@ -0,0 +1,206 @@ +from typing import Tuple, Union, Literal +from build123d import * + + +class Club(Sketch): + def __init__( + self, + height: float, + align: Union[Align, Tuple[Align, Align]] = None, + ): + l0 = Line((0, -188), (76, -188)) + b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81)) + b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67)) + b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18)) + b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188)) + club = l0 + b0 + b1 + b2 + b3 + club += mirror(club, about=Plane.YZ) + club = make_face(club) + club = scale(club, by=height / club.bounding_box().size.Y) + + super().__init__(club.wrapped) + # self._align(align) + + +class Spade(Sketch): + def __init__( + self, + height: float, + align: Union[Align, Tuple[Align, Align]] = None, + ): + b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61)) + b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105)) + b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198)) + l0 = Line(b2 @ 1, (0, -198)) + spade = l0 + b0 + b1 + b2 + spade += mirror(spade, about=Plane.YZ) + spade = make_face(spade) + spade = scale(spade, by=height / spade.bounding_box().size.Y) + + super().__init__(spade.wrapped) + # self._align(align) + + +class Heart(Sketch): + def __init__( + self, + height: float, + align: Union[Align, Tuple[Align, Align]] = None, + ): + b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198)) + b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167)) + b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31)) + b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95)) + b5 = Bezier(b4 @ 1, (40, -128), (0, -198)) + heart = b1 + b2 + b3 + b4 + b5 + heart += mirror(heart, about=Plane.YZ) + heart = make_face(heart) + heart = scale(heart, by=height / heart.bounding_box().size.Y) + + super().__init__(heart.wrapped) + # self._align(align) + + +class Diamond(Sketch): + def __init__( + self, + height: float, + align: Union[Align, Tuple[Align, Align]] = None, + ): + diamond = Bezier((135, 0), (94, 69), (47, 134), (0, 198)) + diamond += mirror(diamond, about=Plane.XZ) + diamond += mirror(diamond, about=Plane.YZ) + diamond = make_face(diamond) + diamond = scale(diamond, by=height / diamond.bounding_box().size.Y) + + super().__init__(diamond.wrapped) + # self._align(align) + + +# The inside of the box fits 2.5x3.5" playing card deck with a small gap +pocket_w = 2.5 * IN + 2 * MM +pocket_l = 3.5 * IN + 2 * MM +pocket_t = 0.5 * IN + 2 * MM +wall_t = 3 * MM # Wall thickness +bottom_t = wall_t / 2 # Top and bottom thickness +lid_gap = 0.5 * MM # Spacing between base and lid +lip_t = wall_t / 2 - lid_gap / 2 # Lip thickness + + +box_plan = RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15) +box = extrude(box_plan, amount=bottom_t + pocket_t / 2) +base_top = box.faces().sort_by(Axis.Z).last +walls = Plane(base_top) * offset(box_plan, amount=-lip_t) +box += extrude(walls, amount=pocket_t / 2) +top = Plane.XY.offset(wall_t / 2) * offset(box_plan, amount=-wall_t) +box -= extrude(top, amount=pocket_t) + + +pocket = extrude(box_plan, amount=pocket_t / 2 + bottom_t) +lid_bottom = offset(box_plan, amount=-(wall_t - lip_t)) +pocket -= extrude(lid_bottom, amount=pocket_t / 2) +pocket = Pos(0, 0, (wall_t + pocket_t) / 2) * pocket + +plane = Plane(pocket.faces().sort_by().last) +suites = Pos(-0.3 * pocket_w, 0.3 * pocket_l) * Heart(pocket_l / 5) +suites += Pos(-0.3 * pocket_w, -0.3 * pocket_l) * Diamond(pocket_l / 5) +suites += Pos(0.3 * pocket_w, 0.3 * pocket_l) * Spade(pocket_l / 5) +suites += Pos(0.3 * pocket_w, -0.3 * pocket_l) * Club(pocket_l / 5) +suites = plane * suites + +lid = pocket - extrude(suites, dir=(0, 0, 1), amount=-wall_t) + + +class PlayingCard(Compound): + """PlayingCard + + A standard playing card modelled as a Face. + + Args: + rank (Literal['A', '2' .. '9', 'J', 'Q', 'K']): card rank + suit (Literal['Clubs', 'Spades', 'Hearts', 'Diamonds']): card suit + """ + + width = 2.5 * IN + height = 3.5 * IN + suits = {"Clubs": Club, "Spades": Spade, "Hearts": Heart, "Diamonds": Diamond} + ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "J", "Q", "K"] + + def __init__( + self, + rank: Literal["A", "2", "3", "4", "5", "6", "7", "8", "9", "J", "Q", "K"], + suit: Literal["Clubs", "Spades", "Hearts", "Diamonds"], + ): + w = PlayingCard.width + h = PlayingCard.height + playing_card = Rectangle(w, h, align=(Align.MIN, Align.MIN)) + show(playing_card) + playing_card = fillet( + *playing_card.vertices(), radius=w / 15, target=playing_card + ) + show(playing_card) + playing_card -= Pos(w / 7, 8 * h / 9) * Text( + txt=rank, + font_size=w / 7, + ) + show(playing_card) + playing_card -= Pos(w / 7, 7 * h / 9,) * PlayingCard.suits[ + suit + ](height=w / 12) + show(playing_card) + + playing_card -= ( + Pos((6 * w / 7, 1 * h / 9)) + * Rot(0, 0, 180) + * Text(txt=rank, font_size=w / 7) + ) + show(playing_card) + + playing_card -= ( + Pos((6 * w / 7, 2 * h / 9)) + * Rot(0, 0, 180) + * PlayingCard.suits[suit](height=w / 12) + ) + show(playing_card) + rank_int = PlayingCard.ranks.index(rank) + 1 + rank_int = rank_int if rank_int < 10 else 1 + + center_radius = 0 if rank_int == 1 else w / 3.5 + suit_rotation = 0 if rank_int == 1 else -90 + suit_height = (0.00159 * rank_int**2 - 0.0380 * rank_int + 0.37) * w + + playing_card -= ( + Pos(w / 2, h / 2) + * Pos( + radius=center_radius, + count=rank_int, + start_angle=90 if rank_int > 1 else 0, + ) + * Rot(0, 0, suit_rotation) + * PlayingCard.suits[suit]( + height=suit_height, + ) + ) + super().__init__(playing_card.wrapped) + + +playing_card = PlayingCard(rank="A", suit="Spades") + +if "show_object" in locals(): + show_object(playing_card) + # show_object(outer_box_builder.part, "outer") + # show_object(b, name="b", options={"alpha": 0.8}) + show_object(box, "box") + show_object(lid, "lid", options={"alpha": 0.7}) + # show_object(walls.sketch, "walls") + # show_object(o, "o") + # show_object(half_club.line) + # show_object(spade_outline.line) + # show_object(b0, "b0") + # show_object(b1, "b1") + # show_object(b2, "b2") + # show_object(b3, "b3") + # show_object(b4, "b4") + # show_object(b5, "b5") + # show_object(l0, "l0") + # show_object(l0, "l0") diff --git a/examples/projection_algebra.py b/examples/projection_algebra.py new file mode 100644 index 0000000..e13e200 --- /dev/null +++ b/examples/projection_algebra.py @@ -0,0 +1,77 @@ +from build123d import * + +# A sphere used as a projection target +sphere = Sphere(50) + +"""Example 1 - Mapping A Face on Sphere""" +projection_direction = Vector(0, 1, 0) + +square = Plane.ZX.offset(-80) * Rectangle(20, 20) +square_projected = square.faces()[0].project_to_shape(sphere, projection_direction) +square_solids = Part() + [f.thicken(2) for f in square_projected] +face = square.faces()[0] +projection_beams = loft(face, Pos(0, 160, 0) * face) + + +"""Example 2 - Flat Projection of Text on Sphere""" +projection_direction = Vector(0, -1, 0) + +flat_planar_text = Rot(90, 0, 0) * Text("Flat", font_size=30) +flat_projected_text_faces = Sketch() + [ + f.project_to_shape(sphere, projection_direction)[0] + for f in flat_planar_text.faces() +] +flat_projection_beams = Part() + [ + extrude(f, dir=projection_direction, amount=80) for f in flat_planar_text.faces() +] + + +"""Example 3 - Project a text string along a path onto a shape""" +cyl = Plane.YZ * Cylinder(80, 100, align=(Align.CENTER, Align.CENTER, Align.MIN)) +obj = sphere - Pos(-50, 0, -70) * cyl + +arch_path: Edge = obj.edges().sort_by().first + +arch_path_start = Vertex(arch_path.position_at(0)) +text = Text( + "'the quick brown fox jumped over the lazy dog'", + font_size=15, + align=(Align.MIN, Align.CENTER), +) +projected_text = sphere.project_faces(text.faces(), path=arch_path) + +if "show_object" in locals(): + # Example 1 + show_object(sphere, name="sphere_solid", options={"alpha": 0.8}) + show_object(square, name="square") + show_object(square_solids, name="square_solids") + show_object( + Compound.make_compound(projection_beams), + name="projection_beams", + options={"alpha": 0.9, "color": (170 / 255, 170 / 255, 255 / 255)}, + ) + + # Example 2 + show_object( + Pos(-100, -100) * sphere, + name="sphere_solid for text", + options={"alpha": 0.8}, + ) + show_object( + Pos(-100, -100) * flat_projected_text_faces, name="flat_projected_text_faces" + ) + show_object( + Pos(-100, -100) * flat_projection_beams, + name="flat_projection_beams", + options={"alpha": 0.95, "color": (170 / 255, 170 / 255, 255 / 255)}, + ) + + # Example 3 + show_object( + sphere.moved(Location((100, 100))), + name="sphere_solid for text on path", + options={"alpha": 0.8}, + ) + show_object( + projected_text.moved(Location((100, 100))), name="projected_text on path" + )