mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 10:41:20 -08:00
365 lines
12 KiB
Python
365 lines
12 KiB
Python
"""
|
|
|
|
name: tutorial_joints.py
|
|
by: Gumyr
|
|
date: December 27h 2022
|
|
|
|
desc:
|
|
|
|
This example a box with a lid attached with a hinge.
|
|
|
|
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.
|
|
"""
|
|
# [import]
|
|
from build123d import *
|
|
|
|
|
|
# [Hinge Class]
|
|
class Hinge(Compound):
|
|
"""Hinge
|
|
|
|
Half a simple hinge with several joints. The joints are:
|
|
- "leaf": RigidJoint where hinge attaches to object
|
|
- "hinge_axis": RigidJoint (inner) or RevoluteJoint (outer)
|
|
- "hole0", "hole1", "hole2": CylindricalJoints for attachment screws
|
|
|
|
Args:
|
|
width (float): width of one leaf
|
|
length (float): hinge length
|
|
barrel_diameter (float): size of hinge pin barrel
|
|
thickness (float): hinge leaf thickness
|
|
pin_diameter (float): hinge pin diameter
|
|
inner (bool, optional): inner or outer half of hinge . Defaults to True.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
width: float,
|
|
length: float,
|
|
barrel_diameter: float,
|
|
thickness: float,
|
|
pin_diameter: float,
|
|
inner: bool = True,
|
|
):
|
|
# The profile of the hinge used to create the tabs
|
|
with BuildPart() as hinge_profile:
|
|
with BuildSketch():
|
|
for i, loc in enumerate(
|
|
GridLocations(0, length / 5, 1, 5, align=(Align.MIN, Align.MIN))
|
|
):
|
|
if i % 2 == inner:
|
|
with Locations(loc):
|
|
Rectangle(width, length / 5, align=(Align.MIN, Align.MIN))
|
|
Rectangle(
|
|
width - barrel_diameter,
|
|
length,
|
|
align=(Align.MIN, Align.MIN),
|
|
)
|
|
extrude(amount=-barrel_diameter)
|
|
|
|
# The hinge pin
|
|
with BuildPart() as pin:
|
|
Cylinder(
|
|
radius=pin_diameter / 2,
|
|
height=length,
|
|
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
)
|
|
with BuildPart(pin.part.faces().sort_by(Axis.Z)[-1]) as pin_head:
|
|
Cylinder(
|
|
radius=barrel_diameter / 2,
|
|
height=pin_diameter,
|
|
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
)
|
|
fillet(
|
|
pin_head.edges(Select.LAST).filter_by(GeomType.CIRCLE),
|
|
radius=pin_diameter / 3,
|
|
)
|
|
|
|
# Either the external and internal leaf with joints
|
|
with BuildPart() as leaf_builder:
|
|
with BuildSketch():
|
|
with BuildLine():
|
|
l1 = Line((0, 0), (width - barrel_diameter / 2, 0))
|
|
l2 = RadiusArc(
|
|
l1 @ 1,
|
|
l1 @ 1 + Vector(0, barrel_diameter),
|
|
-barrel_diameter / 2,
|
|
)
|
|
l3 = RadiusArc(
|
|
l2 @ 1,
|
|
(
|
|
width - barrel_diameter,
|
|
barrel_diameter / 2,
|
|
),
|
|
-barrel_diameter / 2,
|
|
)
|
|
l4 = Line(l3 @ 1, (width - barrel_diameter, thickness))
|
|
l5 = Line(l4 @ 1, (0, thickness))
|
|
Line(l5 @ 1, l1 @ 0)
|
|
make_face()
|
|
with Locations(
|
|
(width - barrel_diameter / 2, barrel_diameter / 2)
|
|
) as pin_center:
|
|
Circle(pin_diameter / 2 + 0.1 * MM, mode=Mode.SUBTRACT)
|
|
extrude(amount=length)
|
|
add(hinge_profile.part, rotation=(90, 0, 0), mode=Mode.INTERSECT)
|
|
|
|
# Create holes for fasteners
|
|
with Locations(leaf_builder.part.faces().filter_by(Axis.Y)[-1]):
|
|
with GridLocations(0, length / 3, 1, 3):
|
|
holes = CounterSinkHole(3 * MM, 5 * MM)
|
|
# Add the hinge pin to the external leaf
|
|
if not inner:
|
|
with Locations(pin_center.locations[0]):
|
|
add(pin.part)
|
|
|
|
# [Create the Joints]
|
|
#
|
|
# Leaf attachment
|
|
RigidJoint(
|
|
label="leaf",
|
|
to_part=leaf_builder.part,
|
|
joint_location=Location(
|
|
(width - barrel_diameter, 0, length / 2), (90, 0, 0)
|
|
),
|
|
)
|
|
# [Hinge Axis] (fixed with inner)
|
|
if inner:
|
|
RigidJoint(
|
|
"hinge_axis",
|
|
leaf_builder.part,
|
|
Location((width - barrel_diameter / 2, barrel_diameter / 2, 0)),
|
|
)
|
|
else:
|
|
RevoluteJoint(
|
|
"hinge_axis",
|
|
leaf_builder.part,
|
|
axis=Axis(
|
|
(width - barrel_diameter / 2, barrel_diameter / 2, 0), (0, 0, 1)
|
|
),
|
|
angular_range=(90, 270),
|
|
)
|
|
# [Fastener holes]
|
|
hole_locations = [hole.location for hole in holes]
|
|
for hole, hole_location in enumerate(hole_locations):
|
|
CylindricalJoint(
|
|
label="hole" + str(hole),
|
|
to_part=leaf_builder.part,
|
|
axis=hole_location.to_axis(),
|
|
linear_range=(-2 * CM, 2 * CM),
|
|
angular_range=(0, 360),
|
|
)
|
|
# [End Fastener holes]
|
|
|
|
super().__init__(leaf_builder.part.wrapped, joints=leaf_builder.part.joints)
|
|
# [Hinge Class]
|
|
|
|
|
|
# [Create instances of the two leaves of the hinge]
|
|
hinge_inner = Hinge(
|
|
width=5 * CM,
|
|
length=12 * CM,
|
|
barrel_diameter=1 * CM,
|
|
thickness=2 * MM,
|
|
pin_diameter=4 * MM,
|
|
)
|
|
hinge_outer = Hinge(
|
|
width=5 * CM,
|
|
length=12 * CM,
|
|
barrel_diameter=1 * CM,
|
|
thickness=2 * MM,
|
|
pin_diameter=4 * MM,
|
|
inner=False,
|
|
)
|
|
|
|
# [Create the box with a RigidJoint to mount the hinge]
|
|
with BuildPart() as box_builder:
|
|
box = Box(30 * CM, 30 * CM, 10 * CM)
|
|
offset(amount=-1 * CM, openings=box_builder.faces().sort_by(Axis.Z)[-1])
|
|
# Create a notch for the hinge
|
|
with Locations((-15 * CM, 0, 5 * CM)):
|
|
Box(2 * CM, 12 * CM, 4 * MM, mode=Mode.SUBTRACT)
|
|
bbox = box.bounding_box()
|
|
with Locations(
|
|
Plane(origin=(bbox.min.X, 0, bbox.max.Z - 30 * MM), z_dir=(-1, 0, 0))
|
|
):
|
|
with GridLocations(0, 40 * MM, 1, 3):
|
|
Hole(3 * MM, 1 * CM)
|
|
RigidJoint(
|
|
"hinge_attachment",
|
|
box_builder.part,
|
|
Location((-15 * CM, 0, 4 * CM), (180, 90, 0)),
|
|
)
|
|
|
|
# [Demonstrate that objects with Joints can be moved and the joints follow]
|
|
box = box_builder.part.moved(Location((0, 0, 5 * CM)))
|
|
|
|
# [The lid with a RigidJoint for the hinge]
|
|
with BuildPart() as lid_builder:
|
|
Box(30 * CM, 30 * CM, 1 * CM, align=(Align.MIN, Align.CENTER, Align.MIN))
|
|
with Locations((2 * CM, 0, 0)):
|
|
with GridLocations(0, 40 * MM, 1, 3):
|
|
Hole(3 * MM, 1 * CM)
|
|
RigidJoint(
|
|
"hinge_attachment",
|
|
lid_builder.part,
|
|
Location((0, 0, 0), (0, 0, 180)),
|
|
)
|
|
lid = lid_builder.part
|
|
|
|
# [A screw to attach the hinge to the box]
|
|
m6_screw = import_step("M6-1x12-countersunk-screw.step")
|
|
m6_joint = RigidJoint("head", m6_screw, Location((0, 0, 0), (0, 0, 0)))
|
|
# [End of screw creation]
|
|
|
|
# [Export SVG files]
|
|
#
|
|
# SVG Export options
|
|
svg_opts = {"pixel_scale": 5, "show_axes": False, "show_hidden": True}
|
|
Compound.make_compound([box, box.joints["hinge_attachment"].symbol]).export_svg(
|
|
"assets/tutorial_joint_box.svg", (-100, 100, 150), (0, 0, 1), svg_opts=svg_opts
|
|
)
|
|
Compound.make_compound(
|
|
[
|
|
hinge_inner,
|
|
hinge_inner.joints["leaf"].symbol,
|
|
hinge_inner.joints["hinge_axis"].symbol,
|
|
hinge_inner.joints["hole0"].symbol,
|
|
hinge_inner.joints["hole1"].symbol,
|
|
hinge_inner.joints["hole2"].symbol,
|
|
]
|
|
).export_svg(
|
|
"assets/tutorial_joint_inner_leaf.svg",
|
|
(100, 100, -50),
|
|
(0, 0, 1),
|
|
svg_opts=svg_opts,
|
|
)
|
|
Compound.make_compound(
|
|
[
|
|
hinge_outer,
|
|
hinge_outer.joints["leaf"].symbol,
|
|
hinge_outer.joints["hinge_axis"].symbol,
|
|
hinge_outer.joints["hole0"].symbol,
|
|
hinge_outer.joints["hole1"].symbol,
|
|
hinge_outer.joints["hole2"].symbol,
|
|
]
|
|
).export_svg(
|
|
"assets/tutorial_joint_outer_leaf.svg",
|
|
(100, 100, -50),
|
|
(0, 0, 1),
|
|
svg_opts=svg_opts,
|
|
)
|
|
Compound.make_compound([box, hinge_outer]).export_svg(
|
|
"assets/tutorial_joint_box_outer.svg",
|
|
(-100, -100, 50),
|
|
(0, 0, 1),
|
|
svg_opts=svg_opts,
|
|
)
|
|
Compound.make_compound([lid, lid.joints["hinge_attachment"].symbol]).export_svg(
|
|
"assets/tutorial_joint_lid.svg", (-100, 100, 150), (0, 0, 1), svg_opts=svg_opts
|
|
)
|
|
Compound.make_compound([m6_screw, m6_joint.symbol]).export_svg(
|
|
"assets/tutorial_joint_m6_screw.svg",
|
|
(-100, 100, 150),
|
|
(0, 0, 1),
|
|
svg_opts={"pixel_scale": 20, "show_axes": False, "show_hidden": False},
|
|
)
|
|
|
|
# [Connect Box to Outer Hinge]
|
|
box.joints["hinge_attachment"].connect_to(hinge_outer.joints["leaf"])
|
|
# [Connect Box to Outer Hinge]
|
|
Compound.make_compound([box, hinge_outer]).export_svg(
|
|
"assets/tutorial_joint_box_outer.svg",
|
|
(-100, -100, 50),
|
|
(0, 0, 1),
|
|
svg_opts=svg_opts,
|
|
)
|
|
# [Connect Hinge Leaves]
|
|
hinge_outer.joints["hinge_axis"].connect_to(hinge_inner.joints["hinge_axis"], angle=120)
|
|
# [Connect Hinge Leaves]
|
|
Compound.make_compound([box, hinge_outer, hinge_inner]).export_svg(
|
|
"assets/tutorial_joint_box_outer_inner.svg",
|
|
(-100, -100, 50),
|
|
(0, 0, 1),
|
|
svg_opts=svg_opts,
|
|
)
|
|
# [Connect Hinge to Lid]
|
|
hinge_inner.joints["leaf"].connect_to(lid.joints["hinge_attachment"])
|
|
# [Connect Hinge to Lid]
|
|
Compound.make_compound([box, hinge_outer, hinge_inner, lid]).export_svg(
|
|
"assets/tutorial_joint_box_outer_inner_lid.svg",
|
|
(-100, -100, 50),
|
|
(0, 0, 1),
|
|
svg_opts=svg_opts,
|
|
)
|
|
# [Connect Screw to Hole]
|
|
hinge_outer.joints["hole2"].connect_to(m6_joint, position=5 * MM, angle=30)
|
|
# [Connect Screw to Hole]
|
|
|
|
# [Add labels]
|
|
box.label = "box"
|
|
lid.label = "lid"
|
|
hinge_outer.label = "outer hinge"
|
|
hinge_inner.label = "inner hinge"
|
|
m6_screw.label = "M6 screw"
|
|
|
|
# [Create assembly]
|
|
box_assembly = Compound(label="assembly", children=[box, lid, hinge_inner, hinge_outer])
|
|
# [Display assembly]
|
|
print(box_assembly.show_topology())
|
|
|
|
# [Add to the assembly by assigning the parent attribute of an object]
|
|
m6_screw.parent = box_assembly
|
|
print(box_assembly.show_topology())
|
|
|
|
# [Check that the components in the assembly don't intersect]
|
|
child_intersect, children, volume = box_assembly.do_children_intersect(
|
|
include_parent=False
|
|
)
|
|
print(f"do children intersect: {child_intersect}")
|
|
if child_intersect:
|
|
print(f"{children} by {volume:0.3f} mm^3")
|
|
|
|
# [Export Final SVG file]
|
|
box_assembly.export_svg(
|
|
"assets/tutorial_joint.svg", (-100, -100, 50), (0, 0, 1), svg_opts=svg_opts
|
|
)
|
|
|
|
|
|
if "show_object" in locals():
|
|
show_object(box, name="box", options={"alpha": 0.8})
|
|
# show_object(box.joints["hinge_attachment"].symbol, name="box attachment point")
|
|
show_object(hinge_outer, name="hinge_outer")
|
|
# show_object(hinge_outer.joints["leaf"].symbol, name="hinge_outer leaf joint")
|
|
# show_object(hinge_outer.joints["hinge_axis"].symbol, name="hinge_outer hinge axis")
|
|
show_object(lid, name="lid")
|
|
# show_object(lid.joints["hinge_attachment"].symbol, name="lid attachment point")
|
|
show_object(hinge_inner, name="hinge_inner")
|
|
# show_object(hinge_inner.joints["leaf"].symbol, name="hinge_inner leaf joint")
|
|
# show_object(hinge_inner.joints["hinge_axis"].symbol, name="hinge_inner hinge axis")
|
|
for hole in [0, 1, 2]:
|
|
show_object(
|
|
hinge_inner.joints["hole" + str(hole)].symbol,
|
|
name="hinge_inner hole " + str(hole),
|
|
)
|
|
show_object(
|
|
hinge_outer.joints["hole" + str(hole)].symbol,
|
|
name="hinge_outer hole " + str(hole),
|
|
)
|
|
show_object(m6_screw, name="m6 screw")
|
|
show_object(m6_joint.symbol, name="m6 screw symbol")
|
|
show_object(box_assembly, name="box assembly")
|