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