1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 '''
4 Copyright (C) 2009 John Beard john.j.beard@gmail.com
6 ######DESCRIPTION######
8 This extension renders a wireframe sphere constructed from lines of latitude
9 and lines of longitude.
11 The number of lines of latitude and longitude is independently variable. Lines
12 of latitude and longtude are in separate subgroups. The whole figure is also in
13 its own group.
15 The whole sphere can be tilted towards or away from the veiwer by a given
16 number of degrees. If the whole sphere is then rotated normally in Inkscape,
17 any position can be acheived.
19 There is an option to hide the lines at the back of the sphere, as if the
20 sphere were opaque.
21 #FIXME: Lines of latitude only have an approximation of the function needed
22 to hide the back portion. If you can derive the proper equation,
23 please add it in.
24 Line of longitude have the exact method already.
25 Workaround: Use the Inkscape ellipse tool to edit the start and end
26 points of the lines of latitude to end at the horizon circle.
29 #TODO: Add support for odd numbers of lines of longitude. This means breaking
30 the line at the poles, and having two half ellipses for each line.
31 The angles at which the ellipse arcs pass the poles are not constant and
32 need to be derived before this can be implemented.
33 #TODO: Add support for prolate and oblate spheroids
35 ######LICENCE#######
36 This program is free software; you can redistribute it and/or modify
37 it under the terms of the GNU General Public License as published by
38 the Free Software Foundation; either version 2 of the License, or
39 (at your option) any later version.
41 This program is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 GNU General Public License for more details.
46 You should have received a copy of the GNU General Public License
47 along with this program; if not, write to the Free Software
48 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
50 ######VERSION HISTORY#####
51 Ver. Date Notes
53 0.10 2009-10-25 First version. Basic spheres supported.
54 Hidden lines of latitude still not properly calculated.
55 Prolate and oblate spheroids not considered.
56 '''
58 import inkex, simplestyle
60 import gettext
61 _ = gettext.gettext
63 from math import *
65 #SVG OUTPUT FUNCTIONS ================================================
66 def draw_SVG_ellipse((rx, ry), (cx, cy), parent, start_end=(0,2*pi),transform='' ):
68 style = { 'stroke' : '#000000',
69 'stroke-width' : '1',
70 'fill' : 'none' }
71 circ_attribs = {'style':simplestyle.formatStyle(style),
72 inkex.addNS('cx','sodipodi') :str(cx),
73 inkex.addNS('cy','sodipodi') :str(cy),
74 inkex.addNS('rx','sodipodi') :str(rx),
75 inkex.addNS('ry','sodipodi') :str(ry),
76 inkex.addNS('start','sodipodi') :str(start_end[0]),
77 inkex.addNS('end','sodipodi') :str(start_end[1]),
78 inkex.addNS('open','sodipodi') :'true', #all ellipse sectors we will draw are open
79 inkex.addNS('type','sodipodi') :'arc',
80 'transform' :transform
82 }
83 circ = inkex.etree.SubElement(parent, inkex.addNS('path','svg'), circ_attribs )
85 class Wireframe_Sphere(inkex.Effect):
86 def __init__(self):
87 inkex.Effect.__init__(self)
89 #PARSE OPTIONS
90 self.OptionParser.add_option("--num_lat",
91 action="store", type="int",
92 dest="NUM_LAT", default=19)
93 self.OptionParser.add_option("--num_long",
94 action="store", type="int",
95 dest="NUM_LONG", default=24)
96 self.OptionParser.add_option("--radius",
97 action="store", type="float",
98 dest="RADIUS", default=100.0)
99 self.OptionParser.add_option("--tilt",
100 action="store", type="float",
101 dest="TILT", default=35.0)
102 self.OptionParser.add_option("--rotation",
103 action="store", type="float",
104 dest="ROT_OFFSET", default=4)
105 self.OptionParser.add_option("--hide_back",
106 action="store", type="inkbool",
107 dest="HIDE_BACK", default=False)
109 def effect(self):
111 so = self.options
113 #PARAMETER PROCESSING
115 if so.NUM_LONG % 2 != 0: #lines of longitude are odd : abort
116 inkex.errormsg(_('Please enter an even number of lines of longitude.'))
117 else:
118 if so.TILT < 0: # if the tilt is backwards
119 flip = ' scale(1, -1)' # apply a vertical flip to the whole sphere
120 else:
121 flip = '' #no flip
123 so.TILT = abs(so.TILT)*(pi/180) #Convert to radians
124 so.ROT_OFFSET = so.ROT_OFFSET*(pi/180) #Convert to radians
126 EPSILON = 0.001 #add a tiny value to the ellipse radii, so that if we get a zero radius, the ellipse still shows up as a line
128 #INKSCAPE GROUP TO CONTAIN EVERYTHING
130 centre = self.view_center #Put in in the centre of the current view
131 grp_transform = 'translate' + str( centre ) + flip
132 grp_name = 'WireframeSphere'
133 grp_attribs = {inkex.addNS('label','inkscape'):grp_name,
134 'transform':grp_transform }
135 grp = inkex.etree.SubElement(self.current_layer, 'g', grp_attribs)#the group to put everything in
137 #LINES OF LONGITUDE
139 if so.NUM_LONG > 0: #only process longitudes if we actually want some
141 #GROUP FOR THE LINES OF LONGITUDE
142 grp_name = 'Lines of Longitude'
143 grp_attribs = {inkex.addNS('label','inkscape'):grp_name}
144 grp_long = inkex.etree.SubElement(grp, 'g', grp_attribs)
146 delta_long = 360.0/so.NUM_LONG #angle between neighbouring lines of longitude in degrees
148 for i in range(0,so.NUM_LONG/2):
149 long_angle = so.ROT_OFFSET + (i*delta_long)*(pi/180.0); #The longitude of this particular line in radians
150 if long_angle > pi:
151 long_angle -= 2*pi
152 width = so.RADIUS * cos(long_angle)
153 height = so.RADIUS * sin(long_angle) * sin(so.TILT) #the rise is scaled by the sine of the tilt
154 # length = sqrt(width*width+height*height) #by pythagorean theorem
155 # inverse = sin(acos(length/so.RADIUS))
156 inverse = abs(sin(long_angle)) * cos(so.TILT)
158 minorRad = so.RADIUS * inverse
159 minorRad=minorRad + EPSILON
161 #calculate the rotation of the ellipse to get it to pass through the pole (in degrees)
162 rotation = atan(height/width)*(180.0/pi)
163 transform = "rotate("+str(rotation)+')' #generate the transform string
164 #the rotation will be applied about the group centre (the centre of the sphere)
166 # remove the hidden side of the ellipses if required
167 # this is always exactly half the ellipse, but we need to find out which half
168 start_end = (0, 2*pi) #Default start and end angles -> full ellipse
169 if so.HIDE_BACK:
170 if long_angle <= pi/2: #cut out the half ellispse that is hidden
171 start_end = (pi/2, 3*pi/2)
172 else:
173 start_end = (3*pi/2, pi/2)
175 #finally, draw the line of longitude
176 #the centre is always at the centre of the sphere
177 draw_SVG_ellipse( ( minorRad, so.RADIUS ), (0,0), grp_long , start_end,transform)
179 # LINES OF LATITUDE
180 if so.NUM_LAT > 0:
182 #GROUP FOR THE LINES OF LATITUDE
183 grp_name = 'Lines of Latitude'
184 grp_attribs = {inkex.addNS('label','inkscape'):grp_name}
185 grp_lat = inkex.etree.SubElement(grp, 'g', grp_attribs)
188 so.NUM_LAT = so.NUM_LAT + 1 #Account for the fact that we loop over N-1 elements
189 delta_lat = 180.0/so.NUM_LAT #Angle between the line of latitude (subtended at the centre)
191 for i in range(1,so.NUM_LAT):
192 lat_angle=((delta_lat*i)*(pi/180)) #The angle of this line of latitude (from a pole)
194 majorRad=so.RADIUS*sin(lat_angle) #The width of the LoLat (no change due to projection)
195 minorRad=so.RADIUS*sin(lat_angle) * sin(so.TILT) #The projected height of the line of latitude
196 minorRad=minorRad + EPSILON
198 cy=so.RADIUS*cos(lat_angle) * cos(so.TILT) #The projected y position of the LoLat
199 cx=0 #The x position is just the center of the sphere
201 if so.HIDE_BACK:
202 if lat_angle > so.TILT: #this LoLat is partially or fully visible
203 if lat_angle > pi-so.TILT: #this LoLat is fully visible
204 draw_SVG_ellipse((majorRad, minorRad), (cx,cy), grp_lat)
205 else: #this LoLat is partially visible
206 proportion = -(acos( tan(lat_angle - pi/2)/tan(pi/2 - so.TILT)) )/pi + 1
207 start_end = ( pi/2 - proportion*pi, pi/2 + proportion*pi ) #make the start and end angles (mirror image around pi/2)
208 draw_SVG_ellipse((majorRad, minorRad), (cx,cy), grp_lat, start_end)
210 else: #just draw the full lines of latitude
211 draw_SVG_ellipse((majorRad, minorRad), (cx,cy), grp_lat)
214 #THE HORIZON CIRCLE
215 draw_SVG_ellipse((so.RADIUS, so.RADIUS), (0,0), grp) #circle, centred on the sphere centre
217 if __name__ == '__main__':
218 e = Wireframe_Sphere()
219 e.affect()
221 # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99