1//////////////////////////////////////////////////////////////////////
  2// LibFile: masks2d.scad
  3//   This file provides 2D masking shapes that you can use with {{edge_profile()}} to mask edges.
  4//   The shapes include the simple roundover and chamfer as well as more elaborate shapes
  5//   like the cove and ogee found in furniture and architecture.  You can make the masks
  6//   as geometry or as 2D paths.
  7// Includes:
  8//   include <BOSL2/std.scad>
  9// FileGroup: Basic Modeling
 10// FileSummary: 2D masking shapes for edge profiling: including roundover, cove, teardrop, ogee.
 11// FileFootnotes: STD=Included in std.scad
 12//////////////////////////////////////////////////////////////////////
 13
 14
 15// Section: 2D Masking Shapes
 16
 17// Function&Module: mask2d_roundover()
 18// Usage: As module
 19//   mask2d_roundover(r|d=, [inset], [excess]) [ATTACHMENTS];
 20// Usage: As function
 21//   path = mask2d_roundover(r|d=, [inset], [excess]);
 22// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
 23// See Also: corner_profile(), edge_profile(), face_profile()
 24// Description:
 25//   Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for a 90° edge.
 26//   Conversely, you can use that same extruded shape to make an interior fillet between two walls at a 90º angle.
 27//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
 28//   If called as a function, this just returns a 2D path of the outline of the mask shape.
 29// Arguments:
 30//   r = Radius of the roundover.
 31//   inset = Optional bead inset size.  Default: 0
 32//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.  Default: 0.01
 33//   ---
 34//   d = Diameter of the roundover.
 35//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 36//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 37// Example(2D): 2D Roundover Mask
 38//   mask2d_roundover(r=10);
 39// Example(2D): 2D Bead Mask
 40//   mask2d_roundover(r=10,inset=2);
 41// Example: Masking by Edge Attachment
 42//   diff()
 43//   cube([50,60,70],center=true)
 44//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
 45//           mask2d_roundover(r=10, inset=2);
 46// Example: Making an interior fillet
 47//   %render() difference() {
 48//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
 49//       cube(310, anchor=BOT+LEFT);
 50//   }
 51//   xrot(90)
 52//       linear_extrude(height=30, center=true)
 53//           mask2d_roundover(r=10);
 54module mask2d_roundover(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) {
 55    path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset);
 56    attachable(anchor,spin, two_d=true, path=path) {
 57        polygon(path);
 58        children();
 59    }
 60}
 61
 62function mask2d_roundover(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) =
 63    assert(is_finite(r)||is_finite(d))
 64    assert(is_finite(excess))
 65    assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2))
 66    let(
 67        inset = is_list(inset)? inset : [inset,inset],
 68        r = get_radius(r=r,d=d,dflt=1),
 69        steps = quantup(segs(r),4)/4,
 70        step = 90/steps,
 71        path = [
 72            [r+inset.x,-excess],
 73            [-excess,-excess],
 74            [-excess, r+inset.y],
 75            for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step)
 76        ]
 77    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
 78
 79
 80// Function&Module: mask2d_cove()
 81// Usage: As module
 82//   mask2d_cove(r|d=, [inset], [excess]) [ATTACHMENTS];
 83// Usage: As function
 84//   path = mask2d_cove(r|d=, [inset], [excess]);
 85// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
 86// See Also: corner_profile(), edge_profile(), face_profile()
 87// Description:
 88//   Creates a 2D cove mask shape that is useful for extruding into a 3D mask for a 90° edge.
 89//   Conversely, you can use that same extruded shape to make an interior rounded shelf decoration between two walls at a 90º angle.
 90//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
 91//   If called as a function, this just returns a 2D path of the outline of the mask shape.
 92// Arguments:
 93//   r = Radius of the cove.
 94//   inset = Optional amount to inset code from corner.  Default: 0
 95//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.  Default: 0.01
 96//   ---
 97//   d = Diameter of the cove.
 98//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 99//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
