1 /** This file is part of Shapes.
3 ** Shapes is free software: you can redistribute it and/or modify
4 ** it under the terms of the GNU General Public License as published by
5 ** the Free Software Foundation, either version 3 of the License, or
8 ** Shapes is distributed in the hope that it will be useful,
9 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
10 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 ** GNU General Public License for more details.
13 ** You should have received a copy of the GNU General Public License
14 ** along with Shapes. If not, see <http://www.gnu.org/licenses/>.
16 ** Copyright 2008, 2010, 2014 Henrik Tidefelt
19 /** This example is included to compare Shapes with Sketch.
20 ** Kjell Magne Fauske draw a nice robot arm using Sketch, and I want to see
21 ** what the corresponding Shapes source could look like (if possible to reproduce at all).
22 ** Many thanks to Kjell Magne for letting me use his example.
24 ** Unfortunately, I created this example at a time when I was not familiar with the
25 ** Denavit-Hartenberg convention, so I am afraid that the source is not fully
26 ** utilizing the beauty of the convention, even though it produces the correct picture.
37 cylinderLid: \ radius height sides smallRadius →
38 [shift (0cm,0cm,height)] [] ( newGroup3D << [facet [immerse [polyDisc radius sides]]]
39 << [stroke [immerse [polyDisc radius sides]]]
40 << @nonstroking:[gray 0.3] | [facet [immerse [polyDisc smallRadius sides]] tiebreaker:1bp]
43 |** First object to be defined is a joint.
46 jointThickness: 0.5u |** Take this times two to obtain the total height.
48 @nonstroking:[gray 0.8]
49 & @width:0.01u |** used to fill in object edges
50 & @stroking:[gray 0.8] |** used to fill in object edges
51 & @reflections:0.3*[phong 25] + 0.7*[phong 0.5]
52 & @facetresolution:0.5u
53 & @shadeorder:'0 |** use '2 when targeting Adobe Reader or other viewers that can deal with gradient fills.
58 joint: jointSurface | ( newGroup3D << [shift (0m,0m,~jointThickness)] [] [cylinderWall jointRadius 2*jointThickness jointSides]
59 << [cylinderLid jointRadius ~jointThickness jointSides 0.3*jointRadius]
60 << [cylinderLid jointRadius jointThickness jointSides 0.3*jointRadius]
63 jointYSeparation: 2*jointThickness + 0.1u
66 armPath: \ tfStart tfEnd →
68 pStart: tfStart [] (0u,0u,0u)
69 pEnd: tfEnd [] (0u,0u,0u)
70 hr: 0.4 * [abs pEnd - pStart]
72 pStart > [tfStart [rotate3D (0,0,1) ~armTwist] [] (hr,0u,0u)]
74 [tfEnd [rotate3D (0,0,1) armTwist] [] (~hr,0u,0u)] < pEnd
77 edgyCylinder: \ pth0 pth1 →
78 [if [duration pth0] ≠ [duration pth1]
79 [error `edgyCylinder: Paths don't have the same duration.´]
84 [[range '0 [duration pth0]-'1].foldl
88 ( [facet [pth0 e*1].p--[pth0 (e+'1)*1].p--[pth1 (e+'1)*1].p--cycle] )
90 ( [facet [pth0 e*1].p--[pth1 (e+'1)*1].p--[pth1 e*1].p--cycle] )
94 [[range '0 [duration pth0]].foldl
96 p & [stroke [pth0 e*1].p--[pth1 e*1].p]
102 paintArm: \ pth width height steps →
104 lStep: [abs pth] / steps
106 [[range '0 steps-'1].foldl
114 dx: sl.N * 0.5 * width
115 dy: sl.B * 0.5 * height
116 [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
121 sl: [pth (e*1+0.999)*lStep]
122 dx: sl.N * 0.5 * width
123 dy: sl.B * 0.5 * height
124 [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
127 [edgyCylinder pth0 pth1]
132 myArrowHead: [MetaPostArrow3D ahLength:8bp ...]
149 & @nonstroking:RGB_BLUE
152 •res << [stroke (0m,0m,0m)--x head:[myArrowHead (0,~1,0) ...]]
153 •res << [stroke (0m,0m,0m)--y head:[myArrowHead (0,0,1) ...]]
154 •res << [stroke (0m,0m,0m)--z head:[myArrowHead (0,~1,0) ...]]
160 /** The arguments <d1> and <d2> will be normalized, so they can be either float triples or coordinates to be interpreted
161 ** as relative to to <p>. <r> is just the radius, measured along <d1> and <d2>.
162 ** Although <p> only shifts the result, it is taken as an argument as a convenience for the caller.
164 rightAnglePath: \ p d1 d2 r →
166 p1: [normalized d1]*r
167 p2: [normalized d2]*r
168 [shift p] [] ( p1--(p1+p2)--p2 )
170 myRightAnglePath: [rightAnglePath r:0.5u ...]
171 myDash: @dash:[dashpattern 0.1u 0.05u]
173 |** The Denavit-Hartenberg parameters seen in the figure are set here.
174 |** The index $i-1$ is replaced by 0, $i$ by 1, and so forth.
177 d1: 2*jointThickness + 0.7u
181 d2: 2*jointThickness + 0.1u
182 theta2: 0° |** Never actually seen.
184 /** Setup frames according to figure. The Denavit-Hartenberg parameters do not directly
185 ** correspond to the pieces of machinery that shall be drawn, but almost. What they do not give
186 ** is the location of the upstream half of the joint, and since the rotation in the joint
187 ** applies only to the downstream half of the joint, it is more natural to define the frame of the
188 ** downstream half in terms of the upstream half, than vice versa. The tf_d transforms define the
189 ** frames of the downstream half of the joints, that is, the frames that the parameterization cares
190 ** about. The tf_u transforms define the frames of the upstream half of the joints, and are only
191 ** needed to draw the machinery (besides being used to define the downstream frames).
192 ** The difference between adjacent tf_u and tf_d is thus only a shift in the z-direction.
194 tf0d: [rotate3D (1,0,0) ~75°] * [rotate3D (0,0,1) ~20°]
195 tf1u: tf0d * [shift (a0,0u,0u)] * [rotate3D (1,0,0) alpha0] * [shift (0u,0u,d1-jointYSeparation)]
196 tf1d: tf1u * [shift (0u,0u,jointYSeparation)] * [rotate3D (0,0,1) theta1]
197 tf2u: tf1d * [shift (a1,0u,0u)] * [rotate3D (1,0,0) alpha1] * [shift (0u,0u,d2-jointYSeparation)]
198 tf2d: tf2u * [shift (0u,0u,jointYSeparation)] * [rotate3D (0,0,1) theta2]
200 frame0: tf0d [] frameArrows
201 frame1: tf1d [] frameArrows
203 |** This is the part of the picture where the machinery is drawn.
205 •zbuf << [shift (0cm,0cm,10cm)] [] [specular_light [gray 0.9]]
206 •zbuf << [ambient_light [gray 0.3]]
208 |** Annotations to be placed on top of the z-buffer goes here.
209 •zbufAnnot: newGroup3D
211 |** This is the part of the picture where parameters are shown.
214 •zbuf << tf0d [] joint
215 •zbuf << tf1u [] joint
216 •zbufAnnot << @nonstroking:GRAY_WHITE | tf1u [] [facing [center [TeX `\small Link $i$´] (0,2)]]
217 •zbuf << tf1d [] joint
218 •zbuf << tf2u [] joint
219 •zbufAnnot << @nonstroking:GRAY_WHITE | tf2u [] [facing [center [TeX `\small Link $i+1$´] (0,1.2)]]
221 •zbuf << jointSurface | [paintArm [armPath tf0d tf1u] 0.5*jointRadius 0.2*jointRadius '10]
222 •zbuf << jointSurface | [paintArm [armPath tf1d tf2u] 0.5*jointRadius 0.2*jointRadius '10]
224 /** The axes defined here turn out to be very useful, since they provide
225 ** a compact way to refer to certain directions in the picture.
227 axis0: tf0d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
228 axis1: tf1d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
229 axis2: tf2d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
232 •zbuf << @stroking:RGB_RED | [stroke axis0]
234 •zbuf << @stroking:RGB_RED | [stroke axis1]
236 •zbuf << @stroking:RGB_RED | [stroke axis2]
238 •pbuf << @stroking:RGB_RED | [stroke axis0]
240 •pbuf << [shift [find frame0 'x]] [] [facing [center_wlm [TeX `$x_{i-1}$´] (0,~1)]]
241 •pbuf << [shift [find frame0 'y]] [] [facing [center_wlm [TeX `$y_{i-1}$´] (0,~1)]]
242 •pbuf << [shift [find frame0 'z]] [] [facing [center_wlm [TeX `$z_{i-1}$´] (1,0)]]
243 •pbuf << @stroking:RGB_RED | [stroke axis1]
245 •pbuf << [shift [find frame1 'x]] [] [facing [center_wlm [TeX `$x_{i}$´] (0,~1)]]
246 •pbuf << [shift [find frame1 'y]] [] [facing [center_wlm [TeX `$y_{i}$´] (1,0)]]
247 •pbuf << [shift [find frame1 'z]] [] [facing [center_wlm [TeX `$z_{i}$´] (1,0)]]
248 •pbuf << @stroking:RGB_RED | [stroke axis2]
255 |** This is the distance $a_{i-1}$ along with angle marks.
256 pth: tf0d [] ( (0u,0u,0u)--(a0,0u,0u) )
257 •pbuf << stroke [] pth
258 •pbuf << [shift [pth 0.5*[abs pth]].p] [] [facing [center_wlm [TeX `$a_{i-1}$´] (0,1)]]
259 •pbuf << stroke [] [myRightAnglePath [tf0d (0u,0u,0u)] [axis0 0].T [pth 0].T]
260 •pbuf << stroke [] [myRightAnglePath [tf0d (a0,0u,0u)] [axis1 0].T [pth 1].rT]
263 |** This is the distance $a_{i}$ along with angle marks.
264 pth: tf1d [] ( (0u,0u,0u)--(a1,0u,0u) )
265 •pbuf << stroke [] pth
266 •pbuf << [shift [pth 0.5*[abs pth]].p] [] [facing [center_wlm [TeX `$a_{i}$´] (0,1)]]
267 •pbuf << stroke [] [myRightAnglePath [tf1d (0u,0u,0u)] [axis1 0].T [pth 0].T]
268 •pbuf << stroke [] [myRightAnglePath [tf1d (a1,0u,0u)] [axis2 0].T [pth 1].rT]
270 |** This is the angle $\theta_{i}$ along helpers and angle marks.
271 helper1: [shift [tf0d (a0,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[pth 0].T) )
272 helper2: tf0d [] ( (a0,0u,0u)--(a0+angleR+angleExtra,0u,0u) )
273 |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
274 /** In the choice of using tf1d or tf1u, I chose tf1d, which makes the positioning rather natural,
275 ** although the angles do not really correspond to the figure.
277 arc: tf1d*[shift (0u,0u,~d1)] [] [immerse (angleR*[dir ~theta1])>(1%c^90°-theta1)--(1%c^~90°)<(angleR,0)]
278 •pbuf << myDash | stroke [] helper1
279 •pbuf << myDash | stroke [] helper2
280 •pbuf << [stroke arc head:[MetaPostArrow3D (0,0,1) ahLength:5bp ...]]
281 •pbuf << [shift [arc 0.5*[abs arc]].p] [] [facing [center_wlm [TeX `$\theta_{i}$´] (~1,0)]]
284 |** This is the angle $\alpha_{i-1}$ along helpers and angle marks.
285 helper: [shift [tf0d (0u,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[axis1 0].T) )
286 |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
287 arc: tf0d*[rotate3D (0,0,1) 90°]*[rotate3D (1,0,0) 90°] [] [immerse (0,~angleR)>(1%c^0°)--(1%c^180°+alpha0)<(angleR*[dir ~90°+alpha0])]
288 •pbuf << myDash | stroke [] helper
289 •pbuf << [stroke arc head:[MetaPostArrow3D [axis1 0].T ahLength:5bp ...]]
290 •pbuf << [shift [arc 0.5*[abs arc]].p] [] [facing [center_wlm [TeX `$\alpha_{i-1}$´] (~1,1)]]
293 •pbuf << [shift 0.5 * ([tf0d (a0,0u,0u)] + [tf1d (0u,0u,0u)])] [] [facing [center_wlm [TeX `$d_{i}$´] (~1,0)]]
296 zbufAnnot: •zbufAnnot;
299 |** Finally, the two buffers are drawn. The parameters are moved down a bit to avoid cluttering.
305 •page << [shift (0,~5u)] [] [view pbuf]