|
| 1 | +import math |
| 2 | + |
| 3 | +def intersection(cline1, cline2): |
| 4 | + """Return intersection (x,y) of 2 clines expressed as (a,b,c) coeff.""" |
| 5 | + a, b, c = cline1 |
| 6 | + d, e, f = cline2 |
| 7 | + i = b*f-c*e |
| 8 | + j = c*d-a*f |
| 9 | + k = a*e-b*d |
| 10 | + if k: |
| 11 | + return (i/k, j/k) |
| 12 | + return None |
| 13 | + |
| 14 | + |
| 15 | +def cnvrt_2pts_to_coef(pt1, pt2): |
| 16 | + """Return (a,b,c) coefficients of cline defined by 2 (x,y) pts.""" |
| 17 | + x1, y1 = pt1 |
| 18 | + x2, y2 = pt2 |
| 19 | + a = y2 - y1 |
| 20 | + b = x1 - x2 |
| 21 | + c = x2*y1-x1*y2 |
| 22 | + return (a, b, c) |
| 23 | + |
| 24 | + |
| 25 | +def proj_pt_on_line(cline, pt): |
| 26 | + """Return point which is the projection of pt on cline.""" |
| 27 | + a, b, c = cline |
| 28 | + x, y = pt |
| 29 | + denom = a**2 + b**2 |
| 30 | + if not denom: |
| 31 | + return pt |
| 32 | + xp = (b**2 * x - a*b*y - a*c) / denom |
| 33 | + yp = (a**2 * y - a*b*x - b*c) / denom |
| 34 | + return (xp, yp) |
| 35 | + |
| 36 | + |
| 37 | +def pnt_in_box_p(pnt, box): |
| 38 | + '''Point in box predicate: Return True if pnt is in box.''' |
| 39 | + x, y = pnt |
| 40 | + x1, y1, x2, y2 = box |
| 41 | + return bool(x1 < x < x2 and y1 < y < y2) |
| 42 | + |
| 43 | + |
| 44 | +def midpoint(p1, p2, f=.5): |
| 45 | + """Return point part way (f=.5 by def) between points p1 and p2.""" |
| 46 | + return (((p2[0]-p1[0])*f)+p1[0], ((p2[1]-p1[1])*f)+p1[1]) |
| 47 | + |
| 48 | + |
| 49 | +def p2p_dist(p1, p2): |
| 50 | + """Return the distance between two points""" |
| 51 | + x, y = p1 |
| 52 | + u, v = p2 |
| 53 | + return math.sqrt((x-u)**2 + (y-v)**2) |
| 54 | + |
| 55 | + |
| 56 | +def p2p_angle(p0, p1): |
| 57 | + """Return angle (degrees) from p0 to p1.""" |
| 58 | + return math.atan2(p1[1]-p0[1], p1[0]-p0[0])*180/math.pi |
| 59 | + |
| 60 | + |
| 61 | +def add_pt(p0, p1): |
| 62 | + return (p0[0]+p1[0], p0[1]+p1[1]) |
| 63 | + |
| 64 | + |
| 65 | +def sub_pt(p0, p1): |
| 66 | + return (p0[0]-p1[0], p0[1]-p1[1]) |
| 67 | + |
| 68 | + |
| 69 | +def line_circ_inters(x1, y1, x2, y2, xc, yc, r): |
| 70 | + '''Return list of intersection pts of line defined by pts x1,y1 and x2,y2 |
| 71 | + and circle (cntr xc,yc and radius r). |
| 72 | + Uses algorithm from Paul Bourke's web page.''' |
| 73 | + intpnts = [] |
| 74 | + num = (xc - x1)*(x2 - x1) + (yc - y1)*(y2 - y1) |
| 75 | + denom = (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) |
| 76 | + if denom == 0: |
| 77 | + return |
| 78 | + u = num / denom |
| 79 | + xp = x1 + u*(x2-x1) |
| 80 | + yp = y1 + u*(y2-y1) |
| 81 | + |
| 82 | + a = (x2 - x1)**2 + (y2 - y1)**2 |
| 83 | + b = 2*((x2-x1)*(x1-xc) + (y2-y1)*(y1-yc)) |
| 84 | + c = xc**2+yc**2+x1**2+y1**2-2*(xc*x1+yc*y1)-r**2 |
| 85 | + q = b**2 - 4*a*c |
| 86 | + if q == 0: |
| 87 | + intpnts.append((xp, yp)) |
| 88 | + elif q: |
| 89 | + u1 = (-b+math.sqrt(abs(q)))/(2*a) |
| 90 | + u2 = (-b-math.sqrt(abs(q)))/(2*a) |
| 91 | + intpnts.append(((x1 + u1*(x2-x1)), (y1 + u1*(y2-y1)))) |
| 92 | + intpnts.append(((x1 + u2*(x2-x1)), (y1 + u2*(y2-y1)))) |
| 93 | + return intpnts |
| 94 | + |
| 95 | + |
| 96 | +def circ_circ_inters(x1, y1, r1, x2, y2, r2): |
| 97 | + '''Return list of intersection pts of 2 circles. |
| 98 | + Uses algorithm from Robert S. Wilson's web page.''' |
| 99 | + pts = [] |
| 100 | + D = (x2-x1)**2 + (y2-y1)**2 |
| 101 | + if not D: |
| 102 | + return pts # circles have same cntr; no intersection |
| 103 | + q = math.sqrt(abs(((r1+r2)**2-D)*(D-(r2-r1)**2))) |
| 104 | + pts = [((x2+x1)/2+(x2-x1)*(r1**2-r2**2)/(2*D)+(y2-y1)*q/(2*D), |
| 105 | + (y2+y1)/2+(y2-y1)*(r1**2-r2**2)/(2*D)-(x2-x1)*q/(2*D)), |
| 106 | + ((x2+x1)/2+(x2-x1)*(r1**2-r2**2)/(2*D)-(y2-y1)*q/(2*D), |
| 107 | + (y2+y1)/2+(y2-y1)*(r1**2-r2**2)/(2*D)+(x2-x1)*q/(2*D))] |
| 108 | + if same_pt_p(pts[0], pts[1]): |
| 109 | + pts.pop() # circles are tangent |
| 110 | + return pts |
| 111 | + |
| 112 | + |
| 113 | +def same_pt_p(p1, p2): |
| 114 | + '''Return True if p1 and p2 are within 1e-10 of each other.''' |
| 115 | + return bool(p2p_dist(p1, p2) < 1e-6) |
| 116 | + |
| 117 | + |
| 118 | +def cline_box_intrsctn(cline, box): |
| 119 | + """Return tuple of pts where line intersects edges of box.""" |
| 120 | + x0, y0, x1, y1 = box |
| 121 | + pts = [] |
| 122 | + segments = [((x0, y0), (x1, y0)), |
| 123 | + ((x1, y0), (x1, y1)), |
| 124 | + ((x1, y1), (x0, y1)), |
| 125 | + ((x0, y1), (x0, y0))] |
| 126 | + for seg in segments: |
| 127 | + pt = intersection(cline, cnvrt_2pts_to_coef(seg[0], seg[1])) |
| 128 | + if pt: |
| 129 | + if p2p_dist(pt, seg[0]) <= p2p_dist(seg[0], seg[1]) and \ |
| 130 | + p2p_dist(pt, seg[1]) <= p2p_dist(seg[0], seg[1]): |
| 131 | + if pt not in pts: |
| 132 | + pts.append(pt) |
| 133 | + return tuple(pts) |
| 134 | + |
| 135 | + |
| 136 | +def para_line(cline, pt): |
| 137 | + """Return coeff of newline thru pt and parallel to cline.""" |
| 138 | + a, b, c = cline |
| 139 | + x, y = pt |
| 140 | + cnew = -(a*x + b*y) |
| 141 | + return (a, b, cnew) |
| 142 | + |
| 143 | + |
| 144 | +def para_lines(cline, d): |
| 145 | + """Return 2 parallel lines straddling line, offset d.""" |
| 146 | + a, b, c = cline |
| 147 | + c1 = math.sqrt(a**2 + b**2)*d |
| 148 | + cline1 = (a, b, c + c1) |
| 149 | + cline2 = (a, b, c - c1) |
| 150 | + return (cline1, cline2) |
| 151 | + |
| 152 | + |
| 153 | +def perp_line(cline, pt): |
| 154 | + """Return coeff of newline thru pt and perpend to cline.""" |
| 155 | + a, b, c = cline |
| 156 | + x, y = pt |
| 157 | + cnew = a*y - b*x |
| 158 | + return (b, -a, cnew) |
| 159 | + |
| 160 | + |
| 161 | +def closer(p0, p1, p2): |
| 162 | + """Return closer of p1 or p2 to point p0.""" |
| 163 | + d1 = (p1[0] - p0[0])**2 + (p1[1] - p0[1])**2 |
| 164 | + d2 = (p2[0] - p0[0])**2 + (p2[1] - p0[1])**2 |
| 165 | + if d1 < d2: |
| 166 | + return p1 |
| 167 | + return p2 |
| 168 | + |
| 169 | + |
| 170 | +def farther(p0, p1, p2): |
| 171 | + """Return farther of p1 or p2 from point p0.""" |
| 172 | + d1 = (p1[0] - p0[0])**2 + (p1[1] - p0[1])**2 |
| 173 | + d2 = (p2[0] - p0[0])**2 + (p2[1] - p0[1])**2 |
| 174 | + if d1 > d2: |
| 175 | + return p1 |
| 176 | + return p2 |
| 177 | + |
| 178 | + |
| 179 | +def find_fillet_pts(r, commonpt, end1, end2): |
| 180 | + """Return ctr of fillet (radius r) and tangent pts for corner |
| 181 | + defined by a common pt, and two adjacent corner pts.""" |
| 182 | + line1 = cnvrt_2pts_to_coef(commonpt, end1) |
| 183 | + line2 = cnvrt_2pts_to_coef(commonpt, end2) |
| 184 | + # find 'interior' clines |
| 185 | + cl1a, cl1b = para_lines(line1, r) |
| 186 | + p2a = proj_pt_on_line(cl1a, end2) |
| 187 | + p2b = proj_pt_on_line(cl1b, end2) |
| 188 | + da = p2p_dist(p2a, end2) |
| 189 | + db = p2p_dist(p2b, end2) |
| 190 | + if da <= db: |
| 191 | + cl1 = cl1a |
| 192 | + else: |
| 193 | + cl1 = cl1b |
| 194 | + cl2a, cl2b = para_lines(line2, r) |
| 195 | + p1a = proj_pt_on_line(cl2a, end1) |
| 196 | + p1b = proj_pt_on_line(cl2b, end1) |
| 197 | + da = p2p_dist(p1a, end1) |
| 198 | + db = p2p_dist(p1b, end1) |
| 199 | + if da <= db: |
| 200 | + cl2 = cl2a |
| 201 | + else: |
| 202 | + cl2 = cl2b |
| 203 | + pc = intersection(cl1, cl2) |
| 204 | + p1 = proj_pt_on_line(line1, pc) |
| 205 | + p2 = proj_pt_on_line(line2, pc) |
| 206 | + return (pc, p1, p2) |
| 207 | + |
| 208 | + |
| 209 | +def find_common_pt(apair, bpair): |
| 210 | + """Return (common pt, other pt from a, other pt from b), where a and b |
| 211 | + are coordinate pt pairs in (p1, p2) format.""" |
| 212 | + p0, p1 = apair |
| 213 | + p2, p3 = bpair |
| 214 | + if same_pt_p(p0, p2): |
| 215 | + cp = p0 # common pt |
| 216 | + opa = p1 # other pt a |
| 217 | + opb = p3 # other pt b |
| 218 | + elif same_pt_p(p0, p3): |
| 219 | + cp = p0 |
| 220 | + opa = p1 |
| 221 | + opb = p2 |
| 222 | + elif same_pt_p(p1, p2): |
| 223 | + cp = p1 |
| 224 | + opa = p0 |
| 225 | + opb = p3 |
| 226 | + elif same_pt_p(p1, p3): |
| 227 | + cp = p1 |
| 228 | + opa = p0 |
| 229 | + opb = p2 |
| 230 | + else: |
| 231 | + return |
| 232 | + return (cp, opa, opb) |
| 233 | + |
| 234 | + |
| 235 | +def cr_from_3p(p1, p2, p3): |
| 236 | + """Return ctr pt and radius of circle on which 3 pts reside. |
| 237 | + From Paul Bourke's web page.""" |
| 238 | + chord1 = cnvrt_2pts_to_coef(p1, p2) |
| 239 | + chord2 = cnvrt_2pts_to_coef(p2, p3) |
| 240 | + radial_line1 = perp_line(chord1, midpoint(p1, p2)) |
| 241 | + radial_line2 = perp_line(chord2, midpoint(p2, p3)) |
| 242 | + ctr = intersection(radial_line1, radial_line2) |
| 243 | + if ctr: |
| 244 | + radius = p2p_dist(p1, ctr) |
| 245 | + return (ctr, radius) |
| 246 | + |
| 247 | + |
| 248 | +def extendline(p0, p1, d): |
| 249 | + """Return point which lies on extension of line segment p0-p1, |
| 250 | + beyond p1 by distance d.""" |
| 251 | + pts = line_circ_inters(p0[0], p0[1], p1[0], p1[1], p1[0], p1[1], d) |
| 252 | + if pts: |
| 253 | + return farther(p0, pts[0], pts[1]) |
| 254 | + return |
| 255 | + |
| 256 | + |
| 257 | +def shortenline(p0, p1, d): |
| 258 | + """Return point which lies on line segment p0-p1, |
| 259 | + short of p1 by distance d.""" |
| 260 | + pts = line_circ_inters(p0[0], p0[1], p1[0], p1[1], p1[0], p1[1], d) |
| 261 | + if pts: |
| 262 | + return closer(p0, pts[0], pts[1]) |
| 263 | + return |
| 264 | + |
| 265 | + |
| 266 | +def line_tan_to_circ(circ, p): |
| 267 | + """Return tan pts on circ of line through p.""" |
| 268 | + c, r = circ |
| 269 | + d = p2p_dist(c, p) |
| 270 | + ang0 = p2p_angle(c, p)*math.pi/180 |
| 271 | + theta = math.asin(r/d) |
| 272 | + ang1 = ang0+math.pi/2-theta |
| 273 | + ang2 = ang0-math.pi/2+theta |
| 274 | + p1 = (c[0]+(r*math.cos(ang1)), c[1]+(r*math.sin(ang1))) |
| 275 | + p2 = (c[0]+(r*math.cos(ang2)), c[1]+(r*math.sin(ang2))) |
| 276 | + return (p1, p2) |
| 277 | + |
| 278 | + |
| 279 | +def line_tan_to_2circs(circ1, circ2): |
| 280 | + """Return tangent pts on line tangent to 2 circles. |
| 281 | + Order of circle picks determines which tangent line.""" |
| 282 | + c1, r1 = circ1 |
| 283 | + c2, r2 = circ2 |
| 284 | + d = p2p_dist(c1, c2) # distance between centers |
| 285 | + ang_loc = p2p_angle(c2, c1)*math.pi/180 # angle of line of centers |
| 286 | + f = (r2/r1-1)/d # reciprocal dist from c1 to intersection of loc & tan line |
| 287 | + theta = math.asin(r1*f) # angle between loc and tangent line |
| 288 | + ang1 = (ang_loc + math.pi/2 - theta) |
| 289 | + # ang2 = (ang_loc - math.pi/2 + theta) # unused |
| 290 | + p1 = (c1[0]+(r1*math.cos(ang1)), c1[1]+(r1*math.sin(ang1))) |
| 291 | + p2 = (c2[0]+(r2*math.cos(ang1)), c2[1]+(r2*math.sin(ang1))) |
| 292 | + return (p1, p2) |
| 293 | + |
| 294 | + |
| 295 | +def angled_cline(pt, angle): |
| 296 | + """Return cline through pt at angle (degrees)""" |
| 297 | + ang = angle * math.pi / 180 |
| 298 | + dx = math.cos(ang) |
| 299 | + dy = math.sin(ang) |
| 300 | + p2 = (pt[0]+dx, pt[1]+dy) |
| 301 | + cline = cnvrt_2pts_to_coef(pt, p2) |
| 302 | + return cline |
| 303 | + |
| 304 | + |
| 305 | +def ang_bisector(p0, p1, p2, f=0.5): |
| 306 | + """Return cline coefficients of line through vertex p0, factor=f |
| 307 | + between p1 and p2.""" |
| 308 | + ang1 = math.atan2(p1[1]-p0[1], p1[0]-p0[0]) |
| 309 | + ang2 = math.atan2(p2[1]-p0[1], p2[0]-p0[0]) |
| 310 | + deltang = ang2 - ang1 |
| 311 | + ang3 = (f * deltang + ang1)*180/math.pi |
| 312 | + return angled_cline(p0, ang3) |
| 313 | + |
| 314 | + |
| 315 | +def pt_on_RHS_p(pt, p0, p1): |
| 316 | + """Return True if pt is on right hand side going from p0 to p1.""" |
| 317 | + angline = p2p_angle(p0, p1) |
| 318 | + angpt = p2p_angle(p0, pt) |
| 319 | + if angline >= 0: |
| 320 | + if angline > angpt > angline-180: |
| 321 | + return True |
| 322 | + else: |
| 323 | + angline += 360 |
| 324 | + if angpt < 0: |
| 325 | + angpt += 360 |
| 326 | + if angline > angpt > angline-180: |
| 327 | + return True |
| 328 | + |
| 329 | + |
| 330 | +def rotate_pt(pt, ang, ctr): |
| 331 | + """Return coordinates of pt rotated ang (deg) CCW about ctr. |
| 332 | +
|
| 333 | + This is a 3-step process: |
| 334 | + 1. translate to place ctr at origin. |
| 335 | + 2. rotate about origin (CCW version of Paul Bourke's algorithm. |
| 336 | + 3. apply inverse translation. """ |
| 337 | + x, y = sub_pt(pt, ctr) |
| 338 | + A = ang * math.pi / 180 |
| 339 | + u = x * math.cos(A) - y * math.sin(A) |
| 340 | + v = y * math.cos(A) + x * math.sin(A) |
| 341 | + return add_pt((u, v), ctr) |
| 342 | + |
0 commit comments