]> git.mxchange.org Git - flightgear.git/blob - utils/Modeller/yasim_import.py
Remove specific hack while testing period
[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.2"
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                                -> magenta cube (10 cm side length)
82   weight                              -> inverted cyan cone
83   ballast                             -> yellow cylinder
84   hitch                               -> hexagon (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(6, 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) = (e.getColumnNumber(), e.getLineNumber())
506                 return "%s: %s\n%s%s^"  % (tag, str(e), Global.data[line - 1], column * ' ')
507
508
509         # doc_handler
510         def setDocumentLocator(self, locator):
511                 self.locator = locator
512
513         def startDocument(self):
514                 self.tags = []
515                 self.counter = {}
516                 self.items = [None]
517
518         def endDocument(self):
519                 for o in Item.scene.objects:
520                         o.sel = True
521
522         def startElement(self, tag, attrs):
523                 if len(self.tags) == 0 and tag != "airplane":
524                         raise Abort("this isn't a YASim config file (bad root tag at line %d)" % self.locator.getLineNumber())
525
526                 self.tags.append(tag)
527                 path = string.join(self.tags, '/')
528                 item = Item()
529                 parent = self.items[-1]
530
531                 if self.counter.has_key(tag):
532                         self.counter[tag] += 1
533                 else:
534                         self.counter[tag] = 0
535
536                 if tag == "cockpit":
537                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
538                         log("\033[31mcockpit x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
539                         item = Cockpit(c)
540
541                 elif tag == "fuselage":
542                         a = Vector(float(attrs["ax"]), float(attrs["ay"]), float(attrs["az"]))
543                         b = Vector(float(attrs["bx"]), float(attrs["by"]), float(attrs["bz"]))
544                         width = float(attrs["width"])
545                         taper = float(attrs.get("taper", 1))
546                         midpoint = float(attrs.get("midpoint", 0.5))
547                         log("\033[32mfuselage ax=%f ay=%f az=%f bx=%f by=%f bz=%f width=%f taper=%f midpoint=%f\033[m" % \
548                                         (a[0], a[1], a[2], b[0], b[1], b[2], width, taper, midpoint))
549                         item = Fuselage("YASim_%s#%d" % (tag, self.counter[tag]), a, b, width, taper, midpoint)
550
551                 elif tag == "gear":
552                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
553                         compression = float(attrs.get("compression", 1))
554                         up = Z * compression
555                         if attrs.has_key("upx"):
556                                 up = Vector(float(attrs["upx"]), float(attrs["upy"]), float(attrs["upz"])).normalize() * compression
557                         log("\033[35;1mgear x=%f y=%f z=%f compression=%f upx=%f upy=%f upz=%f\033[m" \
558                                         % (c[0], c[1], c[2], compression, up[0], up[1], up[2]))
559                         item = Gear("YASim_gear#%d" % self.counter[tag], c, up)
560
561                 elif tag == "jet":
562                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
563                         rotate = float(attrs.get("rotate", 0))
564                         log("\033[36;1mjet x=%f y=%f z=%f rotate=%f\033[m" % (c[0], c[1], c[2], rotate))
565                         item = Jet("YASim_jet#%d" % self.counter[tag], c, rotate)
566
567                 elif tag == "propeller":
568                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
569                         radius = float(attrs["radius"])
570                         log("\033[36;1m%s x=%f y=%f z=%f radius=%f\033[m" % (tag, c[0], c[1], c[2], radius))
571                         item = Propeller("YASim_propeller#%d" % self.counter[tag], c, radius)
572
573                 elif tag == "thruster":
574                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
575                         v = Vector(float(attrs["vx"]), float(attrs["vy"]), float(attrs["vz"]))
576                         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]))
577                         item = Thruster("YASim_thruster#%d" % self.counter[tag], c, v)
578
579                 elif tag == "actionpt":
580                         if not isinstance(parent, Thrust):
581                                 raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
582                                                 % (path, self.locator.getLineNumber()))
583
584                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
585                         log("\t\033[36mactionpt x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
586                         parent.set_actionpt(c)
587
588                 elif tag == "dir":
589                         if not isinstance(parent, Thrust):
590                                 raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
591                                                 % (path, self.locator.getLineNumber()))
592
593                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
594                         log("\t\033[36mdir x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
595                         parent.set_dir(c)
596
597                 elif tag == "tank":
598                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
599                         log("\033[34;1m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
600                         item = Tank("YASim_tank#%d" % self.counter[tag], c)
601
602                 elif tag == "ballast":
603                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
604                         log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
605                         item = Ballast("YASim_ballast#%d" % self.counter[tag], c)
606
607                 elif tag == "weight":
608                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
609                         log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
610                         item = Weight("YASim_weight#%d" % self.counter[tag], c)
611
612                 elif tag == "hook":
613                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
614                         length = float(attrs.get("length", 1))
615                         up_angle = float(attrs.get("up-angle", 0))
616                         down_angle = float(attrs.get("down-angle", 70))
617                         log("\033[35m%s x=%f y=%f z=%f length=%f up-angle=%f down-angle=%f\033[m" \
618                                         % (tag, c[0], c[1], c[2], length, up_angle, down_angle))
619                         item = Hook("YASim_hook#%d" % self.counter[tag], c, length, up_angle, down_angle)
620
621                 elif tag == "hitch":
622                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
623                         log("\033[35m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
624                         item = Hitch("YASim_hitch#%d" % self.counter[tag], c)
625
626                 elif tag == "launchbar":
627                         c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
628                         length = float(attrs.get("length", 1))
629                         up_angle = float(attrs.get("up-angle", -45))
630                         down_angle = float(attrs.get("down-angle", 45))
631                         holdback = Vector(float(attrs.get("holdback-x", c[0])), float(attrs.get("holdback-y", c[1])), float(attrs.get("holdback-z", c[2])))
632                         holdback_length = float(attrs.get("holdback-length", 2))
633                         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" \
634                                         % (tag, c[0], c[1], c[2], length, down_angle, up_angle, \
635                                         holdback[0], holdback[1], holdback[2], holdback_length))
636                         item = Launchbar("YASim_launchbar#%d" % self.counter[tag], c, length, holdback, holdback_length, up_angle, down_angle)
637
638                 elif tag == "wing" or tag == "hstab" or tag == "vstab" or tag == "mstab":
639                         root = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
640                         length = float(attrs["length"])
641                         chord = float(attrs["chord"])
642                         incidence = float(attrs.get("incidence", 0))
643                         twist = float(attrs.get("twist", 0))
644                         taper = float(attrs.get("taper", 1))
645                         sweep = float(attrs.get("sweep", 0))
646                         dihedral = float(attrs.get("dihedral", [0, 90][tag == "vstab"]))
647                         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" \
648                                         % (tag, root[0], root[1], root[2], length, chord, incidence, twist, taper, sweep, dihedral))
649                         item = Wing("YASim_%s#%d" % (tag, self.counter[tag]), root, length, chord, incidence, twist, taper, sweep, dihedral)
650
651                 elif tag == "flap0" or tag == "flap1" or tag == "slat" or tag == "spoiler":
652                         if not isinstance(parent, Wing):
653                                 raise Abort("%s is not part of a wing or stab at line %d" \
654                                                 % (path, self.locator.getLineNumber()))
655
656                         start = float(attrs["start"])
657                         end = float(attrs["end"])
658                         log("\t\033[33m%s start=%f end=%f\033[m" % (tag, start, end))
659                         parent.add_flap("YASim_%s#%d" % (tag, self.counter[tag]), start, end)
660
661                 elif tag == "rotor":
662                         c = Vector(float(attrs.get("x", 0)), float(attrs.get("y", 0)), float(attrs.get("z", 0)))
663                         norm = Vector(float(attrs.get("nx", 0)), float(attrs.get("ny", 0)), float(attrs.get("nz", 1)))
664                         fwd = Vector(float(attrs.get("fx", 1)), float(attrs.get("fy", 0)), float(attrs.get("fz", 0)))
665                         diameter = float(attrs.get("diameter", 10.2))
666                         numblades = int(attrs.get("numblades", 4))
667                         chord = float(attrs.get("chord", 0.3))
668                         twist = float(attrs.get("twist", 0))
669                         taper = float(attrs.get("taper", 1))
670                         rel_len_blade_start = float(attrs.get("rel-len-blade-start", 0))
671                         phi0 = float(attrs.get("phi0", 0))
672                         ccw = not not int(attrs.get("ccw", 0))
673
674                         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 " \
675                                         + "chord=%f twist=%f taper=%f rel_len_blade_start=%f phi0=%f ccw=%d\033[m") \
676                                         % (c[0], c[1], c[2], norm[0], norm[1], norm[2], fwd[0], fwd[1], fwd[2], numblades, \
677                                         diameter, chord, twist, taper, rel_len_blade_start, phi0, ccw))
678                         item = Rotor("YASim_rotor#%d" % self.counter[tag], c, norm, fwd, numblades, 0.5 * diameter, chord, \
679                                         twist, taper, rel_len_blade_start, phi0, ccw)
680
681                 elif tag not in self.ignored:
682                         log("\033[30;1m%s\033[m" % path)
683
684                 self.items.append(item)
685
686         def endElement(self, tag):
687                 self.tags.pop()
688                 self.items.pop()
689
690
691
692 def extract_matrix(filedata, tag):
693         v = { 'x': 0.0, 'y': 0.0, 'z': 0.0, 'h': 0.0, 'p': 0.0, 'r': 0.0 }
694         has_offsets = False
695         for line in filedata:
696                 line = string.strip(line)
697                 if not line.startswith("<!--") or not line.endswith("-->"):
698                         continue
699                 line = string.strip(line[4:-3])
700                 if not string.lower(line).startswith("%s:" % tag):
701                         continue
702                 line = string.strip(line[len(tag) + 1:])
703                 for assignment in string.split(line):
704                         (key, value) = string.split(assignment, '=', 2)
705                         v[string.strip(key)] = float(string.strip(value))
706                         has_offsets = True
707
708         if not has_offsets:
709                 return None
710
711         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'])))
712         return Euler(v['r'], v['p'], v['h']).toMatrix().resize4x4() * TranslationMatrix(Vector(v['x'], v['y'], v['z']))
713
714
715
716 def load_yasim_config(path):
717         if BPyMessages.Error_NoFile(path):
718                 return
719
720         Blender.Window.WaitCursor(1)
721         Blender.Window.EditMode(0)
722
723         print(("loading '%s'" % path))
724         try:
725                 for o in Item.scene.objects:
726                         if o.name.startswith("YASim_"):
727                                 Item.scene.objects.unlink(o)
728
729                 f = open(path)
730                 Global.data = f.readlines()
731                 f.close
732
733                 Global.path = path
734                 Global.matrix = YASIM_MATRIX
735                 matrix = extract_matrix(Global.data, "offsets")
736                 if matrix:
737                         Global.matrix *= matrix.invert()
738
739                 Global.yasim.parse(path)
740                 Blender.Registry.SetKey("FGYASimImportExport", { "path": path }, False)
741                 Global.data = None
742
743         except Abort, e:
744                 print(("%s\nAborting ..." % (e.term or e.msg)))
745                 Blender.Draw.PupMenu("Error%t|" + e.msg)
746
747         Blender.Window.RedrawAll()
748         Blender.Window.WaitCursor(0)
749
750
751
752 def gui_draw():
753         from Blender import BGL, Draw
754         (width, height) = Blender.Window.GetAreaSize()
755
756         BGL.glClearColor(0.4, 0.4, 0.45, 1)
757         BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
758
759         BGL.glColor3f(1, 1, 1)
760         BGL.glRasterPos2f(5, 55)
761         Draw.Text("FlightGear YASim Import:   '%s'" % Global.path)
762
763         Draw.PushButton("Reload", RELOAD_BUTTON, 5, 5, 80, 32, "reload YASim config file")
764         Global.mirror_button = Draw.Toggle("Mirror", MIRROR_BUTTON, 100, 5, 50, 16, Global.mirror_button.val, \
765                         "show symmetric surfaces on both sides (reloads config)")
766         Draw.PushButton("Update Cursor", CURSOR_BUTTON, width - 650, 5, 100, 32, "update cursor display (in YASim coordinate system)")
767
768         BGL.glRasterPos2f(width - 530 + Blender.Draw.GetStringWidth("Vector from last") - Blender.Draw.GetStringWidth("Current"), 24)
769         Draw.Text("Current cursor pos:    x = %+.3f    y = %+.3f    z = %+.3f" % tuple(Global.cursor))
770
771         c = Global.cursor - Global.last_cursor
772         BGL.glRasterPos2f(width - 530, 7)
773         Draw.Text("Vector from last cursor pos:    x = %+.3f    y = %+.3f    z = %+.3f    length = %.3f m" % (c[0], c[1], c[2], c.length))
774
775
776
777 def gui_event(ev, value):
778         if ev == Blender.Draw.ESCKEY:
779                 Blender.Draw.Exit()
780
781
782
783 def gui_button(n):
784         if n == NO_EVENT:
785                 return
786
787         elif n == RELOAD_BUTTON:
788                 load_yasim_config(Global.path)
789
790         elif n == CURSOR_BUTTON:
791                 Global.last_cursor = Global.cursor
792                 Global.cursor = Vector(Blender.Window.GetCursorPos()) * Global.matrix.invert()
793                 d = Global.cursor - Global.last_cursor
794                 print(("cursor:   x=\"%f\" y=\"%f\" z=\"%f\"   dx=%f dy=%f dz=%f   length=%f" \
795                                 % (Global.cursor[0], Global.cursor[1], Global.cursor[2], d[0], d[1], d[2], d.length)))
796
797         elif n == MIRROR_BUTTON:
798                 load_yasim_config(Global.path)
799
800         Blender.Draw.Redraw(1)
801
802
803
804 def main():
805         log(6 * "\n")
806         registry = Blender.Registry.GetKey("FGYASimImportExport", False)
807         if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
808                 path = registry["path"]
809         else:
810                 path = ""
811
812         xml_handler = import_yasim()
813         Global.yasim = make_parser()
814         Global.yasim.setContentHandler(xml_handler)
815         Global.yasim.setErrorHandler(xml_handler)
816
817         if Blender.Window.GetScreenInfo(Blender.Window.Types.SCRIPT):
818                 Blender.Draw.Register(gui_draw, gui_event, gui_button)
819
820         Blender.Window.FileSelector(load_yasim_config, "Import YASim Configuration File", path)
821
822
823
824 main()
825