-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathpathbuilder.scad
More file actions
1168 lines (1057 loc) · 60.9 KB
/
Copy pathpathbuilder.scad
File metadata and controls
1168 lines (1057 loc) · 60.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// pathbuilder
//
// This libray is designed to create complex 2D shapes
// It uses commands similar to the svg path syntax
//
// examples
//
// in code:
// linear_extrude(1) m(0,0) chamfer(10) h(20) fillet(2) v(10) segment(10, 10, 10) h(10) fillet(2) v(-10) fillet(2) l(35,20) fillet(2) L(40, 50) fillet(2) v(-10) h(-10) Segment(0,10,-30) draw();
//
// or SVG path syntax:
//
//
// The above statement creates a 2D right angle arrow that is then extruded
// Lowercase commands are drawn relative to the last drawn point.
// Uppercase commands are drawn as absolute coordinates.
//
// Latest version here: https://github.com/dinther/pathbuilder
//
// By: Paul van Dinther
// Global values
// $pb_spline
// This value controls how many line segments are created for all types of spline curves.
$pb_spline = 10;
// function svgPoints(s)
//
// Processes a SVG path string and returns a 2D point list. This allows user point manipulation before the points are used.
// path (list) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
// return (list) List of lists of 2D points that each outline the intended SVG path. Can be directly consumed by the polygon command.
function svgPoints(path, z=undef) = let(
pointlists = pb_postProcessPathLists(pb_processCommands(pb_tokenizeSvgPath(path)))
) z==undef? pointlists : [for (pts=pointlists) pb_translate_pts(pts,[0,0,z])];
// module svgShape(s)
//
// Processes a SVG path string and returns a 2D polygon.
// path (list) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
// return (polygon) polygon can be further handled by any openSCAD command.
module svgShape(path="", _i=-2, _p=undef, _first_CW=undef, $pb_spline=10){
_p = _p==undef? svgPoints(path) : _p;
l = len(_p);
_first_CW = _i<-1? pb_is_CW(_p[0]) : _first_CW;
_i = _i==-2? l-1 : _i;
if (l>0){
if (l==1) polygon(_p[0]);
if (_i>=0){
CW2 = pb_is_CW(_p[_i]);
if (CW2 == _first_CW) union(){
polygon(_p[_i]);
svgShape(path, _i-1, _p, _first_CW);
} else difference(){
svgShape(path, _i-1, _p, _first_CW);
polygon(_p[_i]);
}
}
}
}
// function svgTweenPath(s)
//
// Processes two similar paths where the command sequence is identical but the parameters are different.
// The return value is a path string that is either Path1, Path2 or somewhere in between depending on the factor value
// which must be somewhere between 0 and 1. This function is great to create in between splines.
// path1 (list) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
// path2 (list) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
// factor (number) Number between 0 and 1 determining the path values of the return path.
// return (list) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
function svgTweenPath(path1="", path2="", factor=0) = let(
f = max(0, min(1, factor)),
commandList1 = pb_tokenizeSvgPath(path1),
commandList2 = pb_tokenizeSvgPath(path2),
a = assert(len(commandList1) == len(commandList2), "path1 and path2 must have an equal number of commands."),
commandList = [for (i=[0:1:len(commandList1)-1]) let(
a = assert(commandList1[i][0] == commandList2[i][0], "Command mismatch. The command sequence of path1 and path2 must be identical.")
) [commandList1[i][0],
len(commandList1[i][1])==0? [] : [for(j=[0:len(commandList1[i][1])-1]) let(
v1 = commandList1[i][1][j],
v2 = commandList2[i][1][j]
) v1 + ((v2-v1) * f)]]]
) pb_commandListToPath(commandList);
// Helper functions:
// function pb_angle(p1,p2)
//
// Calculates the angle of line between vector v1 and v2
// v1 (list) 2D vector with x and y value.
// v1 (list) 2D vector with x and y value.
// return (number) Angle between the two vectors in degrees.
function pb_angle(v1, v2) = let(a = atan2(v2[0] - v1[0], v2[1] - v1[1])) a;
// function pb_angle(p1,p2)
//
// Calculates the angle of the last line segment in the points list pts.
// Returns 0 when the point list only has one value in it.
// pts (list) List of 2D points.
// angle (number) Last known angle. Returned in case pts is empty
// return (number) Angle of the last line segment in the list.
function pb_calcExitAngle(pts=[],angle) = let(
l = is_list(pts)? len(pts) : -1,
p1=l>1? pts[l-2] : [0,0],
p2=l>0? pts[l-1] : undef) p2==undef || p1==p2? angle==undef? 0 : angle : pb_angle(p1, p2);
// pb_reflectPntOn(p1, pc)
//
// Rotates p1 180 degrees around pc. All points are absolute coordinates.
// p1 (list) List of two numbers representing the 2D point to be reflected.
// pc (list) (default=[0,0]) List of two numbers representing the 2D center point around which p1 is rotated
// return (list) List of two numbers representing the new rotated point.
function pb_reflectPntOn(p1=[], pc=[0,0]) = pc + (p1-pc)*-1;
// pb_translate_pts(pts, translate)
//
// translates points in point list. A 2D translate turns 3D points into 2D points by dropping the last value.
// A 3D translate on a 2D point will use the third translate value as the third value and turn the point into 3D.
// pts (list) List of 2D or 3D points.
// translate (list) List of three or two numbers representing the 3D vector to translate the points with
// return (list) List of translated 3D points.
function pb_translate_pts(pts, translate=[0,0,0])= translate[2]!=undef? [for(p=pts)[p[0]+translate[0],p[1]? p[1]+translate[1] : translate[1], p[2]? p[2]+translate[2] : translate[2]]] : [for(p=pts)[p[0]+translate[0],p[1]? p[1]+translate[1] : translate[1]]];
// pb_scale_pts(pts, translate)
//
// scales points in point list. A 2D scale turns 3D points into 2D points by dropping the last value.abs
// A 3D scale on a 2D point will use the third scale value as the third value and turn the point into 3D.
// pts (list) List of 2D or 3D points.
// scale (list) List of two or three scale values for each axis x,y, and z
// return (list) List of translated 2D or 3D points.
function pb_scale_pts(pts, scale=[1,1,1])= scale[2]!=undef? [for(p=pts)[p[0]*scale[0],p[1]? p[1]*scale[1] : scale[1], p[2]!=undef? p[2]*scale[2] : scale[2]]] : [for(p=pts)[p[0]*scale[0],p[1]? p[1]*scale[1] : scale[1]]];
// function pb_segmentsPerCircle(r)
//
// Calculates the number of segments used per circle (per 360 deg) based on circle radius and openSCAD $fn, $fa and $fs settings.
// r (number) The circle radius for which the number of segments needs to be calculated.
// return (number) The number of segments that would be used by openSCAD if it would draw the circle.
function pb_segmentsPerCircle(r=1) = $fn>0?($fn>=3?$fn:3):ceil(max(min(360/$fa,abs(r)*2*PI/$fs),5));
// function pb_last(pts)
//
// Returns last item from the list. If the list is empty it will return [0,0]
// pts (list) List of zero or more 2D points.
// return (list) List of two numbers which is a 2D point.
function pb_last(pts) = let(l=is_list(pts)? len(pts) : 0) l==0? [0,0] : pts[l-1];
// function pb_subList(list, start, end)
//
// Returns subset of list defined by start and end which are indexes to the list. Indexes are zero based.
// When indexes are negative they refer from the end of the list rather than the start.
// list (list) List of arbitrary data.
// start (number) (default = 0) index from the start where items need to be included in the returned list. Negative numbers represent an index from the end of the list.
// end (number) (default = list length - 1) index from the start after which items no longer need to be included. Negative numbers represent an index from the end of the list.
// return (list) sub list of items from the input list. Items can appear reversed when end references an index that is less than the start index.
//
// examples:
// pb_subList([0,1,2,3,4,5,6]) => [0,1,2,3,4,5,6] // copies the list
// pb_subList([0,1,2,3,4,5,6],3) => [3,4,5,6] // starts at index 3 up to the end
// pb_subList([0,1,2,3,4,5,6],3, 4) => [3,4] // starts at index 3 up to end index 4
// pb_subList([0,1,2,3,4,5,6],3, 2) => [3,2] // starts at index 3 down to end index 2
// pb_subList([0,1,2,3,4,5,6],-2) => [5,6] // starts 2 before the end of the list up to the end
// pb_subList([0,1,2,3,4,5,6],-3, -4) => [4,3] // starts 3 before the end of the list down to 4 before the end of the list
// pb_subList([0,1,2,3,4,5,6],-1, 0) => [6,5,4,3,2,1,0] // Reverse list
function pb_subList(list, start=0, end) = let(l = len(list), s = start<0? l+start: start, e = end==undef? l-1 : end<0? l+end: end, sp=e<s? -1 : 1) [for(i=[s:sp:e]) list[i]];
// function pb_valuesToString(s)
//
// Converts list into a continuous string adding the seperator in between each item.
// list (list) List of values.
// separator (string) Separator string that will be inserted between each item in the list.
// return (string) String containing all items from the list.
function pb_valuesToString(list, seperator=",", _i=0, _s="") = _i>len(list)-1? _s : pb_valuesToString(list, seperator, _i+1, str(_s, list[_i],_i==len(list)-1? "" : seperator));
// function pb_removeAdjacentDuplicates(s)
//
// Removes adjacent duplicate points
// pts (list) List of points.
// testLastToFirst (boolean) Set to false if you want to skip testing last point to first point.
// return (string) String containing all items from the list.
function pb_removeAdjacentDuplicates(pts=[], testLastToFirst=true, _i=0, _r=[]) = let(
l = len(pts), _n = testLastToFirst? (_i==l-1? 0: _i+1) : _i+1
) _i<l? pb_removeAdjacentDuplicates(pts, testLastToFirst, _i+1, pts[_i] == pts[_n]? _r : concat(_r,[pts[_i]])) : _r;
// string handling code from rColyer on Github
// https://github.com/thehans/funcutils/blob/master/string.scad
function join(l,delimiter="") = let(s = len(l), d = delimiter,
jb = function (b,e) let(s = e-b, m = floor(b+s/2)) // join binary
s > 2 ? str(jb(b,m), jb(m,e)) : s == 2 ? str(l[b],l[b+1]) : l[b],
jd = function (b,e) let(s = e-b, m = floor(b+s/2)) // join delimiter
s > 2 ? str(jd(b,m), d, jd(m,e)) : s == 2 ? str(l[b],d,l[b+1]) : l[b])
s > 0 ? (d=="" ? jb(0,s) : jd(0,s)) : "";
function substr(s,b,e) = let(e=is_undef(e) || e > len(s) ? len(s) : e) (b==e) ? "" : join([for(i=[b:1:e-1]) s[i] ]);
function split(s,separator=" ") = separator=="" ? [for(i=[0:1:len(s)-1]) s[i]] :
let(t=separator, e=len(s), f=len(t),
_s=function(b,c,d,r) b<e ?
(s[b]==t[c] ?
(c+1 == f ?
_s(b+1,0,b+1,concat(r,substr(s,d,b-c))) : // full separator match, concat substr to result
_s(b+1,c+1,d,r) ) : // separator match char, more to test
_s(b-c+1,0,d,r) ) : // separator mismatch
concat(r,substr(s,d,e))) // end of input string, return result
_s(0,0,0,[]);
function replace(s, oldText, newText) = join(split(s, oldText),newText);
// function pb_replacechar(s)
// string (string) Source string
// char (char) Character to be deleted from string
// Returns sub string of string.
function pb_replacechar(string, findchar=" ", repchar="", _s=0) = _s < len(string) ?
str(string[_s]==findchar? repchar : string[_s], pb_replacechar(string, findchar, repchar, _s + 1)) : "";
// function pb_substring(s)
// string (string) Source string
// start (number) Index to start character in string
// end (number) Max number of character to return
// Returns sub string of string.
function pb_substring(string, start, length) = start < length ?
str(string[start], pb_substring(string, start + 1, length)) : "";
// function pb_groupsOf(n, list, skip, drop, only_groups)
//
// After skipping skip values this function will group subsequent values in groups of n until the end of the list but minus drop values.
// This is useful to group values into point lists and handle left over values in a different way. Perfect to deal with SVG number sequences.
// n (number) Define how many values go in a group. for example, n=3 to turn [x,y,z,x,y,z,x,y,z...] into [[x,y,z],[x,y,z],[x,y,z]...]
// list (list) List containing the input data. This can be kind of data.
// skip (number) Defines how many list values to skip before grouping starts.
// drop (number) Defines how many list values at the end of the list should not be grouped.
// only_groups (bool) (default = true) Set to false if you want left-over values that can not be grouped and are not skipped or dropped to be included in the return data.
// return (list) Returns list with three value sets.
// return[0] Skipped values.
// return[1] Grouped values.
// return[2] leftover values if only_groups==false. Followed by the dropped values.
function pb_groupsOf(n, list=[], skip=0, drop=0, only_groups=true) = list==[]? list : let(l=len(list), remain=((l-skip-drop) % n),
result = [skip<1? [] : [for(i=[0:skip-1]) list[i]],
[for(j=[skip:n:l-drop-n]) [for(k=[0:n-1]) list[j+k]]],
drop>0 || (remain>0&&!only_groups) ? [for(m=[l-remain-drop:l-1]) list[m]] : []] ) result;
// function pb_is_CW(pts)
//
// Tests if pts forms a Clock Wise (CW) or Counter Clock Wize (CCW) winding
// pts (list) A list of 2D points assumed a closed polyline.
// return (bool) true if pts is a wound Clock Wize otherwise false.
function pb_is_CW(pts=[], _i=0, _r=0) = _i==len(pts)? _r>0 : pb_is_CW(pts, _i+1, _r + cross(_i==len(pts)-1? pts[0] : pts[_i+1], pts[_i]));
// function pb_is_2d(p)
//
// Tests if the value p is a 2D point.
// p (list) A list of two numbers representing a 2D point.
// return (bool) true if p is a 2D point otherwise false.
function pb_is_2d(p=[]) = is_list(p) && len(p)==2 && is_num(p[0]) && is_num(p[1]);
// function is_between(v1, v, v2)
//
// Tests if value v is between or equal to v1 and v2. It does not matter if v1 is greater than v2 or vice versa.
// v1 (number or list) Defines one of the two value boundaries. Value can be a number or a list representing a 2D point.
// v (number or list) Defines one value to be tested against v1 and v2. It does not matter
// v2 (number or list) Defines one of the two value boundaries. Value can be a number or a list representing a 2D point.
// return (bool)
// false if the value v is outside the boundaries v1 and v2.
// true if the value is inside the boundaries v1 and v2.
function pb_is_between(v1, v, v2) = let( p=is_num(v)? [v,0]: v, p1=is_num(v1)? [v1,0]: v1, p2=is_num(v2)? [v2,0]: v2, d = p2-p1,
i=abs(d[0])>abs(d[1])? 0:1, a = p[i]>= min(p1[i],p2[i])&& p[i]<=max(p1[i],p2[i])) a;
// pb_intersectLineWithPolyline(line, pts, mode, all, sort) returns list with points if any
//
// calculates the intersections between a line and polyline.
// line (list) A list of two 2D points. eg [[0,0],[0,5]]
// pts (list) A list of one or more 2D points forming a polyline. Polyline does not self close. A a single point is converted into three points, forming one horizontal and one vertical line.
// all (bool)
// false (default) stops looking for intersections once a valid one is found. sort has no effect.
// true builds a list of all valid intersections. The result can be sorted.
// sort (list) (default = []) The intersection results will be sorted by distance to the 2D point that can be provided here.
// false The list of valid intersections is returned in the order they were found.
// true (default) The list of valid intersections is sorted based on proximity to the line end point. Nearest is at the top of the list.
// on_line (bool) On line result filter.
// undef (default) no intersections are filtered out.
// false only intersections that are not on line are returned.
// true only intersections that are on line are returned.
// on_pts (bool) On pts polyline result filter.
// undef (default) no intersections are filtered out.
// false only intersections that are not on the pts polyline are returned.
// true only intersections that are on the pts polyline are returned.
// return (list) A list of intersection results. Each item represents an intersection. An empty list is returned if there are no intersections.
// return[n] Data block representing one intersection
// return[n][0] (list) A list of two numbers representing a 2D point where the intersection was found.
// return[n][1] (bool) Intersection found on line. true when it was found on the line otherwise false.
// return[n][2] (bool) Intersection found on polyline segment. true when it was found on a line segment, otherwise false.
// return[n][3] (number) index of polyline segment if the intersection was found on a polyline otherwise -1.
// return[n][4] (number) Distance from the the intersection to the sort point if one was provided. Otherwize value is -1. This value is used by the sort routine.
function pb_intersectLineWithPolyline(line=[], pts=[],all=false, sort=[], on_line=undef, on_pts=undef, _i=0, _r=[]) =
(!all&&len(_r)>0) ||_i==len(pts)-1? pb_is_2d(sort)? _pb_intersect_sort(_r,3) : _r : let(
da = line[0]-line[1], db = pts[_i] - pts[_i+1], the = cross(da,db),
d = (the == 0)? [] : let( A = cross(line[0], line[1]), B = cross(pts[_i], pts[_i+1]),
x=( A*db[0] - da[0]*B ) / the, y=( A*db[1] - da[1]*B ) / the, a=[x,y],
ol = pb_is_between(line[0] ,[x,y] ,line[1]),
op = pb_is_between(pts[_i], [x,y] ,pts[_i+1]),
p = [a, ol, op, pb_is_2d(sort)? norm(a-sort):-1, op?_i:-1] ) p,
a = pb_intersectLineWithPolyline(line, pts, all, sort, on_line, on_pts, _i+1, d==[]||d==pb_last(_r)? _r : concat(_r, [d]))
) [for(i=a) if ((on_line==undef || on_line==i[1]) && (on_pts==undef || on_pts==i[2])) i];
// function _pb_intersect_sort(list)
//
// Sorts intersection data produced by pb_intersectLineWithPolyline from near to far.
// Function is used internally but can be used externally.
// list (list) List of intersection data. See pb_intersectLineWithPolyline for structure details.
// return (list) List of intersection data sorted from near to far
function _pb_intersect_sort(list, sort_idx=2) =
len(list)<=1 ? list : let(
pivot = list[floor(len(list)/2)][sort_idx],
lesser = [ for (d = list) if (d[sort_idx] < pivot) d ],
equal = [ for (d = list) if (d[sort_idx] == pivot) d ],
greater = [ for (d = list) if (d[sort_idx] > pivot) d ]
) concat( _pb_intersect_sort(lesser, sort_idx), equal, _pb_intersect_sort(greater, sort_idx) );
// function pb_parseNum(s)
//
// Converts a string into a number. this can be an integer or a floating point variable.
// The function can not handle hex notation.
// s (list) String representing a number. Valid characters are +-0123456789e and .
// return (number) Can be either integer positive or negative or floating point value positive or negative
function pb_parseNum(s, _i=0, _n=0, _d=0, _r1=0, _r2=0) = _i==len(s)? s[0]=="-"? -(_r1+_r2) : _r1+_r2 : let(
o = ord(s[_i]), f = o==101? pb_parseNum(pb_substring(s, _i+1, len(s))) : 0, _n = o==45 || o==43? 1: _n,
_d = o == 46? _i+1 : _d, c = (o>47 && o<58), _r1 = c&&_d==0? _r1*10+(o-48) : _r1, _r2 = c&&_d!=0? _r2+(o-48) * pow(10,-_i+_d-1) : _r2
) pb_parseNum(pb_replacechar(s), f==0? _i+1 : len(s), _n, _d, _r1, _r2) * pow(10,f);
// function pb_commandListToPath(commandList)
//
// Converts command list into a path string.
// commandList (list) List of SVG and Pathbuilder commands. Each item consists of a command identifier and a number list.
// return (string) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
function pb_commandListToPath(commandList = []) =
pb_valuesToString([for(command = commandList) str(command[0],pb_valuesToString(command[1]))],"");
// function pb_tokenizeSvgPath(s)
//
// Parses a path string and turns it into a command list.
// s (list) String compliant with SVG path syntax plus the extra commands introduced in pathBuilder.
// return (list) Command list
// command (string) Command identifier
// values (list) Values associated with the command
// value (number) Value associated with the command
function pb_tokenizeSvgPath(s, _i=0, _cmds=[], _cmd=[], _w = "", _d=0) =
_i>len(s)-1? _cmds : let(
l=len(s), c1 = s[_i], a1 = ord(c1), a2 = ord(s[min(l-1,_i+1)]), _d = a2==46? _d+1 : _d,
// 0=number 1=sign 3=sep 4=dot 5=exp 2=char
t1 = a1>47 && a1<58? 0 : a1==43 || a1==45? 1 : a1==32 || a1==44? 3 : a1==46? 4 : (a1==101 || a1==69)? 5 : 2,
// 6=end 0=number 1=sign 3=sep 4=dot 5=exp 2=char
t2 = _i==l-1? 6 : (a2>47 && a2<58)? 0 : a2==43 || a2==45? 1 : a2==32 || a2==44? 3 : a2==46? 4 : (a2==101 || a2==69)? 5 : 2,
c =
t1==2&&t2==0? 1 : // char to num "m 0 0 cha
t1==2&&t2==3? 2 : // char to sep
t1==2&&t2==1? 3 : // char to sign
t1==2&&t2==4? 4 : // char to dot
t1==2&&t2==6? 5 : // char to end
t1==0&&t2==2? 6 : // num to char
t1==0&&t2==3? 7 : // num to sep
t1==0&&t2==1? 8 : // num to sign
t1==0&&t2==6? 9 : // num to end
t1==0&&t2==4&&_d>1? 11 : // num to next dot
t1==5&&t2==0? 0 : // exp to num
t1==5&&t2==1? 0 : // exp to sign
0, // not important
dc = c==6 || c==7 || c==8 || c==9? 0 : _d,
//w = t1!=3&&(c1!="z" && c1!="Z)? str(_w,c1) : _w,
w = t1!=3? str(_w,c1) : _w,
//w = t1!=3? str(_w,c1) : _w,
_cmd = c>0||t2==6? t1==2? [w,[]] : [_cmd[0],concat(_cmd[1],[pb_parseNum(w)])] : _cmd,
_cmds = (t1==0 || t1==3 || t2==6) && (t2==2|| t2==6)? concat(_cmds, [_cmd]) : _cmds,
_w = c>0? "" : w
)pb_tokenizeSvgPath(s=s, _i=_i+1, _cmds=_cmds, _cmd=_cmd, _w=_w, _d=dc);
// function checks command list and splits command list to multiple command lists for every m or M command
//
function pb_splitCommandLists(cmds=[], _i=0, _p=[], _r=[]) = _i==len(cmds)? _r : let(
n = cmds[_i][0] =="m" || cmds[_i][0] =="M"? true : false,
_r = n && _p!=[]? concat(_r, [_p]) : _r,
_p = n? [cmds[_i]] : concat(_p,[cmds[_i]]),
r = _i==len(cmds)-1? concat(_r, [_p]) : _r
) pb_splitCommandLists(cmds, _i+1, _p, r);
function pb_processCommandLists(cmds_list) = [for (cmds=cmds_list) pb_processCommands(cmds)];
// function pb_processCommands(cmds)
//
// Processes all the commands in the command list and generates a preliminary 2D point list and a post process command list
// When finished, the point list contains all points except fillet, chamfer and z. Those are applied in a seprate pb_postProcessPath command.
// cmds (list) List of path commands
// return (list) Intermediate 2D points list and post processing list
// return[0] (list) List with 2D points.
// return[1] (list) List with post process commands such as fillet and chamfer which are applied at the end.
// return[2] (number) current angle in degrees
// return[3] (list) List containing 2 bezier spline control points. However, only one can be set at a time and the other should be empty.
// return[3][0] (list) 2D point representing the control point of the last command which must have been a quadratic spline type. Otherwise empty ([])
// return[3][1] (list) 2D point representing the control point of the last command which must have been a cubic spline type. Otherwise empty ([])
function pb_processCommands(cmds=[], _i=0, _r=[[],[],0,[[],[]]], _f=[]) =
assert(is_list(cmds) && len(cmds) > 0 && (cmds[0][0] == "m" || cmds[0][0] == "M"), str("cmds must be a list and start with a M (move) command but started with ",cmds[0][0]))
_i==len(cmds)? concat(_f,[[_r[0],concat(_r[1],[[4,len(_r[0])-1]])]]) : let(
cmd = cmds[_i],
o = ord(cmd[0]),
c = cmd[0],
a = _r[2],
ctl = _r[3],
l = pb_last(_r[0]),
d = c=="m"? _pb_line(_r[0], true,cmd[1],a,true) :
c=="M"? _pb_line(_r[0], false, cmd[1],a,true) :
c=="l"? _pb_line(_r[0], true, cmd[1], a, false) :
c=="L"? _pb_line(_r[0], false, cmd[1], a, false) :
c=="h"? _pb_horz(l, cmd[1], true, a) :
c=="H"? _pb_horz(l, cmd[1], false, a) :
c=="v"? _pb_vert(l, cmd[1], true, a) :
c=="V"? _pb_vert(l, cmd[1], false, a) :
c=="c"? _pb_cubic(l, cmd[1], true, a) :
c=="C"? _pb_cubic(l, cmd[1], false, a) :
c=="s"? _pb_smooth_cubic(l, cmd[1], true, a, ctl) :
c=="S"? _pb_smooth_cubic(l, cmd[1], false, a, ctl) :
c=="q"? _pb_quadratic(l, cmd[1], true, a) :
c=="Q"? _pb_quadratic(l, cmd[1], false, a) :
c=="t"? _pb_smooth_quadratic(l, cmd[1], true, a, ctl) :
c=="T"? _pb_smooth_quadratic(l, cmd[1], false, a, ctl) :
c=="a"? _pb_arc(l, cmd[1], true, a) :
c=="A"? _pb_arc(l, cmd[1], false, a) :
c=="z" || c=="Z"? _pb_close(_r[0], a) :
c=="polar"? _pb_polar(_r[0], [cmd[1][0], cmd[1][1]+a]) :
c=="Polar"? _pb_polar(_r[0], cmd[1]) :
c=="forward"? _pb_forward(l, cmd[1], true, a) :
c=="Forward"? _pb_forward(l, cmd[1], false, a) :
c=="angle"? _pb_angle(cmd[1], false, a) :
c=="Angle"? _pb_angle(cmd[1], true, a) :
c=="segment"? _pb_segment(last=l, args=cmd[1], rel=true) :
c=="Segment"? _pb_segment(last=l, args=cmd[1], rel=false) :
c=="fillet"? _pb_fillet(_r[0], cmd[1], a) :
c=="chamfer"? _pb_chamfer(_r[0], cmd[1], a) : [],
_f = (c=="m" || c=="M") && _r[0]!=[]? concat(_f, [[_r[0],concat(_r[1],[[4,len(_r[0])-1]]),_r[2],_r[3]]]) : _f,
r = c=="m" || c=="M"? d : d==[]? _r : [concat(_r[0], d[0]),concat(_r[1], d[1]), is_num(d[2])? d[2] : _r[2],d[3]]
) pb_processCommands(cmds, _i+1, c=="m" || c=="M"? d : d==[]? _r : [concat(_r[0], d[0]),concat(_r[1], d[1]), is_num(d[2])? d[2] : _r[2],is_list(d[3])? d[3]: [[],[]]], _f);
// Applies fillets and chamfer commands to the raw point list
// chamfer = 2
// fillet = 3
// z = 4
function pb_postProcessPathLists(data_list =[]) = [for (data=data_list)
let(
pts = pb_removeAdjacentDuplicates(data[0]),
steps = data[1],
l = len(steps),
first_pt = steps[1][1]==0? [] : [pts[0]],
last_pt = steps[l-2][1]==len(pts)-1? [] : [pts[len(pts)-1]],
result = [for (i = [0: len(steps)-2]) let(
step = data[1][i],
next_step = data[1][i+1],
start = step[1],
end = next_step[1],
fill = (step[0]==2)? pb_fillet(pts, step[1], step[2], step[3]) : [],
chamf = (step[0]==3)? pb_chamfer(pts, step[1], step[2]) : [],
post = start+1<=end-1?pb_subList(pts, start+1, end-1) : []
) for (p=concat(fill, chamf, post)) p],
r = concat(first_pt, result, last_pt),
rr = concat(r, len(steps)>0 && pb_last(steps)[0]==4? [r[0]] : [])
) rr];
// Calculates tangent fillet for any given point in a closed points list. Flip the curve by setting the radius negative.
// pts (list) List of 2D points.
// index (number) Index to the point for which a fillet is required.
// radius (number) Radius for the requested fillet
// segments(number) Optional fixed number of segments to draw the fillet.
// return (segments) List of points representing the fillet curve that can replace the given point by index.
function pb_fillet(pts, index, radius, segments=0) = let(
a = index==0? pts[len(pts)-1] : pts[index-1],
b = pts[index],
c = index == len(pts)-1? pts[0]:pts[index+1],
ba = a-b,
bc = c-b,
l1 = norm(ba),
l2 = norm(bc),
cos_angle = ba * bc / (l1 * l2),
tan_half_angle = sqrt((1 - cos_angle) / (1 + cos_angle)),
bf_length = abs(radius) / tan_half_angle,
ba_u = ba/l1,
bc_u = bc/l2,
bf = ba_u*bf_length,
bg = bc_u*bf_length,
f = b + bf,
g = b + bg,
e1=assert(tan_half_angle!=0, "Fillet is not possible on angles of 0 degrees"),
e2=assert(bf_length<l1 || bf_length<l2, str("Fillet can not be applied. Max radius is ",min(l1, l2))),
sig = sign(ba[0] * bc[1] - ba[1] * bc[0]) * (radius<0? -1 : 1),
ps = pb_curveBetweenPoints(f, g, abs(radius) * sig, segments)[0]
) ps;
// Calculates a balanced symetrical chamfer for a given point
// pts (list) List of 2D points.
// index (number) Index to the point for which a chamfer is required.
// size (number) Size for the requested chamfer width
// return (list) List of two points representing the chamfer line that can replace the given point by index.
// The original point is returned when size is zero or when either point adjacent to index
// sharing the same coordinates.
function pb_chamfer(pts, index, size) = let(
a = index==0? pts[len(pts)-1] : pts[index-1],
b = pts[index],
c = index == len(pts)-1? pts[0]:pts[index+1],
d = size/2,
ba = a-b,
bc = c-b,
l1 = norm(ba),
l2 = norm(bc),
cos_angle = ba * bc / (l1 * l2),
tan_half_angle = sqrt((1 - cos_angle) / (1 + cos_angle)),
bf_length = sqrt(pow(d/tan_half_angle,2) + pow(d,2)),
ba_u = ba/l1,
bc_u = bc/l2,
bf = ba_u*bf_length,
bg = bc_u*bf_length,
f = b + bf,
g = b + bg
) (size<=0 || a==b || b==c)? [b] : [f, g];
// function pb_ellipseCenter(p1, p2, rx, ry, angle, long, ccw)
//
// Calculates center of a ellipse rx, ry rotated to angle that runs through both p1 and p2.
// p1 2D start point for the arc segment.
// p2 2D end point of the arc segment.
// rx x radius for the ellipse when angle = 0
// ry y radius for the ellipse when angle = 0
// angle rotation angle of the ellipse around it's center point.
// long Two ways around the ellipse. Set to true to take the long way.
// ccw Set to true of you want the acr drawn following the ellipse counter clock wize.
//
// return List with two values.
// return[0] 2D point list forming a polyline representing the ellipseArc.
// return[1] 2D point which represents the position of the ellipse center point.
function pb_ellipseCenter(p1=[], p2=[], rx, ry, angle=0, long=false, ccw=false)= let(
P = [[cos(-angle), sin(-angle)], [-sin(-angle), cos(-angle)]] * ((p1-p2)*0.5),
x = P[0], y = P[1], a = ((x * x) / (rx * rx) ) + ( (y * y) / (ry * ry) ),
rx = a > 1? (sqrt(a) * abs(rx)) : abs(rx), ry = a > 1? (sqrt(a) * abs(ry)) : abs(ry),
co = (long == ccw? 1 : -1) * sqrt(( (rx*rx*ry*ry) - (rx*rx*y*y) - (ry*ry*x*x) ) / ( (rx*rx*y*y) + (ry*ry*x*x) )),
C = ([[ cos(-angle), -sin(-angle)],[sin(-angle), cos(-angle)]] * [rx*y/ry, -ry*x/rx] * co) + ((p1+p2)*0.5)) C;
// function pb_ellipseArc(p1, p2, rx, ry, angle, long, ccw)
//
// Produces a list of 2D points that approximates the arc segment required to from p1 to p2.
// p1 2D start point for the arc segment.
// p2 2D end point of the arc segment.
// rx x radius for the ellipse when angle = 0
// ry y radius for the ellipse when angle = 0
// angle rotation angle of the ellipse around it's center point.
// long Two ways around the ellipse. Set to true to take the long way.
// ccw Set to true of you want the acr drawn following the ellipse counter clock wize.
//
// return List with two values.
// return[0] 2D point list forming a polyline representing the ellipseArc.
// return[1] 2D point which represents the position of the ellipse center point.
function pb_ellipseArc(p1=[], p2=[], rx, ry, angle=0, long=false, ccw=false, skip_first=false) = rx==0||ry==0? [p1,p2] : let(
d = norm(p2-p1),
e = assert(rx*2>=d, str("pb_ellipseArc - Radius:",rx," is too small for distance:",d)),
pc = pb_ellipseCenter(p2,p1,rx,ry,angle, long, ccw),
m = [[cos(angle), -sin(angle)],[sin(angle), cos(angle)]],
nm = [[cos(-angle), -sin(-angle)],[sin(-angle), cos(-angle)]],
v1 = (p1-pc) * nm, v2 = (p2-pc) * nm,
a1 = (v1[1]<0? 180 : 0)+ atan2(v1[0]/v1[1],rx/ry),
a2 = (v2[1]<0? 180 : 0)+ atan2(v2[0]/v2[1],rx/ry),
da = abs(a2 - a1 % 360), das = da<=180? da : 360-da,
cda = long? 360-das : das,
s = pb_segmentsPerCircle((rx+ry)/2),
steps = floor(abs(cda*s/360)),
sa = ccw? -(cda/steps) : cda/steps,
pts = steps<=2? [p2] : [for(i=[1:steps-1]) let(a = a1 + (sa * i)%360) pc+[sin(a) * rx , cos(a) * ry] * m, p2]
) [skip_first? pts : concat([p1],pts),concat(pc,0)];
// function pb_curveBetweenPoints(p1, p2, radius)
//
// Creates a curve made of line segments connecting the two points with the defined radius.
// p1 (list) List of two numbers representing a 2D point
// p2 (list) List of two numbers representing a 2D point
// radius (number) Required radius of the desired curve. Change the sign of the radius to mirror the curve. The radius value must be at least half the distance between p1 and p2.
// return (list) List of the resulting curve data...
// return[0] (list) List of the 2D point list describing the curve. The list includes p1 and p2.
// return[1] (number) Angle of the last line segment in the curve
// return[2] (list) List of two numbers representing a 2D point. This point is the center of the circle segment drawn.
function pb_curveBetweenPoints(p1=[], p2=[], radius=0, segments=0) = radius==0? [p1,p2] : let(
d = norm(p2-p1),
r = abs(radius),
e = assert(r*2>=d, str("Radius:",r," is too small for distance:",d)),
x3 = (p1[0] + p2[0])/2,
y3 = (p1[1] + p2[1])/2,
base = sqrt(pow(r,2) - pow((d / 2),2)),
basex = base * (p1[1] - p2[1]) / d,
basey = base * (p2[0] - p1[0]) / d,
pc = radius > 0? [x3 - basex, y3 - basey] : [x3 + basex, y3 + basey],
a1 = atan2(p1[0]-pc[0], p1[1]-pc[1]),
a2 = atan2(p2[0]-pc[0], p2[1]-pc[1]),
da = a2 - a1 % 360,
cda = da<-180? 360 + da : da>180? -360 + da : da,
//steps = floor(abs(cda*($fn==0? 1/$fa : $fn/360))),
steps = segments==0? floor(abs(cda * pb_segmentsPerCircle(radius) / 360)) : segments,
sa = cda/steps,
pts = steps<=2? [p1,p2] : [p1,for(i=[1:steps-1]) [sin(a1 + (sa * i)) * r + pc[0], cos(a1 + (sa * i)) * r + pc[1]],p2]
) [pts,sign(sa)*90+a1+cda, pc];
// function pb_bezier_quadratic_curve(p0, c, p1, n)
//
// Generates a Bezier quadratic curve from p0 to p1 using a single control point c. The curve is approximated by a 2D point list containing n points.
// p0 (list) List of two numbers representing the 2D start point of the curve.
// c (list) List of two numbers representing a 2D control point shaping the curve and defines both entry and exit tangents.
// p1 (list) List of two numbers representing the 2D end point of the curve.
// n (number) The number of points that should be returned.
// return (list) List of 2D points resembling the quadratic curve.
function pb_bezier_quadratic_curve(p0, c, p1, n = $pb_spline, skip_first=false) = [for(t = [skip_first? 1: 0 : n]) let(t0=t/n, t1=pow(1 - t0, 2), t2=pow(t0, 2)) [p0[0] * t1 + 2 * c[0] * t0 * (1 - t0) + p1[0] * t2, p0[1] * t1 + 2 * c[1] * t0 * (1 - t0) + p1[1] * t2]];
// function pb_bezier_cubic_curve(p0, c0, c1, p1, n)
//
// Generates a Bezier cubic curve from p0 to p1 using two control points c0 and c1. The curve is approximated by a 2D point list containing n points.
// p0 (list) List of two numbers representing the 2D start point of the curve.
// c0 (list) List of two numbers representing a 2D control point shaping the curve and defining the entry tangent.
// c1 (list) List of two numbers representing a 2D control point shaping the curve and defining the exit tangent.
// p1 (list) List of two numbers representing the 2D end point of the curve.
// n (number) The number of points that should be returned.
// return (list) List of 2D points resembling the cubic curve.
function pb_bezier_cubic_curve(p0, c0, c1, p1, n = $pb_spline, skip_first=false) = [for(t = [skip_first? 1: 0 : n]) let(t0=t/n, t1=pow((1 - t0), 3),t2=pow((1 - t0), 2),t3=pow(t0, 2) * (1 - t0), t4=pow(t0, 3)) [ p0[0] * t1 + 3 * c0[0] * t0 * t2 + 3 * c1[0] * t3 + p1[0] * t4, p0[1] * t1 + 3 * c0[1] * t0 * t2 + 3 * c1[1] * t3 + p1[1] * t4]];
//function pb_do_render($children, parent_module_name) = ($children == 0 || parent_module_name == "M" || parent_module_name == "m");
function pb_do_render(children, parent_module_name) = let(
do_render = (children == 0)// || parent_module_name == "M" || parent_module_name == "m")
) do_render;
// module m(x,y,a)
//
// Start and initialise the pathbuilder. The function initialises the global point list $pb_pts, global post processing instructions $pb_post and global angle $pb_angle.
// x (number) x value of the 2D start point initialisation. Default to zero if not provided.
// y (number) y value of the 2D start point initialisation. Default to zero if not provided.
// a (number) angle of the path initialisation. Default to zero if not provided.
module m(x=0, y=0, a=0, $pb_spline=10){
$pb_fn = $fn;
$pb_fa=$fa;
$pb_fs=$fs;
data = _pb_line([],true, is_list(x)? x: [x,y],a,true);
$pb_pts = data[0];
$pb_post = data[1];
$pb_angle = a;
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// module M(x,y,a)
//
// Start and initialise the pathbuilder. The function initialises the global point list $pb_pts, global post processing instructions $pb_post and global angle $pb_angle.
// x (number) x value of the 2D start point initialisation. Default to zero if not provided.
// y (number) y value of the 2D start point initialisation. Default to zero if not provided.
// a (number) angle of the path initialisation. Default to zero if not provided.
module M(x=0, y=0, a=0, $pb_spline=10){
if (pb_do_render($children, parent_module(0))){
pb_draw();
}
children();
$pb_fn = $fn;
$pb_fa=$fa;
$pb_fs=$fs;
data = _pb_line([],false, is_list(x)? x : [x,y],a,true);
$pb_pts = data[0];
$pb_post = data[1];
$pb_angle = a;
}
// function _pb_horz(last, rel, args, angle)
//
// Adds a point with the same y value as the last point but a new x value. No point is added if the new point is the same as the last point.
// last (list) List of two numbers representing the last known 2D point.
// args (list) Function specific parameters.
// args[0] (number) x value for new 2D point. relative or absolute depending on rel value false or true.
// rel (bool) Set to false when working with absolute coordinates. Set to true when coordinates are relative to the last point.
// angle (number) Last known angle. Returned when no new angle is created.
// return (list) List of a command response alwas consisting of
// return[0] (list) 2D points list of the points generated by the command. The list is empty if not relevant.
// return[1] (list) List representing a post processing command. The list is empty if not relevant.
// return[2] (number) New current angle after the command completed.
function _pb_horz(last=[], args=[], rel=false, angle) = let(x=rel? last[0] + args[0] : args[0]) x==last[0]? [[[]],[],angle,[[],[]]] : [[[x, last[1]]], [], x > last[0]? 90 : 270,[[],[]]];
// module h(last, rel, args)
//
// Adds a point with the same y value as the last point but a new x value relative from the last known point.
// x (number) x value for new 2D point relative from the last known 2D point.
module h(x){
data = _pb_horz(pb_last($pb_pts), [x], true, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2]==undef? $pb_angle : data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// module H(last, rel, args)
//
// Adds a point with the same y value as the last point but a new absolute x value .
// x (number) x value for new 2D point in absolute coordinates.
module H(x){
data = _pb_horz(pb_last($pb_pts), [x], false, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_vert(last, rel, args, angle)
//
// Adds a point vertically from the last point y units away.
// pts (list) The point list being constructed.
// args (list) Function specific parameters.
// args[0] (number) Distance along y axis either relative or absolute
// rel (bool) Set to false when working with relative values. Set to true when working with absolute values.
// angle (number) Last known angle. Returned when no new angle is created.
// return (list) List of a command response alwas consisting of
// return[0] (list) 2D points list of the points generated by the command. The list is empty if not relevant.
// return[1] (list) List representing a post processing command. The list is empty if not relevant.
// return[2] (number) New current angle after the command completed.
function _pb_vert(last=[], args=[], rel=false, angle) = let(y=rel? last[1] + args[0] : args[0]) y==last[1]? [[[]],[],angle,[[],[]]] : [[[last[0],y]], [], y > last[0]? 0 : 180,[[],[]]];
// Adds point y units away from the last
module v(y){
data = _pb_vert(pb_last($pb_pts), [y], true, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// Adds a point vertically from the last point to absolute coordinate y
module V(y){
data = _pb_vert(pb_last($pb_pts), [y], false, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_close(pts, angle)
//
// Closes points list if not already closed by adding the first point to the end of the list.
// pts (list) The point list being constructed.
// angle (number) Last known angle. Returned when no new angle is created.
// return (list) List of a command response alwas consisting of
// return[0] (list) 2D points list of the points generated by the command. The list is empty if not relevant.
// return[1] (list) List representing a post processing command. The list is empty if not relevant.
// return[2] (number) New current angle after the command completed.
function _pb_close(pts, angle) = [[], [], angle];
// function _pb_line
//
// Adds one or multiple points starting from last point x units away. Points are only added if the previous point is different.
// pts (list) List of points created so far.
// rel (bool) Set to false when working with relative values. Set to true when working with absolute values.
// args (list) Flat list of numbers. Numbers are x,y value pairs. Should have 1 .. n value pairs.
// args[0] (number) x value for the next point.
// args[1] (number) y value for the next point.
// args[...] (number) provide as many x,y value pairs as required like this U shape [0,5, 0,0, 5,0, 5,5]
// angle (number) Last known angle. Returned when no new angle is created.
// return (list) List of a command response alwas consisting of
// return[0] (list) 2D points list of the points generated by the command. The list is empty if not relevant.
// return[1] (list) List representing a post processing command. The list is empty if not relevant.
// return[2] (number) New current angle after the command completed.
function _pb_line(pts=[], rel=false, args=[], angle, move=false, _i=0, _g, _r=[]) = let(
_l = _r==[]? pts==[]? [0,0] : pb_last(pts) : pb_last(_r),
_g = _g==undef? pb_groupsOf(2,args)[1] : _g,
np = rel? _l+_g[_i] : _g[_i],
_r = np==_l&&pts!=[]&&_r!=[]? _r : concat(_r, [np])
) _i>=len(_g)-1? [_r,move?[[0,0,0]]:[],move? angle: pb_calcExitAngle(concat([_l],_r),angle),[[],[]]] : _pb_line(pts, rel, args, angle, move, _i+1, _g, _r);
// module l
//
// Adds one or multiple points relative from the last point
// x (list) Flat list of numbers. Numbers are x,y value pairs. Should have 1 .. n value pairs or...
// x (number) x value for the next point.
// y (number) y value for the next point.
module l(x, y){
args = is_list(x)? x : [x,y];
data = _pb_line($pb_pts, true, args, $pb_angle, false);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// module L
//
// Adds one or multiple points. Values are absolute coordinates.
// x (list) Flat list of numbers. Numbers are x,y value pairs. Should have 1 .. n value pairs or...
// x (number) x value for the next point.
// y (number) y value for the next point.
module L(x, y){
args = is_list(x)? x : [x,y];
data = _pb_line($pb_pts, false, args, $pb_angle, false);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_cubic(last, args, rel, angle)
//
// Generates multiple points forming line segments approximating one or multiple discrete cubic Bezier curves.
//
function _pb_cubic(last=[], args=[], rel=false, angle, _i=0, _g, _r=[]) = let(
_g = _g==undef? pb_groupsOf(6, args)[1] : _g,
b = _g[_i],
p0 = last,
c0 = rel? last + [b[0],b[1]] : [b[0],b[1]],
c1 = rel? last + [b[2],b[3]] : [b[2],b[3]],
p1 = rel? last + [b[4],b[5]] : [b[4],b[5]],
_r = concat(_r, pb_bezier_cubic_curve(p0, c0, c1, p1, skip_first=true))) _i==len(_g)-1? [_r,[],pb_calcExitAngle(_r),[[],pb_reflectPntOn(c1, p1)]] : _pb_cubic(p1, args, rel, angle, _i+1, _g, _r);
module c(cx1, cy1, cx2, cy2, x, y){
args = is_num(cx1)? [cx1, cy1, cx2, cy2, x, y] : cx1;
data = _pb_cubic(pb_last($pb_pts), args, true, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
module C(cx1, cy1, cx2, cy2, x, y){
args = is_num(cx1)? [cx1, cy1, cx2, cy2, x, y] : cx1;
data = _pb_cubic(pb_last($pb_pts), args, false, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_smooth_cubic(last, args, rel, angle)
//
// Generates multiple points forming line segments approximating one or multiple discrete cubic Bezier curves.
//
function _pb_smooth_cubic(last=[], args=[], rel=false, angle, ctrl_pts, _i=0, _g, _r=[]) = let(
_g = _g==undef? pb_groupsOf(4, args)[1] : _g,
b = _g[_i],
p0 = last,
c0 = len(ctrl_pts[1])==2? ctrl_pts[1] : p0,
c1 = rel? last + [b[0],b[1]] : [b[0],b[1]],
p1 = rel? last + [b[2],b[3]] : [b[2],b[3]],
cn = pb_reflectPntOn(c1,p1),
_r = concat(_r, pb_bezier_cubic_curve(p0, c0, c1, p1, skip_first=true))) _i==len(_g)-1? [_r,[], pb_calcExitAngle(_r),[[],cn]] : _pb_smooth_cubic(p1, args, rel, angle, [[],cn], _i+1, _g, _r);
module s(cx2, cy2, x, y, n=$pb_spline){
args = is_num(cx2)? [cx2, cy2, x, y] : cx2;
data = _pb_smooth_cubic(pb_last($pb_pts), args, true, $pb_angle, $pb_ctrl_pts,$pb_spline);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
module S(cx2, cy2, x, y, n=$pb_spline){
args = is_num(cx2)? [cx2, cy2, x, y] : cx2;
data = _pb_smooth_cubic(pb_last($pb_pts), args, false, $pb_angle, $pb_ctrl_pts,$pb_spline);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_quadratic(last, args, rel, angle,ctrl_pt)
//
// Generates multiple points forming line segments approximating one or multiple discrete cubic Bezier curves.
//
function _pb_quadratic(last=[], args=[], rel=false, angle, _i=0, _g, _r=[]) = let(
_g = _g==undef? pb_groupsOf(4, args)[1] : _g,
b = _g[_i],
p0 = last,
c = rel? last + [b[0],b[1]] : [b[0],b[1]],
p1 = rel? last + [b[2],b[3]] : [b[2],b[3]],
_r = concat(_r, pb_bezier_quadratic_curve(p0, c, p1, skip_first=true))) _i==len(_g)-1? [_r,[],pb_calcExitAngle(_r),[pb_reflectPntOn(c,p1),[]]] : _pb_quadratic(p1, args, rel, angle, _i+1, _g, _r);
module q(cx, cy, x, y, n=$pb_spline){
args = is_num(cx)? [cx, cy, x, y] : cx;
data = _pb_quadratic(pb_last($pb_pts), args, true, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
module Q(cx, cy, x, y, n=$pb_spline){
args = is_num(cx)? [cx, cy, x, y] : cx;
data = _pb_quadratic(pb_last($pb_pts), args, false, $pb_angle);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_smooth_quadratic(last, args, rel, angle)
//
// Generates multiple points forming line segments approximating one or multiple discrete cubic Bezier curves.
//
function _pb_smooth_quadratic(last=[], args=[], rel=false, angle, ctrl_pts, _i=0, _g, _r=[]) = let(
_g = _g==undef? pb_groupsOf(2, args)[1] : _g,
b = _g[_i],
p0 = last,
c = len(ctrl_pts[0])==2? ctrl_pts[0] : p0,
p1 = rel? last + [b[0],b[1]] : [b[0],b[1]],
cn = pb_reflectPntOn(c, p1),
_r = concat(_r, pb_bezier_quadratic_curve(p0, c, p1, skip_first=true))) _i==len(_g)-1? [_r,[],pb_calcExitAngle(_r),[cn,[]]] : _pb_smooth_quadratic(p1, args, rel, angle, [cn,[]], _i+1, _g, _r);
module t(x, y, n=$pb_spline){
args = is_num(x)? [x, y] : x;
data = _pb_smooth_quadratic(pb_last($pb_pts), args, true, $pb_angle, $pb_ctrl_pts);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
module T(x, y, n=$pb_spline){
args = is_num(x)? [x, y] : x;
data = _pb_smooth_quadratic(pb_last($pb_pts), args, false, $pb_angle, $pb_ctrl_pts);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
// function _pb_arc(last, args, rel, angle)
//
// Generates multiple points forming line segments approximating one or multiple discrete cubic Bezier curves.
//
function _pb_arc(last=[], args=[], rel=false, angle, _i=0, _g, _r=[]) = let(
_g = _g==undef? pb_groupsOf(7, args)[1] : _g,
b = _g[_i],
rx = b[0],
ry = b[1],
angle = b[2],
long = b[3],
sweep = b[4],
p2 = rel? last + [b[5], b[6]] : [b[5], b[6]],
d = pb_ellipseArc(last, p2, rx, ry, angle, long, sweep, true),
_r = concat(_r, d[0])) _i==len(_g)-1? [_r, [], pb_calcExitAngle(d[0]),[[],[]]] : _pb_arc(p2, args, rel, angle, _i+1, _g, _r);
// arc creates an ellipse according the x and y radius.
//
// rx (number) Radius for the ellipse along the x axis.
// ry (number) Radius for the ellipse along the y axis.
// angle (number) Degrees of rotation for the ellipse.
// long (bool) Ensures arc will be greater than 180 degrees when true.
// ccw (bool) Ensures arc will be drawn counter clockwize when true.
// x (number) x value of the desired 2D end point.
// y (number) y value of the desired 2D end point.
module a(rx, ry, angle, long, sweep, x, y){
args = is_num(rx)? [rx, ry, angle, long, sweep, x, y] : rx;
data = _pb_arc(pb_last($pb_pts), args, true, 0, 0, undef, []);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];
if (pb_do_render($children, parent_module(0))) pb_draw();
children();
}
module A(rx, ry, angle, long, sweep, x, y){
args = is_num(rx)? [rx, ry, angle, long, sweep, x, y] : rx;
data = _pb_arc(pb_last($pb_pts), args, false, 0, 0, undef, []);
$pb_pts = concat($pb_pts, data[0]);
$pb_angle = data[2];
$pb_ctrl_pts = data[3];