Code

Extensions. Text support improvement in XAML and FXG export.
[inkscape.git] / share / extensions / draw_from_triangle.py
1 #!/usr/bin/env python 
2 '''
3 Copyright (C) 2007 John Beard john.j.beard@gmail.com
5 ##This extension allows you to draw various triangle constructions
6 ##It requires a path to be selected
7 ##It will use the first three nodes of this path
9 ## Dimensions of a triangle__
10 #
11 #        /`__
12 #       / a_c``--__
13 #      /           ``--__ s_a
14 # s_b /                  ``--__
15 #    /a_a                    a_b`--__  
16 #   /--------------------------------``B
17 #  A              s_b
19 This program is free software; you can redistribute it and/or modify
20 it under the terms of the GNU General Public License as published by
21 the Free Software Foundation; either version 2 of the License, or
22 (at your option) any later version.
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 GNU General Public License for more details.
29 You should have received a copy of the GNU General Public License
30 along with this program; if not, write to the Free Software
31 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
32 '''
34 import inkex
35 import simplestyle, sys, simplepath
36 from math import *
37 import gettext
38 _ = gettext.gettext
40 #DRAWING ROUTINES
42 #draw an SVG triangle given in trilinar coords
43 def draw_SVG_circle(rad, centre, params, style, name, parent):#draw an SVG circle with a given radius as trilinear coordinates
44     if rad == 0: #we want a dot
45         r = style.d_rad #get the dot width from the style
46         circ_style = { 'stroke':style.d_col, 'stroke-width':str(style.d_th), 'fill':style.d_fill }
47     else:
48         r = rad #use given value
49         circ_style = { 'stroke':style.c_col, 'stroke-width':str(style.c_th), 'fill':style.c_fill }
51     cx,cy = get_cartesian_pt(centre, params)
52     circ_attribs = {'style':simplestyle.formatStyle(circ_style),
53                     inkex.addNS('label','inkscape'):name,
54                     'cx':str(cx), 'cy':str(cy), 
55                     'r':str(r)}
56     inkex.etree.SubElement(parent, inkex.addNS('circle','svg'), circ_attribs )
58 #draw an SVG triangle given in trilinar coords
59 def draw_SVG_tri(vert_mat, params, style, name, parent):
60     p1,p2,p3 = get_cartesian_tri(vert_mat, params) #get the vertex matrix in cartesian points
61     tri_style   = { 'stroke': style.l_col, 'stroke-width':str(style.l_th), 'fill': style.l_fill }
62     tri_attribs = {'style':simplestyle.formatStyle(tri_style),
63                     inkex.addNS('label','inkscape'):name,
64                     'd':'M '+str(p1[0])+','+str(p1[1])+
65                        ' L '+str(p2[0])+','+str(p2[1])+
66                        ' L '+str(p3[0])+','+str(p3[1])+
67                        ' L '+str(p1[0])+','+str(p1[1])+' z'}
68     inkex.etree.SubElement(parent, inkex.addNS('path','svg'), tri_attribs )
70 #draw an SVG line segment between the given (raw) points
71 def draw_SVG_line( (x1, y1), (x2, y2), style, name, parent):
72     line_style   = { 'stroke': style.l_col, 'stroke-width':str(style.l_th), 'fill': style.l_fill }
73     line_attribs = {'style':simplestyle.formatStyle(line_style),
74                     inkex.addNS('label','inkscape'):name,
75                     'd':'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)}
76     inkex.etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
78 #lines from each vertex to a corresponding point in trilinears
79 def draw_vertex_lines( vert_mat, params, width, name, parent):
80     for i in range(3):
81         oppositepoint = get_cartesian_pt( vert_mat[i], params)
82         draw_SVG_line(params[3][-i%3], oppositepoint, width, name+':'+str(i), parent)
83         
84 #MATHEMATICAL ROUTINES
86 def distance( (x0,y0),(x1,y1)):#find the pythagorean distance
87     return sqrt( (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1) )
89 def vector_from_to( (x0,y0),(x1,y1) ):#get the vector from (x0,y0) to (x1,y1)
90     return (x1-x0, y1-y0)
92 def get_cartesian_pt( t, p):#get the cartesian coordinates from a trilinear set
93     denom = p[0][0]*t[0] + p[0][1]*t[1] + p[0][2]*t[2]
94     c1 = p[0][1]*t[1]/denom
95     c2 = p[0][2]*t[2]/denom
96     return ( c1*p[2][1][0]+c2*p[2][0][0], c1*p[2][1][1]+c2*p[2][0][1] )
98 def get_cartesian_tri( ((t11,t12,t13),(t21,t22,t23),(t31,t32,t33)), params):#get the cartesian points from a trilinear vertex matrix
99     p1=get_cartesian_pt( (t11,t12,t13), params )
100     p2=get_cartesian_pt( (t21,t22,t23), params )
101     p3=get_cartesian_pt( (t31,t32,t33), params )
102     return (p1,p2,p3)
104 def angle_from_3_sides(a, b, c): #return the angle opposite side c
105     cosx = (a*a + b*b - c*c)/(2*a*b)  #use the cosine rule
106     return acos(cosx)
107     
108 def translate_string(string, os): #translates s_a, a_a, etc to params[x][y], with cyclic offset
109     string = string.replace('s_a', 'params[0]['+str((os+0)%3)+']') #replace with ref. to the relvant values,
110     string = string.replace('s_b', 'params[0]['+str((os+1)%3)+']') #cycled by i
111     string = string.replace('s_c', 'params[0]['+str((os+2)%3)+']')
112     string = string.replace('a_a', 'params[1]['+str((os+0)%3)+']')
113     string = string.replace('a_b', 'params[1]['+str((os+1)%3)+']')
114     string = string.replace('a_c', 'params[1]['+str((os+2)%3)+']')
115     string = string.replace('area','params[4][0]')
116     string = string.replace('semiperim','params[4][1]')
117     return string
119 def pt_from_tcf( tcf , params):#returns a trilinear triplet from a triangle centre function
120     trilin_pts=[]#will hold the final points
121     for i in range(3):
122         temp = tcf #read in the tcf
123         temp = translate_string(temp, i)
124         func = eval('lambda params: ' + temp.strip('"')) #the function leading to the trilinar element
125         trilin_pts.append(func(params))#evaluate the function for the first trilinear element
126     return trilin_pts
127     
128 #SVG DATA PROCESSING
129     
130 def get_n_points_from_path( node, n):#returns a list of first n points (x,y) in an SVG path-representing node
132     p = simplepath.parsePath(node.get('d')) #parse the path
133     
134     xi = [] #temporary storage for x and y (will combine at end)
135     yi = []
136     
137     for cmd,params in p:                    #a parsed path is made up of (cmd, params) pairs
138         defs = simplepath.pathdefs[cmd]
139         for i in range(defs[1]):
140             if   defs[3][i] == 'x' and len(xi) < n:#only collect the first three
141                 xi.append(params[i])
142             elif defs[3][i] == 'y' and len(yi) < n:#only collect the first three
143                 yi.append(params[i])
145     if len(xi) == n and len(yi) == n:
146         points = [] # returned pairs of points
147         for i in range(n):
148             points.append( [ xi[i], yi[i] ] )
149     else:
150         #inkex.errormsg(_('Error: Not enough nodes to gather coordinates.')) #fail silently and exit, rather than invoke an error console
151         return [] #return a blank
152         
153     return points
154     
155 #EXTRA MATHS FUNCTIONS
156 def sec(x):#secant(x)
157     if x == pi/2 or x==-pi/2 or x == 3*pi/2 or x == -3*pi/2: #sec(x) is undefined
158         return 100000000000
159     else:
160         return 1/cos(x)
162 def csc(x):#cosecant(x)
163     if x == 0 or x==pi or x==2*pi or x==-2*pi: #csc(x) is undefined
164         return 100000000000
165     else:
166         return 1/sin(x)
168 def cot(x):#cotangent(x)
169     if x == 0 or x==pi or x==2*pi or x==-2*pi: #cot(x) is undefined
170         return 100000000000
171     else:
172         return 1/tan(x)
173         
174 def report_properties( params ):#report to the Inkscape console using errormsg
175     inkex.errormsg(_("Side Length 'a' (px): " + str( params[0][0] ) ))
176     inkex.errormsg(_("Side Length 'b' (px): " + str( params[0][1] ) ))
177     inkex.errormsg(_("Side Length 'c' (px): " + str( params[0][2] ) ))
178     inkex.errormsg(_("Angle 'A' (radians): " + str( params[1][0] ) ))
179     inkex.errormsg(_("Angle 'B' (radians): " + str( params[1][1] ) ))
180     inkex.errormsg(_("Angle 'C' (radians): " + str( params[1][2] ) ))
181     inkex.errormsg(_("Semiperimeter (px): " + str( params[4][1] ) ))
182     inkex.errormsg(_("Area (px^2): " + str( params[4][0] ) ))
183     return
184     
186 class Style(object): #container for style information
187     def __init__(self, options):
188         #dot markers
189         self.d_rad = 4 #dot marker radius
190         self.d_th  = 2 #stroke width
191         self.d_fill= '#aaaaaa' #fill colour
192         self.d_col = '#000000' #stroke colour
194         #lines
195         self.l_th  = 2
196         self.l_fill= 'none'
197         self.l_col = '#000000'
198         
199         #circles
200         self.c_th  = 2
201         self.c_fill= 'none'
202         self.c_col = '#000000'
204 class Draw_From_Triangle(inkex.Effect):
205     def __init__(self):
206         inkex.Effect.__init__(self)
207         self.OptionParser.add_option("--tab",
208                         action="store", type="string", 
209                         dest="tab", default="sampling",
210                         help="The selected UI-tab when OK was pressed") 
211 #PRESET POINT OPTIONS
212         self.OptionParser.add_option("--circumcircle",
213                         action="store", type="inkbool", 
214                         dest="do_circumcircle", default=False)
215         self.OptionParser.add_option("--circumcentre",
216                         action="store", type="inkbool", 
217                         dest="do_circumcentre", default=False)
218         self.OptionParser.add_option("--incircle",
219                         action="store", type="inkbool", 
220                         dest="do_incircle", default=False)
221         self.OptionParser.add_option("--incentre",
222                         action="store", type="inkbool", 
223                         dest="do_incentre", default=False)
224         self.OptionParser.add_option("--contact_tri",
225                         action="store", type="inkbool", 
226                         dest="do_contact_tri", default=False)
227         self.OptionParser.add_option("--excircles",
228                         action="store", type="inkbool", 
229                         dest="do_excircles", default=False)
230         self.OptionParser.add_option("--excentres",
231                         action="store", type="inkbool", 
232                         dest="do_excentres", default=False)
233         self.OptionParser.add_option("--extouch_tri",
234                         action="store", type="inkbool", 
235                         dest="do_extouch_tri", default=False)
236         self.OptionParser.add_option("--excentral_tri",
237                         action="store", type="inkbool", 
238                         dest="do_excentral_tri", default=False)
239         self.OptionParser.add_option("--orthocentre",
240                         action="store", type="inkbool", 
241                         dest="do_orthocentre", default=False)
242         self.OptionParser.add_option("--orthic_tri",
243                         action="store", type="inkbool", 
244                         dest="do_orthic_tri", default=False)
245         self.OptionParser.add_option("--altitudes",
246                         action="store", type="inkbool", 
247                         dest="do_altitudes", default=False)
248         self.OptionParser.add_option("--anglebisectors",
249                         action="store", type="inkbool", 
250                         dest="do_anglebisectors", default=False)
251         self.OptionParser.add_option("--centroid",
252                         action="store", type="inkbool", 
253                         dest="do_centroid", default=False)
254         self.OptionParser.add_option("--ninepointcentre",
255                         action="store", type="inkbool", 
256                         dest="do_ninepointcentre", default=False)
257         self.OptionParser.add_option("--ninepointcircle",
258                         action="store", type="inkbool", 
259                         dest="do_ninepointcircle", default=False)
260         self.OptionParser.add_option("--symmedians",
261                         action="store", type="inkbool", 
262                         dest="do_symmedians", default=False)
263         self.OptionParser.add_option("--sym_point",
264                         action="store", type="inkbool", 
265                         dest="do_sym_pt", default=False)
266         self.OptionParser.add_option("--sym_tri",
267                         action="store", type="inkbool", 
268                         dest="do_sym_tri", default=False)
269         self.OptionParser.add_option("--gergonne_pt",
270                         action="store", type="inkbool", 
271                         dest="do_gergonne_pt", default=False)
272         self.OptionParser.add_option("--nagel_pt",
273                         action="store", type="inkbool", 
274                         dest="do_nagel_pt", default=False)
275 #CUSTOM POINT OPTIONS
276         self.OptionParser.add_option("--mode",
277                         action="store", type="string", 
278                         dest="mode", default='trilin')
279         self.OptionParser.add_option("--cust_str",
280                         action="store", type="string", 
281                         dest="cust_str", default='s_a')
282         self.OptionParser.add_option("--cust_pt",
283                         action="store", type="inkbool", 
284                         dest="do_cust_pt", default=False)
285         self.OptionParser.add_option("--cust_radius",
286                         action="store", type="inkbool", 
287                         dest="do_cust_radius", default=False)
288         self.OptionParser.add_option("--radius",
289                         action="store", type="string", 
290                         dest="radius", default='s_a')
291         self.OptionParser.add_option("--isogonal_conj",
292                         action="store", type="inkbool", 
293                         dest="do_isogonal_conj", default=False)
294         self.OptionParser.add_option("--isotomic_conj",
295                         action="store", type="inkbool", 
296                         dest="do_isotomic_conj", default=False)
297         self.OptionParser.add_option("--report",
298                         action="store", type="inkbool", 
299                         dest="report", default=False)
302     def effect(self):
303         
304         so = self.options #shorthand
305         
306         pts = [] #initialise in case nothing is selected and following loop is not executed
307         for id, node in self.selected.iteritems():
308             if node.tag == inkex.addNS('path','svg'):
309                 pts = get_n_points_from_path( node, 3 ) #find the (x,y) coordinates of the first 3 points of the path
312         if len(pts) == 3: #if we have right number of nodes, else skip and end program
313             st = Style(so)#style for dots, lines and circles
314             
315             #CREATE A GROUP TO HOLD ALL GENERATED ELEMENTS IN
316             #Hold relative to point A (pt[0])
317             group_translation = 'translate(' + str( pts[0][0] ) + ','+ str( pts[0][1] ) + ')'
318             group_attribs = {inkex.addNS('label','inkscape'):'TriangleElements',
319                   'transform':group_translation }
320             layer = inkex.etree.SubElement(self.current_layer, 'g', group_attribs)
321             
322             #GET METRICS OF THE TRIANGLE
323             #vertices in the local coordinates (set pt[0] to be the origin)
324             vtx = [[0,0],
325                    [pts[1][0]-pts[0][0],pts[1][1]-pts[0][1]],
326                    [pts[2][0]-pts[0][0],pts[2][1]-pts[0][1]]]
327             
328             s_a = distance(vtx[1],vtx[2])#get the scalar side lengths
329             s_b = distance(vtx[0],vtx[1])
330             s_c = distance(vtx[0],vtx[2])
331             sides=(s_a,s_b,s_c)#side list for passing to functions easily and for indexing
332             
333             a_a = angle_from_3_sides(s_b, s_c, s_a)#angles in radians
334             a_b = angle_from_3_sides(s_a, s_c, s_b)
335             a_c = angle_from_3_sides(s_a, s_b, s_c)
336             angles=(a_a,a_b,a_c)
337             
338             ab  = vector_from_to(vtx[0], vtx[1]) #vector from a to b
339             ac  = vector_from_to(vtx[0], vtx[2]) #vector from a to c
340             bc  = vector_from_to(vtx[1], vtx[2]) #vector from b to c
341             vecs= (ab,ac) # vectors for finding cartesian point from trilinears
343             semiperim = (s_a+s_b+s_c)/2.0 #semiperimeter
344             area      = sqrt( semiperim*(semiperim-s_a)*(semiperim-s_b)*(semiperim-s_c) ) #area of the triangle by heron's formula
345             uvals = (area, semiperim) #useful values
346                     
347             params = (sides, angles, vecs, vtx, uvals) #all useful triangle parameters in one object
348             
349             if so.report:
350                 report_properties( params )
352             #BEGIN DRAWING
353             if so.do_circumcentre or so.do_circumcircle:
354                 r  = s_a*s_b*s_c/(4*area)
355                 pt = (cos(a_a),cos(a_b),cos(a_c))
356                 if so.do_circumcentre:
357                     draw_SVG_circle(0, pt, params, st, 'Circumcentre', layer)
358                 if so.do_circumcircle:
359                     draw_SVG_circle(r, pt, params, st, 'Circumcircle', layer)
360             
361             if so.do_incentre or so.do_incircle:
362                 pt = [1,1,1]
363                 if so.do_incentre:
364                     draw_SVG_circle(0, pt, params, st, 'Incentre', layer)
365                 if so.do_incircle:
366                     r  = area/semiperim
367                     draw_SVG_circle(r, pt, params, st, 'Incircle', layer)
368             
369             if so.do_contact_tri:
370                 t1 = s_b*s_c/(-s_a+s_b+s_c)
371                 t2 = s_a*s_c/( s_a-s_b+s_c)
372                 t3 = s_a*s_b/( s_a+s_b-s_c)
373                 v_mat = ( (0,t2,t3),(t1,0,t3),(t1,t2,0))
374                 draw_SVG_tri(v_mat, params, st,'ContactTriangle',layer)
375                 
376             if so.do_extouch_tri:
377                 t1 = (-s_a+s_b+s_c)/s_a
378                 t2 = ( s_a-s_b+s_c)/s_b
379                 t3 = ( s_a+s_b-s_c)/s_c
380                 v_mat = ( (0,t2,t3),(t1,0,t3),(t1,t2,0))
381                 draw_SVG_tri(v_mat, params, st,'ExtouchTriangle',layer)
382                           
383             if so.do_orthocentre:
384                 pt = pt_from_tcf('cos(a_b)*cos(a_c)', params)
385                 draw_SVG_circle(0, pt, params, st, 'Orthocentre', layer)
386             
387             if so.do_orthic_tri:
388                 v_mat = [[0,sec(a_b),sec(a_c)],[sec(a_a),0,sec(a_c)],[sec(a_a),sec(a_b),0]]
389                 draw_SVG_tri(v_mat, params, st,'OrthicTriangle',layer)
390             
391             if so.do_centroid:
392                 pt = [1/s_a,1/s_b,1/s_c]
393                 draw_SVG_circle(0, pt, params, st, 'Centroid', layer)
394             
395             if so.do_ninepointcentre or so.do_ninepointcircle:
396                 pt = [cos(a_b-a_c),cos(a_c-a_a),cos(a_a-a_b)]
397                 if so.do_ninepointcentre:
398                     draw_SVG_circle(0, pt, params, st, 'NinePointCentre', layer)
399                 if so.do_ninepointcircle:
400                     r    = s_a*s_b*s_c/(8*area)
401                     draw_SVG_circle(r, pt, params, st, 'NinePointCircle', layer)
402             
403             if so.do_altitudes:
404                 v_mat  = [[0,sec(a_b),sec(a_c)],[sec(a_a),0,sec(a_c)],[sec(a_a),sec(a_b),0]]
405                 draw_vertex_lines( v_mat, params, st, 'Altitude', layer)
406             
407             if so.do_anglebisectors:
408                 v_mat  = ((0,1,1),(1,0,1),(1,1,0))
409                 draw_vertex_lines(v_mat,params, st, 'AngleBisectors', layer)
410             
411             if so.do_excircles or so.do_excentres or so.do_excentral_tri:
412                 v_mat = ((-1,1,1),(1,-1,1),(1,1,-1))
413                 if so.do_excentral_tri:
414                     draw_SVG_tri(v_mat, params, st,'ExcentralTriangle',layer)
415                 for i in range(3):
416                     if so.do_excircles:
417                         r = area/(semiperim-sides[i])
418                         draw_SVG_circle(r, v_mat[i], params, st, 'Excircle:'+str(i), layer)
419                     if so.do_excentres:
420                         draw_SVG_circle(0, v_mat[i], params, st, 'Excentre:'+str(i), layer)
421             
422             if so.do_sym_tri or so.do_symmedians:
423                 v_mat = ((0,s_b,s_c), (s_a, 0, s_c), (s_a, s_b, 0))
424                 if so.do_sym_tri:
425                     draw_SVG_tri(v_mat, params, st,'SymmedialTriangle',layer)
426                 if so.do_symmedians:
427                     draw_vertex_lines(v_mat,params, st, 'Symmedian', layer)
428             
429             if so.do_sym_pt:
430                 pt = (s_a,s_b,s_c)
431                 draw_SVG_circle(0, pt, params, st, 'SymmmedianPoint', layer)
432             
433             if so.do_gergonne_pt:
434                 pt = pt_from_tcf('1/(s_a*(s_b+s_c-s_a))', params)
435                 draw_SVG_circle(0, pt, params, st, 'GergonnePoint', layer)
436             
437             if so.do_nagel_pt:
438                 pt = pt_from_tcf('(s_b+s_c-s_a)/s_a', params)
439                 draw_SVG_circle(0, pt, params, st, 'NagelPoint', layer)
440             
441             if so.do_cust_pt or so.do_cust_radius or so.do_isogonal_conj or so.do_isotomic_conj:
442                 pt = []#where we will store the point in trilinears
443                 if so.mode == 'trilin':#if we are receiving from trilinears
444                     for i in range(3):
445                         strings = so.cust_str.split(':')#get split string
446                         strings[i] = translate_string(strings[i],0)
447                         func = eval('lambda params: ' + strings[i].strip('"')) #the function leading to the trilinar element
448                         pt.append(func(params)) #evaluate the function for the trilinear element
449                 else:#we need a triangle function
450                     string = so.cust_str #don't need to translate, as the pt_from_tcf function does that for us
451                     pt = pt_from_tcf(string, params)#get the point from the tcf directly
452                     
453                 if so.do_cust_pt:#draw the point
454                     draw_SVG_circle(0, pt, params, st, 'CustomTrilinearPoint', layer)
455                 if so.do_cust_radius:#draw the circle with given radius
456                     strings = translate_string(so.radius,0)
457                     func = eval('lambda params: ' + strings.strip('"')) #the function leading to the radius
458                     r = func(params)
459                     draw_SVG_circle(r, pt, params, st, 'CustomTrilinearCircle', layer)
460                 if so.do_isogonal_conj:
461                     isogonal=[0,0,0]
462                     for i in range (3):
463                         isogonal[i] = 1/pt[i]
464                     draw_SVG_circle(0, isogonal, params, st, 'CustomIsogonalConjugate', layer)
465                 if so.do_isotomic_conj:
466                     isotomic=[0,0,0]
467                     for i in range (3):
468                         isotomic[i] = 1/( params[0][i]*params[0][i]*pt[i] )
469                     draw_SVG_circle(0, isotomic, params, st, 'CustomIsotomicConjugate', layer)
472 if __name__ == '__main__':   #pragma: no cover
473     e = Draw_From_Triangle()
474     e.affect()