22 KiB
Executable File
Joints
Joints enable Solid and Compound objects to be arranged relative to each other in an intuitive manner – with the same degree of motion that is found with the equivalent physical joints.
Joints always work in pairs – a Joint can only be connected to another Joint as follows:
| Joint Type | Connects to | Example |
|---|---|---|
| BallJoint | RigidJoint |
Gimbal |
| CylindricalJoint | RigidJoint |
Screw |
| LinearJoint | RigidJoint, RevoluteJoint |
Slider or Pin Slot |
| RevoluteJoint | RigidJoint |
Hinge |
| RigidJoint | RigidJoint |
Fixed |
Objects may have many joints bound to them, each with an identifying label. All Joint objects have a symbol property that can be displayed to help visualize their position and orientation (the ocp-vscode viewer has built-in support for displaying joints).
Note: If joints are created within the scope of a
BuildPartbuilder, theto_partparameter need not be specified. The builder will, on exit, automatically transfer the joints created in its scope to the part created.
The following sections provide more detail on the available joints and describe how they are used.
Rigid Joint
A rigid joint positions two components relative to each other with no freedom of movement. When a RigidJoint is instantiated, it is assigned a label, a part to bind to (to_part), and a joint_location which defines both the position and orientation of the joint (see Location) – as follows:
RigidJoint(label="outlet", to_part=pipe, joint_location=path.location_at(1))
Once a joint is bound to a part this way, the connect_to() method can be used to reposition another part relative to self (which stays fixed) – as follows:
pipe.joints["outlet"].connect_to(flange_outlet.joints["pipe"])
Note: Within a part, all of the joint labels must be unique.
The connect_to() method only does a one-time re-position of a part and does not bind them in any way; however, putting them into an Assembly will maintain their relative locations, as will combining parts with boolean operations or within a BuildPart context.
Example: Connecting Flanges to a Pipe
Consider the following code where flanges are attached to the ends of a curved pipe:
import copy
from build123d import *
from bd_warehouse.flange import WeldNeckFlange
from bd_warehouse.pipe import PipeSection
from ocp_vscode import *
flange_inlet = WeldNeckFlange(nps="10", flange_class=300)
flange_outlet = copy.copy(flange_inlet)
with BuildPart() as pipe_builder:
# Create the pipe
with BuildLine():
path = TangentArc((0, 0, 0), (2 * FT, 0, 1 * FT), tangent=(1, 0, 0))
with BuildSketch(Plane(origin=path @ 0, z_dir=path % 0)):
PipeSection("10", material="stainless", identifier="40S")
sweep()
# Add the joints
RigidJoint(label="inlet", joint_location=-path.location_at(0))
RigidJoint(label="outlet", joint_location=path.location_at(1))
# Place the flanges at the ends of the pipe
pipe_builder.part.joints["inlet"].connect_to(flange_inlet.joints["pipe"])
pipe_builder.part.joints["outlet"].connect_to(flange_outlet.joints["pipe"])
show(pipe_builder, flange_inlet, flange_outlet, render_joints=True)
Key Observations:
- The locations of the joints are determined by the
location_at()method. - The
-(negate) operator is used to reverse the direction of the location without changing its position. - The
WeldNeckFlangeclass predefines two joints (one at the pipe end and one at the face end), which are visualized whenrender_joints=Trueis set in the viewer.
API Reference
class RigidJoint
class RigidJoint(label: str, to_part: Solid | Compound | None = None, joint_location: Location | None = None)
A rigid joint fixes two components to one another.
Parameters:
label(str): Joint label.to_part(Union[Solid, Compound], optional): Object to attach joint to.joint_location(Location): Global location of joint.
Variables:
relative_location(Location): Joint location relative to bound object.
Methods
connect_to
connect_to(other: BallJoint, *, angles: Rotation | tuple[float, float, float] | None = None, **kwargs)
connect_to(other: CylindricalJoint, *, position: float | None = None, angle: float | None = None)
connect_to(other: LinearJoint, *, position: float | None = None)
connect_to(other: RevoluteJoint, *, angle: float | None = None)
connect_to(other: RigidJoint)
Connect the RigidJoint to another Joint.
- Parameters:
other(Joint): Joint to connect to.angle(float, optional): Angle in degrees. Defaults to range min.angles(RotationLike, optional): Angles about axes in degrees. Defaults to range minimums.position(float, optional): Linear position. Defaults to linear range min.
relative_to
relative_to(other: BallJoint, *, angles: Rotation | tuple[float, float, float] | None = None)
relative_to(other: CylindricalJoint, *, position: float | None = None, angle: float | None = None)
relative_to(other: LinearJoint, *, position: float | None = None)
relative_to(other: RevoluteJoint, *, angle: float | None = None)
relative_to(other: RigidJoint)
Relative location of RigidJoint to another Joint.
- Parameters:
other(RigidJoint): Relative to joint.angle(float, optional): Angle in degrees. Defaults to range min.angles(RotationLike, optional): Angles about axes in degrees. Defaults to range minimums.position(float, optional): Linear position. Defaults to linear range min.
- Raises:
TypeError:othermust be of a type in:BallJoint,CylindricalJoint,LinearJoint,RevoluteJoint,RigidJoint.
Properties
location:Location- Location of joint.
symbol:Compound- A CAD symbol (XYZ indicator) as bound to part.
Revolute Joint
A Revolute Joint functions effectively as a hinge, allowing a component to rotate around a specific axis.
Reference: The Joint Tutorial covers Revolute Joints in detail.
During the instantiation of a RevoluteJoint, there are three additional parameters compared to RigidJoints that allow the circular motion to be fully defined:
axis: Defines the line around which rotation occurs.angle_reference: Defines the starting point (0 degrees) for rotation.range: Limits the rotation (min, max).
When using connect_to() with a Revolute Joint, an extra angle parameter is available. This allows you to change the relative position of the joined parts by modifying a single value.
API Reference
class RevoluteJoint
class RevoluteJoint(label: str, to_part: Solid | Compound | None = None, axis: Axis = Axis((0, 0, 0), (0, 0, 1)), angle_reference: Vector | tuple[float, float] | tuple[float, float, float] | Sequence[float] | None = None, angular_range: tuple[float, float] = (0, 360))
Component rotates around axis like a hinge.
Parameters:
label(str): Joint label.to_part(Union[Solid, Compound], optional): Object to attach joint to.axis(Axis): Axis of rotation.angle_reference(VectorLike, optional): Direction normal to axis defining where angles will be measured from. Defaults to None.range(tuple[float, float], optional): (min, max) angle of joint. Defaults to (0, 360).
Variables:
angle(float): Current angle of the joint.angle_reference(Vector): Reference vector for angular positions.angular_range(tuple[float, float]): Min and max angular position of joint.relative_axis(Axis): Joint axis relative to the bound part.
Raises:
ValueError: Ifangle_referenceis not normal toaxis.
Methods
connect_to
connect_to(other: RigidJoint, *, angle: float | None = None)
Connect RevoluteJoint and RigidJoint.
- Parameters:
other(RigidJoint): The joint to connect to.angle(float, optional): Angle in degrees. Defaults to range min.
- Raises:
TypeError: Ifotheris not of typeRigidJoint.ValueError: Ifangleis out of the specified range.
relative_to
relative_to(other: RigidJoint, *, angle: float | None = None)
Relative location of RevoluteJoint to RigidJoint.
- Parameters:
other(RigidJoint): The joint to connect to.angle(float, optional): Angle in degrees. Defaults to range min.
- Raises:
TypeError: Ifotheris not of typeRigidJoint.ValueError: Ifangleis out of the specified range.
Properties
location:Location- Global location of the joint.
symbol:Compound- A CAD symbol representing the axis of rotation as bound to the part.
Linear Joint
A Linear Joint constrains a component to move along a single axis, similar to a sliding latch.
Example: Sliding Latch
The code below demonstrates how to generate a latch and a slider, and then connect them using a LinearJoint for the latch and a RigidJoint for the slider.
from build123d import *
from ocp_vscode import *
with BuildPart() as latch:
# Basic box shape to start with filleted corners
Box(70, 30, 14)
end = latch.faces().sort_by(Axis.X)[-1] # save the end with the hole
fillet(latch.edges().filter_by(Axis.Z), 2)
fillet(latch.edges().sort_by(Axis.Z)[-1], 1)
# Make screw tabs
with BuildSketch(latch.faces().sort_by(Axis.Z)[0]) as l4:
with Locations((-30, 0), (30, 0)):
SlotOverall(50, 10, rotation=90)
Rectangle(50, 30)
fillet(l4.vertices(Select.LAST), radius=2)
extrude(amount=-2)
with GridLocations(60, 40, 2, 2):
Hole(2)
# Create the hole from the end saved previously
with BuildSketch(end) as slide_hole:
add(end)
offset(amount=-2)
fillet(slide_hole.vertices(), 1)
extrude(amount=-68, mode=Mode.SUBTRACT)
# Slot for the handle to slide in
with BuildSketch(latch.faces().sort_by(Axis.Z)[-1]):
SlotOverall(32, 8)
extrude(amount=-2, mode=Mode.SUBTRACT)
# The slider will move align the x axis 12mm in each direction
LinearJoint("latch", axis=Axis.X, linear_range=(-12, 12))
with BuildPart() as slide:
# The slide will be a little smaller than the hole
with BuildSketch() as s1:
add(slide_hole.sketch)
offset(amount=-0.25)
# The extrusions aren't symmetric
extrude(amount=46)
extrude(slide.faces().sort_by(Axis.Z)[0], amount=20)
# Round off the ends
fillet(slide.edges().group_by(Axis.Z)[0], 1)
fillet(slide.edges().group_by(Axis.Z)[-1], 1)
# Create the knob
with BuildSketch() as s2:
with Locations((12, 0)):
SlotOverall(15, 4, rotation=90)
Rectangle(12, 7, align=(Align.MIN, Align.CENTER))
fillet(s2.vertices(Select.LAST), 1)
split(bisect_by=Plane.XZ)
revolve(axis=Axis.X)
# Align the joint to Plane.ZY flipped
RigidJoint("slide", joint_location=Location(-Plane.ZY))
# Position the slide in the latch: -12 >= position <= 12
latch.part.joints["latch"].connect_to(slide.part.joints["slide"], position=12)
show(latch.part, slide.part, render_joints=True)
Key Concepts:
- LinearJoint Definition: The latch defines a
LinearJointwith an axis (Axis.X) and movement limits (linear_range=(-12, 12)). - RigidJoint Definition: The slider uses a
RigidJointwith a specific location/orientation (Location(-Plane.ZY)) so the knob points "up". - Connection: The
connect_tomethod links the joints and specifies aposition(12mm) which must be within the defined range. - Movement: The slider can be moved by changing the
positionvalue. Values outside thelinear_rangelimits will raise an exception. - Orientation: Note that the slide is constructed in a different orientation than the direction of motion, which is handled by the joint definitions.
API Reference
class LinearJoint
class LinearJoint(label: str, to_part: Solid | Compound | None = None, axis: Axis = Axis((0, 0, 0), (0, 0, 1)), linear_range: tuple[float, float] = (0, inf))
Component moves along a single axis.
Parameters:
label(str): Joint label.to_part(Union[Solid, Compound], optional): Object to attach joint to.axis(Axis): Axis of linear motion.range(tuple[float, float], optional): (min, max) position of joint. Defaults to (0, inf).
Variables:
axis(Axis): Joint axis.angle(float): Angle of joint.linear_range(tuple[float, float]): Min and max positional values.position(float): Joint position.relative_axis(Axis): Joint axis relative to bound part.
Methods
connect_to
connect_to(other: RevoluteJoint, *, position: float | None = None, angle: float | None = None)
connect_to(other: RigidJoint, *, position: float | None = None)
Connect LinearJoint to another Joint.
- Parameters:
other(Joint): Joint to connect to.angle(float, optional): Angle in degrees. Defaults to range min.position(float, optional): Linear position. Defaults to linear range min.
- Raises:
TypeError: Ifotheris not of typeRevoluteJointorRigidJoint.ValueError: Ifpositionis out of range.ValueError: Ifangleis out of range.
relative_to
relative_to(other: RigidJoint, *, position: float | None = None)
relative_to(other: RevoluteJoint, *, position: float | None = None, angle: float | None = None)
Relative location of LinearJoint to RevoluteJoint or RigidJoint.
- Parameters:
other(Joint): Joint to connect to.angle(float, optional): Angle in degrees. Defaults to range min.position(float, optional): Linear position. Defaults to linear range min.
- Raises:
TypeError: Ifotheris not of typeRevoluteJointorRigidJoint.ValueError: Ifpositionis out of range.ValueError: Ifangleis out of range.
Properties
location:Location- Location of the joint.
symbol:Compound- A CAD symbol of the linear axis positioned relative to
to_part.
- A CAD symbol of the linear axis positioned relative to
Cylindrical Joint
A CylindricalJoint allows a component to rotate around and move along a single axis, effectively acting like a screw. It combines the functionality of a LinearJoint and a RevoluteJoint.
The connect_to method for these joints accepts both position and angle parameters.
API Reference
class CylindricalJoint
class CylindricalJoint(label: str, to_part: Solid | Compound | None = None, axis: Axis = Axis((0, 0, 0), (0, 0, 1)), angle_reference: Vector | tuple[float, float] | tuple[float, float, float] | Sequence[float] | None = None, linear_range: tuple[float, float] = (0, inf), angular_range: tuple[float, float] = (0, 360))
Component rotates around and moves along a single axis like a screw.
Parameters:
label(str): Joint label.to_part(Union[Solid, Compound], optional): Object to attach joint to.axis(Axis): Axis of rotation and linear motion.angle_reference(VectorLike, optional): Direction normal to axis defining where angles will be measured from. Defaults to None.linear_range(tuple[float, float], optional): (min, max) position of joint. Defaults to (0, inf).angular_range(tuple[float, float], optional): (min, max) angle of joint. Defaults to (0, 360).
Variables:
axis(Axis): Joint axis.linear_position(float): Linear joint position.rotational_position(float): Revolute joint angle in degrees.angle_reference(Vector): Reference for angular positions.angular_range(tuple[float, float]): Min and max angular position of joint.linear_range(tuple[float, float]): Min and max positional values.relative_axis(Axis): Joint axis relative to bound part.position(float): Joint position.angle(float): Angle of joint.
Raises:
ValueError: Ifangle_referencemust be normal to axis.
Methods
connect_to
connect_to(other: RigidJoint, *, position: float | None = None, angle: float | None = None)
Connect CylindricalJoint and RigidJoint.
- Parameters:
other(Joint): Joint to connect to.position(float, optional): Linear position. Defaults to linear range min.angle(float, optional): Angle in degrees. Defaults to range min.
- Raises:
TypeError: Ifotheris not of typeRigidJoint.ValueError: Ifpositionis out of range.ValueError: Ifangleis out of range.
relative_to
relative_to(other: RigidJoint, *, position: float | None = None, angle: float | None = None)
Relative location of CylindricalJoint to RigidJoint.
- Parameters:
other(Joint): Joint to connect to.position(float, optional): Linear position. Defaults to linear range min.angle(float, optional): Angle in degrees. Defaults to range min.
- Raises:
TypeError: Ifotheris not of typeRigidJoint.ValueError: Ifpositionis out of range.ValueError: Ifangleis out of range.
Properties
location:Location- Location of joint.
symbol:Compound- A CAD symbol representing the cylindrical axis as bound to part.
Ball Joint
A Ball Joint allows a component to rotate around all 3 axes using a gimbal system (3 nested rotations). A common example is a rod end.
Example: Rod End
The following code demonstrates creating a rod end with a threaded shaft and a ball, connecting them via a BallJoint.
from build123d import *
from bd_warehouse.thread import IsoThread
from ocp_vscode import *
# Create the thread so the min radius is available below
thread = IsoThread(major_diameter=6, pitch=1, length=20, end_finishes=("fade", "raw"))
inner_radius = 15.89 / 2
inner_gap = 0.2
with BuildPart() as rod_end:
# Create the outer shape
with BuildSketch():
Circle(22.25 / 2)
with Locations((0, -12)):
Rectangle(8, 1)
make_hull()
split(bisect_by=Plane.YZ)
revolve(axis=Axis.Y)
# Refine the shape
with BuildSketch(Plane.YZ) as s2:
Rectangle(25, 8, align=(Align.MIN, Align.CENTER))
Rectangle(9, 10, align=(Align.MIN, Align.CENTER))
chamfer(s2.vertices(), 0.5)
revolve(axis=Axis.Z, mode=Mode.INTERSECT)
# Add the screw shaft
Cylinder(
thread.min_radius,
30,
rotation=(90, 0, 0),
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
# Cutout the ball socket
Sphere(inner_radius, mode=Mode.SUBTRACT)
# Add thread
with Locations((0, -30, 0)):
add(thread, rotation=(-90, 0, 0))
# Create the ball joint
BallJoint(
"socket",
joint_location=Location(),
angular_range=((-14, 14), (-14, 14), (0, 360)),
)
with BuildPart() as ball:
Sphere(inner_radius - inner_gap)
Box(50, 50, 13, mode=Mode.INTERSECT)
Hole(4)
ball.part.color = Color("aliceblue")
RigidJoint("ball", joint_location=Location())
rod_end.part.joints["socket"].connect_to(ball.part.joints["ball"], angles=(5, 10, 0))
show(rod_end.part, ball.part, s2)
Key Observations:
- Limits: Limits are defined during the instantiation of the
BallJointto ensure that the pin or bolt within the rod end does not interfere with the rod end itself. - Connection: The
connect_tomethod sets the three angles (angles=(5, 10, 0)), though in this specific example only two might be significant depending on the constraints.
API Reference
class BallJoint
class BallJoint(label: str, to_part: Solid | Compound | None = None, joint_location: Location | None = None, angular_range: tuple[tuple[float, float], tuple[float, float], tuple[float, float]] = ((0, 360), (0, 360), (0, 360)), angle_reference: Plane = Plane((0, 0, 0), (1, 0, 0), (0, 0, 1)))
A component rotates around all 3 axes using a gimbal system (3 nested rotations).
Parameters:
label(str): Joint label.to_part(Union[Solid, Compound], optional): Object to attach joint to.joint_location(Location): Global location of joint.angular_range(tuple[tuple[float, float], ...], optional): X, Y, Z angle (min, max) pairs. Defaults to((0, 360), (0, 360), (0, 360)).angle_reference(Plane, optional): Plane relative to part defining zero degrees of rotation. Defaults toPlane.XY.
Variables:
relative_location(Location): Joint location relative to bound part.angular_range(tuple): X, Y, Z angle (min, max) pairs.angle_reference(Plane): Plane relative to part defining zero degrees.
Methods
connect_to
connect_to(other: RigidJoint, *, angles: Rotation | tuple[float, float, float] | None = None)
Connect BallJoint and RigidJoint.
- Parameters:
other(RigidJoint): Joint to connect to.angles(RotationLike, optional): Angles about axes in degrees. Defaults to range minimums.
- Raises:
TypeError: Invalid other joint type.ValueError: If angles are out of range.
relative_to
relative_to(other: RigidJoint, *, angles: Rotation | tuple[float, float, float] | None = None)
Return the relative location from this joint to the RigidJoint of another object.
- Parameters:
other(RigidJoint): Joint to connect to.angles(RotationLike, optional): Angles about axes in degrees. Defaults to range minimums.
- Raises:
TypeError: Invalid other joint type.ValueError: If angles are out of range.
Properties
location:Location- Location of joint.
symbol:Compound- A CAD symbol representing joint as bound to part.