]> git.mxchange.org Git - flightgear.git/blob - utils/Modeller/yasim_import.py
faster reload; colorize symbols; environment variable; comments++; cleanup
[flightgear.git] / utils / Modeller / yasim_import.py
1 #!BPY
2
3 # """
4 # Name: 'YASim (.xml)'
5 # Blender: 245
6 # Group: 'Import'
7 # Tooltip: 'Loads and visualizes a YASim FDM geometry'
8 # """
9
10 __author__ = "Melchior FRANZ < mfranz # aon : at >"
11 __url__ = ["http://www.flightgear.org/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/yasim_import.py"]
12 __version__ = "0.1"
13 __bpydoc__ = """\
14 yasim_import.py loads and visualizes a YASim FDM geometry
15 =========================================================
16
17 It is recommended to load the model superimposed over a greyed out and immutable copy of the aircraft model:
18
19   (0) put this script into ~/.blender/scripts/
20   (1) load or import aircraft model (menu -> "File" -> "Import" -> "AC3D (.ac) ...")
21   (2) create new *empty* scene (menu -> arrow button left of "SCE:scene1" combobox -> "ADD NEW" -> "empty")
22   (3) rename scene to yasim (not required)
23   (4) link to scene1 (F10 -> "Output" tab in "Buttons Window" -> arrow button left of text entry "No Set Scene" -> "scene1")
24   (5) now load the YASim config file (menu -> "File" -> "Import" -> "YASim (.xml) ...")
25
26 This is good enough for simple checks. But if you are working on the YASim configuration, then you need a
27 quick and convenient way to reload the file. In that case continue after (4):
28
29   (5) switch the button area at the bottom of the blender screen to "Scripts Window" mode (green python snake icon)
30   (6) load the YASim config file (menu -> "Scripts" -> "Import" -> "YASim (.xml) ...")
31   (7) make the "Scripts Window" area as small as possible by dragging the area separator down
32   (8) optionally split the "3D View" area and switch the right part to the "Outliner"
33   (9) press the "Reload YASim" button in the script area to reload the file
34
35
36 If the 3D model is displaced with respect to the FDM model, then the <offsets> values from the
37 model animation XML file should be added as comment to the YASim config file, as a line all by
38 itself, with no spaces surrounding the equal signs. Spaces elsewhere are allowed. For example:
39
40   <offsets>
41       <x-m>3.45</x-m>
42       <z-m>-0.4</z-m>
43       <pitch-deg>5</pitch-deg>
44   </offsets>
45
46 becomes:
47
48   <!-- offsets: x=3.45 z=-0.4 p=5 -->
49
50 Possible variables are:
51
52   x ... <x-m>
53   y ... <y-m>
54   z ... <z-m>
55   h ... <heading-deg>
56   p ... <pitch-deg>
57   r ... <roll-deg>
58
59 Of course, absolute FDM coordinates can then no longer directly be read from Blender's 3D view.
60 The cursor coordinates display in the script area, however, shows the coordinates in YASim space.
61 Note that object names don't contain XML indices but element numbers. YASim_flap0#2 is the third
62 flap0 in the whole file, not necessarily in its parent XML group. A floating point part in the
63 object name (e.g. YASim_flap0#2.004) only means that the geometry has been reloaded that often.
64 It's an unavoidable consequence of how Blender deals with meshes.
65
66
67 Elements are displayed as follows:
68
69   cockpit                             -> monkey head
70   fuselage                            -> blue "tube" (with only 12 sides for less clutter); center at "a"
71   vstab                               -> red with yellow control surfaces (flap0, flap1, slat, spoiler)
72   wing/mstab/hstab                    -> green with yellow control surfaces (which are always 20 cm deep);
73                                          symmetric surfaces are only displayed on the left side, unless
74                                          the "Mirror" button is active
75   thrusters (jet/propeller/thruster)  -> dashed line from center to actionpt;
76                                          arrow from actionpt along thrust vector (always 1 m long);
77                                          propeller circle
78   rotor                               -> radius and rel_len_blade_start circle, normal and forward vector,
79                                          one blade at phi0 with direction arrow near blade tip
80   gear                                -> contact point and compression vector (no arrow head)
81   tank                                -> cube (10 cm side length)
82   weight                              -> inverted cone
83   ballast                             -> cylinder
84   hitch                               -> circle (10 cm diameter)
85   hook                                -> dashed line for up angle, T-line for down angle
86   launchbar                           -> dashed line for up angles, T-line for down angles
87                                          (launchbar and holdback each)
88
89
90 The Mirror button complements symmetrical surfaces (wing/hstab/mstab) and control surfaces
91 (flap0/flap1/slat/spoiler). This is useful for asymmetrical aircraft, but has the disadvantage
92 that it moves the surfaces' object centers from their usual place, yasim's [x, y, z] value,
93 to [0, 0, 0]. Turning mirroring off restores the object center.
94
95
96
97 Environment variable BLENDER_YASIM_IMPORT can be set to a space-separated list of options:
98
99   $ BLENDER_YASIM_IMPORT="mirror verbose"  blender
100
101 whereby:
102
103   verbose  ... enables verbose logs
104   mirror   ... enables mirroring of symmetric surfaces
105 """
106
107
108 #--------------------------------------------------------------------------------
109 # Copyright (C) 2009  Melchior FRANZ  < mfranz # aon : at >
110 #
111 # This program is free software; you can redistribute it and/or
112 # modify it under the terms of the GNU General Public License as
113 # published by the Free Software Foundation; either version 2 of the
114 # License, or (at your option) any later version.
115 #
116 # This program is distributed in the hope that it will be useful, but
117 # WITHOUT ANY WARRANTY; without even the implied warranty of
118 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
119 # General Public License for more details.
120 #
121 # You should have received a copy of the GNU General Public License
122 # along with this program; if not, write to the Free Software
123 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
124 #--------------------------------------------------------------------------------
125
126
127 import Blender, BPyMessages, string, math, os
128 from Blender.Mathutils import *
129 from xml.sax import handler, make_parser
130
131
132 CONFIG = string.split(os.getenv("BLENDER_YASIM_IMPORT") or "")
133 YASIM_MATRIX = Matrix([-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1])
134 ORIGIN = Vector(0, 0, 0)
135 X = Vector(1, 0, 0)
136 Y = Vector(0, 1, 0)
137 Z = Vector(0, 0, 1)
138 DEG2RAD = math.pi / 180
139 RAD2DEG = 180 / math.pi
140
141 NO_EVENT = 0
142 RELOAD_BUTTON = 1
143 CURSOR_BUTTON = 2
144 MIRROR_BUTTON = 3
145
146
147
148 class Global:
149         verbose = "verbose" in CONFIG
150         path = ""
151         matrix = None
152         data = None
153         cursor = ORIGIN
154         last_cursor = Vector(Blender.Window.GetCursorPos())
155         mirror_button = Blender.Draw.Create("mirror" in CONFIG)
156
157
158
159 class Abort(Exception):
160         def __init__(self, msg, term = None):
161                 self.msg = msg
162                 self.term = term
163
164
165
166 def log(msg):
167         if Global.verbose:
168                 print(msg)
169
170
171
172 def draw_dashed_line(mesh, start, end):
173         w = 0.04
174         step = w * (end - start).normalize()
175         n = len(mesh.verts)
176         for i in range(int(1 + 0.5 * (end - start).length / w)):
177                 a = start + 2 * i * step
178                 b = a + step
179                 if (b - end).length < step.length:
180                         b = end
181                 mesh.verts.extend([a, b])
182                 mesh.edges.extend([n + 2 * i, n + 2 * i + 1])
183
184
185
186 def draw_arrow(mesh, start, end):
187         v = end - start
188         m = v.toTrackQuat('x', 'z').toMatrix().resize4x4() * TranslationMatrix(start)
189         v = v.length * X
190         n = len(mesh.verts)
191         mesh.verts.extend([ORIGIN * m , v * m, (v - 0.05 * X + 0.05 * Y) * m, (v - 0.05 * X - 0.05 * Y) * m]) # head
192         mesh.verts.extend([(ORIGIN + 0.05 * Y) * m, (ORIGIN - 0.05 * Y) * m]) # base
193         mesh.edges.extend([[n, n + 1], [n + 1, n + 2], [n + 1, n + 3], [n + 4, n + 5]])
194
195
196
197 def draw_circle(mesh, numpoints, radius, matrix):
198         n = len(mesh.verts)
199         for i in range(numpoints):
200                 angle = 2.0 * math.pi * i / numpoints
201                 v = Vector(radius * math.cos(angle), radius * math.sin(angle), 0)
202                 mesh.verts.extend([v * matrix])
203         for i in range(numpoints):
204                 i1 = (i + 1) % numpoints
205                 mesh.edges.extend([[n + i, n + i1]])
206
207
208
209 class Item:
210         scene = Blender.Scene.GetCurrent()
211
212         def make_twosided(self, mesh):
213                 mesh.faceUV = True
214                 for f in mesh.faces:
215                         f.mode |= Blender.Mesh.FaceModes.TWOSIDE | Blender.Mesh.FaceModes.OBCOL
216
217         def set_color(self, obj, color):
218                 mat = Blender.Material.New()
219                 mat.setRGBCol(color[0], color[1], color[2])
220                 mat.setAlpha(color[3])
221                 mat.mode |= Blender.Material.Modes.ZTRANSP | Blender.Material.Modes.TRANSPSHADOW
222                 obj.transp = True
223
224                 mesh = obj.getData(mesh = True)
225                 mesh.materials += [mat]
226
227                 for f in mesh.faces:
228                         f.smooth = True
229                 mesh.calcNormals()
230
231
232
233 class Cockpit(Item):
234         def __init__(self, center):
235                 mesh = Blender.Mesh.Primitives.Monkey()
236                 mesh.transform(ScaleMatrix(0.13, 4) * Euler(90, 0, 90).toMatrix().resize4x4() * TranslationMatrix(Vector(-0.1, 0, -0.032)))
237                 obj = self.scene.objects.new(mesh, "YASim_cockpit")
238                 obj.setMatrix(TranslationMatrix(center) * Global.matrix)
239
240
241
242 class Tank(Item):
243         def __init__(self, name, center):
244                 mesh = Blender.Mesh.Primitives.Cube()
245                 mesh.transform(ScaleMatrix(0.05, 4))
246                 obj = self.scene.objects.new(mesh, name)
247                 obj.setMatrix(TranslationMatrix(center) * Global.matrix)
248                 self.set_color(obj, [1, 0, 1, 0.5])
249
250
251
252 class Ballast(Item):
253         def __init__(self, name, center):
254                 mesh = Blender.Mesh.Primitives.Cylinder()
255                 mesh.transform(ScaleMatrix(0.05, 4))
256                 obj = self.scene.objects.new(mesh, name)
257                 obj.setMatrix(TranslationMatrix(center) * Global.matrix)
258                 self.set_color(obj, [1, 1, 0, 0.5])
259
260
261
262 class Weight(Item):
263         def __init__(self, name, center):
264                 mesh = Blender.Mesh.Primitives.Cone()
265                 mesh.transform(ScaleMatrix(0.05, 4))
266                 obj = self.scene.objects.new(mesh, name)
267                 obj.setMatrix(TranslationMatrix(center) * Global.matrix)
268                 self.set_color(obj, [0, 1, 1, 0.5])
269
270
271
272 class Gear(Item):
273         def __init__(self, name, center, compression):
274                 mesh = Blender.Mesh.New()
275                 mesh.verts.extend([ORIGIN, compression])
276                 mesh.edges.extend([0, 1])
277                 obj = self.scene.objects.new(mesh, name)
278                 obj.setMatrix(TranslationMatrix(center) * Global.matrix)
279
280
281
282 class Hook(Item):
283         def __init__(self, name, center, length, up_angle, dn_angle):
284                 mesh = Blender.Mesh.New()
285                 up = ORIGIN - length * math.cos(up_angle * DEG2RAD) * X - length * math.sin(up_angle * DEG2RAD) * Z
286                 dn = ORIGIN - length * math.cos(dn_angle * DEG2RAD) * X - length * math.sin(dn_angle * DEG2RAD) * Z
287                 mesh.verts.extend([ORIGIN, dn, dn + 0.05 * Y, dn - 0.05 * Y])
288                 mesh.edges.extend([[0, 1], [2, 3]])
289                 draw_dashed_line(mesh, ORIGIN, up)
290                 draw_dashed_line(mesh, ORIGIN, dn)
291                 obj = self.scene.objects.new(mesh, name)
292                 obj.setMatrix(TranslationMatrix(center) * Global.matrix)
293
294
295
296 class Launchbar(Item):
297         def __init__(self, name, lb, lb_length, hb, hb_length, up_angle, dn_angle):
298                 mesh = Blender.Mesh.New()
299                 hb = hb - lb
300                 lb_tip = ORIGIN + lb_length * math.cos(dn_angle * DEG2RAD) * X - lb_length * math.sin(dn_angle * DEG2RAD) * Z
301                 hb_tip = hb - hb_length * math.cos(dn_angle * DEG2RAD) * X - hb_length * math.sin(dn_angle * DEG2RAD) * Z
302                 mesh.verts.extend([lb_tip, ORIGIN, hb, hb_tip, lb_tip + 0.05 * Y, lb_tip - 0.05 * Y, hb_tip + 0.05 * Y, hb_tip - 0.05 * Y])
303                 mesh.edges.extend([[0, 1], [1, 2], [2, 3], [4, 5], [6, 7]])
304                 draw_dashed_line(mesh, ORIGIN, lb_length * math.cos(up_angle * DEG2RAD) * X - lb_length * math.sin(up_angle * DEG2RAD) * Z)
305                 draw_dashed_line(mesh, hb, hb - hb_length * math.cos(up_angle * DEG2RAD) * X - hb_length * math.sin(up_angle * DEG2RAD) * Z)
306                 obj = self.scene.objects.new(mesh, name)
307                 obj.setMatrix(TranslationMatrix(lb) * Global.matrix)
308
309
310
311 class Hitch(Item):
312         def __init__(self, name, center):
313                 mesh = Blender.Mesh.Primitives.Circle(8, 0.1)
314                 obj = self.scene.objects.new(mesh, name)
315                 obj.setMatrix(RotationMatrix(90, 4, "x") * TranslationMatrix(center) * Global.matrix)
316
317
318
319 class Thrust:
320         def set_actionpt(self, p):
321                 self.actionpt = p
322
323         def set_dir(self, d):
324                 self.thrustvector = d
325
326
327
328 class Thruster(Thrust, Item):
329         def __init__(self, name, center, thrustvector):
330                 (self.name, self.center, self.actionpt, self.thrustvector) = (name, center, center, thrustvector)
331
332         def __del__(self):
333                 a = self.actionpt - self.center
334                 mesh = Blender.Mesh.New()
335                 draw_dashed_line(mesh, ORIGIN, a)
336                 draw_arrow(mesh, a, a + self.thrustvector.normalize())
337                 obj = self.scene.objects.new(mesh, self.name)
338                 obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
339
340
341
342 class Propeller(Thrust, Item):
343         def __init__(self, name, center, radius):
344                 (self.name, self.center, self.radius, self.actionpt, self.thrustvector) = (name, center, radius, center, -X)
345
346         def __del__(self):
347                 a = self.actionpt - self.center
348                 matrix = self.thrustvector.toTrackQuat('z', 'x').toMatrix().resize4x4() * TranslationMatrix(a)
349
350                 mesh = Blender.Mesh.New()
351                 mesh.verts.extend([ORIGIN * matrix, (ORIGIN + self.radius * X) * matrix])
352                 mesh.edges.extend([[0, 1]])
353                 draw_dashed_line(mesh, ORIGIN, a)
354                 draw_arrow(mesh, a, a + self.thrustvector.normalize())
355
356                 draw_circle(mesh, 128, self.radius, matrix)
357                 obj = self.scene.objects.new(mesh, self.name)
358                 obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
359
360
361
362 class Jet(Thrust, Item):
363         def __init__(self, name, center, rotate):
364                 (self.name, self.center, self.actionpt) = (name, center, center)
365                 self.thrustvector = -X * RotationMatrix(rotate, 4, "y")
366
367         def __del__(self):
368                 a = self.actionpt - self.center
369                 mesh = Blender.Mesh.New()
370                 draw_dashed_line(mesh, ORIGIN, a)
371                 draw_arrow(mesh, a, a + self.thrustvector.normalize())
372                 obj = self.scene.objects.new(mesh, self.name)
373                 obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
374
375
376
377 class Fuselage(Item):
378         def __init__(self, name, a, b, width, taper, midpoint):
379                 numvert = 12
380                 angle = []
381                 for i in range(numvert):
382                         alpha = i * 2 * math.pi / float(numvert)
383                         angle.append([math.cos(alpha), math.sin(alpha)])
384
385                 axis = b - a
386                 length = axis.length
387                 mesh = Blender.Mesh.New()
388
389                 for i in range(numvert):
390                         mesh.verts.extend([[0, 0.5 * width * taper * angle[i][0], 0.5 * width * taper * angle[i][1]]])
391                 for i in range(numvert):
392                         mesh.verts.extend([[midpoint * length, 0.5 * width * angle[i][0], 0.5 * width * angle[i][1]]])
393                 for i in range(numvert):
394                         mesh.verts.extend([[length, 0.5 * width * taper * angle[i][0], 0.5 * width * taper * angle[i][1]]])
395                 for i in range(numvert):
396                         i1 = (i + 1) % numvert
397                         mesh.faces.extend([[i, i1, i1 + numvert, i + numvert]])
398                         mesh.faces.extend([[i + numvert, i1 + numvert, i1 + 2 * numvert, i + 2 * numvert]])
399
400                 mesh.verts.extend([ORIGIN, length * X])
401                 obj = self.scene.objects.new(mesh, name)
402                 obj.setMatrix(axis.toTrackQuat('x', 'y').toMatrix().resize4x4() * TranslationMatrix(a) * Global.matrix)
403                 self.set_color(obj, [0, 0, 0.5, 0.4])
404
405
406
407 class Rotor(Item):
408         def __init__(self, name, center, up, fwd, numblades, radius, chord, twist, taper, rel_len_blade_start, phi0, ccw):
409                 matrix = RotationMatrix(phi0, 4, "z") * up.toTrackQuat('z', 'x').toMatrix().resize4x4()
410                 invert = matrix.copy().invert()
411                 direction = [-1, 1][ccw]
412                 twist *= DEG2RAD
413                 a = ORIGIN + rel_len_blade_start * radius * X
414                 b = ORIGIN + radius * X
415                 tw = 0.5 * chord * taper * math.cos(twist) * Y + 0.5 * direction * chord * taper * math.sin(twist) * Z
416
417                 mesh = Blender.Mesh.New()
418                 mesh.verts.extend([ORIGIN, a, b, a + 0.5 * chord * Y, a - 0.5 * chord * Y, b + tw, b - tw])
419                 mesh.edges.extend([[0, 1], [1, 2], [1, 3], [1, 4], [3, 5], [4, 6], [5, 6]])
420                 draw_circle(mesh, 64, rel_len_blade_start * radius, Matrix())
421                 draw_circle(mesh, 128, radius, Matrix())
422                 draw_arrow(mesh, ORIGIN, up * invert)
423                 draw_arrow(mesh, ORIGIN, fwd * invert)
424                 b += 0.1 * X + direction * chord * Y
425                 draw_arrow(mesh, b, b + min(0.5 * radius, 1) * direction * Y)
426                 obj = self.scene.objects.new(mesh, name)
427                 obj.setMatrix(matrix * TranslationMatrix(center) * Global.matrix)
428
429
430
431 class Wing(Item):
432         def __init__(self, name, root, length, chord, incidence, twist, taper, sweep, dihedral):
433                 #  <1--0--2
434                 #   \  |  /
435                 #    4-3-5
436                 self.is_symmetric = not name.startswith("YASim_vstab#")
437                 mesh = Blender.Mesh.New()
438                 mesh.verts.extend([ORIGIN, ORIGIN + 0.5 * chord * X, ORIGIN - 0.5 * chord * X])
439                 tip = ORIGIN + math.cos(sweep * DEG2RAD) * length * Y - math.sin(sweep * DEG2RAD) * length * X
440                 tipfore = tip + 0.5 * taper * chord * math.cos(twist * DEG2RAD) * X + 0.5 * taper * chord * math.sin(twist * DEG2RAD) * Z
441                 tipaft = tip + tip - tipfore
442                 mesh.verts.extend([tip, tipfore, tipaft])
443                 mesh.faces.extend([[0, 1, 4, 3], [2, 0, 3, 5]])
444
445                 self.make_twosided(mesh)
446
447                 obj = self.scene.objects.new(mesh, name)
448                 mesh.transform(Euler(dihedral, -incidence, 0).toMatrix().resize4x4())
449                 self.set_color(obj, [[0.5, 0.0, 0, 0.5], [0.0, 0.5, 0, 0.5]][self.is_symmetric])
450                 (self.obj, self.mesh) = (obj, mesh)
451
452                 if self.is_symmetric and Global.mirror_button.val:
453                         mod = obj.modifiers.append(Blender.Modifier.Type.MIRROR)
454                         mod[Blender.Modifier.Settings.AXIS_X] = False
455                         mod[Blender.Modifier.Settings.AXIS_Y] = True
456                         mod[Blender.Modifier.Settings.AXIS_Z] = False
457                         mesh.transform(TranslationMatrix(root)) # must move object center to x axis
458                         obj.setMatrix(Global.matrix)
459                 else:
460                         obj.setMatrix(TranslationMatrix(root) * Global.matrix)
461
462         def add_flap(self, name, start, end):
463                 a = Vector(self.mesh.verts[2].co)
464                 b = Vector(self.mesh.verts[5].co)
465                 c = 0.2 * (Vector(self.mesh.verts[0].co - a)).normalize()
466                 m = self.obj.getMatrix()
467
468                 mesh = Blender.Mesh.New()
469                 i0 = a + start * (b - a)
470                 i1 = a + end * (b - a)
471                 mesh.verts.extend([i0, i1, i0 + c, i1 + c])
472                 mesh.faces.extend([[0, 1, 3, 2]])
473
474                 self.make_twosided(mesh)
475
476                 obj = self.scene.objects.new(mesh, name)
477                 obj.setMatrix(m)
478                 self.set_color(obj, [0.8, 0.8, 0, 0.9])
479
480                 if self.is_symmetric and Global.mirror_button.val:
481                         mod = obj.modifiers.append(Blender.Modifier.Type.MIRROR)
482                         mod[Blender.Modifier.Settings.AXIS_X] = False
483                         mod[Blender.Modifier.Settings.AXIS_Y] = True
484                         mod[Blender.Modifier.Settings.AXIS_Z] = False
485
486
487
488 class import_yasim(handler.ErrorHandler, handler.ContentHandler):
489         ignored = ["cruise", "approach", "control-input", "control-output", "control-speed", \
490                         "control-setting", "stall", "airplane", "piston-engine", "turbine-engine", \
491                         "rotorgear", "tow", "winch", "solve-weight"]
492
493
494         # err_handler
495         def warning(self, exception):
496                 print(self.error_string("WARNING", exception))
497
498         def error(self, exception):
499                 print(self.error_string("ERROR", exception))
500
501         def fatalError(self, exception):
502                 raise Abort(str(exception), self.error_string("FATAL", exception))
503
504         def error_string(self, tag, e):
505                 (column, line, msg) = (e.getColumnNumber(), e.getLineNumber(), e.getMessage())
506                 return "\n%s%s^--------%s: %s at line %d, column %d" \
507                                 % (Global.data[line - 1], (column) * ' ', tag, msg, line, column)
508
509
510         # doc_handler
511         def setDocumentLocator(self, locator):
512                 self.locator = locator
513
514         def startDocument(self):
515                 self.tags = []
516                 self.counter = {}
517                 self.items = [None]
518
519         def endDocument(self):
520                 for o in Item.scene.objects:
521                         o.sel = True
522
523         def startElement(self, tag, attrs):
524                 if len(self.tags) == 0 and tag != "airplane":
525                         raise Abort("this isn't a YASim config file (bad root tag at line %d)" % self.locator.getLineNumber())
526
527                 self.tags.append(tag)
528                 path = string.join(self.tags, '/')
529                 item = Item()
530                 parent = self.items[-1]
531
532                 if self.counter.has_key(tag):
533                         self.counter[tag] += 1
534                 else:
535                         self.counter[tag] = 0
536
537                 if tag == "cockpit":
538                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
539                         log("\033[31mcockpit x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
540                         item = Cockpit(c)
541
542                 elif tag == "fuselage":
543                         a = Vector(float(attrs["ax"]), float(attrs["ay"]), float(attrs["az"]))
544                         b = Vector(float(attrs["bx"]), float(attrs["by"]), float(attrs["bz"]))
545                         width = float(attrs["width"])
546                         taper = float(attrs.get("taper", 1))
547                         midpoint = float(attrs.get("midpoint", 0.5))
548                         log("\033[32mfuselage ax=%f ay=%f az=%f bx=%f by=%f bz=%f width=%f taper=%f midpoint=%f\033[m" % \
549                                         (a[0], a[1], a[2], b[0], b[1], b[2], width, taper, midpoint))
550                         item = Fuselage("YASim_%s#%d" % (tag, self.counter[tag]), a, b, width, taper, midpoint)
551
552                 elif tag == "gear":
553                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
554                         compression = float(attrs.get("compression", 1))
555                         up = Z * compression
556                         if attrs.has_key("upx"):
557                                 up = Vector(float(attrs["upx"]), float(attrs["upy"]), float(attrs["upz"])).normalize() * compression
558                         log("\033[35;1mgear x=%f y=%f z=%f compression=%f upx=%f upy=%f upz=%f\033[m" \
559                                         % (c[0], c[1], c[2], compression, up[0], up[1], up[2]))
560                         item = Gear("YASim_gear#%d" % self.counter[tag], c, up)
561
562                 elif tag == "jet":
563                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
564                         rotate = float(attrs.get("rotate", 0))
565                         log("\033[36;1mjet x=%f y=%f z=%f rotate=%f\033[m" % (c[0], c[1], c[2], rotate))
566                         item = Jet("YASim_jet#%d" % self.counter[tag], c, rotate)
567
568                 elif tag == "propeller":
569                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
570                         radius = float(attrs["radius"])
571                         log("\033[36;1m%s x=%f y=%f z=%f radius=%f\033[m" % (tag, c[0], c[1], c[2], radius))
572                         item = Propeller("YASim_propeller#%d" % self.counter[tag], c, radius)
573
574                 elif tag == "thruster":
575                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
576                         v = Vector(float(attrs["vx"]), float(attrs["vy"]), float(attrs["vz"]))
577                         log("\033[36;1m%s x=%f y=%f z=%f vx=%f vy=%f vz=%f\033[m" % (tag, c[0], c[1], c[2], v[0], v[1], v[2]))
578                         item = Thruster("YASim_thruster#%d" % self.counter[tag], c, v)
579
580                 elif tag == "actionpt":
581                         if not isinstance(parent, Thrust):
582                                 raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
583                                                 % (path, self.locator.getLineNumber()))
584
585                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
586                         log("\t\033[36mactionpt x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
587                         parent.set_actionpt(c)
588
589                 elif tag == "dir":
590                         if not isinstance(parent, Thrust):
591                                 raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
592                                                 % (path, self.locator.getLineNumber()))
593
594                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
595                         log("\t\033[36mdir x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
596                         parent.set_dir(c)
597
598                 elif tag == "tank":
599                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
600                         log("\033[34;1m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
601                         item = Tank("YASim_tank#%d" % self.counter[tag], c)
602
603                 elif tag == "ballast":
604                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
605                         log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
606                         item = Ballast("YASim_ballast#%d" % self.counter[tag], c)
607
608                 elif tag == "weight":
609                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
610                         log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
611                         item = Weight("YASim_weight#%d" % self.counter[tag], c)
612
613                 elif tag == "hook":
614                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
615                         length = float(attrs.get("length", 1))
616                         up_angle = float(attrs.get("up-angle", 0))
617                         down_angle = float(attrs.get("down-angle", 70))
618                         log("\033[35m%s x=%f y=%f z=%f length=%f up-angle=%f down-angle=%f\033[m" \
619                                         % (tag, c[0], c[1], c[2], length, up_angle, down_angle))
620                         item = Hook("YASim_hook#%d" % self.counter[tag], c, length, up_angle, down_angle)
621
622                 elif tag == "hitch":
623                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
624                         log("\033[35m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
625                         item = Hitch("YASim_hitch#%d" % self.counter[tag], c)
626
627                 elif tag == "launchbar":
628                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
629                         length = float(attrs.get("length", 1))
630                         up_angle = float(attrs.get("up-angle", -45))
631                         down_angle = float(attrs.get("down-angle", 45))
632                         holdback = Vector(float(attrs.get("holdback-x", c[0])), float(attrs.get("holdback-y", c[1])), float(attrs.get("holdback-z", c[2])))
633                         holdback_length = float(attrs.get("holdback-length", 2))
634                         log("\033[35m%s x=%f y=%f z=%f length=%f down-angle=%f up-angle=%f holdback-x=%f holdback-y=%f holdback-z+%f holdback-length=%f\033[m" \
635                                         % (tag, c[0], c[1], c[2], length, down_angle, up_angle, \
636                                         holdback[0], holdback[1], holdback[2], holdback_length))
637                         item = Launchbar("YASim_launchbar#%d" % self.counter[tag], c, length, holdback, holdback_length, up_angle, down_angle)
638
639                 elif tag == "wing" or tag == "hstab" or tag == "vstab" or tag == "mstab":
640                         root = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
641                         length = float(attrs["length"])
642                         chord = float(attrs["chord"])
643                         incidence = float(attrs.get("incidence", 0))
644                         twist = float(attrs.get("twist", 0))
645                         taper = float(attrs.get("taper", 1))
646                         sweep = float(attrs.get("sweep", 0))
647                         dihedral = float(attrs.get("dihedral", [0, 90][tag == "vstab"]))
648                         log("\033[33;1m%s x=%f y=%f z=%f length=%f chord=%f incidence=%f twist=%f taper=%f sweep=%f dihedral=%f\033[m" \
649                                         % (tag, root[0], root[1], root[2], length, chord, incidence, twist, taper, sweep, dihedral))
650                         item = Wing("YASim_%s#%d" % (tag, self.counter[tag]), root, length, chord, incidence, twist, taper, sweep, dihedral)
651
652                 elif tag == "flap0" or tag == "flap1" or tag == "slat" or tag == "spoiler":
653                         if not isinstance(parent, Wing):
654                                 raise Abort("%s is not part of a wing or stab at line %d" \
655                                                 % (path, self.locator.getLineNumber()))
656
657                         start = float(attrs["start"])
658                         end = float(attrs["end"])
659                         log("\t\033[33m%s start=%f end=%f\033[m" % (tag, start, end))
660                         parent.add_flap("YASim_%s#%d" % (tag, self.counter[tag]), start, end)
661
662                 elif tag == "rotor":
663                         c = Vector(float(attrs.get("x", 0)), float(attrs.get("y", 0)), float(attrs.get("z", 0)))
664                         norm = Vector(float(attrs.get("nx", 0)), float(attrs.get("ny", 0)), float(attrs.get("nz", 1)))
665                         fwd = Vector(float(attrs.get("fx", 1)), float(attrs.get("fy", 0)), float(attrs.get("fz", 0)))
666                         diameter = float(attrs.get("diameter", 10.2))
667                         numblades = int(attrs.get("numblades", 4))
668                         chord = float(attrs.get("chord", 0.3))
669                         twist = float(attrs.get("twist", 0))
670                         taper = float(attrs.get("taper", 1))
671                         rel_len_blade_start = float(attrs.get("rel-len-blade-start", 0))
672                         phi0 = float(attrs.get("phi0", 0))
673                         ccw = not not int(attrs.get("ccw", 0))
674
675                         log(("\033[36;1mrotor x=%f y=%f z=%f nx=%f ny=%f nz=%f fx=%f fy=%f fz=%f numblades=%d diameter=%f " \
676                                         + "chord=%f twist=%f taper=%f rel_len_blade_start=%f phi0=%f ccw=%d\033[m") \
677                                         % (c[0], c[1], c[2], norm[0], norm[1], norm[2], fwd[0], fwd[1], fwd[2], numblades, \
678                                         diameter, chord, twist, taper, rel_len_blade_start, phi0, ccw))
679                         item = Rotor("YASim_rotor#%d" % self.counter[tag], c, norm, fwd, numblades, 0.5 * diameter, chord, \
680                                         twist, taper, rel_len_blade_start, phi0, ccw)
681
682                 elif tag not in self.ignored:
683                         log("\033[30;1m%s\033[m" % path)
684
685                 self.items.append(item)
686
687         def endElement(self, tag):
688                 self.tags.pop()
689                 self.items.pop()
690
691
692
693 def extract_matrix(filedata, tag):
694         v = { 'x': 0.0, 'y': 0.0, 'z': 0.0, 'h': 0.0, 'p': 0.0, 'r': 0.0 }
695         has_offsets = False
696         for line in filedata:
697                 line = string.strip(line)
698                 if not line.startswith("<!--") or not line.endswith("-->"):
699                         continue
700                 line = string.strip(line[4:-3])
701                 if not string.lower(line).startswith("%s:" % tag):
702                         continue
703                 line = string.strip(line[len(tag) + 1:])
704                 for assignment in string.split(line):
705                         (key, value) = string.split(assignment, '=', 2)
706                         v[string.strip(key)] = float(string.strip(value))
707                         has_offsets = True
708
709         if not has_offsets:
710                 return None
711
712         print(("using offsets: x=%f y=%f z=%f h=%f p=%f r=%f" % (v['x'], v['y'], v['z'], v['h'], v['p'], v['r'])))
713         return Euler(v['r'], v['p'], v['h']).toMatrix().resize4x4() * TranslationMatrix(Vector(v['x'], v['y'], v['z']))
714
715
716
717 def load_yasim_config(path):
718         if BPyMessages.Error_NoFile(path):
719                 return
720
721         Blender.Window.WaitCursor(1)
722         Blender.Window.EditMode(0)
723
724         print(("loading '%s'" % path))
725         try:
726                 for o in Item.scene.objects:
727                         if o.name.startswith("YASim_"):
728                                 Item.scene.objects.unlink(o)
729
730                 f = open(path)
731                 Global.data = f.readlines()
732                 f.close
733
734                 Global.path = path
735                 Global.matrix = YASIM_MATRIX
736                 matrix = extract_matrix(Global.data, "offsets")
737                 if matrix:
738                         Global.matrix *= matrix.invert()
739
740                 Global.yasim.parse(path)
741                 Blender.Registry.SetKey("FGYASimImportExport", { "path": path }, False)
742                 Global.data = None
743
744         except Abort, e:
745                 print("%s\nAborting ..." % (e.term or e.msg))
746                 Blender.Draw.PupMenu("Error%t|" + e.msg)
747
748         Blender.Window.RedrawAll()
749         Blender.Window.WaitCursor(0)
750
751
752
753 def gui_draw():
754         from Blender import BGL, Draw
755         (width, height) = Blender.Window.GetAreaSize()
756
757         BGL.glClearColor(0.4, 0.4, 0.45, 1)
758         BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
759
760         BGL.glColor3f(1, 1, 1)
761         BGL.glRasterPos2f(5, 55)
762         Draw.Text("FlightGear YASim Import:   '%s'" % Global.path)
763
764         Draw.PushButton("Reload", RELOAD_BUTTON, 5, 5, 80, 32, "reload YASim config file")
765         Global.mirror_button = Draw.Toggle("Mirror", MIRROR_BUTTON, 100, 5, 50, 16, Global.mirror_button.val, \
766                         "show symmetric surfaces on both sides (reloads config)")
767         Draw.PushButton("Update Cursor", CURSOR_BUTTON, width - 650, 5, 100, 32, "update cursor display (in YASim coordinate system)")
768
769         BGL.glRasterPos2f(width - 530 + Blender.Draw.GetStringWidth("Vector from last") - Blender.Draw.GetStringWidth("Current"), 24)
770         Draw.Text("Current cursor pos:    x = %+.3f    y = %+.3f    z = %+.3f" % tuple(Global.cursor))
771
772         c = Global.cursor - Global.last_cursor
773         BGL.glRasterPos2f(width - 530, 7)
774         Draw.Text("Vector from last cursor pos:    x = %+.3f    y = %+.3f    z = %+.3f    length = %.3f m" % (c[0], c[1], c[2], c.length))
775
776
777
778 def gui_event(ev, value):
779         if ev == Blender.Draw.ESCKEY:
780                 Blender.Draw.Exit()
781
782
783
784 def gui_button(n):
785         if n == NO_EVENT:
786                 return
787
788         elif n == RELOAD_BUTTON:
789                 load_yasim_config(Global.path)
790
791         elif n == CURSOR_BUTTON:
792                 Global.last_cursor = Global.cursor
793                 Global.cursor = Vector(Blender.Window.GetCursorPos()) * Global.matrix.invert()
794                 d = Global.cursor - Global.last_cursor
795                 print(("cursor:   x=\"%f\" y=\"%f\" z=\"%f\"   dx=%f dy=%f dz=%f   length=%f" \
796                                 % (Global.cursor[0], Global.cursor[1], Global.cursor[2], d[0], d[1], d[2], d.length)))
797
798         elif n == MIRROR_BUTTON:
799                 load_yasim_config(Global.path)
800
801         Blender.Draw.Redraw(1)
802
803
804
805 def main():
806         log(6 * "\n")
807         registry = Blender.Registry.GetKey("FGYASimImportExport", False)
808         if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
809                 path = registry["path"]
810         else:
811                 path = ""
812
813         xml_handler = import_yasim()
814         Global.yasim = make_parser()
815         Global.yasim.setContentHandler(xml_handler)
816         Global.yasim.setErrorHandler(xml_handler)
817
818         if Blender.Window.GetScreenInfo(Blender.Window.Types.SCRIPT):
819                 Blender.Draw.Register(gui_draw, gui_event, gui_button)
820
821         Blender.Window.FileSelector(load_yasim_config, "Import YASim Configuration File", path)
822
823
824
825 main()
826