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.
29 ##needs ..Shapes..Graphics / elementary
30 ##needs ..Shapes..Graphics3D / elementary
31 ##needs ..Shapes..Graphics3D / metapostarrow
34 ##lookin ..Shapes..Geometry3D
39 cylinderLid: \ radius height sides smallRadius →
40 [shift (0cm,0cm,height)] [] ( Graphics3D..newGroup << [Graphics3D..facet [immerse [Graphics..polyDisc radius sides]]]
41 << [Graphics..stroke [immerse [Graphics..polyDisc radius sides]]]
42 << Traits..@nonstroking:[Traits..gray 0.3] | [Graphics3D..facet [immerse [Graphics..polyDisc smallRadius sides]] tiebreaker:1bp]
45 |** First object to be defined is a joint.
48 jointThickness: 0.5u |** Take this times two to obtain the total height.
50 Traits..@nonstroking:[Traits..gray 0.8]
51 & Traits..@width:0.01u |** used to fill in object edges
52 & Traits..@stroking:[Traits..gray 0.8] |** used to fill in object edges
53 & @reflections:0.3*[phong 25] + 0.7*[phong 0.5]
54 & @facetresolution:0.5u
55 & @shadeorder:'0 |** use '2 when targeting Adobe Reader or other viewers that can deal with gradient fills.
60 joint: jointSurface | ( Graphics3D..newGroup << [shift (0m,0m,~jointThickness)] [] [Graphics3D..cylinderWall jointRadius 2*jointThickness jointSides]
61 << [cylinderLid jointRadius ~jointThickness jointSides 0.3*jointRadius]
62 << [cylinderLid jointRadius jointThickness jointSides 0.3*jointRadius]
65 jointYSeparation: 2*jointThickness + 0.1u
68 armPath: \ tfStart tfEnd →
70 pStart: tfStart [] (0u,0u,0u)
71 pEnd: tfEnd [] (0u,0u,0u)
72 hr: 0.4 * [Numeric..Math..abs pEnd - pStart]
74 pStart > [tfStart [Geometry3D..rotate (0,0,1) ~armTwist] [] (hr,0u,0u)]
76 [tfEnd [Geometry3D..rotate (0,0,1) armTwist] [] (~hr,0u,0u)] < pEnd
79 edgyCylinder: \ pth0 pth1 →
80 [if [Geometry..duration pth0] ≠ [Geometry..duration pth1]
81 [error `edgyCylinder: Paths don't have the same duration.´]
83 •res: Graphics3D..newGroup
86 [[Data..range '0 [Geometry..duration pth0]-'1].foldl
90 ( [Graphics3D..facet [pth0 e*1].p--[pth0 (e+'1)*1].p--[pth1 (e+'1)*1].p--cycle] )
92 ( [Graphics3D..facet [pth0 e*1].p--[pth1 (e+'1)*1].p--[pth1 e*1].p--cycle] )
96 [[Data..range '0 [Geometry..duration pth0]].foldl
98 p & [Graphics..stroke [pth0 e*1].p--[pth1 e*1].p]
104 paintArm: \ pth width height steps →
106 lStep: [Numeric..Math..abs pth] / steps
108 [[Data..range '0 steps-'1].foldl
116 dx: sl.N * 0.5 * width
117 dy: sl.B * 0.5 * height
118 [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
123 sl: [pth (e*1+0.999)*lStep]
124 dx: sl.N * 0.5 * width
125 dy: sl.B * 0.5 * height
126 [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
129 [edgyCylinder pth0 pth1]
134 myArrowHead: [Graphics3D..MetaPostArrow ahLength:8bp ...]
138 •res: Graphics3D..newGroup
146 •res << [Graphics..Tag..tag 'x x]
147 << [Graphics..Tag..tag 'y y]
148 << [Graphics..Tag..tag 'z z]
150 Traits..@stroking:Traits..RGB..BLUE
151 & Traits..@nonstroking:Traits..RGB..BLUE
154 •res << [Graphics..stroke (0m,0m,0m)--x head:[myArrowHead (0,~1,0) ...]]
155 •res << [Graphics..stroke (0m,0m,0m)--y head:[myArrowHead (0,0,1) ...]]
156 •res << [Graphics..stroke (0m,0m,0m)--z head:[myArrowHead (0,~1,0) ...]]
162 /** The arguments <d1> and <d2> will be normalized, so they can be either float triples or coordinates to be interpreted
163 ** as relative to to <p>. <r> is just the radius, measured along <d1> and <d2>.
164 ** Although <p> only shifts the result, it is taken as an argument as a convenience for the caller.
166 rightAnglePath: \ p d1 d2 r →
168 p1: [normalized d1]*r
169 p2: [normalized d2]*r
170 [shift p] [] ( p1--(p1+p2)--p2 )
172 myRightAnglePath: [rightAnglePath r:0.5u ...]
173 myDash: Traits..@dash:[Traits..dashpattern 0.1u 0.05u]
175 |** The Denavit-Hartenberg parameters seen in the figure are set here.
176 |** The index $i-1$ is replaced by 0, $i$ by 1, and so forth.
179 d1: 2*jointThickness + 0.7u
183 d2: 2*jointThickness + 0.1u
184 theta2: 0° |** Never actually seen.
186 /** Setup frames according to figure. The Denavit-Hartenberg parameters do not directly
187 ** correspond to the pieces of machinery that shall be drawn, but almost. What they do not give
188 ** is the location of the upstream half of the joint, and since the rotation in the joint
189 ** applies only to the downstream half of the joint, it is more natural to define the frame of the
190 ** downstream half in terms of the upstream half, than vice versa. The tf_d transforms define the
191 ** frames of the downstream half of the joints, that is, the frames that the parameterization cares
192 ** about. The tf_u transforms define the frames of the upstream half of the joints, and are only
193 ** needed to draw the machinery (besides being used to define the downstream frames).
194 ** The difference between adjacent tf_u and tf_d is thus only a shift in the z-direction.
196 tf0d: [Geometry3D..rotate (1,0,0) ~75°] * [Geometry3D..rotate (0,0,1) ~20°]
197 tf1u: tf0d * [shift (a0,0u,0u)] * [Geometry3D..rotate (1,0,0) alpha0] * [shift (0u,0u,d1-jointYSeparation)]
198 tf1d: tf1u * [shift (0u,0u,jointYSeparation)] * [Geometry3D..rotate (0,0,1) theta1]
199 tf2u: tf1d * [shift (a1,0u,0u)] * [Geometry3D..rotate (1,0,0) alpha1] * [shift (0u,0u,d2-jointYSeparation)]
200 tf2d: tf2u * [shift (0u,0u,jointYSeparation)] * [Geometry3D..rotate (0,0,1) theta2]
202 frame0: tf0d [] frameArrows
203 frame1: tf1d [] frameArrows
205 |** This is the part of the picture where the machinery is drawn.
206 •zbuf: Graphics3D..newZSorter
207 •zbuf << [shift (0cm,0cm,10cm)] [] [specular_light [Traits..gray 0.9]]
208 •zbuf << [ambient_light [Traits..gray 0.3]]
210 |** Annotations to be placed on top of the z-buffer goes here.
211 •zbufAnnot: Graphics3D..newGroup
213 |** This is the part of the picture where parameters are shown.
214 •pbuf: Graphics3D..newGroup
216 •zbuf << tf0d [] joint
217 •zbuf << tf1u [] joint
218 •zbufAnnot << Traits..@nonstroking:Traits..BW..WHITE | tf1u [] [facing [Layout..center [Graphics..TeX `\small Link $i$´] (0,2)]]
219 •zbuf << tf1d [] joint
220 •zbuf << tf2u [] joint
221 •zbufAnnot << Traits..@nonstroking:Traits..BW..WHITE | tf2u [] [facing [Layout..center [Graphics..TeX `\small Link $i+1$´] (0,1.2)]]
223 •zbuf << jointSurface | [paintArm [armPath tf0d tf1u] 0.5*jointRadius 0.2*jointRadius '10]
224 •zbuf << jointSurface | [paintArm [armPath tf1d tf2u] 0.5*jointRadius 0.2*jointRadius '10]
226 /** The axes defined here turn out to be very useful, since they provide
227 ** a compact way to refer to certain directions in the picture.
229 axis0: tf0d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
230 axis1: tf1d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
231 axis2: tf2d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
234 •zbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis0]
236 •zbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis1]
238 •zbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis2]
240 •pbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis0]
242 •pbuf << [shift [Graphics..Tag..find frame0 'x]] [] [facing [Layout..center_wlm [Graphics..TeX `$x_{i-1}$´] (0,~1)]]
243 •pbuf << [shift [Graphics..Tag..find frame0 'y]] [] [facing [Layout..center_wlm [Graphics..TeX `$y_{i-1}$´] (0,~1)]]
244 •pbuf << [shift [Graphics..Tag..find frame0 'z]] [] [facing [Layout..center_wlm [Graphics..TeX `$z_{i-1}$´] (1,0)]]
245 •pbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis1]
247 •pbuf << [shift [Graphics..Tag..find frame1 'x]] [] [facing [Layout..center_wlm [Graphics..TeX `$x_{i}$´] (0,~1)]]
248 •pbuf << [shift [Graphics..Tag..find frame1 'y]] [] [facing [Layout..center_wlm [Graphics..TeX `$y_{i}$´] (1,0)]]
249 •pbuf << [shift [Graphics..Tag..find frame1 'z]] [] [facing [Layout..center_wlm [Graphics..TeX `$z_{i}$´] (1,0)]]
250 •pbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis2]
251 Traits..@width: 0.5bp
257 |** This is the distance $a_{i-1}$ along with angle marks.
258 pth: tf0d [] ( (0u,0u,0u)--(a0,0u,0u) )
259 •pbuf << stroke [] pth
260 •pbuf << [shift [pth 0.5*[Numeric..Math..abs pth]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$a_{i-1}$´] (0,1)]]
261 •pbuf << stroke [] [myRightAnglePath [tf0d (0u,0u,0u)] [axis0 0].T [pth 0].T]
262 •pbuf << stroke [] [myRightAnglePath [tf0d (a0,0u,0u)] [axis1 0].T [pth 1].rT]
265 |** This is the distance $a_{i}$ along with angle marks.
266 pth: tf1d [] ( (0u,0u,0u)--(a1,0u,0u) )
267 •pbuf << stroke [] pth
268 •pbuf << [shift [pth 0.5*[Numeric..Math..abs pth]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$a_{i}$´] (0,1)]]
269 •pbuf << stroke [] [myRightAnglePath [tf1d (0u,0u,0u)] [axis1 0].T [pth 0].T]
270 •pbuf << stroke [] [myRightAnglePath [tf1d (a1,0u,0u)] [axis2 0].T [pth 1].rT]
272 |** This is the angle $\theta_{i}$ along helpers and angle marks.
273 helper1: [shift [tf0d (a0,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[pth 0].T) )
274 helper2: tf0d [] ( (a0,0u,0u)--(a0+angleR+angleExtra,0u,0u) )
275 |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
276 /** In the choice of using tf1d or tf1u, I chose tf1d, which makes the positioning rather natural,
277 ** although the angles do not really correspond to the figure.
279 arc: tf1d*[shift (0u,0u,~d1)] [] [immerse (angleR*[dir ~theta1])>(1%c^90°-theta1)--(1%c^~90°)<(angleR,0)]
280 •pbuf << myDash | stroke [] helper1
281 •pbuf << myDash | stroke [] helper2
282 •pbuf << [Graphics..stroke arc head:[Graphics3D..MetaPostArrow (0,0,1) ahLength:5bp ...]]
283 •pbuf << [shift [arc 0.5*[Numeric..Math..abs arc]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$\theta_{i}$´] (~1,0)]]
286 |** This is the angle $\alpha_{i-1}$ along helpers and angle marks.
287 helper: [shift [tf0d (0u,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[axis1 0].T) )
288 |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
289 arc: tf0d*[Geometry3D..rotate (0,0,1) 90°]*[Geometry3D..rotate (1,0,0) 90°] [] [immerse (0,~angleR)>(1%c^0°)--(1%c^180°+alpha0)<(angleR*[dir ~90°+alpha0])]
290 •pbuf << myDash | stroke [] helper
291 •pbuf << [Graphics..stroke arc head:[Graphics3D..MetaPostArrow [axis1 0].T ahLength:5bp ...]]
292 •pbuf << [shift [arc 0.5*[Numeric..Math..abs arc]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$\alpha_{i-1}$´] (~1,1)]]
295 •pbuf << [shift 0.5 * ([tf0d (a0,0u,0u)] + [tf1d (0u,0u,0u)])] [] [facing [Layout..center_wlm [Graphics..TeX `$d_{i}$´] (~1,0)]]
298 zbufAnnot: freeze •zbufAnnot
301 |** Finally, the two buffers are drawn. The parameters are moved down a bit to avoid cluttering.
305 IO..•page << [view zbuf]
307 IO..•page << [shift (0,~5u)] [] [view pbuf]