-
Notifications
You must be signed in to change notification settings - Fork 8
/
pointutils.scad
314 lines (256 loc) · 14.4 KB
/
pointutils.scad
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
// pointutils.scad
//
// This utility libray is designed to help with the generation of complex 3D point lists
// Amazing results can be achieved when used in combination with Pathbuilder and MeshBuilder
//
// Latest version here: https://github.com/dinther/pathbuilder
//
// By: Paul van Dinther
// function rectPoints(rect=[], div=[], z=undef, center=false)
//
// Generates a list of points describing the requested rectangle. The number of points per size can be controlled. List is 2D if rect contains 2 values otherwise 3D.
// rect (vector) 2D or 3D Defines the size of the rectangle.
// return (list) List of 2D or 3D points forming a rectangle.
function rectPoints(rect=[1,1], center=false) = let(
x=center? -rect[0] * 0.5 : 0,
y=center? -rect[1] * 0.5 : 0,
pts = [[0+x,0+y],[0+x,rect[1]+y],[rect[0]+x,rect[1]+y],[rect[0]+x, 0+y]]) rect[2]==undef? pts : appendValueToPoints(pts, rect[2]);
// function circlePoints(r=undef, d=undef, z=undef)
//
// Generates a list of 2D points describing the requested circle. The number of points will depend
// on the usual openSCAD $fa, $fs, $fn settings.
// will be a 3D point.
// r (number) Radius of the circle.
// d (number) Diameter of the circle.
// z (number) Z coordinate optional. Causes a 3D point list to be returned.
// return (list) List of 2D or 3D points forming a circle.
function circlePoints(r=undef, d=undef, z=undef) = let(
_r = r!=undef? r : d!=undef? d*0.5 : 1,
_c = _pu_segmentsPerCircle(_r),
_s = 360 / _c
) [for(i=[0:_c-1]) let(_cos = cos(i*_s), _sin = sin(i*_s)) z==undef? [_cos * _r, _sin * _r,] : [_cos * _r, _sin * _r, z]];
// function appendValueToPoints(pts, value)
//
// Appends a value to every point in the list. Typically used when you want to turn a 2D point list into a 3D point list.
// pts (list) List of points.
// value (number) Value to be apended to every point in the list.
// return (point) Resulting list of points with the extra value.
function appendValueToPoints(pts, value) = [for(pt=pts) concat(pt,[value])];
// function keep(dataList=[], indexes = [])
//
// Takes any list of data and only returns the fields given by the indexes. This function is also perfect when you want to reorder data.
// Turn 3D points lists into 2D point lists etc. A warning will be given when an index exceeds the number of data items.
//
// dataList (list) List of any kind of data of any length. For example mixed 2D and 3D points.
// indexes (list) List of indexes for the required fields.
// return (point) Resulting organized list
//
// Example keep(dataList=[[0,0,3],[2,10,0],[12,10]], indexes=[1,0]); // Produces list with only y,x coodinates [[0, 0], [10, 2], [10, 12]]
function keep(dataList=[], indexes = [], _i=0, _data=[]) = let(
a = assert(max(indexes) < len(dataList[_i]), str("Index ",max(indexes)," in indexes list is too large for given data ",dataList[_i])),
d = [for(i=indexes) dataList[_i][i]],
_data = concat(_data, [d])) _i<len(dataList)-1? keep(dataList, indexes, _i+1, _data) : _data;
// function rotatePoint(pt, angle)
//
// Rotates a 2D point around the z axis relative to the origin. Input can either be a 2D or 3D point.
// will be a 3D point.
// pt (point) Array with X and Y value.
// angle (number) Angle around which the 2D point is rotated. Angle is in degrees.
// return (point) Resulting rotated point.
function rotatePoint(pt, angle=0) = angle==0? pt : let(_c=cos(angle), _s=sin(angle)) pt[2]==undef? [(_c * pt[0]) + (_s * pt[1]), (_c * pt[1]) - (_s * pt[0])] : [(_c * pt[0]) + (_s * pt[1]), (_c * pt[1]) - (_s * pt[0]), pt[2]];
function rotate(pt, angle) = let (
c = cos(angle),
s = sin(angle),
nx = (c * pt[0]) + (s * pt[1]),
ny = (c * pt[1]) - (s * pt[0])) [nx, ny, z];
//echo(rotatePoint([0,60],-10));
// function rotatePoints(pts=[], angles=[0,0,0], z_offset=0)
//
// Rotates a list of points around along ONE, TWO OR three axis [x, y,z]. Although the input can be a 2D point, the output
// will be a 3D point except when the input is a 2D point and rotation is only along the Z axis
// pts (list) List of zero or more 2D or 3D points.
// angles (list) angles along X, Y and Z axis in that order. Angles in degrees.
// z_offset (number) moves the final z value by Z-offset.
// return (list) Rotated list of 3D points.
function rotatePoints1(pts=[], angles=[0,0,0], z_offset=0, _i=0, _pts=[]) = let(
z1 = pts[_i][2]==undef? z_offset : pts[_i][2] + z_offset,
ax = angles[0] == undef? 0 : angles[0],
ay = angles[1] == undef? 0 : angles[1],
az = angles[2] == undef? 0 : angles[2],
npx = rotatePoint([pts[_i][1],pts[_i][2],0], ax),
npx1 = [pts[_i][0],npx[0],npx[1]],
npy = rotatePoint([npx1[0], npx1[2], npx1[1]], ay),
npy1 = [npy[0], npx1[1],npy[1]],
npz = rotatePoint([npy1[0], npy1[1], npy1[2]], az),
npz1 = pts[_i][2]==undef && ax==0 && ay==0? [npz[0], npz[1]] : [npz[0], npz[1],npy1[2]+z1],
_pts = concat(_pts, [npz1])
) _i<len(pts)-1? rotatePoints(pts, angles, z_offset, _i+1, _pts) : _pts;
function rotatePoints(pts=[], angles=[0,0,0], z_offset=0, _i=0, _pts=[]) = let(
ax = angles[0] == undef? 0 : angles[0],
ay = angles[1] == undef? 0 : angles[1],
az = angles[2] == undef? 0 : angles[2],
npx = rotatePoint([pts[_i][1],pts[_i][2],0], ax),
npx1 = [pts[_i][0],npx[0],npx[1]],
npy = rotatePoint([npx1[0], npx1[2], npx1[1]], ay),
npy1 = [npy[0], npx1[1],npy[1]],
npz = rotatePoint([npy1[0], npy1[1], npy1[2]], az),
npz1 = pts[_i][2]==undef && ax==0 && ay==0? [npz[0], npz[1]] : [npz[0], npz[1],npz[2]],
_pts = concat(_pts, [npz1])
) _i<len(pts)-1? rotatePoints(pts, angles, z_offset, _i+1, _pts) : translatePoints(_pts,[0,0,z_offset]);
//pts = [[40,0,30],[40,0,40],[80,0,40],[80,0,30]];
//pts1 = rotatePoints(pts, [0,0,0],-30);
//sp(pts1);
// translatePoints(pts, scale)
//
// Translates points in point list. You can use 2D points on 3D translation vectors and vice versa.
// pts (list) List of 2D or 3D points.
// translate (list) List of two or three numbers representing the vector to translate the points with
// return (list) List of scales points.
function translatePoints(pts=[], translate=[0,0]) = [for(p=pts)[ for(j=[0:len(p)-1]) translate[j]!=undef? p[j]+translate[j] : p[j]]];
// scalePoints(pts=[], scale=[])
//
// Scales points in point list. You can use 2D points on 3D scale vectors and vice versa.
// pts (list) List of 2D or 3D points.
// scale (list) List of two or three numbers representing the vector to scale the points with
// return (list) List of scales points.
function scalePoints(pts=[], scale=[0,0]) = [for(p=pts)[ for(j=[0:len(p)-1]) scale[j]!=undef? p[j]*scale[j] : p[j]]];
// BETA - maxXDist(pts=[], dist=1)
//
// Inserts additional points when the X distance between two points is greater than the given distance.
// The space will then be deviced equally.
// pts (list) List of 2D or 3D points.
// dist (number) The maximum distance allowed before dividing starts
// return (list) Adjusted list of 2D or 3D points.
function maxXDist(pts=[], dist=1, _i=0, _pts=[]) = let(
dx = pts[_i+1][0] - pts[_i][0],
sx = dist==0? 1 : floor(abs(dx)/dist)+1,
seg = sx==1? dx : dx / sx,
ipt = sx<0? [for(i=[sx:-1:0]) [pts[_i][0]+ i*seg, pts[_i][1], pts[_i][2]]] : [for(i=[0:sx]) [pts[_i][0]+ i*seg, pts[_i][1], pts[_i][2]]]
//e=echo(dx, sx, seg)
)_i<len(pts)-1? maxXDist(pts, dist, _i+1, concat(_pts,ipt)) : _pts;
// BETA - bendPoints(pts=[], x=0, r=1)
//
// Bends x and z coordinates of the 2D or 3D point list around a circle with offset x and radius r.
// pts (list) List of 2D or 3D points.
// x (number) The x offset for the circle center around which the bend radius is projected.
// r (number) The bend radius used for the bend.
// return (list) Adjusted list of 2D or 3D points.
function bendPoints(pts=[], x=0, r=1) = let(
c = 2 * PI *r,
degPerUnit = 360 / c,
e=echo(degPerUnit),
seg = _pu_segmentsPerCircle(r),
_pts = maxXDist(pts, c/seg)
) [for (pt=_pts)let(
dx = pt[0] - x,
d = degPerUnit * dx,
np = [sin(d) * r, pt[1], cos(d) * r]
) [sin(d) * r, pt[1], cos(d) * r]];
// openSCAD polyline offset routine retrieved from newgroup https://forum.openscad.org/Polygon-Offset-Function-td17186.html
//
// Offsets a list of points around along three axis (x, y,z). Although the input can be a 2D point, the output
// will be a 3D point.
// The return value is a 3D points list
// pts (list) List of zero or more 2D or 3D points.
// angles (list) angles along X, Y and Z axis in that order. Angles in degrees.
// z_offset (number) moves the final z value by Z-offset.
// return (list) Rotated list of 3D points.
function offsetPoints(pts, dist) =
_os_offset_poly(_os_iterative_remove_edges(pts, dist), dist);
// Orders the members of each point in the points list and removes or adds members as required.
// The return value is a 1D,2D or 3D points list
// pts (list) List of zero or more 1D, 2D or 3D points.
// order (list) Index list for member order. [0,1,2] causes no change. [1,0,2] swaps X and Y
// defaults (list) List of member values if none is found. This makes it possible to turn 2D lists into 3D lists
// return (list) re-ordered list of 1D, 2D or 3D points.
function orderPoints(pts, order=[0,1,2], defaults=[0,0,0]) = [for(pt = pts) [for(i=[0:len(order)-1]) order[i]>=len(pt)? defaults[i] : pt[order[i]]] ];
// *********************************************************************************************************************************
// Helper functions
// function sp(r)
//
// SP (Show points) to quickly visualise a list of points.
//
// pts (list) List of 2D or 3D points.
// r (number) Radius of the point markers.
// color (string) Color of the point markers.
module sp(pts=[], r=1, color="yellow"){
for(pt=pts) translate(pt) color(color) sphere(r=r);
}
// function _pu_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 _pu_segmentsPerCircle(r=1) = $fn>0?($fn>=3?$fn:3):ceil(max(min(360/$fa,abs(r)*2*PI/$fs),5));
// function distanceBetween calculates the distance between two 2D or 3D points
function distanceBetween(pt1, pt2) = sqrt(pow(pt2[0] - pt1[0], 2) + pow(pt2[1] - pt1[1], 2) + pow(pt2[2]==undef? 0 : pt2[2] - pt1[2]==undef? 0 : pt1[2], 2));
// function lengthPoints(pts=[])
//
// Calculates the length of a point list by adding up the distances between all points.
// this can be 2D or 3D points.
// pts (list) 2D or 3D point list
// return (number) Total length of the path.
function lengthPoints(pts=[], _i=0, _sum=0) = _i<len(pts)-2? lengthPoints(pts, _i+1, _sum + distanceBetween(pts[_i], pts[_i+1])) : _sum;
// given two points, a, b, find equation for line that is parallel to line
// segment but offset to the right by offset dist
// equation is of the form c*x+d*y=e
// represented as array [ c, d, e ]
function _os_seg2eq(pa, pb, dist) =
let (ab = [pb[0]-pa[0], pb[1]-pa[1]])
let (abl_un = [-ab[1], ab[0]])
let (abl_len = sqrt(abl_un[0]*abl_un[0] + abl_un[1]*abl_un[1]))
let (abl = [ abl_un[0]/abl_len, abl_un[1]/abl_len ])
[ abl[0], abl[1], abl[0]*pa[0] + abl[1]*pa[1] - dist ];
// given two equations for lines, solve two equations to find intersection
function _os_solve2eq(eq1, eq2) =
let (a=eq1[0], b=eq1[1], c = eq1[2], d=eq2[0], e=eq2[1], f=eq2[2])
let (det=a*e-b*d)
[ (e*c-b*f)/det, (-d*c+a*f)/det ];
// given a corner as two line segments, AB and BC, find the new corner B' that results
// when both line segments are offet. Works by generating two equations and then solving
function _os_offset_corner(pa, pb, pc, dist) =
_os_solve2eq(_os_seg2eq(pa, pb, dist), _os_seg2eq(pb, pc, dist));
// given a polygon, offset each vertex using the corner offset method above
// note: this can produce self-intersections depending on the 'curvature' and offset
function _os_offset_poly(p, dist) = [
for (i=[0:len(p)-1])
i == 0 ? _os_offset_corner(p[len(p)-1], p[i], p[i+1], dist) :
i == len(p)-1 ? _os_offset_corner(p[i-1], p[i], p[0], dist) :
_os_offset_corner(p[i-1], p[i], p[i+1], dist)
];
// each segment of a polygon will 'vanish' at some offset value (unless adjacent
// segments are parallel). Compute the offset at which each segment vanishes
// (produces 'nan' or plus or minus infinity when adjacent segments parallel)
function _os_offset_limit(p) =
let (N = len(p))
let (offp = _os_offset_poly(p, 1)) // each vertex adjusted for unit of offset
[ for (i=[0:N-1]) // for each segment
// equations for segment for each vertex (angle bisector)
let (eqv1 = _os_seg2eq(p[i], offp[i], 0), eqv2 = _os_seg2eq(p[(i+1)%N], offp[(i+1)%N], 0))
let (singv = _os_solve2eq(eqv1, eqv2)) // 'singular' vertex where edge vanishes
let (offv = offp[i]-p[i]) // vertex shifts this much per unit of offset
let (targetv = singv - p[i]) // what offset produces this coordinate?
// essentially quotient of lengths, but need to take into account negative offsets
// use dot product to find cosine of angle, should be 1 or -1
let (sgn = (offv[0]*targetv[0] + offv[1]*targetv[1])/(norm(offv)*norm(targetv)))
sgn * norm(targetv)/norm(offv)
]
;
// transform polygon into sequence of edges as equations, skipping flipped edges
// and then transform back into vertices by solving adjacent equations
function _os_remove_edges(p, dist) =
let (N = len(p), offlim = _os_offset_limit(p))
let (eqlist = [ for (i=[0:N-1])
if (!(dist/offlim[i] > 1))
_os_seg2eq(p[i], p[(i+1)%N], 0)
])
let (N2 = len(eqlist))
[ for (i=[0:N2-1])
_os_solve2eq(eqlist[(i+N2-1)%N2], eqlist[i]) ];
function _os_iterative_remove_edges(p, dist) =
let (cleaned = _os_remove_edges(p, dist))
len(cleaned) == len(p) ? p : _os_iterative_remove_edges(cleaned, dist);
// given a polygon, offset each vertex using the corner offset method above
// note: this can produce self-intersections depending on the 'curvature' and offset
function offset_poly(pts, dist) =
_offset_poly(_iterative_remove_edges(pts, dist), dist);