7 # Tooltip: 'Loads and visualizes a YASim FDM geometry'
10 __author__ = "Melchior FRANZ < mfranz # aon : at >"
11 __url__ = ["http://www.flightgear.org/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/yasim_import.py"]
14 yasim_import.py loads and visualizes a YASim FDM geometry
15 =========================================================
17 It is recommended to load the model superimposed over a greyed out and immutable copy of the aircraft model:
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) ...")
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):
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
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:
43 <pitch-deg>5</pitch-deg>
48 <!-- offsets: x=3.45 z=-0.4 p=5 -->
50 Possible variables are:
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.
67 Elements are displayed as follows:
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);
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)
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.
97 Environment variable BLENDER_YASIM_IMPORT can be set to a space-separated list of options:
99 $ BLENDER_YASIM_IMPORT="mirror verbose" blender
103 verbose ... enables verbose logs
104 mirror ... enables mirroring of symmetric surfaces
108 #--------------------------------------------------------------------------------
109 # Copyright (C) 2009 Melchior FRANZ < mfranz # aon : at >
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.
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.
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 #--------------------------------------------------------------------------------
127 import Blender, BPyMessages, string, math, os
128 from Blender.Mathutils import *
129 from xml.sax import handler, make_parser
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)
138 DEG2RAD = math.pi / 180
139 RAD2DEG = 180 / math.pi
149 verbose = "verbose" in CONFIG
154 last_cursor = Vector(Blender.Window.GetCursorPos())
155 mirror_button = Blender.Draw.Create("mirror" in CONFIG)
159 class Abort(Exception):
160 def __init__(self, msg, term = None):
172 def draw_dashed_line(mesh, start, end):
174 step = w * (end - start).normalize()
176 for i in range(int(1 + 0.5 * (end - start).length / w)):
177 a = start + 2 * i * step
179 if (b - end).length < step.length:
181 mesh.verts.extend([a, b])
182 mesh.edges.extend([n + 2 * i, n + 2 * i + 1])
186 def draw_arrow(mesh, start, end):
188 m = v.toTrackQuat('x', 'z').toMatrix().resize4x4() * TranslationMatrix(start)
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]])
197 def draw_circle(mesh, numpoints, radius, matrix):
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]])
210 scene = Blender.Scene.GetCurrent()
212 def make_twosided(self, mesh):
215 f.mode |= Blender.Mesh.FaceModes.TWOSIDE | Blender.Mesh.FaceModes.OBCOL
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
224 mesh = obj.getData(mesh = True)
225 mesh.materials += [mat]
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)
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])
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])
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])
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)
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)
296 class Launchbar(Item):
297 def __init__(self, name, lb, lb_length, hb, hb_length, up_angle, dn_angle):
298 mesh = Blender.Mesh.New()
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)
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)
320 def set_actionpt(self, p):
323 def set_dir(self, d):
324 self.thrustvector = d
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)
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)
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)
347 a = self.actionpt - self.center
348 matrix = self.thrustvector.toTrackQuat('z', 'x').toMatrix().resize4x4() * TranslationMatrix(a)
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())
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)
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")
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)
377 class Fuselage(Item):
378 def __init__(self, name, a, b, width, taper, midpoint):
381 for i in range(numvert):
382 alpha = i * 2 * math.pi / float(numvert)
383 angle.append([math.cos(alpha), math.sin(alpha)])
387 mesh = Blender.Mesh.New()
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]])
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])
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]
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
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)
432 def __init__(self, name, root, length, chord, incidence, twist, taper, sweep, dihedral):
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]])
445 self.make_twosided(mesh)
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)
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)
460 obj.setMatrix(TranslationMatrix(root) * Global.matrix)
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()
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]])
474 self.make_twosided(mesh)
476 obj = self.scene.objects.new(mesh, name)
478 self.set_color(obj, [0.8, 0.8, 0, 0.9])
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
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"]
495 def warning(self, exception):
496 print((self.error_string("Warning", exception)))
498 def error(self, exception):
499 print((self.error_string("Error", exception)))
501 def fatalError(self, exception):
502 raise Abort(str(exception), self.error_string("Fatal", exception))
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 * ' ')
510 def setDocumentLocator(self, locator):
511 self.locator = locator
513 def startDocument(self):
518 def endDocument(self):
519 for o in Item.scene.objects:
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())
526 self.tags.append(tag)
527 path = string.join(self.tags, '/')
529 parent = self.items[-1]
531 if self.counter.has_key(tag):
532 self.counter[tag] += 1
534 self.counter[tag] = 0
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]))
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)
552 c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
553 compression = float(attrs.get("compression", 1))
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)
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)
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)
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)
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()))
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)
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()))
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]))
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)
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)
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)
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)
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)
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)
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)
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()))
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)
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))
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)
681 elif tag not in self.ignored:
682 log("\033[30;1m%s\033[m" % path)
684 self.items.append(item)
686 def endElement(self, tag):
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 }
695 for line in filedata:
696 line = string.strip(line)
697 if not line.startswith("<!--") or not line.endswith("-->"):
699 line = string.strip(line[4:-3])
700 if not string.lower(line).startswith("%s:" % tag):
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))
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']))
716 def load_yasim_config(path):
717 if BPyMessages.Error_NoFile(path):
720 Blender.Window.WaitCursor(1)
721 Blender.Window.EditMode(0)
723 print(("loading '%s'" % path))
725 for o in Item.scene.objects:
726 if o.name.startswith("YASim_"):
727 Item.scene.objects.unlink(o)
730 Global.data = f.readlines()
734 Global.matrix = YASIM_MATRIX
735 matrix = extract_matrix(Global.data, "offsets")
737 Global.matrix *= matrix.invert()
739 Global.yasim.parse(path)
740 Blender.Registry.SetKey("FGYASimImportExport", { "path": path }, False)
744 print(("%s\nAborting ..." % (e.term or e.msg)))
745 Blender.Draw.PupMenu("Error%t|" + e.msg)
747 Blender.Window.RedrawAll()
748 Blender.Window.WaitCursor(0)
753 from Blender import BGL, Draw
754 (width, height) = Blender.Window.GetAreaSize()
756 BGL.glClearColor(0.4, 0.4, 0.45, 1)
757 BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
759 BGL.glColor3f(1, 1, 1)
760 BGL.glRasterPos2f(5, 55)
761 Draw.Text("FlightGear YASim Import: '%s'" % Global.path)
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)")
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))
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))
777 def gui_event(ev, value):
778 if ev == Blender.Draw.ESCKEY:
787 elif n == RELOAD_BUTTON:
788 load_yasim_config(Global.path)
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)))
797 elif n == MIRROR_BUTTON:
798 load_yasim_config(Global.path)
800 Blender.Draw.Redraw(1)
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"]
812 xml_handler = import_yasim()
813 Global.yasim = make_parser()
814 Global.yasim.setContentHandler(xml_handler)
815 Global.yasim.setErrorHandler(xml_handler)
817 if Blender.Window.GetScreenInfo(Blender.Window.Types.SCRIPT):
818 Blender.Draw.Register(gui_draw, gui_event, gui_button)
820 Blender.Window.FileSelector(load_yasim_config, "Import YASim Configuration File", path)