1#!/usr/local/bin/python3.8 2# coding=utf-8 3# 4# Copyright (C) 2007 John Beard john.j.beard@gmail.com 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19# 20""" 21This extension allows you to draw various triangle constructions 22It requires a path to be selected 23It will use the first three nodes of this path 24 25Dimensions of a triangle__ 26 27 /`__ 28 / a_c``--__ 29 / ``--__ s_a 30s_b / ``--__ 31 /a_a a_b`--__ 32 /--------------------------------``B 33 A s_b 34""" 35 36from math import acos, cos, pi, sin, sqrt, tan 37 38import inkex 39from inkex import PathElement, Circle 40 41(X, Y) = range(2) 42 43# DRAWING ROUTINES 44 45# draw an SVG triangle given in trilinar coords 46def draw_SVG_circle(rad, centre, params, style, name, parent): # draw an SVG circle with a given radius as trilinear coordinates 47 if rad == 0: # we want a dot 48 r = style.d_rad # get the dot width from the style 49 circ_style = {'stroke': style.d_col, 'stroke-width': str(style.d_th), 'fill': style.d_fill} 50 else: 51 r = rad # use given value 52 circ_style = {'stroke': style.c_col, 'stroke-width': str(style.c_th), 'fill': style.c_fill} 53 54 cx, cy = get_cartesian_pt(centre, params) 55 circ_attribs = {'cx': str(cx), 'cy': str(cy), 'r': str(r)} 56 elem = parent.add(Circle(**circ_attribs)) 57 elem.style = circ_style 58 elem.label = name 59 60 61# draw an SVG triangle given in trilinar coords 62def draw_SVG_tri(vert_mat, params, style, name, parent): 63 p1, p2, p3 = get_cartesian_tri(vert_mat, params) # get the vertex matrix in cartesian points 64 elem = parent.add(PathElement()) 65 elem.path = 'M ' + str(p1[0]) + ',' + str(p1[1]) +\ 66 ' L ' + str(p2[0]) + ',' + str(p2[1]) +\ 67 ' L ' + str(p3[0]) + ',' + str(p3[1]) +\ 68 ' L ' + str(p1[0]) + ',' + str(p1[1]) + ' z' 69 elem.style = {'stroke': style.l_col, 'stroke-width': str(style.l_th), 'fill': style.l_fill} 70 elem.label = name 71 72 73# draw an SVG line segment between the given (raw) points 74def draw_SVG_line(a, b, style, name, parent): 75 (x1, y1) = a 76 (x2, y2) = b 77 line = parent.add(PathElement()) 78 line.style = {'stroke': style.l_col, 'stroke-width': str(style.l_th), 'fill': style.l_fill} 79 line.path = 'M ' + str(x1) + ',' + str(y1) + ' L ' + str(x2) + ',' + str(y2) 80 line.lavel = name 81 82 83# lines from each vertex to a corresponding point in trilinears 84def draw_vertex_lines(vert_mat, params, width, name, parent): 85 for i in range(3): 86 oppositepoint = get_cartesian_pt(vert_mat[i], params) 87 draw_SVG_line(params[3][-i % 3], oppositepoint, width, name + ':' + str(i), parent) 88 89 90# MATHEMATICAL ROUTINES 91 92def distance(a, b): 93 """find the pythagorean distance""" 94 (x0, y0) = a 95 (x1, y1) = b 96 return sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1)) 97 98 99def vector_from_to(a, b): 100 """get the vector from (x0,y0) to (x1,y1)""" 101 return b[X] - a[X], b[Y], a[Y] 102 103 104def get_cartesian_pt(t, p): # get the cartesian coordinates from a trilinear set 105 denom = p[0][0] * t[0] + p[0][1] * t[1] + p[0][2] * t[2] 106 c1 = p[0][1] * t[1] / denom 107 c2 = p[0][2] * t[2] / denom 108 return c1 * p[2][1][0] + c2 * p[2][0][0], c1 * p[2][1][1] + c2 * p[2][0][1] 109 110 111def get_cartesian_tri(arg, params): 112 """get the cartesian points from a trilinear vertex matrix""" 113 (t11, t12, t13), (t21, t22, t23), (t31, t32, t33) = arg 114 p1 = get_cartesian_pt((t11, t12, t13), params) 115 p2 = get_cartesian_pt((t21, t22, t23), params) 116 p3 = get_cartesian_pt((t31, t32, t33), params) 117 return p1, p2, p3 118 119 120def angle_from_3_sides(a, b, c): # return the angle opposite side c 121 cosx = (a * a + b * b - c * c) / (2 * a * b) # use the cosine rule 122 return acos(cosx) 123 124 125def translate_string(string, os): # translates s_a, a_a, etc to params[x][y], with cyclic offset 126 string = string.replace('s_a', 'params[0][' + str((os + 0) % 3) + ']') # replace with ref. to the relvant values, 127 string = string.replace('s_b', 'params[0][' + str((os + 1) % 3) + ']') # cycled by i 128 string = string.replace('s_c', 'params[0][' + str((os + 2) % 3) + ']') 129 string = string.replace('a_a', 'params[1][' + str((os + 0) % 3) + ']') 130 string = string.replace('a_b', 'params[1][' + str((os + 1) % 3) + ']') 131 string = string.replace('a_c', 'params[1][' + str((os + 2) % 3) + ']') 132 string = string.replace('area', 'params[4][0]') 133 string = string.replace('semiperim', 'params[4][1]') 134 return string 135 136 137def pt_from_tcf(tcf, params): # returns a trilinear triplet from a triangle centre function 138 trilin_pts = [] # will hold the final points 139 for i in range(3): 140 temp = tcf # read in the tcf 141 temp = translate_string(temp, i) 142 func = eval('lambda params: ' + temp.strip('"')) # the function leading to the trilinar element 143 trilin_pts.append(func(params)) # evaluate the function for the first trilinear element 144 return trilin_pts 145 146 147# SVG DATA PROCESSING 148 149def get_n_points_from_path(node, n): 150 """returns a list of first n points (x,y) in an SVG path-representing node""" 151 points = list(node.path.control_points) 152 if len(points) < 3: 153 return [] 154 return points[:3] 155 156# EXTRA MATHS FUNCTIONS 157def sec(x): # secant(x) 158 if x == pi / 2 or x == -pi / 2 or x == 3 * pi / 2 or x == -3 * pi / 2: # sec(x) is undefined 159 return 100000000000 160 else: 161 return 1 / cos(x) 162 163 164def csc(x): # cosecant(x) 165 if x == 0 or x == pi or x == 2 * pi or x == -2 * pi: # csc(x) is undefined 166 return 100000000000 167 else: 168 return 1 / sin(x) 169 170 171def cot(x): # cotangent(x) 172 if x == 0 or x == pi or x == 2 * pi or x == -2 * pi: # cot(x) is undefined 173 return 100000000000 174 else: 175 return 1 / tan(x) 176 177 178class Style(object): # container for style information 179 def __init__(self, svg): 180 # dot markers 181 self.d_rad = svg.unittouu('4px') # dot marker radius 182 self.d_th = svg.unittouu('2px') # stroke width 183 self.d_fill = '#aaaaaa' # fill colour 184 self.d_col = '#000000' # stroke colour 185 186 # lines 187 self.l_th = svg.unittouu('2px') 188 self.l_fill = 'none' 189 self.l_col = '#000000' 190 191 # circles 192 self.c_th = svg.unittouu('2px') 193 self.c_fill = 'none' 194 self.c_col = '#000000' 195 196 197class DrawFromTriangle(inkex.EffectExtension): 198 def add_arguments(self, pars): 199 pars.add_argument("--tab") 200 # PRESET POINT OPTIONS 201 pars.add_argument("--circumcircle", type=inkex.Boolean, default=False) 202 pars.add_argument("--circumcentre", type=inkex.Boolean, default=False) 203 pars.add_argument("--incircle", type=inkex.Boolean, default=False) 204 pars.add_argument("--incentre", type=inkex.Boolean, default=False) 205 pars.add_argument("--contact_tri", type=inkex.Boolean, default=False) 206 pars.add_argument("--excircles", type=inkex.Boolean, default=False) 207 pars.add_argument("--excentres", type=inkex.Boolean, default=False) 208 pars.add_argument("--extouch_tri", type=inkex.Boolean, default=False) 209 pars.add_argument("--excentral_tri", type=inkex.Boolean, default=False) 210 pars.add_argument("--orthocentre", type=inkex.Boolean, default=False) 211 pars.add_argument("--orthic_tri", type=inkex.Boolean, default=False) 212 pars.add_argument("--altitudes", type=inkex.Boolean, default=False) 213 pars.add_argument("--anglebisectors", type=inkex.Boolean, default=False) 214 pars.add_argument("--centroid", type=inkex.Boolean, default=False) 215 pars.add_argument("--ninepointcentre", type=inkex.Boolean, default=False) 216 pars.add_argument("--ninepointcircle", type=inkex.Boolean, default=False) 217 pars.add_argument("--symmedians", type=inkex.Boolean, default=False) 218 pars.add_argument("--sym_point", type=inkex.Boolean, default=False) 219 pars.add_argument("--sym_tri", type=inkex.Boolean, default=False) 220 pars.add_argument("--gergonne_pt", type=inkex.Boolean, default=False) 221 pars.add_argument("--nagel_pt", type=inkex.Boolean, default=False) 222 # CUSTOM POINT OPTIONS 223 pars.add_argument("--mode", default='trilin') 224 pars.add_argument("--cust_str", default='s_a') 225 pars.add_argument("--cust_pt", type=inkex.Boolean, default=False) 226 pars.add_argument("--cust_radius", type=inkex.Boolean, default=False) 227 pars.add_argument("--radius", default='s_a') 228 pars.add_argument("--isogonal_conj", type=inkex.Boolean, default=False) 229 pars.add_argument("--isotomic_conj", type=inkex.Boolean, default=False) 230 231 def effect(self): 232 so = self.options # shorthand 233 234 pts = [] # initialise in case nothing is selected and following loop is not executed 235 for node in self.svg.selection.filter(inkex.PathElement): 236 # find the (x,y) coordinates of the first 3 points of the path 237 pts = get_n_points_from_path(node, 3) 238 239 if len(pts) == 3: # if we have right number of nodes, else skip and end program 240 st = Style(self.svg) # style for dots, lines and circles 241 242 # CREATE A GROUP TO HOLD ALL GENERATED ELEMENTS IN 243 # Hold relative to point A (pt[0]) 244 layer = self.svg.get_current_layer().add(inkex.Group.new('TriangleElements')) 245 layer.transform = 'translate(' + str(pts[0][0]) + ',' + str(pts[0][1]) + ')' 246 247 # GET METRICS OF THE TRIANGLE 248 # vertices in the local coordinates (set pt[0] to be the origin) 249 vtx = [[0, 0], 250 [pts[1][0] - pts[0][0], pts[1][1] - pts[0][1]], 251 [pts[2][0] - pts[0][0], pts[2][1] - pts[0][1]]] 252 253 s_a = distance(vtx[1], vtx[2]) # get the scalar side lengths 254 s_b = distance(vtx[0], vtx[1]) 255 s_c = distance(vtx[0], vtx[2]) 256 sides = (s_a, s_b, s_c) # side list for passing to functions easily and for indexing 257 258 a_a = angle_from_3_sides(s_b, s_c, s_a) # angles in radians 259 a_b = angle_from_3_sides(s_a, s_c, s_b) 260 a_c = angle_from_3_sides(s_a, s_b, s_c) 261 angles = (a_a, a_b, a_c) 262 263 ab = vector_from_to(vtx[0], vtx[1]) # vector from a to b 264 ac = vector_from_to(vtx[0], vtx[2]) # vector from a to c 265 bc = vector_from_to(vtx[1], vtx[2]) # vector from b to c 266 vecs = (ab, ac) # vectors for finding cartesian point from trilinears 267 268 semiperim = (s_a + s_b + s_c) / 2.0 # semiperimeter 269 area = sqrt(semiperim * (semiperim - s_a) * (semiperim - s_b) * (semiperim - s_c)) # area of the triangle by heron's formula 270 uvals = (area, semiperim) # useful values 271 272 params = (sides, angles, vecs, vtx, uvals) # all useful triangle parameters in one object 273 274 # BEGIN DRAWING 275 if so.circumcentre or so.circumcircle: 276 r = s_a * s_b * s_c / (4 * area) 277 pt = (cos(a_a), cos(a_b), cos(a_c)) 278 if so.circumcentre: 279 draw_SVG_circle(0, pt, params, st, 'Circumcentre', layer) 280 if so.circumcircle: 281 draw_SVG_circle(r, pt, params, st, 'Circumcircle', layer) 282 283 if so.incentre or so.incircle: 284 pt = [1, 1, 1] 285 if so.incentre: 286 draw_SVG_circle(0, pt, params, st, 'Incentre', layer) 287 if so.incircle: 288 r = area / semiperim 289 draw_SVG_circle(r, pt, params, st, 'Incircle', layer) 290 291 if so.contact_tri: 292 t1 = s_b * s_c / (-s_a + s_b + s_c) 293 t2 = s_a * s_c / (s_a - s_b + s_c) 294 t3 = s_a * s_b / (s_a + s_b - s_c) 295 v_mat = ((0, t2, t3), (t1, 0, t3), (t1, t2, 0)) 296 draw_SVG_tri(v_mat, params, st, 'ContactTriangle', layer) 297 298 if so.extouch_tri: 299 t1 = (-s_a + s_b + s_c) / s_a 300 t2 = (s_a - s_b + s_c) / s_b 301 t3 = (s_a + s_b - s_c) / s_c 302 v_mat = ((0, t2, t3), (t1, 0, t3), (t1, t2, 0)) 303 draw_SVG_tri(v_mat, params, st, 'ExtouchTriangle', layer) 304 305 if so.orthocentre: 306 pt = pt_from_tcf('cos(a_b)*cos(a_c)', params) 307 draw_SVG_circle(0, pt, params, st, 'Orthocentre', layer) 308 309 if so.orthic_tri: 310 v_mat = [[0, sec(a_b), sec(a_c)], [sec(a_a), 0, sec(a_c)], [sec(a_a), sec(a_b), 0]] 311 draw_SVG_tri(v_mat, params, st, 'OrthicTriangle', layer) 312 313 if so.centroid: 314 pt = [1 / s_a, 1 / s_b, 1 / s_c] 315 draw_SVG_circle(0, pt, params, st, 'Centroid', layer) 316 317 if so.ninepointcentre or so.ninepointcircle: 318 pt = [cos(a_b - a_c), cos(a_c - a_a), cos(a_a - a_b)] 319 if so.ninepointcentre: 320 draw_SVG_circle(0, pt, params, st, 'NinePointCentre', layer) 321 if so.ninepointcircle: 322 r = s_a * s_b * s_c / (8 * area) 323 draw_SVG_circle(r, pt, params, st, 'NinePointCircle', layer) 324 325 if so.altitudes: 326 v_mat = [[0, sec(a_b), sec(a_c)], [sec(a_a), 0, sec(a_c)], [sec(a_a), sec(a_b), 0]] 327 draw_vertex_lines(v_mat, params, st, 'Altitude', layer) 328 329 if so.anglebisectors: 330 v_mat = ((0, 1, 1), (1, 0, 1), (1, 1, 0)) 331 draw_vertex_lines(v_mat, params, st, 'AngleBisectors', layer) 332 333 if so.excircles or so.excentres or so.excentral_tri: 334 v_mat = ((-1, 1, 1), (1, -1, 1), (1, 1, -1)) 335 if so.excentral_tri: 336 draw_SVG_tri(v_mat, params, st, 'ExcentralTriangle', layer) 337 for i in range(3): 338 if so.excircles: 339 r = area / (semiperim - sides[i]) 340 draw_SVG_circle(r, v_mat[i], params, st, 'Excircle:' + str(i), layer) 341 if so.excentres: 342 draw_SVG_circle(0, v_mat[i], params, st, 'Excentre:' + str(i), layer) 343 344 if so.sym_tri or so.symmedians: 345 v_mat = ((0, s_b, s_c), (s_a, 0, s_c), (s_a, s_b, 0)) 346 if so.sym_tri: 347 draw_SVG_tri(v_mat, params, st, 'SymmedialTriangle', layer) 348 if so.symmedians: 349 draw_vertex_lines(v_mat, params, st, 'Symmedian', layer) 350 351 if so.sym_point: 352 pt = (s_a, s_b, s_c) 353 draw_SVG_circle(0, pt, params, st, 'SymmmedianPoint', layer) 354 355 if so.gergonne_pt: 356 pt = pt_from_tcf('1/(s_a*(s_b+s_c-s_a))', params) 357 draw_SVG_circle(0, pt, params, st, 'GergonnePoint', layer) 358 359 if so.nagel_pt: 360 pt = pt_from_tcf('(s_b+s_c-s_a)/s_a', params) 361 draw_SVG_circle(0, pt, params, st, 'NagelPoint', layer) 362 363 if so.cust_pt or so.cust_radius or so.isogonal_conj or so.isotomic_conj: 364 pt = [] # where we will store the point in trilinears 365 if so.mode == 'trilin': # if we are receiving from trilinears 366 for i in range(3): 367 strings = so.cust_str.split(':') # get split string 368 strings[i] = translate_string(strings[i], 0) 369 func = eval('lambda params: ' + strings[i].strip('"')) # the function leading to the trilinar element 370 pt.append(func(params)) # evaluate the function for the trilinear element 371 else: # we need a triangle function 372 string = so.cust_str # don't need to translate, as the pt_from_tcf function does that for us 373 pt = pt_from_tcf(string, params) # get the point from the tcf directly 374 375 if so.cust_pt: # draw the point 376 draw_SVG_circle(0, pt, params, st, 'CustomTrilinearPoint', layer) 377 if so.cust_radius: # draw the circle with given radius 378 strings = translate_string(so.radius, 0) 379 func = eval('lambda params: ' + strings.strip('"')) # the function leading to the radius 380 r = func(params) 381 draw_SVG_circle(r, pt, params, st, 'CustomTrilinearCircle', layer) 382 if so.isogonal_conj: 383 isogonal = [0, 0, 0] 384 for i in range(3): 385 isogonal[i] = 1 / pt[i] 386 draw_SVG_circle(0, isogonal, params, st, 'CustomIsogonalConjugate', layer) 387 if so.isotomic_conj: 388 isotomic = [0, 0, 0] 389 for i in range(3): 390 isotomic[i] = 1 / (params[0][i] * params[0][i] * pt[i]) 391 draw_SVG_circle(0, isotomic, params, st, 'CustomIsotomicConjugate', layer) 392 393 394if __name__ == '__main__': 395 DrawFromTriangle().run() 396