100// Example(2D): 2D Cove Mask
101//   mask2d_cove(r=10);
102// Example(2D): 2D Inset Cove Mask
103//   mask2d_cove(r=10,inset=3);
104// Example: Masking by Edge Attachment
105//   diff()
106//   cube([50,60,70],center=true)
107//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
108//           mask2d_cove(r=10, inset=2);
109// Example: Making an interior rounded shelf
110//   %render() difference() {
111//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
112//       cube(310, anchor=BOT+LEFT);
113//   }
114//   xrot(90)
115//       linear_extrude(height=30, center=true)
116//           mask2d_cove(r=5, inset=5);
117module mask2d_cove(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) {
118    path = mask2d_cove(r=r,d=d,excess=excess,inset=inset);
119    attachable(anchor,spin, two_d=true, path=path) {
120        polygon(path);
121        children();
122    }
123}
124
125function mask2d_cove(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) =
126    assert(is_finite(r)||is_finite(d))
127    assert(is_finite(excess))
128    assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2))
129    let(
130        inset = is_list(inset)? inset : [inset,inset],
131        r = get_radius(r=r,d=d,dflt=1),
132        steps = quantup(segs(r),4)/4,
133        step = 90/steps,
134        path = [
135            [r+inset.x,-excess],
136            [-excess,-excess],
137            [-excess, r+inset.y],
138            for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step)
139        ]
140    ) reorient(anchor,spin, two_d=true, path=path, p=path);
141
142
143// Function&Module: mask2d_chamfer()
144// Usage: As Module
145//   mask2d_chamfer(edge, [angle], [inset], [excess]) [ATTACHMENTS];
146//   mask2d_chamfer(y=, [angle=], [inset=], [excess=]) [ATTACHMENTS];
147//   mask2d_chamfer(x=, [angle=], [inset=], [excess=]) [ATTACHMENTS];
148// Usage: As Function
149//   path = mask2d_chamfer(edge, [angle], [inset], [excess]);
150//   path = mask2d_chamfer(y=, [angle=], [inset=], [excess=]);
151//   path = mask2d_chamfer(x=, [angle=], [inset=], [excess=]);
152// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
153// See Also: corner_profile(), edge_profile(), face_profile()
154// Description:
155//   Creates a 2D chamfer mask shape that is useful for extruding into a 3D mask for a 90° edge.
156//   Conversely, you can use that same extruded shape to make an interior chamfer between two walls at a 90º angle.
157//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
158//   If called as a function, this just returns a 2D path of the outline of the mask shape.
159//   The edge parameter specifies the length of the chamfer's slanted edge.  Alternatively you can give x or y to
160//   specify the width or height.  Only one of x, y, or width is permitted.  
161// Arguments:
162//   edge = The length of the edge of the chamfer.
163//   angle = The angle of the chamfer edge, away from vertical.  Default: 45.
164//   inset = Optional amount to inset code from corner.  Default: 0
165//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.  Default: 0.01
166//   ---
167//   x = The width of the chamfer.
168//   y = The height of the chamfer.
169//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
170//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
171// Example(2D): 2D Chamfer Mask
172//   mask2d_chamfer(x=10);
173// Example(2D): 2D Chamfer Mask by Width.
174//   mask2d_chamfer(x=10, angle=30);
175// Example(2D): 2D Chamfer Mask by Height.
176//   mask2d_chamfer(y=10, angle=30);
177// Example(2D): 2D Inset Chamfer Mask
178//   mask2d_chamfer(x=10, inset=2);
179// Example: Masking by Edge Attachment
180//   diff()
181//   cube([50,60,70],center=true)
182//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
183//           mask2d_chamfer(x=10, inset=2);
184// Example: Making an interior chamfer
185//   %render() difference() {
186//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
187//       cube(310, anchor=BOT+LEFT);
188//   }
189//   xrot(90)
190//       linear_extrude(height=30, center=true)
191//           mask2d_chamfer(edge=10);
192module mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) {
193    path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset);
194    attachable(anchor,spin, two_d=true, path=path, extent=true) {
195        polygon(path);
196        children();
197    }
198}
199
200function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) =
201    let(dummy=one_defined([x,y,edge],["x","y","edge"]))
202    assert(is_finite(angle))
203    assert(is_finite(excess))
204    assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2))
205    let(
206        inset = is_list(inset)? inset : [inset,inset],
207        x = is_def(x)? x :
208            is_def(y)? adj_ang_to_opp(adj=y,ang=angle) :
209            hyp_ang_to_opp(hyp=edge,ang=angle),
210        y = opp_ang_to_adj(opp=x,ang=angle),
211        path = [
212            [x+inset.x, -excess],
213            [-excess, -excess],
214            [-excess, y+inset.y],
215            [inset.x, y+inset.y],
216            [x+inset.x, inset.y]
217        ]
218    ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path);
219
220
221// Function&Module: mask2d_rabbet()
222// Usage: As Module
223//   mask2d_rabbet(size, [excess]) [ATTACHMENTS];
224// Usage: As Function
225//   path = mask2d_rabbet(size, [excess]);
226// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
227// See Also: corner_profile(), edge_profile(), face_profile()
228// Description:
229//   Creates a 2D rabbet mask shape that is useful for extruding into a 3D mask for a 90° edge.
230//   Conversely, you can use that same extruded shape to make an interior shelf decoration between two walls at a 90º angle.
231//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
232//   If called as a function, this just returns a 2D path of the outline of the mask shape.
233// Arguments:
234//   size = The size of the rabbet, either as a scalar or an [X,Y] list.
235//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01
236//   ---
237//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
238//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
239// Example(2D): 2D Rabbet Mask
240//   mask2d_rabbet(size=10);
241// Example(2D): 2D Asymmetrical Rabbet Mask
242//   mask2d_rabbet(size=[5,10]);
243// Example: Masking by Edge Attachment
244//   diff()
245//   cube([50,60,70],center=true)
246//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
247//           mask2d_rabbet(size=10);
248// Example: Making an interior shelf
249//   %render() difference() {
250//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
251//       cube(310, anchor=BOT+LEFT);
252//   }
253//   xrot(90)
254//       linear_extrude(height=30, center=true)
255//           mask2d_rabbet(size=[5,10]);
256module mask2d_rabbet(size, excess=0.01, anchor=CENTER,spin=0) {
257    path = mask2d_rabbet(size=size, excess=excess);
258    attachable(anchor,spin, two_d=true, path=path, extent=false) {
259        polygon(path);
260        children();
261    }
262}
263
264function mask2d_rabbet(size, excess=0.01, anchor=CENTER,spin=0) =
265    assert(is_finite(size)||(is_vector(size)&&len(size)==2))
266    assert(is_finite(excess))
267    let(
268        size = is_list(size)? size : [size,size],
269        path = [
270            [size.x, -excess],
271            [-excess, -excess],
272            [-excess, size.y],
273            size
274        ]
275    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
276
277
278// Function&Module: mask2d_dovetail()
279// Usage: As Module
280//   mask2d_dovetail(edge, [angle], [inset], [shelf], [excess], ...) [ATTACHMENTS];
281//   mask2d_dovetail(x=, [angle=], [inset=], [shelf=], [excess=], ...) [ATTACHMENTS];
282//   mask2d_dovetail(y=, [angle=], [inset=], [shelf=], [excess=], ...) [ATTACHMENTS];
283// Usage: As Function
284//   path = mask2d_dovetail(edge, [angle], [inset], [shelf], [excess]);
285// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
286// See Also: corner_profile(), edge_profile(), face_profile()
287// Description:
288//   Creates a 2D dovetail mask shape that is useful for extruding into a 3D mask for a 90° edge.
289//   Conversely, you can use that same extruded shape to make an interior dovetail between two walls at a 90º angle.
290//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
291//   If called as a function, this just returns a 2D path of the outline of the mask shape.
292// Arguments:
293//   edge = The length of the edge of the dovetail.
294//   angle = The angle of the chamfer edge, away from vertical.  Default: 30.
295//   inset = Optional amount to inset code from corner.  Default: 0
296//   shelf = The extra height to add to the inside corner of the dovetail.  Default: 0
297//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.  Default: 0.01
298//   ---
299//   x = The width of the dovetail.
300//   y = The height of the dovetail.
301//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
302//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
303// Example(2D): 2D Dovetail Mask
304//   mask2d_dovetail(x=10);
305// Example(2D): 2D Dovetail Mask by Width.
306//   mask2d_dovetail(x=10, angle=30);
307// Example(2D): 2D Dovetail Mask by Height.
308//   mask2d_dovetail(y=10, angle=30);
309// Example(2D): 2D Inset Dovetail Mask
310//   mask2d_dovetail(x=10, inset=2);
311// Example: Masking by Edge Attachment
312//   diff()
313//   cube([50,60,70],center=true)
314//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
315//           mask2d_dovetail(x=10, inset=2);
316// Example: Making an interior dovetail
317//   %render() difference() {
318//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
319//       cube(310, anchor=BOT+LEFT);
320//   }
321//   xrot(90)
322//       linear_extrude(height=30, center=true)
323//           mask2d_dovetail(x=10);
324module mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) {
325    path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess);
326    attachable(anchor,spin, two_d=true, path=path) {
327        polygon(path);
328        children();
329    }
330}
331
332function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) =
333    assert(num_defined([x,y,edge])==1)
334    assert(is_finite(first_defined([x,y,edge])))
335    assert(is_finite(angle))
336    assert(is_finite(excess))
337    assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2))
338    let(
339        inset = is_list(inset)? inset : [inset,inset],
340        x = !is_undef(x)? x :
341            !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
342            hyp_ang_to_opp(hyp=edge,ang=angle),
343        y = opp_ang_to_adj(opp=x,ang=angle),
344        path = [
345            [inset.x,0],
346            [-excess, 0],
347            [-excess, y+inset.y+shelf],
348            inset+[x,y+shelf],
349            inset+[x,y],
350            inset
351        ]
352    ) reorient(anchor,spin, two_d=true, path=path, p=path);
353
354
355// Function&Module: mask2d_teardrop()
356// Usage: As Module
357//   mask2d_teardrop(r|d=, [angle], [excess]) [ATTACHMENTS];
358// Usage: As Function
359//   path = mask2d_teardrop(r|d=, [angle], [excess]);
360// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
361// See Also: corner_profile(), edge_profile(), face_profile()
362// Description:
363//   Creates a 2D teardrop mask shape that is useful for extruding into a 3D mask for a 90° edge.
364//   Conversely, you can use that same extruded shape to make an interior teardrop fillet between two walls at a 90º angle.
365//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
366//   If called as a function, this just returns a 2D path of the outline of the mask shape.
367//   This is particularly useful to make partially rounded bottoms, that don't need support to print.
368// Arguments:
369//   r = Radius of the rounding.
370//   angle = The maximum angle from vertical.
371//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01
372//   ---
373//   d = Diameter of the rounding.
374//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
375//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
376// Example(2D): 2D Teardrop Mask
377//   mask2d_teardrop(r=10);
378// Example(2D): Using a Custom Angle
379//   mask2d_teardrop(r=10,angle=30);
380// Example: Masking by Edge Attachment
381//   diff()
382//   cube([50,60,70],center=true)
383//       edge_profile(BOT)
384//           mask2d_teardrop(r=10, angle=40);
385// Example: Making an interior teardrop fillet
386//   %render() difference() {
387//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
388//       cube(310, anchor=BOT+LEFT);
389//   }
390//   xrot(90)
391//       linear_extrude(height=30, center=true)
392//           mask2d_teardrop(r=10);
393function mask2d_teardrop(r, angle=45, excess=0.01, d, anchor=CENTER, spin=0) =  
394    assert(is_finite(angle))
395    assert(angle>0 && angle<90)
396    assert(is_finite(excess))
397    let(
398        r = get_radius(r=r, d=d, dflt=1),
399        n = ceil(segs(r) * angle/360),
400        cp = [r,r],
401        tp = cp + polar_to_xy(r,180+angle),
402        bp = [tp.x+adj_ang_to_opp(tp.y,angle), 0],
403        step = angle/n,
404        path = [
405            bp, bp-[0,excess], [-excess,-excess], [-excess,r],
406            for (i=[0:1:n]) cp+polar_to_xy(r,180+i*step)
407        ]
408    ) reorient(anchor,spin, two_d=true, path=path, p=path);
409
410module mask2d_teardrop(r, angle=45, excess=0.01, d, anchor=CENTER, spin=0) {
411    path = mask2d_teardrop(r=r, d=d, angle=angle, excess=excess);
412    attachable(anchor,spin, two_d=true, path=path) {
413        polygon(path);
414        children();
415    }
416}
417
418// Function&Module: mask2d_ogee()
419// Usage: As Module
420//   mask2d_ogee(pattern, [excess], ...) [ATTAHCMENTS];
421// Usage: As Function
422//   path = mask2d_ogee(pattern, [excess], ...);
423// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
424// See Also: corner_profile(), edge_profile(), face_profile()
425//
426// Description:
427//   Creates a 2D Ogee mask shape that is useful for extruding into a 3D mask for a 90° edge.
428//   Conversely, you can use that same extruded shape to make an interior ogee decoration between two walls at a 90º angle.
429//   As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
430//   Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern.
431//   Patterns are given as TYPE, VALUE pairs.  ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`.  See Patterns below.
432//   If called as a function, this just returns a 2D path of the outline of the mask shape.
433//   .
434//   ### Patterns
435//   .
436//   Type     | Argument  | Description
437//   -------- | --------- | ----------------
438//   "step"   | [x,y]     | Makes a line to a point `x` right and `y` down.
439//   "xstep"  | dist      | Makes a `dist` length line towards X+.
440//   "ystep"  | dist      | Makes a `dist` length line towards Y-.
441//   "round"  | radius    | Makes an arc that will mask a roundover.
442//   "fillet" | radius    | Makes an arc that will mask a fillet.
443//
444// Arguments:
445//   pattern = A list of pattern pieces to describe the Ogee.
446//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01
447//   ---
448//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
449//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
450//
451// Example(2D): 2D Ogee Mask
452//   mask2d_ogee([
453//       "xstep",1,  "ystep",1,  // Starting shoulder.
454//       "fillet",5, "round",5,  // S-curve.
455//       "ystep",1,  "xstep",1   // Ending shoulder.
456//   ]);
457// Example: Masking by Edge Attachment
458//   diff()
459//   cube([50,60,70],center=true)
460//       edge_profile(TOP)
461//           mask2d_ogee([
462//               "xstep",1,  "ystep",1,  // Starting shoulder.
463//               "fillet",5, "round",5,  // S-curve.
464//               "ystep",1,  "xstep",1   // Ending shoulder.
465//           ]);
466// Example: Making an interior ogee
467//   %render() difference() {
468//       move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
469//       cube(310, anchor=BOT+LEFT);
470//   }
471//   xrot(90)
472//       linear_extrude(height=30, center=true)
473//           mask2d_ogee([
474//               "xstep", 1, "round",5,
475//               "ystep",1, "fillet",5,
476//               "xstep", 1, "ystep", 1,
477//           ]);
478module mask2d_ogee(pattern, excess=0.01, anchor=CENTER,spin=0) {
479    path = mask2d_ogee(pattern, excess=excess);
480    attachable(anchor,spin, two_d=true, path=path) {
481        polygon(path);
482        children();
483    }
484}
485
486function mask2d_ogee(pattern, excess=0.01, anchor=CENTER, spin=0) =
487    assert(is_list(pattern))
488    assert(len(pattern)>0)
489    assert(len(pattern)%2==0,"pattern must be a list of TYPE, VAL pairs.")
490    assert(all([for (i = idx(pattern,step=2)) in_list(pattern[i],["step","xstep","ystep","round","fillet"])]))
491    let(
492        x = concat([0], cumsum([
493            for (i=idx(pattern,step=2)) let(
494                type = pattern[i],
495                val = pattern[i+1]
496            ) (
497                type=="step"?   val.x :
498                type=="xstep"?  val :
499                type=="round"?  val :
500                type=="fillet"? val :
501                0
502            )
503        ])),
504        y = concat([0], cumsum([
505            for (i=idx(pattern,step=2)) let(
506                type = pattern[i],
507                val = pattern[i+1]
508            ) (
509                type=="step"?   val.y :
510                type=="ystep"?  val :
511                type=="round"?  val :
512                type=="fillet"? val :
513                0
514            )
515        ])),
516        tot_x = last(x),
517        tot_y = last(y),
518        data = [
519            for (i=idx(pattern,step=2)) let(
520                type = pattern[i],
521                val = pattern[i+1],
522                pt = [x[i/2], tot_y-y[i/2]] + (
523                    type=="step"?   [val.x,-val.y] :
524                    type=="xstep"?  [val,0] :
525                    type=="ystep"?  [0,-val] :
526                    type=="round"?  [val,0] :
527                    type=="fillet"? [0,-val] :
528                    [0,0]
529                )
530            ) [type, val, pt]
531        ],
532        path = [
533            [tot_x,-excess],
534            [-excess,-excess],
535            [-excess,tot_y],
536            for (pat = data) each
537                pat[0]=="step"?  [pat[2]] :
538                pat[0]=="xstep"? [pat[2]] :
539                pat[0]=="ystep"? [pat[2]] :
540                let(
541                    r = pat[1],
542                    steps = segs(abs(r)),
543                    step = 90/steps
544                ) [
545                    for (i=[0:1:steps]) let(
546                        a = pat[0]=="round"? (180+i*step) : (90-i*step)
547                    ) pat[2] + abs(r)*[cos(a),sin(a)]
548                ]
549        ],
550        path2 = deduplicate(path)
551    ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
552
553
554
555// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap