Skip to content

Commit 53aa8ba

Browse files
committed
Improve documentation, provide examples
Signed-off-by: TobiPeterG <github.threefold020@passmail.net>
1 parent 607c82e commit 53aa8ba

File tree

3 files changed

+642
-78
lines changed

3 files changed

+642
-78
lines changed

README.md

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,319 @@
11
# mechanismaf
22
A wrapper around the "mechanism" library for the "Algorithm Folding" lecture @ HPI
33

4+
## Why mechanismaf?
5+
6+
**mechanismaf** is a high-level wrapper around the [mechanism](https://github.com/gabemorris12/mechanism) library designed for the "Algorithm Folding" lecture at HPI. Its primary goal is to simplify the creation, transformation, and animation of linkage mechanisms by abstracting away the low-level complexities of the underlying library.
7+
8+
The underlying **mechanism** library is very powerful but also very complicated. For example, when creating a Peaucellier–Lipkin linkage directly with **mechanism**, you must manually:
9+
- Define each joint and bar (vector).
10+
- Set up the loop equations for solving the unknown angles.
11+
- Specify initial guesses, sweep angles, and other details.
12+
- Build and manage the animation.
13+
14+
**mechanismaf** provides a more accessible API that allows you to define your mechanism using simple specifications (lists of bars with coordinates and optional properties) while automatically handling:
15+
- Joint and vector creation.
16+
- Grounding, sweep configuration, and loop detection.
17+
- Geometric transformations (scaling, rotation, and translation).
18+
- Animation enhancements (injecting bar angles and joint names).
19+
420
## Installation
521

22+
Install **mechanismaf** using pip:
23+
624
```bash
725
pip install mechanismaf
826
```
27+
28+
## What mechanismaf Does
29+
30+
- **Simplifies Linkage Creation:** Define mechanisms by listing bars and their properties instead of manually creating joints, vectors, loops, and initial guesses.
31+
- **Reusable Components:** Easily combine and transform mechanism specifications with functions such as transform_spec and combine_specs.
32+
- **Enhanced Animations:** Automatically inject text annotations (bar angles and joint names) into your mechanism animations using add_angle_joints_texts.
33+
- **Automatic Parameter Management:** Functions like set_style_ground and set_angle_sweep let you adjust properties such as fixed (ground) angles and sweep ranges without dealing with the underlying low-level API.
34+
35+
## Exposed Functions
36+
The package exposes a number of functions that make working with mechanisms more straightforward:
37+
38+
- `create_linkage_from_spec(spec, follow_points=None, log_level=logging.INFO, log_file=None)`: Creates and solves a linkage mechanism from a simplified specification list. This function automatically sets up joints, vectors, loops, and iterations.
39+
- `transform_spec(spec, scale=1.0, origin=(0, 0), rotation_deg=0.0)`: Transforms a mechanism specification by applying scaling, rotation, and translation to all bar coordinates.
40+
- `combine_specs(*specs)`: Combines multiple mechanism specifications into a single list, removing duplicate bars.
41+
- `set_style_ground(spec, bar_list, decimal=3)`: Marks specific bars as ground (fixed) in the specification by setting their style attribute.
42+
- `set_angle_sweep(spec, bar_sweep_dict, decimal=3)`: Configures custom angle sweep ranges for specified bars in the specification.
43+
- `add_angle_joints_texts(mech, ani, ax)`: Injects annotations for bar angles and joint names into the animation of a mechanism.
44+
45+
Additional helper functions such as `round_coord` and `transform_follow_points` further ease the process of working with coordinates and transformations.
46+
47+
## Examples
48+
### Creating a Peaucellier–Lipkin Linkage Using the Bare mechanism Library
49+
The following example shows the amount of work needed when using the underlying mechanism library directly:
50+
51+
``` python
52+
#!/usr/bin/env python3
53+
54+
import numpy as np
55+
import matplotlib.pyplot as plt
56+
from mechanism import *
57+
58+
# Create Joints
59+
O, A, B, C, D, E = get_joints("O A B C D E")
60+
C.follow, D.follow, E.follow = True, True, True
61+
62+
# Define Bars (Vectors)
63+
OA = Vector((O, A), r=1, theta=0, style='ground') # ground
64+
AB = Vector((A, B), r=1) # input
65+
BC = Vector((B, C), r=np.sqrt(2))
66+
CD = Vector((C, D), r=np.sqrt(2))
67+
DE = Vector((D, E), r=np.sqrt(2))
68+
EB = Vector((E, B), r=np.sqrt(2))
69+
OC = Vector((O, C), r=np.sqrt(10)) # pivot about O
70+
OE = Vector((O, E), r=np.sqrt(10)) # pivot about O
71+
72+
# Define the loop equations (6 unknown angles, 3 loops)
73+
def loops(x, input_angle):
74+
"""
75+
x = [θ_BC, θ_CD, θ_DE, θ_EB, θ_OC, θ_OE]
76+
input_angle = angle for AB (our driven link)
77+
"""
78+
theta_bc, theta_cd, theta_de, theta_eb, theta_oc, theta_oe = x
79+
80+
temp = np.zeros((3, 2))
81+
82+
# Loop1: O->A->B->C->O
83+
temp[0] = OA() + AB(input_angle) + BC(theta_bc) - OC(theta_oc)
84+
85+
# Loop2: O->C->D->E->O
86+
temp[1] = OC(theta_oc) + CD(theta_cd) + DE(theta_de) - OE(theta_oe)
87+
88+
# Loop3: B->C->D->E->B
89+
temp[2] = BC(theta_bc) + CD(theta_cd) + DE(theta_de) + EB(theta_eb)
90+
91+
return temp.flatten()
92+
93+
# Build "Up-and-Down" Angles for sweeping the mechanism.
94+
angles_up = np.linspace(-25, 25, 50) # 50 points
95+
angles_down = np.linspace(25, -25, 50) # 50 points
96+
angles_full = np.concatenate([angles_up, angles_down[1:]])
97+
angles_rad = np.deg2rad(angles_full)
98+
99+
# Initial guess for the 6 unknown angles
100+
guess0 = np.deg2rad([45, -45, -135, 135, 18, -18])
101+
102+
# Construct the Mechanism
103+
mechanism = Mechanism(
104+
vectors=(OA, AB, BC, CD, DE, EB, OC, OE),
105+
origin=O,
106+
loops=loops,
107+
pos=angles_rad,
108+
guess=(guess0,)
109+
)
110+
111+
# Solve and animate
112+
mechanism.iterate()
113+
ani, fig, ax = mechanism.get_animation(cushion=1.0)
114+
ax.set_title("Peaucellier–Lipkin linkage")
115+
plt.show()
116+
```
117+
118+
### Creating a Peaucellier–Lipkin Linkage Using mechanismaf
119+
With **mechanismaf**, creating the same mechanism is much simpler. You simply define a specification that describes the bars and their properties:
120+
121+
``` python
122+
#!/usr/bin/env python3
123+
124+
from mechanismaf import create_linkage_from_spec
125+
import matplotlib.pyplot as plt
126+
127+
if __name__ == "__main__":
128+
spec = [
129+
["bar", (0.0, 0.0), (2, 1)],
130+
["bar", (0.0, 0.0), (2, -1)],
131+
["bar", (2, 1), (1, 0)],
132+
["bar", (1, 0), (2, -1)],
133+
["bar", (2, -1), (3, 0)],
134+
["bar", (3, 0), (2, 1)],
135+
["bar", (0, 0), (0.5, 0.0), {"style": "ground", "angle": 0}],
136+
["bar", (0.5, 0.0), (1.0, 0.0), {"angle_sweep": (-25, 25, 50)}],
137+
]
138+
139+
follow_points = [(2, 1), (2, -1), (3, 0)]
140+
mechanism = create_linkage_from_spec(spec, follow_points=follow_points)
141+
ani, fig, ax = mechanism.get_animation(cushion=1.0)
142+
ax.set_title("Peaucellier–Lipkin linkage")
143+
plt.show()
144+
```
145+
146+
### Further Examples
147+
148+
#### Create & Use Resuable Components
149+
This example will show you how to create reusable components and how to use them. Let's define a simple rectangle:
150+
151+
``` python
152+
from mechanismaf import (
153+
create_linkage_from_spec,
154+
)
155+
import matplotlib.pyplot as plt
156+
157+
spec = [
158+
["bar", (0, 0), (0, 1), {"style": "ground"}],
159+
["bar", (0, 1), (1, 1), {"angle_sweep": (20, -20, 20)}],
160+
["bar", (1, 1), (1, 0)],
161+
["bar", (1, 0), (0, 0)],
162+
]
163+
164+
mech = create_linkage_from_spec(spec)
165+
ani, fig, ax = mech.get_animation()
166+
ax.set_title("Simple rectangle")
167+
plt.show()
168+
```
169+
170+
To make this rectangle, reusable, all we need to do is to create a function that defines our bars and calls the functions to tranform the spec. Please note that you should remove the ground and angle_sweep bar and set their role later:
171+
172+
``` python
173+
from mechanismaf import (
174+
set_style_ground,
175+
set_angle_sweep,
176+
transform_spec,
177+
transform_follow_points
178+
)
179+
180+
def create_rectangle(
181+
scale=1.0,
182+
origin=(0, 0),
183+
rotation_deg=0.0,
184+
bars_to_ground=None,
185+
sweep_bars_dict=None,
186+
base_follow_points=None
187+
):
188+
# Bare-bones spec (no ground, no angle_sweep)
189+
base_spec = [
190+
["bar", (0, 0), (0, 1)],
191+
["bar", (0, 1), (1, 1)],
192+
["bar", (1, 1), (1, 0)],
193+
["bar", (1, 0), (0, 0)],
194+
]
195+
196+
# Optionally set bars as ground
197+
if bars_to_ground:
198+
set_style_ground(base_spec, bars_to_ground)
199+
200+
# Optionally set bars to angle_sweep
201+
if sweep_bars_dict:
202+
set_angle_sweep(base_spec, sweep_bars_dict)
203+
204+
# Transform the geometry:
205+
transformed_spec = transform_spec(base_spec, scale=scale, origin=origin, rotation_deg=rotation_deg)
206+
207+
# Transform follow points (if provided)
208+
if base_follow_points:
209+
transformed_follow = transform_follow_points(base_follow_points, scale=scale,
210+
rotation_deg=rotation_deg, origin=origin)
211+
else:
212+
transformed_follow = None
213+
214+
return transformed_spec, transformed_follow
215+
```
216+
217+
We can use this now by adding this snippet that actually creates the spec:
218+
219+
``` python
220+
from mechanismaf import (
221+
create_linkage_from_spec,
222+
add_angle_joints_texts
223+
)
224+
import matplotlib.pyplot as plt
225+
226+
bars_ground1 = [((0.0, 0.0), (0.0, 1.0))]
227+
bars_sweep_dict = {
228+
((0.0, 1.0), (1, 1)): (-30, 30, 30),
229+
}
230+
base_follow_points1 = [(1, 1)]
231+
232+
rectangle, follow_points1 = create_rectangle(
233+
scale=2.0,
234+
rotation_deg=60,
235+
bars_to_ground=bars_ground1,
236+
sweep_bars_dict=bars_sweep_dict,
237+
base_follow_points=base_follow_points1
238+
)
239+
240+
mech = create_linkage_from_spec(rectangle, follow_points=follow_points1)
241+
ani, fig, ax = mech.get_animation()
242+
ax.set_title("Reusable rectangle")
243+
add_angle_joints_texts(mech, ani, ax)
244+
ani
245+
plt.show()
246+
```
247+
248+
This code now calls the function to create the rectangle, scales it, rotates it etc. We can also create a second rectangle on the other side as well:
249+
250+
``` python
251+
from mechanismaf import (
252+
combine_specs,
253+
create_linkage_from_spec,
254+
add_angle_joints_texts
255+
)
256+
import matplotlib.pyplot as plt
257+
258+
bars_ground1 = [((0.0, 0.0), (0.0, 1.0))]
259+
bars_sweep_dict = {
260+
((0.0, 1.0), (1, 1)): (-30, 30, 30),
261+
}
262+
base_follow_points1 = [(1, 1)]
263+
264+
rectangle, follow_points1 = create_rectangle(
265+
scale=2.0,
266+
rotation_deg=60,
267+
bars_to_ground=bars_ground1,
268+
sweep_bars_dict=bars_sweep_dict,
269+
base_follow_points=base_follow_points1
270+
)
271+
272+
rectangle2, follow_points2 = create_rectangle(
273+
scale=2.0,
274+
rotation_deg=-120,
275+
bars_to_ground=bars_ground1,
276+
sweep_bars_dict=bars_sweep_dict,
277+
base_follow_points=base_follow_points1
278+
)
279+
280+
spec=combine_specs(rectangle, rectangle2)
281+
follow_points=follow_points1 + follow_points2
282+
mech = create_linkage_from_spec(spec, follow_points=follow_points)
283+
ani, fig, ax = mech.get_animation()
284+
ax.set_title("Reusable contra-parallelogram")
285+
add_angle_joints_texts(mech, ani, ax)
286+
ani
287+
plt.show()
288+
```
289+
290+
We now use the `combine_spec` function to combine the two rectangles to one spec to pass it to **mechanismaf**.
291+
292+
### What You Don't Need to Do with mechanismaf
293+
When using **mechanismaf**:
294+
295+
- **No Manual Joint/Vector Creation:** You do not need to create joints and vectors manually.
296+
- **No Loop Equations:** There is no need to write your own loop equations; the library automatically detects loops and sets up the equations.
297+
- **No Initial Guess Management:** The mechanism’s unknown angles and sweep parameters are automatically handled.
298+
- **Enhanced Transformations:** Functions for transforming and combining specifications allow you to work at a higher level of abstraction.
299+
300+
Extra Features
301+
302+
- **Reusable Components:** Easily transform and merge specifications using functions like transform_spec and combine_specs.
303+
- **Animation Annotations:** Automatically inject angle and joint annotations into your mechanism animations via add_angle_joints_texts.
304+
- **Parameter Flexibility:** Quickly set or update bar properties (such as ground style or sweep ranges) without delving into the underlying mechanism details.
305+
306+
## Additional Information
307+
For more details, visit the [PyPI project page](https://pypi.org/project/mechanismaf/).
308+
309+
**mechanismaf** was developed to enable researchers, educators, and students to experiment with and animate linkage mechanisms with minimal boilerplate code. By wrapping the complexities of the underlying mechanism library, it lets you focus on design and analysis rather than implementation details. **mechanismaf** has been created as part of the HCI project seminar at HPI.
310+
311+
## License
312+
**mechanismaf** is licensed under the **MIT** license.
313+
314+
## Contributing
315+
Contributions, feature requests, and bug reports are welcome! Please open an issue or submit a pull request on the GitHub repository.
316+
317+
## Acknowledgements
318+
Special thanks to the developers of the underlying mechanism library and to the HPI community for their support.
319+

0 commit comments

Comments
 (0)