]> git.mxchange.org Git - flightgear.git/blob - utils/Modeller/fgfs_animation.py
Remove a duplicate line.
[flightgear.git] / utils / Modeller / fgfs_animation.py
1 #!BPY
2
3 # """
4 # Name: 'FlightGear Animation (.xml)'
5 # Blender: 243
6 # Group: 'Export'
7 # Submenu: 'Generate textured lights'                   LIGHTS
8 # Submenu: 'Rotation'                                   ROTATE
9 # Submenu: 'Range (LOD)'                                RANGE
10 # Submenu: 'Translation from origin'                    TRANS0
11 # Submenu: 'Translation from cursor'                    TRANSC
12 # Tooltip: 'FlightGear Animation'
13 # """
14
15 # BLENDER PLUGIN
16 #
17 # Put this file into ~/.blender/scripts/. You'll then find
18 # it in Blender under "File->Export->FlightGear Animation (*.xml)"
19 #
20 # For the script to work properly, make sure a directory
21 # ~/.blender/scripts/bpydata/config/ exists. To change the
22 # script parameters, edit file fgfs_animation.cfg in that
23 # dir, or in blender, switch to "Scripts Window" view, select
24 # "Scripts->System->Scripts Config Editor" and there select
25 # "Export->FlightGear Animation (*.xml)". Don't forget to
26 # "apply" the changes.
27 #
28 # This file is Public Domain.
29
30
31 __author__ = "Melchior FRANZ < mfranz # aon : at >"
32 __url__ = "http://members.aon.at/mfranz/flightgear/"
33 __version__ = "$Revision$ -- $Date$"
34 __bpydoc__ = """\
35 == Generate textured lights ==
36
37 Adds linked "light objects" (square consisting of two triangles at
38 origin) for each selected vertex, and creates FlightGear animation
39 code that scales all faces according to view distance, moves them to
40 the vertex location, turns the face towards the viewer and blends
41 it with other (semi)transparent objects. All lights are turned off
42 at daylight.
43
44
45 == Translation from origin ==
46
47 Generates "translate" animation for each selected vertex and for the
48 cursor, if it is not at origin. This mode is thought for moving
49 objects to their proper location after scaling them at origin, a
50 technique that is used for lights.
51
52
53 == Translation from cursor ==
54
55 Same as above, except that movements from the cursor location are
56 calculated.
57
58
59 == Rotation ==
60
61 Generates one "rotate" animation from two selected vertices
62 (rotation axis), and the cursor (rotation center).
63
64
65 == Range ==
66
67 Generates "range" animation skeleton with list of all selected objects.
68 """
69
70
71 #==================================================================================================
72
73
74 import sys
75 import Blender
76 from math import sqrt
77 from Blender import Mathutils
78 from Blender.Mathutils import Vector, VecMultMat
79
80
81 REG_KEY = 'fgfs_animation'
82 MODE = __script__['arg']
83 ORIGIN = [0, 0, 0]
84
85 FILENAME = Blender.Get("filename")
86 (BASENAME, EXTNAME) = Blender.sys.splitext(FILENAME)
87 DIRNAME = Blender.sys.dirname(BASENAME)
88 FILENAME = Blender.sys.basename(BASENAME) + "-export.xml"
89
90 TOOLTIPS = {
91         'PATHNAME': "where exported data should be saved (current dir if empty)",
92         'INDENT': "number of spaces per indentation level, or 0 for tabs",
93 }
94
95 reg = Blender.Registry.GetKey(REG_KEY, True)
96 if not reg:
97         print "WRITING REGISTRY"
98         Blender.Registry.SetKey(REG_KEY, {
99                 'INDENT': 0,
100                 'PATHNAME': "",
101                 'tooltips': TOOLTIPS,
102         }, True)
103
104
105
106
107 #==================================================================================================
108
109
110 class Error(Exception):
111         pass
112
113
114 class BlenderSetup:
115         def __init__(self):
116                 #Blender.Window.WaitCursor(1)
117                 self.is_editmode = Blender.Window.EditMode()
118                 if self.is_editmode:
119                         Blender.Window.EditMode(0)
120
121         def __del__(self):
122                 #Blender.Window.WaitCursor(0)
123                 if self.is_editmode:
124                         Blender.Window.EditMode(1)
125
126
127 class XMLExporter:
128         def __init__(self, filename):
129                 cursor = Blender.Window.GetCursorPos()
130                 self.file = open(filename, "w")
131                 self.write('<?xml version="1.0"?>\n')
132                 self.write("<!-- project: %s -->\n" % Blender.Get("filename"))
133                 self.write("<!-- cursor: %0.12f %0.12f %0.12f -->\n\n" % (cursor[0], cursor[1], cursor[2]))
134                 self.write("<PropertyList>\n")
135                 self.write("\t<path>%s.ac</path>\n\n" % BASENAME)
136
137         def __del__(self):
138                 try:
139                         self.write("</PropertyList>\n")
140                         self.file.close()
141                 except AttributeError:          # opening in __init__ failed
142                         pass
143
144         def write(self, s):
145                 global INDENT
146                 if INDENT <= 0:
147                         self.file.write(s)
148                 else:
149                         self.file.write(s.expandtabs(INDENT))
150
151         def comment(self, s, e = "", a = ""):
152                 self.write(a + "<!-- " + s + " -->\n" + e)
153
154         def translate(self, name, va, vb):
155                 x = vb[0] - va[0]
156                 y = vb[1] - va[1]
157                 z = vb[2] - va[2]
158                 length = sqrt(x * x + y * y + z * z)
159                 s = """\
160         <animation>
161                 <type>translate</type>
162                 <object-name>%s</object-name>
163                 <offset-m>%s</offset-m>
164                 <axis>
165                         <x>%s</x>
166                         <y>%s</y>
167                         <z>%s</z>
168                 </axis>
169         </animation>\n\n"""
170                 self.write(s % (name, Round(length), Round(x), Round(y), Round(z)))
171
172         def rotate(self, name, center, va, vb):
173                 x = Round(vb[0] - va[0])
174                 y = Round(vb[1] - va[1])
175                 z = Round(vb[2] - va[2])
176                 self.write("\t<!-- Vertex 0: %f %f %f -->\n" % (va[0], va[1], va[2]))
177                 self.write("\t<!-- Vertex 1: %f %f %f -->\n" % (vb[0], vb[1], vb[2]))
178                 s = """\
179         <animation>
180                 <type>rotate</type>
181                 <object-name>%s</object-name>
182                 <property>null</property>
183                 <!--min-deg>0</min-deg-->
184                 <!--max-deg>360</max-deg-->
185                 <!--factor>1</factor-->
186                 <center>
187                         <x-m>%s</x-m>
188                         <y-m>%s</y-m>
189                         <z-m>%s</z-m>
190                 </center>
191                 <axis>
192                         <x>%s</x>
193                         <y>%s</y>
194                         <z>%s</z>
195                 </axis>
196         </animation>\n\n"""
197                 self.write(s % (name, Round(center[0]), Round(center[1]), Round(center[2]), x, y, z))
198
199         def billboard(self, name, spherical = "true"):
200                 s = """\
201         <animation>
202                 <type>billboard</type>
203                 <object-name>%s</object-name>
204                 <spherical type="bool">%s</spherical>
205         </animation>\n\n"""
206                 self.write(s % (name, spherical))
207
208         def group(self, name, objects):
209                 self.write("\t<animation>\n");
210                 self.write("\t\t<name>%s</name>\n" % name);
211                 for o in objects:
212                         self.write("\t\t<object-name>%s</object-name>\n" % o.name)
213                 self.write("\t</animation>\n\n");
214
215         def alphatest(self, name, factor = 0.001):
216                 s = """\
217         <animation>
218                 <type>alpha-test</type>
219                 <object-name>%s</object-name>
220                 <alpha-factor>%s</alpha-factor>
221         </animation>\n\n"""
222                 self.write(s % (name, factor))
223
224         def sunselect(self, name, angle = 1.57):
225                 s = """\
226         <animation>
227                 <type>select</type>
228                 <name>%s</name>
229                 <object-name>%s</object-name>
230                 <condition>
231                         <greater-than>
232                                 <property>/sim/time/sun-angle-rad</property>
233                                 <value>%s</value>
234                         </greater-than>
235                 </condition>
236         </animation>\n\n"""
237                 self.write(s % (name + "Night", name, angle))
238
239         def distscale(self, name, base):
240                 s = """\
241         <animation>
242                 <type>dist-scale</type>
243                 <object-name>%s</object-name>
244                 <interpolation>
245                         <entry>
246                                 <ind>0</ind>
247                                 <dep alias="../../../../%sparams/light-near"/>
248                         </entry>
249                         <entry>
250                                 <ind>500</ind>
251                                 <dep alias="../../../../%sparams/light-med"/>
252                         </entry>
253                         <entry>
254                                 <ind>16000</ind>
255                                 <dep alias="../../../../%sparams/light-far"/>
256                         </entry>
257                 </interpolation>
258         </animation>\n\n"""
259                 self.write(s % (name, base, base, base))
260
261         def range(self, objects):
262                 self.write("\t<animation>\n")
263                 self.write("\t\t<type>range</type>\n")
264                 for o in objects:
265                         self.write("\t\t<object-name>%s</object-name>\n" % o.name)
266                 self.write("""\
267                 <min-m>0</min-m>
268                 <!--
269                 <max-property>/sim/rendering/static-lod/detailed</max-property>
270
271                 <min-property>/sim/rendering/static-lod/detailed</min-property>
272                 <max-property>/sim/rendering/static-lod/rough</max-property>
273
274                 <min-property>/sim/rendering/static-lod/rough</min-property>
275                 -->
276                 <max-property>/sim/rendering/static-lod/bare</max-property>\n""")
277                 self.write("\t</animation>\n\n")
278
279         def lightrange(self, dist = 25000):
280                 s = """\
281         <animation>
282                 <type>range</type>
283                 <min-m>0</min-m>
284                 <max-m>%s</max-m>
285         </animation>\n\n"""
286                 self.write(s % dist)
287
288
289 #==================================================================================================
290
291
292 def Round(f, digits = 6):
293         r = round(f, digits)
294         if r == int(r):
295                 return str(int(r))
296         else:
297                 return str(r)
298
299
300 def serial(i, max = 0):
301         if max == 1:
302                 return ""
303         if max < 10:
304                 return "%d" % i
305         if max < 100:
306                 return "%02d" % i
307         if max < 1000:
308                 return "%03d" % i
309         if max < 10000:
310                 return "%04d" % i
311         return "%d" % i
312
313
314 def needsObjects(objects, n):
315         def error(e):
316                 raise Error("wrong number of selected mesh objects: please select " + e)
317         if n < 0 and len(objects) < -n:
318                 if n == -1:
319                         error("at least one object")
320                 else:
321                         error("at least %d objects" % -n)
322         elif n > 0 and len(objects) != n:
323                 if n == 1:
324                         error("exactly one object")
325                 else:
326                         error("exactly %d objects" % n)
327
328
329 def checkName(name):
330         """ check if name is already in use """
331         try:
332                 Blender.Object.Get(name)
333                 raise Error("can't generate object '" + name + "'; name already in use")
334         except AttributeError:
335                 pass
336         except ValueError:
337                 pass
338
339
340 def selectedVertices(object):
341         verts = []
342         mat = object.getMatrix('worldspace')
343         for v in object.getData().verts:
344                 if not v.sel:
345                         continue
346                 vec = Vector([v[0], v[1], v[2]])
347                 vec.resize4D()
348                 vec *= mat
349                 v[0], v[1], v[2] = vec[0], vec[1], vec[2]
350                 verts.append(v)
351         return verts
352
353
354 def createLightMesh(name, size = 1):
355         mesh = Blender.NMesh.New(name + "mesh")
356         mesh.mode = Blender.NMesh.Modes.NOVNORMALSFLIP
357         mesh.verts.append(Blender.NMesh.Vert(-size, 0, -size))
358         mesh.verts.append(Blender.NMesh.Vert(size, 0, -size))
359         mesh.verts.append(Blender.NMesh.Vert(size, 0, size))
360         mesh.verts.append(Blender.NMesh.Vert(-size, 0, size))
361
362         face1 = Blender.NMesh.Face()
363         face1.v.append(mesh.verts[0])
364         face1.v.append(mesh.verts[1])
365         face1.v.append(mesh.verts[2])
366         mesh.faces.append(face1)
367
368         face2 = Blender.NMesh.Face()
369         face2.v.append(mesh.verts[0])
370         face2.v.append(mesh.verts[2])
371         face2.v.append(mesh.verts[3])
372         mesh.faces.append(face2)
373
374         mat = Blender.Material.New(name + "mat")
375         mat.setRGBCol(1, 1, 1)
376         mat.setMirCol(1, 1, 1)
377         mat.setAlpha(1)
378         mat.setEmit(1)
379         mat.setSpecCol(0, 0, 0)
380         mat.setSpec(0)
381         mat.setAmb(0)
382         mesh.setMaterials([mat])
383         return mesh
384
385
386 def createLight(mesh, name):
387         object = Blender.Object.New("Mesh", name)
388         object.link(mesh)
389         Blender.Scene.getCurrent().link(object)
390         return object
391
392
393 # modes ===========================================================================================
394
395
396 class mode:
397         def __init__(self, xml = None):
398                 self.cursor = Blender.Window.GetCursorPos()
399                 self.objects = [o for o in Blender.Object.GetSelected() if o.getType() == 'Mesh']
400                 if xml != None:
401                         BlenderSetup()
402                         self.test()
403                         self.execute(xml)
404
405         def test(self):
406                 pass
407
408
409 class translationFromOrigin(mode):
410         def execute(self, xml):
411                 if self.cursor != ORIGIN:
412                         xml.translate("BlenderCursor", ORIGIN, self.cursor)
413
414                 needsObjects(self.objects, 1)
415                 object = self.objects[0]
416                 verts = selectedVertices(object)
417                 if len(verts):
418                         xml.comment('[%s] translate from origin: "%s"' % (BASENAME, object.name), '\n')
419                         for i, v in enumerate(verts):
420                                 xml.translate("X%d" % i, ORIGIN, v)
421
422
423 class translationFromCursor(mode):
424         def test(self):
425                 needsObjects(self.objects, 1)
426                 self.object = self.objects[0]
427                 self.verts = selectedVertices(self.object)
428                 if not len(self.verts):
429                         raise Error("no vertex selected")
430
431         def execute(self, xml):
432                 xml.comment('[%s] translate from cursor: "%s"' % (BASENAME, self.object.name), '\n')
433                 for i, v in enumerate(self.verts):
434                         xml.translate("X%d" % i, self.cursor, v)
435
436
437 class rotation(mode):
438         def test(self):
439                 needsObjects(self.objects, 1)
440                 self.object = self.objects[0]
441                 self.verts = selectedVertices(self.object)
442                 if len(self.verts) != 2:
443                         raise Error("you have to select two vertices that define the rotation axis!")
444
445         def execute(self, xml):
446                 xml.comment('[%s] rotate "%s"' % (BASENAME, self.object.name), '\n')
447                 if self.cursor == ORIGIN:
448                         Blender.Draw.PupMenu("The cursor is still at origin!%t|"\
449                                         "But nevertheless, I pretend it is the rotation center.")
450                 xml.rotate(self.object.name, self.cursor, self.verts[0], self.verts[1])
451
452
453 class levelOfDetail(mode):
454         def test(self):
455                 needsObjects(self.objects, -1)
456
457         def execute(self, xml):
458                 xml.comment('[%s] level of detail' % BASENAME, '\n')
459                 xml.range(self.objects)
460
461
462 class interpolation(mode):
463         def test(self):
464                 needsObjects(self.objects, -2)
465
466         def execute(self, xml):
467                 print
468                 for i, o in enumerate(self.objects):
469                         print "%d: %s" % (i, o.name)
470
471                 raise Error("this mode doesn't do anything useful yet")
472
473
474 class texturedLights(mode):
475         def test(self):
476                 needsObjects(self.objects, 1)
477                 self.object = self.objects[0]
478                 self.verts = selectedVertices(self.object)
479                 if not len(self.verts):
480                         raise Error("there are no vertices to put lights at")
481                 self.lightname = self.object.name + "X"
482
483                 checkName(self.lightname)
484                 for i, v in enumerate(self.verts):
485                         checkName(self.lightname + serial(i, len(self.verts)))
486
487         def execute(self, xml):
488                 lightname = self.lightname
489                 verts = self.verts
490                 object = self.object
491
492                 lightmesh = createLightMesh(lightname)
493
494                 lights = []
495                 for i, v in enumerate(verts):
496                         lights.append(createLight(lightmesh, lightname + serial(i, len(verts))))
497
498                 parent = object.getParent()
499                 if parent != None:
500                         parent.makeParent(lights)
501
502                 for l in lights:
503                         l.Layer = object.Layer
504
505                 xml.lightrange()
506                 xml.write("\t<%sparams>\n" % lightname)
507                 xml.write("\t\t<light-near>%s</light-near>\n" % "0.4")
508                 xml.write("\t\t<light-med>%s</light-med>\n" % "0.8")
509                 xml.write("\t\t<light-far>%s</light-far>\n" % "10")
510                 xml.write("\t</%sparams>\n\n" % lightname)
511
512                 if len(lights) == 1:
513                         xml.sunselect(lightname)
514                         xml.alphatest(lightname)
515                 else:
516                         xml.group(lightname + "Group", lights)
517                         xml.sunselect(lightname + "Group")
518                         xml.alphatest(lightname + "Group")
519
520                 for i, l in enumerate(lights):
521                         xml.translate(l.name, ORIGIN, verts[i])
522
523                 for l in lights:
524                         xml.billboard(l.name)
525
526                 for l in lights:
527                         xml.distscale(l.name, lightname)
528
529                 Blender.Redraw(-1)
530
531
532 execute = {
533         'LIGHTS'   : texturedLights,
534         'TRANS0'   : translationFromOrigin,
535         'TRANSC'   : translationFromCursor,
536         'ROTATE'   : rotation,
537         'RANGE'    : levelOfDetail,
538         'INTERPOL' : interpolation
539 }
540
541
542
543 # main() ==========================================================================================
544
545
546 def dofile(filename):
547         try:
548                 xml = XMLExporter(filename)
549                 execute[MODE](xml)
550
551         except Error, e:
552                 xml.comment("ERROR: " + e.args[0], '\n')
553                 raise Error(e.args[0])
554
555         except IOError, (errno, strerror):
556                 raise Error(strerror)
557
558
559 def main():
560         try:
561                 global FILENAME, INDENT
562                 reg = Blender.Registry.GetKey(REG_KEY, True)
563                 if reg:
564                         PATHNAME = reg['PATHNAME'] or ""
565                         INDENT = reg['INDENT'] or 0
566                 else:
567                         PATHNAME = ""
568                         INDENT = 0
569
570                 if PATHNAME:
571                         FILENAME = Blender.sys.join(PATHNAME, FILENAME)
572
573                 print 'writing to "' + FILENAME + '"'
574                 dofile(FILENAME)
575
576         except Error, e:
577                 print "ERROR: " + e.args[0]
578                 Blender.Draw.PupMenu("ERROR%t|" + e.args[0])
579
580
581
582 if MODE:
583         main()
584
585