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