Refactored Joints into separate module, integrated with BuildPart -
Issue#226
|
|
@ -615,6 +615,17 @@
|
|||
<line x1="-74.652943" y1="14.227562" x2="-74.099447" y2="14.121448" />
|
||||
<line x1="-73.356569" y1="18.98605" x2="-73.173546" y2="18.950961" />
|
||||
<path d="M -72.09878,16.706271 Q -72.112749,16.606195 -72.125555,16.510115" />
|
||||
<line x1="-128.876661" y1="32.308173" x2="-112.759048" y2="36.069606" />
|
||||
<line x1="-96.641435" y1="39.83104" x2="-80.523822" y2="43.592474" />
|
||||
<line x1="-144.497936" y1="26.008149" x2="-128.380323" y2="29.769583" />
|
||||
<line x1="-80.027484" y1="41.053885" x2="-63.909871" y2="44.815318" />
|
||||
<line x1="-112.26271" y1="33.531017" x2="-96.145097" y2="37.292451" />
|
||||
<path d="M -66.719166,50.621411 A 5.0,3.620891854720831 -76.86376149427704 0,1 -67.855502,50.603708" />
|
||||
<path d="M -65.582829,40.865383 A 5.0,3.620891854720831 -76.86376149427704 0,1 -66.719166,50.621411" />
|
||||
<path d="M -64.687406,41.074352 A 5.0,3.620891854720831 -76.86376149427704 0,1 -65.823743,50.830379" />
|
||||
<path d="M -79.585966,21.975885 A 5.115900974233,3.357932704042375 79.14709695164629 0,1 -79.61497,22.019933" />
|
||||
<path d="M -82.671784,23.04734 A 5.115900974233,3.357932704042375 79.14709695164629 0,1 -85.457884,15.671632" />
|
||||
<path d="M -84.900483,14.396641 A 5.115900974233,3.357932704042375 79.14709695164629 0,1 -84.439222,13.828359" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
|
@ -230,6 +230,14 @@
|
|||
<line x1="-151.398097" y1="56.0656" x2="-134.961465" y2="53.976305" />
|
||||
<line x1="-120.834215" y1="52.18056" x2="-101.192531" y2="49.683863" />
|
||||
<line x1="-87.065282" y1="47.888118" x2="-66.975763" y2="45.334496" />
|
||||
<line x1="-148.096912" y1="54.524191" x2="-131.212446" y2="52.377971" />
|
||||
<line x1="-80.559045" y1="45.939308" x2="-63.674578" y2="43.793087" />
|
||||
<line x1="-114.327979" y1="50.23175" x2="-97.443512" y2="48.085529" />
|
||||
<line x1="-151.650292" y1="49.121307" x2="-134.765826" y2="46.975086" />
|
||||
<line x1="-84.112425" y1="40.536424" x2="-67.227958" y2="38.390203" />
|
||||
<line x1="-117.881359" y1="44.828865" x2="-100.996892" y2="42.682644" />
|
||||
<path d="M -66.92042,38.271137 A 5.0,3.5251361507946415 -97.24414052335966 0,1 -65.659445,48.191315" />
|
||||
<path d="M -65.982394,38.151902 A 5.000000233566643,3.5251363154654856 -97.24414052335966 0,1 -64.721419,48.072081" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
|
|
@ -314,6 +314,16 @@
|
|||
<line x1="-117.955706" y1="47.673077" x2="-134.762521" y2="49.781204" />
|
||||
<line x1="-83.819354" y1="47.624166" x2="-100.626169" y2="49.732294" />
|
||||
<line x1="-84.342076" y1="43.456822" x2="-101.148891" y2="45.564949" />
|
||||
<line x1="-147.738653" y1="54.403842" x2="-130.931838" y2="52.295715" />
|
||||
<line x1="-80.511393" y1="45.971332" x2="-63.704578" y2="43.863205" />
|
||||
<line x1="-114.125023" y1="50.187587" x2="-97.318208" y2="48.07946" />
|
||||
<line x1="-151.307975" y1="49.011" x2="-134.50116" y2="46.902872" />
|
||||
<line x1="-84.080715" y1="40.57849" x2="-67.2739" y2="38.470362" />
|
||||
<line x1="-117.694345" y1="44.794745" x2="-100.88753" y2="42.686617" />
|
||||
<path d="M -66.962476,38.354124 A 5.0,3.542198278829656 -97.14943683030337 0,1 -65.7179,48.276373" />
|
||||
<path d="M -66.028765,38.237006 A 5.0,3.542198278829656 -97.14943683030337 0,1 -64.784188,48.159255" />
|
||||
<line x1="-131.410037" y1="54.718996" x2="-114.603222" y2="52.610868" />
|
||||
<line x1="-97.796407" y1="50.502741" x2="-80.989592" y2="48.394613" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
|
|
@ -325,6 +325,14 @@
|
|||
<line x1="-94.645553" y1="60.151243" x2="-90.94109" y2="50.976649" />
|
||||
<line x1="-127.074086" y1="51.636653" x2="-123.369623" y2="42.462059" />
|
||||
<line x1="-121.510496" y1="53.883086" x2="-117.806033" y2="44.708492" />
|
||||
<line x1="-144.5044" y1="26.011968" x2="-128.385434" y2="29.772862" />
|
||||
<line x1="-80.028536" y1="41.055544" x2="-63.909571" y2="44.816438" />
|
||||
<line x1="-112.266468" y1="33.533756" x2="-96.147502" y2="37.29465" />
|
||||
<path d="M -66.718535,50.622524 A 5.0,3.6206548152316524 -76.86664507048847 0,1 -67.854627,50.604861" />
|
||||
<path d="M -65.582444,40.866423 A 5.0,3.6206548152316524 -76.86664507048847 0,1 -66.718535,50.622524" />
|
||||
<path d="M -64.686946,41.075361 A 5.0,3.6206548152316524 -76.86664507048847 0,1 -65.823037,50.831463" />
|
||||
<line x1="-128.881738" y1="32.311461" x2="-112.762772" y2="36.072355" />
|
||||
<line x1="-96.643806" y1="39.833249" x2="-80.52484" y2="43.594143" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
|
@ -196,6 +196,10 @@
|
|||
<line x1="-10.838025" y1="0.068747" x2="-10.838025" y2="-17.786514" />
|
||||
<line x1="-15.038025" y1="35.779271" x2="-15.038025" y2="17.924009" />
|
||||
<line x1="-10.838025" y1="35.779271" x2="-10.838025" y2="17.924009" />
|
||||
<line x1="-15.93575" y1="-20.460509" x2="-15.93575" y2="-2.605247" />
|
||||
<line x1="-15.93575" y1="15.250014" x2="-15.93575" y2="33.105276" />
|
||||
<line x1="-8.93632" y1="-19.789636" x2="-8.93632" y2="-1.934374" />
|
||||
<line x1="-8.93632" y1="15.920888" x2="-8.93632" y2="33.776149" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
|
@ -361,6 +361,8 @@
|
|||
<path d="M 0.423725,-2.608461 L 0.29739,-2.583239" />
|
||||
<line x1="-2.359955" y1="-4.607115" x2="-2.359955" y2="-4.776131" />
|
||||
<line x1="2.557513" y1="-4.270011" x2="2.557513" y2="-4.776131" />
|
||||
<path d="M -4.055597,5.260365 A 5.115900974233,3.7783060347967656 -2.358734586570796e-15 0,1 3.078539,-0.015856" />
|
||||
<path d="M 4.253155,0.850436 A 5.115900974233,3.7783060347967656 -2.358734586570796e-15 0,1 -2.88098,6.126658" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
|
@ -207,6 +207,15 @@
|
|||
<line x1="-12.39424" y1="-37.298384" x2="-12.39424" y2="-17.030545" />
|
||||
<line x1="-12.39424" y1="-2.128481" x2="-12.39424" y2="18.139358" />
|
||||
<line x1="-12.39424" y1="33.041422" x2="-12.39424" y2="50.190613" />
|
||||
<line x1="-13.297896" y1="-40.504287" x2="-13.297896" y2="-22.919336" />
|
||||
<line x1="-13.297896" y1="29.835519" x2="-13.297896" y2="47.42047" />
|
||||
<line x1="-13.297896" y1="-5.334384" x2="-13.297896" y2="12.250567" />
|
||||
<line x1="-6.323763" y1="-39.710214" x2="-6.323763" y2="-22.125263" />
|
||||
<line x1="-6.323763" y1="30.629592" x2="-6.323763" y2="48.214544" />
|
||||
<line x1="-6.323763" y1="-4.540311" x2="-6.323763" y2="13.04464" />
|
||||
<path d="M -5.39424,51.167554 A 5.0,3.402724860456303 0.0 1,0 -15.39424,51.167554" />
|
||||
<path d="M -5.39424,52.144496 A 5.0,3.402724860456303 0.0 1,0 -15.39424,52.144496" />
|
||||
<path d="M -13.379256,54.570555 A 3.666666666666667,2.4953315643346223 0.0 0,0 -7.409224,51.672321 A 3.666666666666667,2.4953315643346223 0.0 0,0 -13.379256,54.570555" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
|
@ -94,18 +94,14 @@ Methods and functions specific to exporting and importing build123d objects are
|
|||
:noindex:
|
||||
|
||||
|
||||
*************
|
||||
Joint Objects
|
||||
*************
|
||||
Joint classes which are used to position Solid and Compound objects relative to each
|
||||
other are defined below.
|
||||
************
|
||||
Joint Object
|
||||
************
|
||||
Base Joint class which is used to position Solid and Compound objects relative to each
|
||||
other are defined below. The :ref:`joints` section contains the class description of the
|
||||
derived Joint classes.
|
||||
|
||||
.. py:module:: topology
|
||||
:noindex:
|
||||
|
||||
.. autoclass:: Joint
|
||||
.. autoclass:: RigidJoint
|
||||
.. autoclass:: RevoluteJoint
|
||||
.. autoclass:: LinearJoint
|
||||
.. autoclass:: CylindricalJoint
|
||||
.. autoclass:: BallJoint
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _joints:
|
||||
|
||||
######
|
||||
Joints
|
||||
######
|
||||
|
|
@ -10,15 +12,15 @@ in pairs - a :class:`~topology.Joint` can only be connected to another :class:`~
|
|||
+---------------------------------------+---------------------------------------------------------------------+--------------------+
|
||||
| :class:`~topology.Joint` | connect_to | Example |
|
||||
+=======================================+=====================================================================+====================+
|
||||
| :class:`~topology.BallJoint` | :class:`~topology.RigidJoint` | Gimbal |
|
||||
| :class:`~joints.BallJoint` | :class:`~joints.RigidJoint` | Gimbal |
|
||||
+---------------------------------------+---------------------------------------------------------------------+--------------------+
|
||||
| :class:`~topology.CylindricalJoint` | :class:`~topology.RigidJoint` | Screw |
|
||||
| :class:`~joints.CylindricalJoint` | :class:`~joints.RigidJoint` | Screw |
|
||||
+---------------------------------------+---------------------------------------------------------------------+--------------------+
|
||||
| :class:`~topology.LinearJoint` | :class:`~topology.RigidJoint`, :class:`~topology.RevoluteJoint` | Slider or Pin Slot |
|
||||
| :class:`~joints.LinearJoint` | :class:`~joints.RigidJoint`, :class:`~joints.RevoluteJoint` | Slider or Pin Slot |
|
||||
+---------------------------------------+---------------------------------------------------------------------+--------------------+
|
||||
| :class:`~topology.RevoluteJoint` | :class:`~topology.RigidJoint` | Hinge |
|
||||
| :class:`~joints.RevoluteJoint` | :class:`~joints.RigidJoint` | Hinge |
|
||||
+---------------------------------------+---------------------------------------------------------------------+--------------------+
|
||||
| :class:`~topology.RigidJoint` | :class:`~topology.RigidJoint` | Fixed |
|
||||
| :class:`~joints.RigidJoint` | :class:`~joints.RigidJoint` | Fixed |
|
||||
+---------------------------------------+---------------------------------------------------------------------+--------------------+
|
||||
|
||||
Objects may have many joints bound to them each with an identifying label. All :class:`~topology.Joint`
|
||||
|
|
@ -27,17 +29,20 @@ their position and orientation (the `ocp-vscode <https://github.com/bernhard-42/
|
|||
has built-in support for displaying joints).
|
||||
|
||||
.. note::
|
||||
The :class:`~build_part.BuildPart` builder will currently over-write joints unless they appear at
|
||||
the end of the part definition.
|
||||
If joints are created within the scope of a :class:`~build_part.BuildPart` builder, the ``to_part``
|
||||
parameter need not be specified as 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 describes how they are used.
|
||||
|
||||
.. py:module:: joints
|
||||
|
||||
***********
|
||||
Rigid Joint
|
||||
***********
|
||||
|
||||
A rigid joint positions two components relative to each another with no freedom of movement. When a
|
||||
:class:`~topology.RigidJoint` is instantiated it's assigned a ``label``, a part to bind to (``to_part``),
|
||||
:class:`~joints.RigidJoint` is instantiated it's 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
|
||||
:class:`~geometry.Location`) - as follows:
|
||||
|
||||
|
|
@ -65,6 +70,7 @@ flanges are attached to the ends of a curved pipe:
|
|||
.. image:: assets/rigid_joints_pipe.png
|
||||
|
||||
.. literalinclude:: rigid_joints_pipe.py
|
||||
:emphasize-lines: 19-20, 23-24
|
||||
|
||||
Note how the locations of the joints are determined by the :meth:`~topology.Mixin1D.location_at` method
|
||||
and how the ``-`` negate operator is used to reverse the direction of the location without changing its
|
||||
|
|
@ -72,19 +78,23 @@ poosition. Also note that the ``WeldNeckFlange`` class predefines two joints, o
|
|||
one at the face end - both of which are shown in the above image (generated by ocp-vscode with the
|
||||
``render_joints=True`` flag set in the ``show`` function).
|
||||
|
||||
|
||||
.. autoclass:: RigidJoint
|
||||
|
||||
**************
|
||||
Revolute Joint
|
||||
**************
|
||||
|
||||
Component rotates around axis like a hinge. The :ref:`joint_tutorial` covers Revolute Joints in detail.
|
||||
|
||||
During instantiation of a :class:`~topology.RevoluteJoint` there are three parameters not present with
|
||||
During instantiation of a :class:`~joints.RevoluteJoint` there are three parameters not present with
|
||||
Rigid Joints: ``axis``, ``angle_reference``, and ``range`` that allow the circular motion to be fully
|
||||
defined.
|
||||
|
||||
When :meth:`~topology.Joint.connect_to` with a Revolute Joint, an extra ``angle`` parameter is present
|
||||
which allows one to change the relative position of joined parts by changing a single value.
|
||||
|
||||
.. autoclass:: RevoluteJoint
|
||||
|
||||
************
|
||||
Linear Joint
|
||||
|
|
@ -107,20 +117,22 @@ The code to generate these components follows:
|
|||
Note how the slide is constructed in a different orientation than the direction of motion. The
|
||||
three highlighted lines of code show how the joints are created and connected together:
|
||||
|
||||
* The :class:`~topology.LinearJoint` has an axis and limits of movement
|
||||
* The :class:`~topology.RigidJoint` has a single location, orientated such that the knob will ultimately be "up"
|
||||
* The :class:`~joints.LinearJoint` has an axis and limits of movement
|
||||
* The :class:`~joints.RigidJoint` has a single location, orientated such that the knob will ultimately be "up"
|
||||
* The ``connect_to`` specifies a position that must be within the predefined limits.
|
||||
|
||||
The slider can be moved back and forth by just changing the ``position`` value. Values outside
|
||||
of the limits will raise an exception.
|
||||
|
||||
.. autoclass:: LinearJoint
|
||||
|
||||
*****************
|
||||
Cylindrical Joint
|
||||
*****************
|
||||
|
||||
A :class:`~topology.CylindricalJoint` allows a component to rotate around and moves along a single axis
|
||||
like a screw combining the functionality of a :class:`~topology.LinearJoint` and a
|
||||
:class:`~topology.RevoluteJoint` joint. The ``connect_to`` for these joints have both ``position`` and
|
||||
A :class:`~joints.CylindricalJoint` allows a component to rotate around and moves along a single axis
|
||||
like a screw combining the functionality of a :class:`~joints.LinearJoint` and a
|
||||
:class:`~joints.RevoluteJoint` joint. The ``connect_to`` for these joints have both ``position`` and
|
||||
``angle`` parameters as shown below extracted from the joint tutorial.
|
||||
|
||||
|
||||
|
|
@ -128,20 +140,22 @@ like a screw combining the functionality of a :class:`~topology.LinearJoint` and
|
|||
|
||||
hinge_outer.joints["hole2"].connect_to(m6_joint, position=5 * MM, angle=30)
|
||||
|
||||
.. autoclass:: CylindricalJoint
|
||||
|
||||
**********
|
||||
Ball Joint
|
||||
**********
|
||||
|
||||
A component rotates around all 3 axes using a gimbal system (3 nested rotations). A :class:`~topology.BallJoint`
|
||||
A component rotates around all 3 axes using a gimbal system (3 nested rotations). A :class:`~joints.BallJoint`
|
||||
is found within a rod end as shown here:
|
||||
|
||||
.. image:: assets/rod_end.png
|
||||
|
||||
.. literalinclude:: rod_end.py
|
||||
:emphasize-lines: 37-42,49,51
|
||||
:emphasize-lines: 37-41,48,50
|
||||
|
||||
Note how limits are defined during the instantiation of the ball joint when ensures that the pin or bolt
|
||||
within the rod end does not interfer with the rod end itself. The ``connect_to`` sets the three angles
|
||||
(only two are significant in this example).
|
||||
|
||||
.. autoclass:: BallJoint
|
||||
|
|
|
|||
|
|
@ -16,12 +16,8 @@ with BuildPart() as pipe_builder:
|
|||
sweep()
|
||||
|
||||
# Add the joints
|
||||
RigidJoint(
|
||||
label="inlet", to_part=pipe_builder.part, joint_location=-path.location_at(0)
|
||||
)
|
||||
RigidJoint(
|
||||
label="outlet", to_part=pipe_builder.part, joint_location=path.location_at(1)
|
||||
)
|
||||
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"])
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ with BuildPart() as rod_end:
|
|||
# Create the ball joint
|
||||
BallJoint(
|
||||
"socket",
|
||||
rod_end.part,
|
||||
joint_location=Location(),
|
||||
angular_range=((-14, 14), (-14, 14), (0, 360)),
|
||||
)
|
||||
|
|
@ -46,7 +45,7 @@ with BuildPart() as ball:
|
|||
Box(50, 50, 13, mode=Mode.INTERSECT)
|
||||
Hole(4)
|
||||
ball.part.color = Color("aliceblue")
|
||||
RigidJoint("ball", ball.part, joint_location=Location())
|
||||
RigidJoint("ball", joint_location=Location())
|
||||
|
||||
rod_end.part.joints["socket"].connect_to(ball.part.joints["ball"], angles=(5, 10, 0))
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ with BuildPart() as latch:
|
|||
SlotOverall(32, 8)
|
||||
extrude(amount=-2, mode=Mode.SUBTRACT)
|
||||
# The slider will move align the x axis 12mm in each direction
|
||||
LinearJoint("latch", latch.part, axis=Axis.X, linear_range=(-12, 12))
|
||||
LinearJoint("latch", axis=Axis.X, linear_range=(-12, 12))
|
||||
|
||||
with BuildPart() as slide:
|
||||
# The slide will be a little smaller than the hole
|
||||
|
|
@ -49,7 +49,7 @@ with BuildPart() as slide:
|
|||
split(bisect_by=Plane.XZ)
|
||||
revolve(axis=Axis.X)
|
||||
# Align the joint to Plane.ZY flipped
|
||||
RigidJoint("slide", slide.part, Location(-Plane.ZY))
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ class Hinge(Compound):
|
|||
# Leaf attachment
|
||||
RigidJoint(
|
||||
label="leaf",
|
||||
to_part=leaf_builder.part,
|
||||
joint_location=Location(
|
||||
(width - barrel_diameter, 0, length / 2), (90, 0, 0)
|
||||
),
|
||||
|
|
@ -142,13 +141,13 @@ class Hinge(Compound):
|
|||
if inner:
|
||||
RigidJoint(
|
||||
"hinge_axis",
|
||||
leaf_builder.part,
|
||||
Location((width - barrel_diameter / 2, barrel_diameter / 2, 0)),
|
||||
joint_location=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)
|
||||
),
|
||||
|
|
@ -159,13 +158,11 @@ class Hinge(Compound):
|
|||
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]
|
||||
|
||||
|
|
@ -202,10 +199,8 @@ with BuildPart() as box_builder:
|
|||
Hole(3 * MM, 1 * CM)
|
||||
RigidJoint(
|
||||
"hinge_attachment",
|
||||
box_builder.part,
|
||||
Location((-15 * CM, 0, 4 * CM), (180, 90, 0)),
|
||||
joint_location=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)))
|
||||
|
||||
|
|
@ -217,8 +212,7 @@ with BuildPart() as lid_builder:
|
|||
Hole(3 * MM, 1 * CM)
|
||||
RigidJoint(
|
||||
"hinge_attachment",
|
||||
lid_builder.part,
|
||||
Location((0, 0, 0), (0, 0, 180)),
|
||||
joint_location=Location((0, 0, 0), (0, 0, 180)),
|
||||
)
|
||||
lid = lid_builder.part
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ The second joint to add is either a :class:`~topology.RigidJoint` (on the inner
|
|||
.. literalinclude:: tutorial_joints.py
|
||||
:start-after: [Create the Joints]
|
||||
:end-before: [Fastener holes]
|
||||
:emphasize-lines: 10-25
|
||||
:emphasize-lines: 10-24
|
||||
|
||||
The inner leaf just pivots around the outer leaf and therefore the simple :class:`~topology.RigidJoint` is
|
||||
used to define the Location of this pivot. The outer leaf contains the more complex
|
||||
|
|
@ -141,7 +141,7 @@ the joint used to attach the outer hinge leaf.
|
|||
.. literalinclude:: tutorial_joints.py
|
||||
:start-after: [Create the box with a RigidJoint to mount the hinge]
|
||||
:end-before: [Demonstrate that objects with Joints can be moved and the joints follow]
|
||||
:emphasize-lines: 13-17
|
||||
:emphasize-lines: 13-16
|
||||
|
||||
Since the hinge will be fixed to the box another :class:`~topology.RigidJoint` is used mark where the hinge
|
||||
will go. Note that the orientation of this :class:`~topology.Joint` will control how the hinge leaf is
|
||||
|
|
@ -172,7 +172,7 @@ Much like the box, the lid is created in a :class:`~build_part.BuildPart` contex
|
|||
.. literalinclude:: tutorial_joints.py
|
||||
:start-after: [The lid with a RigidJoint for the hinge]
|
||||
:end-before: [A screw to attach the hinge to the box]
|
||||
:emphasize-lines: 6-10
|
||||
:emphasize-lines: 6-9
|
||||
|
||||
Again, the original orientation of the lid and hinge inner leaf are not important, when the
|
||||
joints are connected together the parts will move into the correct position.
|
||||
|
|
@ -293,3 +293,10 @@ and ``other`` will move to the appropriate :class:`~geometry.Location`.
|
|||
|
||||
show_object(m6_joint.symbol, name="m6 screw symbol")
|
||||
|
||||
or, with the ocp_vscode viewer
|
||||
|
||||
.. code:: python
|
||||
|
||||
show(box, render_joints=True)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
"""build123d import definitions"""
|
||||
from build123d.build_common import *
|
||||
from build123d.build_enums import *
|
||||
from build123d.build_line import *
|
||||
from build123d.build_sketch import *
|
||||
from build123d.build_part import *
|
||||
from build123d.build_sketch import *
|
||||
from build123d.exporters import *
|
||||
from build123d.geometry import *
|
||||
from build123d.topology import *
|
||||
from build123d.build_enums import *
|
||||
from build123d.importers import *
|
||||
from build123d.joints import *
|
||||
from build123d.mesher import *
|
||||
from build123d.objects_curve import *
|
||||
from build123d.objects_part import *
|
||||
from build123d.objects_sketch import *
|
||||
from build123d.operations_generic import *
|
||||
from build123d.operations_part import *
|
||||
from build123d.operations_sketch import *
|
||||
from build123d.objects_part import *
|
||||
from build123d.objects_sketch import *
|
||||
from build123d.objects_curve import *
|
||||
from build123d.mesher import *
|
||||
from build123d.topology import *
|
||||
|
||||
from .version import version as __version__
|
||||
|
||||
__all__ = [
|
||||
|
|
|
|||
|
|
@ -211,10 +211,16 @@ class Builder(ABC):
|
|||
|
||||
return self
|
||||
|
||||
def _exit_extras(self):
|
||||
"""Any builder specific exit actions"""
|
||||
pass
|
||||
|
||||
def __exit__(self, exception_type, exception_value, traceback):
|
||||
"""Upon exiting restore context and send object to parent"""
|
||||
self._current.reset(self._reset_tok)
|
||||
|
||||
self._exit_extras() # custom builder exit code
|
||||
|
||||
if self.builder_parent is not None and self.mode != Mode.PRIVATE:
|
||||
logger.debug(
|
||||
"Transferring object(s) to %s", type(self.builder_parent).__name__
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ from typing import Union
|
|||
from build123d.build_common import Builder, logger
|
||||
from build123d.build_enums import Mode
|
||||
from build123d.geometry import Location, Plane
|
||||
from build123d.topology import Edge, Face, Part, Solid, Wire
|
||||
from build123d.topology import Edge, Face, Joint, Part, Solid, Wire
|
||||
|
||||
|
||||
class BuildPart(Builder):
|
||||
|
|
@ -71,11 +71,17 @@ class BuildPart(Builder):
|
|||
"""Return a wire representation of the pending edges"""
|
||||
return Wire.combine(self.pending_edges)[0]
|
||||
|
||||
@property
|
||||
def location(self) -> Location:
|
||||
"""Builder's location"""
|
||||
return self.part.location if self.part is not None else Location()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*workplanes: Union[Face, Plane, Location],
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
self.joints: dict[str, Joint] = {}
|
||||
self.part: Part = None
|
||||
self.pending_faces: list[Face] = []
|
||||
self.pending_face_planes: list[Plane] = []
|
||||
|
|
@ -106,3 +112,10 @@ class BuildPart(Builder):
|
|||
edge.location,
|
||||
)
|
||||
self.pending_edges.append(edge)
|
||||
|
||||
def _exit_extras(self):
|
||||
"""Transfer joints on exit"""
|
||||
if self.joints:
|
||||
self.part.joints = self.joints
|
||||
for joint in self.part.joints.values():
|
||||
joint.parent = self.part
|
||||
|
|
|
|||
|
|
@ -6810,509 +6810,6 @@ class Joint(ABC):
|
|||
return NotImplementedError
|
||||
|
||||
|
||||
class RigidJoint(Joint):
|
||||
"""RigidJoint
|
||||
|
||||
A rigid joint fixes two components to one another.
|
||||
|
||||
Args:
|
||||
label (str): joint label
|
||||
to_part (Union[Solid, Compound]): object to attach joint to
|
||||
joint_location (Location): global location of joint
|
||||
|
||||
Attributes:
|
||||
relative_location (Location): joint location relative to bound object
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def symbol(self) -> Compound:
|
||||
"""A CAD symbol (XYZ indicator) as bound to part"""
|
||||
size = self.parent.bounding_box().diagonal / 12
|
||||
return Compound.make_triad(axes_scale=size).locate(
|
||||
self.parent.location * self.relative_location
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str,
|
||||
to_part: Union[Solid, Compound],
|
||||
joint_location: Location = Location(),
|
||||
):
|
||||
self.relative_location = to_part.location.inverse() * joint_location
|
||||
to_part.joints[label] = self
|
||||
super().__init__(label, to_part)
|
||||
|
||||
def relative_to(self, other: Joint, **kwargs) -> Location:
|
||||
"""relative_to
|
||||
|
||||
Return the relative position to move the other.
|
||||
|
||||
Args:
|
||||
other (RigidJoint): joint to connect to
|
||||
"""
|
||||
if not isinstance(other, RigidJoint):
|
||||
raise TypeError(f"other must of type RigidJoint not {type(other)}")
|
||||
|
||||
return self.relative_location * other.relative_location.inverse()
|
||||
|
||||
|
||||
class RevoluteJoint(Joint):
|
||||
"""RevoluteJoint
|
||||
|
||||
Component rotates around axis like a hinge.
|
||||
|
||||
Args:
|
||||
label (str): joint label
|
||||
to_part (Union[Solid, Compound]): 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).
|
||||
|
||||
Attributes:
|
||||
angle (float): angle of joint
|
||||
angle_reference (Vector): reference for angular poitions
|
||||
angular_range (tuple[float,float]): min and max angular position of joint
|
||||
relative_axis (Axis): joint axis relative to bound part
|
||||
|
||||
Raises:
|
||||
ValueError: angle_reference must be normal to axis
|
||||
"""
|
||||
|
||||
@property
|
||||
def symbol(self) -> Compound:
|
||||
"""A CAD symbol representing the axis of rotation as bound to part"""
|
||||
radius = self.parent.bounding_box().diagonal / 30
|
||||
|
||||
return Compound.make_compound(
|
||||
[
|
||||
Edge.make_line((0, 0, 0), (0, 0, radius * 10)),
|
||||
Edge.make_circle(radius),
|
||||
]
|
||||
).move(self.parent.location * self.relative_axis.location)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str,
|
||||
to_part: Union[Solid, Compound],
|
||||
axis: Axis = Axis.Z,
|
||||
angle_reference: VectorLike = None,
|
||||
angular_range: tuple[float, float] = (0, 360),
|
||||
):
|
||||
self.angular_range = angular_range
|
||||
if angle_reference:
|
||||
if not axis.is_normal(Axis((0, 0, 0), angle_reference)):
|
||||
raise ValueError("angle_reference must be normal to axis")
|
||||
self.angle_reference = Vector(angle_reference)
|
||||
else:
|
||||
self.angle_reference = Plane(origin=(0, 0, 0), z_dir=axis.direction).x_dir
|
||||
self.angle = None
|
||||
self.relative_axis = axis.located(to_part.location.inverse())
|
||||
to_part.joints[label] = self
|
||||
super().__init__(label, to_part)
|
||||
|
||||
def relative_to(
|
||||
self, other: Joint, angle: float = None
|
||||
): # pylint: disable=arguments-differ
|
||||
"""relative_to
|
||||
|
||||
Return the relative location from this joint to the RigidJoint of another object
|
||||
- a hinge joint.
|
||||
|
||||
Args:
|
||||
other (RigidJoint): joint to connect to
|
||||
angle (float, optional): angle within angular range. Defaults to minimum.
|
||||
|
||||
Raises:
|
||||
TypeError: other must of type RigidJoint
|
||||
ValueError: angle out of range
|
||||
"""
|
||||
if not isinstance(other, RigidJoint):
|
||||
raise TypeError(f"other must of type RigidJoint not {type(other)}")
|
||||
|
||||
angle = self.angular_range[0] if angle is None else angle
|
||||
if angle < self.angular_range[0] or angle > self.angular_range[1]:
|
||||
raise ValueError(f"angle ({angle}) must in range of {self.angular_range}")
|
||||
self.angle = angle
|
||||
# Avoid strange rotations when angle is zero by using 360 instead
|
||||
angle = 360.0 if angle == 0.0 else angle
|
||||
rotation = Location(
|
||||
Plane(
|
||||
origin=(0, 0, 0),
|
||||
x_dir=self.angle_reference.rotate(Axis.Z, angle),
|
||||
z_dir=(0, 0, 1),
|
||||
)
|
||||
)
|
||||
return (
|
||||
self.relative_axis.location * rotation * other.relative_location.inverse()
|
||||
)
|
||||
|
||||
|
||||
class LinearJoint(Joint):
|
||||
"""LinearJoint
|
||||
|
||||
Component moves along a single axis.
|
||||
|
||||
Args:
|
||||
label (str): joint label
|
||||
to_part (Union[Solid, Compound]): 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).
|
||||
|
||||
Attributes:
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def symbol(self) -> Compound:
|
||||
"""A CAD symbol of the linear axis positioned relative to_part"""
|
||||
radius = (self.linear_range[1] - self.linear_range[0]) / 15
|
||||
return Compound.make_compound(
|
||||
[
|
||||
Edge.make_line(
|
||||
(0, 0, self.linear_range[0]), (0, 0, self.linear_range[1])
|
||||
),
|
||||
Edge.make_circle(radius),
|
||||
]
|
||||
).move(self.parent.location * self.relative_axis.location)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str,
|
||||
to_part: Union[Solid, Compound],
|
||||
axis: Axis = Axis.Z,
|
||||
linear_range: tuple[float, float] = (0, inf),
|
||||
):
|
||||
self.axis = axis
|
||||
self.linear_range = linear_range
|
||||
self.position = None
|
||||
self.relative_axis = axis.located(to_part.location.inverse())
|
||||
self.angle = None
|
||||
to_part.joints[label]: dict[str, Joint] = self
|
||||
super().__init__(label, to_part)
|
||||
|
||||
@overload
|
||||
def relative_to(
|
||||
self, other: RigidJoint, position: float = None
|
||||
): # pylint: disable=arguments-differ
|
||||
"""relative_to - RigidJoint
|
||||
|
||||
Return the relative location from this joint to the RigidJoint of another object
|
||||
- a slider joint.
|
||||
|
||||
Args:
|
||||
other (RigidJoint): joint to connect to
|
||||
position (float, optional): position within joint range. Defaults to middle.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def relative_to(
|
||||
self, other: RevoluteJoint, position: float = None, angle: float = None
|
||||
): # pylint: disable=arguments-differ
|
||||
"""relative_to - RevoluteJoint
|
||||
|
||||
Return the relative location from this joint to the RevoluteJoint of another object
|
||||
- a pin slot joint.
|
||||
|
||||
Args:
|
||||
other (RigidJoint): joint to connect to
|
||||
position (float, optional): position within joint range. Defaults to middle.
|
||||
angle (float, optional): angle within angular range. Defaults to minimum.
|
||||
"""
|
||||
|
||||
def relative_to(self, *args, **kwargs): # pylint: disable=arguments-differ
|
||||
"""Return the relative position of other to linear joint defined by self"""
|
||||
|
||||
# Parse the input parameters
|
||||
other, position, angle = None, None, None
|
||||
if args:
|
||||
other = args[0]
|
||||
position = args[1] if len(args) >= 2 else position
|
||||
angle = args[2] if len(args) == 3 else angle
|
||||
|
||||
if kwargs:
|
||||
other = kwargs["other"] if "other" in kwargs else other
|
||||
position = kwargs["position"] if "position" in kwargs else position
|
||||
angle = kwargs["angle"] if "angle" in kwargs else angle
|
||||
|
||||
if not isinstance(other, (RigidJoint, RevoluteJoint)):
|
||||
raise TypeError(
|
||||
f"other must of type RigidJoint or RevoluteJoint not {type(other)}"
|
||||
)
|
||||
|
||||
position = sum(self.linear_range) / 2 if position is None else position
|
||||
if not self.linear_range[0] <= position <= self.linear_range[1]:
|
||||
raise ValueError(
|
||||
f"position ({position}) must in range of {self.linear_range}"
|
||||
)
|
||||
self.position = position
|
||||
|
||||
if isinstance(other, RevoluteJoint):
|
||||
angle = other.angular_range[0] if angle is None else angle
|
||||
if not other.angular_range[0] <= angle <= other.angular_range[1]:
|
||||
raise ValueError(
|
||||
f"angle ({angle}) must in range of {other.angular_range}"
|
||||
)
|
||||
rotation = Location(
|
||||
Plane(
|
||||
origin=(0, 0, 0),
|
||||
x_dir=other.angle_reference.rotate(other.relative_axis, angle),
|
||||
z_dir=other.relative_axis.direction,
|
||||
)
|
||||
)
|
||||
else:
|
||||
angle = 0.0
|
||||
rotation = Location()
|
||||
self.angle = angle
|
||||
joint_relative_position = (
|
||||
Location(
|
||||
self.relative_axis.position + self.relative_axis.direction * position,
|
||||
)
|
||||
* rotation
|
||||
)
|
||||
|
||||
if isinstance(other, RevoluteJoint):
|
||||
other_relative_location = Location(other.relative_axis.position)
|
||||
else:
|
||||
other_relative_location = other.relative_location
|
||||
|
||||
return joint_relative_position * other_relative_location.inverse()
|
||||
|
||||
|
||||
class CylindricalJoint(Joint):
|
||||
"""CylindricalJoint
|
||||
|
||||
Component rotates around and moves along a single axis like a screw.
|
||||
|
||||
Args:
|
||||
label (str): joint label
|
||||
to_part (Union[Solid, Compound]): 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).
|
||||
|
||||
Attributes:
|
||||
axis (Axis): joint axis
|
||||
linear_position (float): linear joint position
|
||||
rotational_position (float): revolute joint angle in degrees
|
||||
angle_reference (Vector): reference for angular poitions
|
||||
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: angle_reference must be normal to axis
|
||||
"""
|
||||
|
||||
@property
|
||||
def symbol(self) -> Compound:
|
||||
"""A CAD symbol representing the cylindrical axis as bound to part"""
|
||||
radius = (self.linear_range[1] - self.linear_range[0]) / 15
|
||||
return Compound.make_compound(
|
||||
[
|
||||
Edge.make_line(
|
||||
(0, 0, self.linear_range[0]), (0, 0, self.linear_range[1])
|
||||
),
|
||||
Edge.make_circle(radius),
|
||||
]
|
||||
).move(self.parent.location * self.relative_axis.location)
|
||||
|
||||
# @property
|
||||
# def axis_location(self) -> Location:
|
||||
# """Current global location of joint axis"""
|
||||
# return self.parent.location * self.relative_axis.location
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str,
|
||||
to_part: Union[Solid, Compound],
|
||||
axis: Axis = Axis.Z,
|
||||
angle_reference: VectorLike = None,
|
||||
linear_range: tuple[float, float] = (0, inf),
|
||||
angular_range: tuple[float, float] = (0, 360),
|
||||
):
|
||||
self.axis = axis
|
||||
self.linear_position = None
|
||||
self.rotational_position = None
|
||||
if angle_reference:
|
||||
if not axis.is_normal(Axis((0, 0, 0), angle_reference)):
|
||||
raise ValueError("angle_reference must be normal to axis")
|
||||
self.angle_reference = Vector(angle_reference)
|
||||
else:
|
||||
self.angle_reference = Plane(origin=(0, 0, 0), z_dir=axis.direction).x_dir
|
||||
self.angular_range = angular_range
|
||||
self.linear_range = linear_range
|
||||
self.relative_axis = axis.located(to_part.location.inverse())
|
||||
self.position = None
|
||||
self.angle = None
|
||||
to_part.joints[label]: dict[str, Joint] = self
|
||||
super().__init__(label, to_part)
|
||||
|
||||
def relative_to(
|
||||
self, other: RigidJoint, position: float = None, angle: float = None
|
||||
): # pylint: disable=arguments-differ
|
||||
"""relative_to - CylindricalJoint
|
||||
|
||||
Return the relative location from this joint to the RigidJoint of another object
|
||||
- a sliding and rotating joint.
|
||||
|
||||
Args:
|
||||
other (RigidJoint): joint to connect to
|
||||
position (float, optional): position within joint linear range. Defaults to middle.
|
||||
angle (float, optional): angle within rotational range.
|
||||
Defaults to angular_range minimum.
|
||||
|
||||
Raises:
|
||||
TypeError: other must be of type RigidJoint
|
||||
ValueError: position out of range
|
||||
ValueError: angle out of range
|
||||
"""
|
||||
if not isinstance(other, RigidJoint):
|
||||
raise TypeError(f"other must of type RigidJoint not {type(other)}")
|
||||
|
||||
position = sum(self.linear_range) / 2 if position is None else position
|
||||
if not self.linear_range[0] <= position <= self.linear_range[1]:
|
||||
raise ValueError(
|
||||
f"position ({position}) must in range of {self.linear_range}"
|
||||
)
|
||||
self.position = position
|
||||
angle = sum(self.angular_range) / 2 if angle is None else angle
|
||||
if not self.angular_range[0] <= angle <= self.angular_range[1]:
|
||||
raise ValueError(f"angle ({angle}) must in range of {self.angular_range}")
|
||||
self.angle = angle
|
||||
|
||||
joint_relative_position = Location(
|
||||
self.relative_axis.position + self.relative_axis.direction * position
|
||||
)
|
||||
joint_rotation = Location(
|
||||
Plane(
|
||||
origin=(0, 0, 0),
|
||||
x_dir=self.angle_reference.rotate(self.relative_axis, angle),
|
||||
z_dir=self.relative_axis.direction,
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
joint_relative_position * joint_rotation * other.relative_location.inverse()
|
||||
)
|
||||
|
||||
|
||||
class BallJoint(Joint):
|
||||
"""BallJoint
|
||||
|
||||
A component rotates around all 3 axes using a gimbal system (3 nested rotations).
|
||||
|
||||
Args:
|
||||
label (str): joint label
|
||||
to_part (Union[Solid, Compound]): object to attach joint to
|
||||
joint_location (Location): global location of joint
|
||||
angular_range
|
||||
(tuple[ tuple[float, float], tuple[float, float], 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 to Plane.XY.
|
||||
|
||||
Attributes:
|
||||
relative_location (Location): joint location relative to bound part
|
||||
angular_range
|
||||
(tuple[ tuple[float, float], tuple[float, float], tuple[float, float] ]):
|
||||
X, Y, Z angle (min, max) pairs.
|
||||
angle_reference (Plane): plane relative to part defining zero degrees of
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def symbol(self) -> Compound:
|
||||
"""A CAD symbol representing joint as bound to part"""
|
||||
radius = self.parent.bounding_box().diagonal / 30
|
||||
circle_x = Edge.make_circle(radius, self.angle_reference)
|
||||
circle_y = Edge.make_circle(radius, self.angle_reference.rotated((90, 0, 0)))
|
||||
circle_z = Edge.make_circle(radius, self.angle_reference.rotated((0, 90, 0)))
|
||||
|
||||
return Compound.make_compound(
|
||||
[
|
||||
circle_x,
|
||||
circle_y,
|
||||
circle_z,
|
||||
Compound.make_text(
|
||||
"X", radius / 5, align=(Align.CENTER, Align.CENTER)
|
||||
).locate(circle_x.location_at(0.125) * Rotation(90, 0, 0)),
|
||||
Compound.make_text(
|
||||
"Y", radius / 5, align=(Align.CENTER, Align.CENTER)
|
||||
).locate(circle_y.location_at(0.625) * Rotation(90, 0, 0)),
|
||||
Compound.make_text(
|
||||
"Z", radius / 5, align=(Align.CENTER, Align.CENTER)
|
||||
).locate(circle_z.location_at(0.125) * Rotation(90, 0, 0)),
|
||||
]
|
||||
).move(self.parent.location * self.relative_location)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str,
|
||||
to_part: Union[Solid, Compound],
|
||||
joint_location: Location = Location(),
|
||||
angular_range: tuple[
|
||||
tuple[float, float], tuple[float, float], tuple[float, float]
|
||||
] = ((0, 360), (0, 360), (0, 360)),
|
||||
angle_reference: Plane = Plane.XY,
|
||||
):
|
||||
self.relative_location = to_part.location.inverse() * joint_location
|
||||
to_part.joints[label] = self
|
||||
self.angular_range = angular_range
|
||||
self.angle_reference = angle_reference
|
||||
super().__init__(label, to_part)
|
||||
|
||||
def relative_to(
|
||||
self, other: RigidJoint, angles: RotationLike = None
|
||||
): # pylint: disable=arguments-differ
|
||||
"""relative_to - CylindricalJoint
|
||||
|
||||
Return the relative location from this joint to the RigidJoint of another object
|
||||
|
||||
Args:
|
||||
other (RigidJoint): joint to connect to
|
||||
angles (RotationLike, optional): orientation of other's parent relative to
|
||||
self. Defaults to the minimums of the angle ranges.
|
||||
|
||||
Raises:
|
||||
TypeError: invalid other joint type
|
||||
ValueError: angles out of range
|
||||
"""
|
||||
|
||||
if not isinstance(other, RigidJoint):
|
||||
raise TypeError(f"other must of type RigidJoint not {type(other)}")
|
||||
|
||||
rotation = (
|
||||
Rotation(*[self.angular_range[i][0] for i in [0, 1, 2]])
|
||||
if angles is None
|
||||
else Rotation(*angles)
|
||||
) * self.angle_reference.location
|
||||
|
||||
for i, rotations in zip(
|
||||
[0, 1, 2],
|
||||
[rotation.orientation.X, rotation.orientation.Y, rotation.orientation.Z],
|
||||
):
|
||||
if not self.angular_range[i][0] <= rotations <= self.angular_range[i][1]:
|
||||
raise ValueError(
|
||||
f"angles ({angles}) must in range of {self.angular_range}"
|
||||
)
|
||||
|
||||
return self.relative_location * rotation * other.relative_location.inverse()
|
||||
|
||||
|
||||
def downcast(obj: TopoDS_Shape) -> TopoDS_Shape:
|
||||
"""Downcasts a TopoDS object to suitable specialized type
|
||||
|
||||
|
|
|
|||
|
|
@ -59,18 +59,13 @@ from build123d.geometry import (
|
|||
Vector,
|
||||
VectorLike,
|
||||
)
|
||||
from build123d.importers import import_brep, import_step, import_stl, import_svg
|
||||
from build123d.importers import import_brep, import_step, import_stl
|
||||
from build123d.mesher import Mesher
|
||||
from build123d.topology import (
|
||||
BallJoint,
|
||||
Compound,
|
||||
CylindricalJoint,
|
||||
Edge,
|
||||
Face,
|
||||
LinearJoint,
|
||||
Plane,
|
||||
RevoluteJoint,
|
||||
RigidJoint,
|
||||
Shape,
|
||||
ShapeList,
|
||||
Shell,
|
||||
|
|
@ -1245,234 +1240,6 @@ class TestImportExport(DirectApiTestCase):
|
|||
self.assertVectorAlmostEquals(stl_box.position, (0, 0, 0), 5)
|
||||
|
||||
|
||||
class TestJoints(DirectApiTestCase):
|
||||
def test_rigid_joint(self):
|
||||
base = Solid.make_box(1, 1, 1)
|
||||
j1 = RigidJoint("top", base, Location(Vector(0.5, 0.5, 1)))
|
||||
fixed_top = Solid.make_box(1, 1, 1)
|
||||
j2 = RigidJoint("bottom", fixed_top, Location((0.5, 0.5, 0)))
|
||||
j1.connect_to(j2)
|
||||
bbox = fixed_top.bounding_box()
|
||||
self.assertVectorAlmostEquals(bbox.min, (0, 0, 1), 5)
|
||||
self.assertVectorAlmostEquals(bbox.max, (1, 1, 2), 5)
|
||||
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.position, (0.5, 0.5, 1), 6)
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.orientation, (0, 0, 0), 6)
|
||||
|
||||
def test_revolute_joint_with_angle_reference(self):
|
||||
revolute_base = Solid.make_cylinder(1, 1)
|
||||
j1 = RevoluteJoint(
|
||||
label="top",
|
||||
to_part=revolute_base,
|
||||
axis=Axis((0, 0, 1), (0, 0, 1)),
|
||||
angle_reference=(1, 0, 0),
|
||||
angular_range=(0, 180),
|
||||
)
|
||||
fixed_top = Solid.make_box(1, 0.5, 1)
|
||||
j2 = RigidJoint("bottom", fixed_top, Location((0.5, 0.25, 0)))
|
||||
|
||||
j1.connect_to(j2, 90)
|
||||
bbox = fixed_top.bounding_box()
|
||||
self.assertVectorAlmostEquals(bbox.min, (-0.25, -0.5, 1), 5)
|
||||
self.assertVectorAlmostEquals(bbox.max, (0.25, 0.5, 2), 5)
|
||||
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.position, (0, 0, 1), 6)
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.orientation, (0, 0, 90), 6)
|
||||
self.assertEqual(len(j1.symbol.edges()), 2)
|
||||
|
||||
def test_revolute_joint_without_angle_reference(self):
|
||||
revolute_base = Solid.make_cylinder(1, 1)
|
||||
j1 = RevoluteJoint(
|
||||
label="top",
|
||||
to_part=revolute_base,
|
||||
axis=Axis((0, 0, 1), (0, 0, 1)),
|
||||
)
|
||||
self.assertVectorAlmostEquals(j1.angle_reference, (1, 0, 0), 5)
|
||||
|
||||
def test_revolute_joint_error_bad_angle_reference(self):
|
||||
"""Test that the angle_reference must be normal to the axis"""
|
||||
revolute_base = Solid.make_cylinder(1, 1)
|
||||
with self.assertRaises(ValueError):
|
||||
RevoluteJoint(
|
||||
"top",
|
||||
revolute_base,
|
||||
axis=Axis((0, 0, 1), (0, 0, 1)),
|
||||
angle_reference=(1, 0, 1),
|
||||
)
|
||||
|
||||
def test_revolute_joint_error_bad_angle(self):
|
||||
"""Test that the joint angle is within bounds"""
|
||||
revolute_base = Solid.make_cylinder(1, 1)
|
||||
j1 = RevoluteJoint("top", revolute_base, Axis.Z, angular_range=(0, 180))
|
||||
fixed_top = Solid.make_box(1, 0.5, 1)
|
||||
j2 = RigidJoint("bottom", fixed_top, Location((0.5, 0.25, 0)))
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, 270)
|
||||
|
||||
def test_revolute_joint_error_bad_joint_type(self):
|
||||
"""Test that the joint angle is within bounds"""
|
||||
revolute_base = Solid.make_cylinder(1, 1)
|
||||
j1 = RevoluteJoint("top", revolute_base, Axis.Z, (0, 180))
|
||||
fixed_top = Solid.make_box(1, 0.5, 1)
|
||||
j2 = RevoluteJoint("bottom", fixed_top, Axis.Z, (0, 180))
|
||||
with self.assertRaises(TypeError):
|
||||
j1.connect_to(j2, 0)
|
||||
|
||||
def test_linear_rigid_joint(self):
|
||||
base = Solid.make_box(1, 1, 1)
|
||||
j1 = LinearJoint(
|
||||
"top", to_part=base, axis=Axis((0, 0.5, 1), (1, 0, 0)), linear_range=(0, 1)
|
||||
)
|
||||
fixed_top = Solid.make_box(1, 1, 1)
|
||||
j2 = RigidJoint("bottom", fixed_top, Location((0.5, 0.5, 0)))
|
||||
j1.connect_to(j2, 0.25)
|
||||
bbox = fixed_top.bounding_box()
|
||||
self.assertVectorAlmostEquals(bbox.min, (-0.25, 0, 1), 5)
|
||||
self.assertVectorAlmostEquals(bbox.max, (0.75, 1, 2), 5)
|
||||
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.position, (0.25, 0.5, 1), 6)
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.orientation, (0, 0, 0), 6)
|
||||
|
||||
def test_linear_revolute_joint(self):
|
||||
linear_base = Solid.make_box(1, 1, 1)
|
||||
j1 = LinearJoint(
|
||||
label="top",
|
||||
to_part=linear_base,
|
||||
axis=Axis((0, 0.5, 1), (1, 0, 0)),
|
||||
linear_range=(0, 1),
|
||||
)
|
||||
revolute_top = Solid.make_box(1, 0.5, 1).locate(Location((-0.5, -0.25, 0)))
|
||||
j2 = RevoluteJoint(
|
||||
label="top",
|
||||
to_part=revolute_top,
|
||||
axis=Axis((0, 0, 0), (0, 0, 1)),
|
||||
angle_reference=(1, 0, 0),
|
||||
angular_range=(0, 180),
|
||||
)
|
||||
j1.connect_to(j2, position=0.25, angle=90)
|
||||
|
||||
bbox = revolute_top.bounding_box()
|
||||
self.assertVectorAlmostEquals(bbox.min, (0, 0, 1), 5)
|
||||
self.assertVectorAlmostEquals(bbox.max, (0.5, 1, 2), 5)
|
||||
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.position, (0.25, 0.5, 1), 6)
|
||||
self.assertVectorAlmostEquals(j2.symbol.location.orientation, (0, 0, 90), 6)
|
||||
self.assertEqual(len(j1.symbol.edges()), 2)
|
||||
|
||||
# Test invalid position
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, position=5, angle=90)
|
||||
|
||||
# Test invalid angle
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, position=0.5, angle=270)
|
||||
|
||||
# Test invalid joint
|
||||
with self.assertRaises(TypeError):
|
||||
j1.connect_to(Solid.make_box(1, 1, 1), position=0.5, angle=90)
|
||||
|
||||
def test_cylindrical_joint(self):
|
||||
cylindrical_base = (
|
||||
Solid.make_box(1, 1, 1)
|
||||
.locate(Location((-0.5, -0.5, 0)))
|
||||
.cut(Solid.make_cylinder(0.3, 1))
|
||||
)
|
||||
j1 = CylindricalJoint(
|
||||
"base",
|
||||
cylindrical_base,
|
||||
Axis((0, 0, 1), (0, 0, -1)),
|
||||
angle_reference=(1, 0, 0),
|
||||
linear_range=(0, 1),
|
||||
angular_range=(0, 90),
|
||||
)
|
||||
dowel = Solid.make_cylinder(0.3, 1).cut(
|
||||
Solid.make_box(1, 1, 1).locate(Location((-0.5, 0, 0)))
|
||||
)
|
||||
j2 = RigidJoint("bottom", dowel, Location((0, 0, 0), (0, 0, 0)))
|
||||
j1.connect_to(j2, 0.25, 90)
|
||||
dowel_bbox = dowel.bounding_box()
|
||||
self.assertVectorAlmostEquals(dowel_bbox.min, (0, -0.3, -0.25), 5)
|
||||
self.assertVectorAlmostEquals(dowel_bbox.max, (0.3, 0.3, 0.75), 5)
|
||||
|
||||
self.assertVectorAlmostEquals(j1.symbol.location.position, (0, 0, 1), 6)
|
||||
self.assertVectorAlmostEquals(
|
||||
j1.symbol.location.orientation, (-180, 0, -180), 6
|
||||
)
|
||||
self.assertEqual(len(j1.symbol.edges()), 2)
|
||||
|
||||
# Test invalid position
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, position=5, angle=90)
|
||||
|
||||
# Test invalid angle
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, position=0.5, angle=270)
|
||||
|
||||
# Test invalid joint
|
||||
with self.assertRaises(TypeError):
|
||||
j1.connect_to(Solid.make_box(1, 1, 1), position=0.5, angle=90)
|
||||
|
||||
def test_cylindrical_joint_error_bad_angle_reference(self):
|
||||
"""Test that the angle_reference must be normal to the axis"""
|
||||
with self.assertRaises(ValueError):
|
||||
CylindricalJoint(
|
||||
"base",
|
||||
Solid.make_box(1, 1, 1),
|
||||
Axis((0, 0, 1), (0, 0, -1)),
|
||||
angle_reference=(1, 0, 1),
|
||||
linear_range=(0, 1),
|
||||
angular_range=(0, 90),
|
||||
)
|
||||
|
||||
def test_cylindrical_joint_error_bad_position_and_angle(self):
|
||||
"""Test that the joint angle is within bounds"""
|
||||
|
||||
j1 = CylindricalJoint(
|
||||
"base",
|
||||
Solid.make_box(1, 1, 1),
|
||||
Axis((0, 0, 1), (0, 0, -1)),
|
||||
linear_range=(0, 1),
|
||||
angular_range=(0, 90),
|
||||
)
|
||||
j2 = RigidJoint("bottom", Solid.make_cylinder(1, 1), Location((0.5, 0.25, 0)))
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, position=0.5, angle=270)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, position=4, angle=30)
|
||||
|
||||
def test_ball_joint(self):
|
||||
socket_base = Solid.make_box(1, 1, 1).cut(
|
||||
Solid.make_sphere(0.3, Plane((0.5, 0.5, 1)))
|
||||
)
|
||||
j1 = BallJoint(
|
||||
"socket",
|
||||
socket_base,
|
||||
Location((0.5, 0.5, 1)),
|
||||
angular_range=((-45, 45), (-45, 45), (0, 360)),
|
||||
)
|
||||
ball_rod = Solid.make_cylinder(0.15, 2).fuse(
|
||||
Solid.make_sphere(0.3).locate(Location((0, 0, 2)))
|
||||
)
|
||||
j2 = RigidJoint("ball", ball_rod, Location((0, 0, 2), (180, 0, 0)))
|
||||
j1.connect_to(j2, (45, 45, 0))
|
||||
self.assertVectorAlmostEquals(
|
||||
ball_rod.faces().filter_by(GeomType.PLANE)[0].center(CenterOf.GEOMETRY),
|
||||
(1.914213562373095, -0.5, 2),
|
||||
5,
|
||||
)
|
||||
|
||||
self.assertVectorAlmostEquals(j1.symbol.location.position, (0.5, 0.5, 1), 6)
|
||||
self.assertVectorAlmostEquals(j1.symbol.location.orientation, (0, 0, 0), 6)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
j1.connect_to(j2, (90, 45, 0))
|
||||
|
||||
# Test invalid joint
|
||||
with self.assertRaises(TypeError):
|
||||
j1.connect_to(Solid.make_box(1, 1, 1), (0, 0, 0))
|
||||
|
||||
|
||||
class TestJupyter(DirectApiTestCase):
|
||||
def test_repr_javascript(self):
|
||||
shape = Solid.make_box(1, 1, 1)
|
||||
|
|
|
|||