first commit
This commit is contained in:
40
utils/Modeller/3dconvert.cxx
Normal file
40
utils/Modeller/3dconvert.cxx
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <GL/glut.h>
|
||||
|
||||
#include <plib/ssg.h>
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
|
||||
int
|
||||
main (int ac, char ** av)
|
||||
{
|
||||
if (ac != 3) {
|
||||
cerr << "Usage: " << av[0] << " <file_in> <file_out>" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fakeac = 1;
|
||||
char * fakeav[] = { "3dconvert",
|
||||
"Convert a 3D Model",
|
||||
0 };
|
||||
glutInit(&fakeac, fakeav);
|
||||
glutCreateWindow(fakeav[1]);
|
||||
|
||||
ssgInit();
|
||||
ssgEntity * object = ssgLoad(av[1]);
|
||||
if (object == 0) {
|
||||
cerr << "Failed to load " << av[1] << endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
ssgSave(av[2], object);
|
||||
}
|
||||
193
utils/Modeller/ac3d-despeckle
Executable file
193
utils/Modeller/ac3d-despeckle
Executable file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/perl -w
|
||||
# $Id$
|
||||
# Melchior FRANZ < mfranz # aon : at > Public Domain
|
||||
#
|
||||
# Older nVidia cards like the GF4 MX440 don't like UV faces
|
||||
# where all coordinates are equal, and thus don't have an area.
|
||||
# This leads to grey spots that are constantly going on and off.
|
||||
# The script fixes UV faces with 3 and 4 references by assigning
|
||||
# a one (half)pixel sized area, and moves coordinates into the
|
||||
# texture area if necessary.
|
||||
#
|
||||
use strict;
|
||||
|
||||
my $SELF = $0;
|
||||
$SELF =~ s,.*\/,,;
|
||||
|
||||
my $VERBOSE = 0;
|
||||
my $NOOP = 0;
|
||||
my $TEXPATH = ".";
|
||||
|
||||
my $help = <<EOF;
|
||||
Usage:
|
||||
$SELF [-v] [-n] [-t <path>] <infile.ac >outfile.ac
|
||||
|
||||
Options:
|
||||
-h this help
|
||||
-v verbose output (also shows line numbers if used twice: -v -v)
|
||||
-n dry run
|
||||
-t <path> texture directory (default $TEXPATH)
|
||||
|
||||
Output:
|
||||
# BAD (face with identical UV coords; will be fixed)
|
||||
? CHECK (only two UV coordinates or more than 4; could be a problem)
|
||||
. OK (wrong coordinates, but no texture assigned; ignore)
|
||||
EOF
|
||||
|
||||
|
||||
my $OBJECTNAME;
|
||||
my $TEXTURE;
|
||||
my $PIXW = 0;
|
||||
my $PIXH = 0;
|
||||
|
||||
sub fatal($) {
|
||||
die shift;
|
||||
}
|
||||
|
||||
sub report($) {
|
||||
print STDERR shift;
|
||||
print STDERR "[$.] " if $VERBOSE > 1;
|
||||
}
|
||||
|
||||
|
||||
sub parse_args() {
|
||||
while (@ARGV) {
|
||||
my $arg = shift @ARGV;
|
||||
if ($arg eq '-h' or $arg eq '--help') {
|
||||
print STDERR $help;
|
||||
exit 0;
|
||||
|
||||
} elsif ($arg eq '-v' or $arg eq '--verbose') {
|
||||
$VERBOSE++;
|
||||
|
||||
} elsif ($arg eq '-n' or $arg eq '--dry-run') {
|
||||
$NOOP = 1;
|
||||
|
||||
} elsif ($arg eq '-t') {
|
||||
$arg = shift @ARGV;
|
||||
defined $arg or fatal('-t option without <text> argument');
|
||||
$TEXPATH = $arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub sgisize($) {
|
||||
my $name = shift;
|
||||
my $i;
|
||||
-f $name or fatal("cannot find texture '$name'");
|
||||
-s $name > 512 or fatal("'$name' cannot be an SGI image (too small)");
|
||||
open(IMG, "<$name") || fatal("cannot open file '$name': $!");
|
||||
read(IMG, $i, 2);
|
||||
unpack("n", $i) == 474 or fatal("'$name' cannot be an SGI image (wrong magic number)");
|
||||
|
||||
my ($x, $y);
|
||||
seek(IMG, 6, 0);
|
||||
read(IMG, $x, 2);
|
||||
read(IMG, $y, 2);
|
||||
close IMG or fatal("cannot close file '$name': $!");
|
||||
return (unpack("n", $x), unpack("n", $y));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# main()
|
||||
|
||||
parse_args();
|
||||
print STDERR "\033[35mTEXTURE PATH: $TEXPATH\033[m" if $VERBOSE;
|
||||
|
||||
|
||||
while (<>) {
|
||||
if (/^OBJECT /) {
|
||||
undef $TEXTURE;
|
||||
|
||||
} elsif (/^name\s+(.*)/) {
|
||||
$OBJECTNAME = $1;
|
||||
$OBJECTNAME =~ /"(.*)"/ and $OBJECTNAME = $1;
|
||||
print STDERR "\033[35;1m\nNAME: $OBJECTNAME\033[m " if $VERBOSE;
|
||||
|
||||
} elsif (/^texture\s+(.*)/) {
|
||||
$TEXTURE = $1;
|
||||
$TEXTURE =~ /"(.*)"/ and $TEXTURE = $1;
|
||||
my ($WIDTH, $HEIGHT) = sgisize($TEXPATH . '/' . $TEXTURE);
|
||||
print STDERR "\033[33;1m\nTEXTURE: $TEXTURE ($WIDTH x $HEIGHT)\033[m " if $VERBOSE;
|
||||
$PIXW = 1.0 / $WIDTH;
|
||||
$PIXH = 1.0 / $HEIGHT;
|
||||
|
||||
} elsif (/^SURF /) {
|
||||
print $_ unless $NOOP;
|
||||
|
||||
my $mat = <>;
|
||||
$mat =~ /^mat / or fatal("line $.: mat entry missing");
|
||||
print $mat unless $NOOP;
|
||||
|
||||
my $refs = <>;
|
||||
$refs =~ /^refs\s+(\d+)/ or fatal("line $.: refs entry missing");
|
||||
print $refs unless $NOOP;
|
||||
my $n = $1;
|
||||
|
||||
if ($n < 3 or $n > 4) {
|
||||
report("?") if $VERBOSE;
|
||||
unless ($NOOP) {
|
||||
print scalar(<>) foreach (1 .. $n);
|
||||
}
|
||||
next;
|
||||
}
|
||||
|
||||
my (@ref0, @ref1, @ref2, @ref3);
|
||||
@ref0 = split /\s+/, <>;
|
||||
@ref1 = split /\s+/, <>;
|
||||
@ref2 = split /\s+/, <>;
|
||||
@ref3 = split /\s+/, <> if $n == 4;
|
||||
|
||||
goto writeuv if $ref0[1] != $ref1[1] or $ref0[2] != $ref1[2] or $ref1[1] != $ref2[1] or $ref1[2] != $ref2[2];
|
||||
goto writeuv if $n == 4 and ($ref2[1] != $ref3[1] or $ref2[2] != $ref3[2]);
|
||||
|
||||
|
||||
if (defined $TEXTURE) {
|
||||
report("#") if $VERBOSE;
|
||||
|
||||
my ($x, $y) = @ref0[1, 2];
|
||||
|
||||
$x = 0.0 if $x < 0.0;
|
||||
$x = 1.0 - $PIXW if $x > 1.0 - $PIXW;
|
||||
$y = 0.0 if $y < 0.0;
|
||||
$y = 1.0 - $PIXH if $y > 1.0 - $PIXH;
|
||||
|
||||
$x = int($x / $PIXW) * $PIXW;
|
||||
$y = int($y / $PIXH) * $PIXH;
|
||||
|
||||
$ref0[1] = $x, $ref0[2] = $y;
|
||||
$ref1[1] = $x + $PIXW;
|
||||
$ref2[1] = $x + $PIXW, $ref2[2] = $y + $PIXH;
|
||||
$ref3[2] = $y + $PIXH if $n == 4;
|
||||
|
||||
} else {
|
||||
report(".") if $VERBOSE;
|
||||
}
|
||||
|
||||
|
||||
writeuv:
|
||||
unless ($NOOP) {
|
||||
print join " ", @ref0;
|
||||
print "\n";
|
||||
print join " ", @ref1;
|
||||
print "\n";
|
||||
print join " ", @ref2;
|
||||
print "\n";
|
||||
if ($n == 4) {
|
||||
print join " ", @ref3;
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
print unless $NOOP;
|
||||
}
|
||||
|
||||
print STDERR "\n" if $VERBOSE;
|
||||
exit 0;
|
||||
|
||||
|
||||
|
||||
79
utils/Modeller/animassist.c
Normal file
79
utils/Modeller/animassist.c
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
// animassist.c
|
||||
//
|
||||
// Jim Wilson 5/8/2003
|
||||
//
|
||||
// Copyright (C) 2003 Jim Wilson - jimw@kelcomaine.com
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
int main( int argc, char **argv ) {
|
||||
float x1,y1,z1,x2,y2,z2 = 0;
|
||||
float center_x,center_y,center_z,axis_x,axis_y,axis_z;
|
||||
float vector_length;
|
||||
|
||||
if (argc < 7) {
|
||||
printf("animassist - utility to calculate axis and center values for SimGear model animation\n\n");
|
||||
printf("Usage: animassist x1 y1 z1 x2 y2 z2\n\n");
|
||||
printf("Defining two vectors in SimGear coords (x1,y1,z1) and (x2,y2,z2)\n\n");
|
||||
printf("Conversion from model to SimGear:\n");
|
||||
printf("SimGear Coord AC3D Coordinate\n");
|
||||
printf(" X = X\n");
|
||||
printf(" Y = Z * -1\n");
|
||||
printf(" Z = Y\n\n");
|
||||
printf("Note: no normalization done, so please make the 2nd vector the furthest out\n");
|
||||
} else {
|
||||
|
||||
x1 = atof(argv[1]);
|
||||
y1 = atof(argv[2]);
|
||||
z1 = atof(argv[3]);
|
||||
x2 = atof(argv[4]);
|
||||
y2 = atof(argv[5]);
|
||||
z2 = atof(argv[6]);
|
||||
|
||||
center_x = (x1+x2)/2;
|
||||
center_y = (y1+y2)/2;
|
||||
center_z = (z1+z2)/2;
|
||||
|
||||
vector_length = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1));
|
||||
|
||||
/* arbitrary axis defined by cos(theta) where theta is angle from x,y or z axis */
|
||||
axis_x = (x2-x1)/vector_length;
|
||||
axis_y = (y2-y1)/vector_length;
|
||||
axis_z = (z2-z1)/vector_length;
|
||||
|
||||
printf("Flightgear Model XML for:\n");
|
||||
printf("Axis from (%f,%f,%f) to (%f,%f,%f)\n", x1,y1,z1,x2,y2,z2);
|
||||
printf("Assuming units in meters!\n\n");
|
||||
printf("<center>\n");
|
||||
printf(" <x-m>%f</x-m>\n",center_x);
|
||||
printf(" <y-m>%f</y-m>\n",center_y);
|
||||
printf(" <z-m>%f</z-m>\n",center_z);
|
||||
printf("</center>\n\n");
|
||||
printf("<axis>\n");
|
||||
printf(" <x>%f</x>\n",axis_x);
|
||||
printf(" <y>%f</y>\n",axis_y);
|
||||
printf(" <z>%f</z>\n",axis_z);
|
||||
printf("</axis>\n\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
68
utils/Modeller/animcheck.pl
Executable file
68
utils/Modeller/animcheck.pl
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
# Quick & dirty script to find booboos in FlightGear animation files
|
||||
# May 2004 Joshua Babcock jbabcock@atlantech.net
|
||||
|
||||
use strict;
|
||||
|
||||
if ( $#ARGV != 1 ) {
|
||||
print "Usage: $0 <xml file> <ac3d file>\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
my %Objects;
|
||||
my %References;
|
||||
my $Key;
|
||||
my $Num;
|
||||
my $First;
|
||||
my $ObjCount=0;
|
||||
my $CheckForm=1;
|
||||
|
||||
# Put whatever you want in here to check for poorly formatted object names.
|
||||
sub CheckForm {
|
||||
my $Bad=0;
|
||||
$_[0] !~ /^[A-Z].*/ && ($Bad=1);
|
||||
$_[0] =~ /\W/ && ($Bad=1);
|
||||
print "$_[0] poorly formatted\n" if $Bad;
|
||||
}
|
||||
|
||||
# Make a hash of all the object names in the AC3D file.
|
||||
open (AC3D, $ARGV[1]) or die "Could not open $ARGV[1]";
|
||||
while (<AC3D>) {
|
||||
/^name \"(.*)\"/ && ($Objects{$1}+=1);
|
||||
}
|
||||
close AC3D;
|
||||
|
||||
# Check for duplicates and proper format.
|
||||
foreach $Key (keys %Objects) {
|
||||
print "$Objects{$Key} instances of $1\n" if ($Objects{$Key}>1);
|
||||
&CheckForm($Key) if $CheckForm;
|
||||
++$ObjCount;
|
||||
}
|
||||
print "$ObjCount objects found.\n\n";
|
||||
|
||||
# Make a hash of objects in the XML file that do not reference an object in the AC3D file.
|
||||
open (XML, $ARGV[0]) or die "Could not open $ARGV[0]";
|
||||
while (<XML>) {
|
||||
if (m|<object-name>(.*)</object-name>| && ! exists($Objects{$1})) {
|
||||
# voodoo, "Perl Cookbook", p140
|
||||
push( @{$References{$1}}, $.);
|
||||
}
|
||||
}
|
||||
close XML;
|
||||
|
||||
# List all the bad referencees.
|
||||
foreach $Key (keys %References) {
|
||||
$First=1;
|
||||
print "Non-existant object $Key at line";
|
||||
print "s" if (scalar( @{$References{$Key}}) > 1);
|
||||
print ":";
|
||||
foreach $Num (@{$References{$Key}}) {
|
||||
$First ? ($First=0) : (print ",");
|
||||
print " $Num"
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
exit 0;
|
||||
|
||||
585
utils/Modeller/fgfs_animation.py
Normal file
585
utils/Modeller/fgfs_animation.py
Normal file
@@ -0,0 +1,585 @@
|
||||
#!BPY
|
||||
|
||||
# """
|
||||
# Name: 'FlightGear Animation (.xml)'
|
||||
# Blender: 243
|
||||
# Group: 'Export'
|
||||
# Submenu: 'Generate textured lights' LIGHTS
|
||||
# Submenu: 'Rotation' ROTATE
|
||||
# Submenu: 'Range (LOD)' RANGE
|
||||
# Submenu: 'Translation from origin' TRANS0
|
||||
# Submenu: 'Translation from cursor' TRANSC
|
||||
# Tooltip: 'FlightGear Animation'
|
||||
# """
|
||||
|
||||
# BLENDER PLUGIN
|
||||
#
|
||||
# Put this file into ~/.blender/scripts/. You'll then find
|
||||
# it in Blender under "File->Export->FlightGear Animation (*.xml)"
|
||||
#
|
||||
# For the script to work properly, make sure a directory
|
||||
# ~/.blender/scripts/bpydata/config/ exists. To change the
|
||||
# script parameters, edit file fgfs_animation.cfg in that
|
||||
# dir, or in blender, switch to "Scripts Window" view, select
|
||||
# "Scripts->System->Scripts Config Editor" and there select
|
||||
# "Export->FlightGear Animation (*.xml)". Don't forget to
|
||||
# "apply" the changes.
|
||||
#
|
||||
# This file is Public Domain.
|
||||
|
||||
|
||||
__author__ = "Melchior FRANZ < mfranz # aon : at >"
|
||||
__url__ = "http://members.aon.at/mfranz/flightgear/"
|
||||
__version__ = "$Revision$ -- $Date$"
|
||||
__bpydoc__ = """\
|
||||
== Generate textured lights ==
|
||||
|
||||
Adds linked "light objects" (square consisting of two triangles at
|
||||
origin) for each selected vertex, and creates FlightGear animation
|
||||
code that scales all faces according to view distance, moves them to
|
||||
the vertex location, turns the face towards the viewer and blends
|
||||
it with other (semi)transparent objects. All lights are turned off
|
||||
at daylight.
|
||||
|
||||
|
||||
== Translation from origin ==
|
||||
|
||||
Generates "translate" animation for each selected vertex and for the
|
||||
cursor, if it is not at origin. This mode is thought for moving
|
||||
objects to their proper location after scaling them at origin, a
|
||||
technique that is used for lights.
|
||||
|
||||
|
||||
== Translation from cursor ==
|
||||
|
||||
Same as above, except that movements from the cursor location are
|
||||
calculated.
|
||||
|
||||
|
||||
== Rotation ==
|
||||
|
||||
Generates one "rotate" animation from two selected vertices
|
||||
(rotation axis), and the cursor (rotation center).
|
||||
|
||||
|
||||
== Range ==
|
||||
|
||||
Generates "range" animation skeleton with list of all selected objects.
|
||||
"""
|
||||
|
||||
|
||||
#==================================================================================================
|
||||
|
||||
|
||||
import sys
|
||||
import Blender
|
||||
from math import sqrt
|
||||
from Blender import Mathutils
|
||||
from Blender.Mathutils import Vector, VecMultMat
|
||||
|
||||
|
||||
REG_KEY = 'fgfs_animation'
|
||||
MODE = __script__['arg']
|
||||
ORIGIN = [0, 0, 0]
|
||||
|
||||
FILENAME = Blender.Get("filename")
|
||||
(BASENAME, EXTNAME) = Blender.sys.splitext(FILENAME)
|
||||
DIRNAME = Blender.sys.dirname(BASENAME)
|
||||
FILENAME = Blender.sys.basename(BASENAME) + "-export.xml"
|
||||
|
||||
TOOLTIPS = {
|
||||
'PATHNAME': "where exported data should be saved (current dir if empty)",
|
||||
'INDENT': "number of spaces per indentation level, or 0 for tabs",
|
||||
}
|
||||
|
||||
reg = Blender.Registry.GetKey(REG_KEY, True)
|
||||
if not reg:
|
||||
print "WRITING REGISTRY"
|
||||
Blender.Registry.SetKey(REG_KEY, {
|
||||
'INDENT': 0,
|
||||
'PATHNAME': "",
|
||||
'tooltips': TOOLTIPS,
|
||||
}, True)
|
||||
|
||||
|
||||
|
||||
|
||||
#==================================================================================================
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BlenderSetup:
|
||||
def __init__(self):
|
||||
#Blender.Window.WaitCursor(1)
|
||||
self.is_editmode = Blender.Window.EditMode()
|
||||
if self.is_editmode:
|
||||
Blender.Window.EditMode(0)
|
||||
|
||||
def __del__(self):
|
||||
#Blender.Window.WaitCursor(0)
|
||||
if self.is_editmode:
|
||||
Blender.Window.EditMode(1)
|
||||
|
||||
|
||||
class XMLExporter:
|
||||
def __init__(self, filename):
|
||||
cursor = Blender.Window.GetCursorPos()
|
||||
self.file = open(filename, "w")
|
||||
self.write('<?xml version="1.0"?>\n')
|
||||
self.write("<!-- project: %s -->\n" % Blender.Get("filename"))
|
||||
self.write("<!-- cursor: %0.12f %0.12f %0.12f -->\n\n" % (cursor[0], cursor[1], cursor[2]))
|
||||
self.write("<PropertyList>\n")
|
||||
self.write("\t<path>%s.ac</path>\n\n" % BASENAME)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.write("</PropertyList>\n")
|
||||
self.file.close()
|
||||
except AttributeError: # opening in __init__ failed
|
||||
pass
|
||||
|
||||
def write(self, s):
|
||||
global INDENT
|
||||
if INDENT <= 0:
|
||||
self.file.write(s)
|
||||
else:
|
||||
self.file.write(s.expandtabs(INDENT))
|
||||
|
||||
def comment(self, s, e = "", a = ""):
|
||||
self.write(a + "<!-- " + s + " -->\n" + e)
|
||||
|
||||
def translate(self, name, va, vb):
|
||||
x = vb[0] - va[0]
|
||||
y = vb[1] - va[1]
|
||||
z = vb[2] - va[2]
|
||||
length = sqrt(x * x + y * y + z * z)
|
||||
s = """\
|
||||
<animation>
|
||||
<type>translate</type>
|
||||
<object-name>%s</object-name>
|
||||
<offset-m>%s</offset-m>
|
||||
<axis>
|
||||
<x>%s</x>
|
||||
<y>%s</y>
|
||||
<z>%s</z>
|
||||
</axis>
|
||||
</animation>\n\n"""
|
||||
self.write(s % (name, Round(length), Round(x), Round(y), Round(z)))
|
||||
|
||||
def rotate(self, name, center, va, vb):
|
||||
x = Round(vb[0] - va[0])
|
||||
y = Round(vb[1] - va[1])
|
||||
z = Round(vb[2] - va[2])
|
||||
self.write("\t<!-- Vertex 0: %f %f %f -->\n" % (va[0], va[1], va[2]))
|
||||
self.write("\t<!-- Vertex 1: %f %f %f -->\n" % (vb[0], vb[1], vb[2]))
|
||||
s = """\
|
||||
<animation>
|
||||
<type>rotate</type>
|
||||
<object-name>%s</object-name>
|
||||
<property>null</property>
|
||||
<!--min-deg>0</min-deg-->
|
||||
<!--max-deg>360</max-deg-->
|
||||
<!--factor>1</factor-->
|
||||
<center>
|
||||
<x-m>%s</x-m>
|
||||
<y-m>%s</y-m>
|
||||
<z-m>%s</z-m>
|
||||
</center>
|
||||
<axis>
|
||||
<x>%s</x>
|
||||
<y>%s</y>
|
||||
<z>%s</z>
|
||||
</axis>
|
||||
</animation>\n\n"""
|
||||
self.write(s % (name, Round(center[0]), Round(center[1]), Round(center[2]), x, y, z))
|
||||
|
||||
def billboard(self, name, spherical = "true"):
|
||||
s = """\
|
||||
<animation>
|
||||
<type>billboard</type>
|
||||
<object-name>%s</object-name>
|
||||
<spherical type="bool">%s</spherical>
|
||||
</animation>\n\n"""
|
||||
self.write(s % (name, spherical))
|
||||
|
||||
def group(self, name, objects):
|
||||
self.write("\t<animation>\n");
|
||||
self.write("\t\t<name>%s</name>\n" % name);
|
||||
for o in objects:
|
||||
self.write("\t\t<object-name>%s</object-name>\n" % o.name)
|
||||
self.write("\t</animation>\n\n");
|
||||
|
||||
def alphatest(self, name, factor = 0.001):
|
||||
s = """\
|
||||
<animation>
|
||||
<type>alpha-test</type>
|
||||
<object-name>%s</object-name>
|
||||
<alpha-factor>%s</alpha-factor>
|
||||
</animation>\n\n"""
|
||||
self.write(s % (name, factor))
|
||||
|
||||
def sunselect(self, name, angle = 1.57):
|
||||
s = """\
|
||||
<animation>
|
||||
<type>select</type>
|
||||
<name>%s</name>
|
||||
<object-name>%s</object-name>
|
||||
<condition>
|
||||
<greater-than>
|
||||
<property>/sim/time/sun-angle-rad</property>
|
||||
<value>%s</value>
|
||||
</greater-than>
|
||||
</condition>
|
||||
</animation>\n\n"""
|
||||
self.write(s % (name + "Night", name, angle))
|
||||
|
||||
def distscale(self, name, base):
|
||||
s = """\
|
||||
<animation>
|
||||
<type>dist-scale</type>
|
||||
<object-name>%s</object-name>
|
||||
<interpolation>
|
||||
<entry>
|
||||
<ind>0</ind>
|
||||
<dep alias="../../../../%sparams/light-near"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<ind>500</ind>
|
||||
<dep alias="../../../../%sparams/light-med"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<ind>16000</ind>
|
||||
<dep alias="../../../../%sparams/light-far"/>
|
||||
</entry>
|
||||
</interpolation>
|
||||
</animation>\n\n"""
|
||||
self.write(s % (name, base, base, base))
|
||||
|
||||
def range(self, objects):
|
||||
self.write("\t<animation>\n")
|
||||
self.write("\t\t<type>range</type>\n")
|
||||
for o in objects:
|
||||
self.write("\t\t<object-name>%s</object-name>\n" % o.name)
|
||||
self.write("""\
|
||||
<min-m>0</min-m>
|
||||
<!--
|
||||
<max-property>/sim/rendering/static-lod/detailed</max-property>
|
||||
|
||||
<min-property>/sim/rendering/static-lod/detailed</min-property>
|
||||
<max-property>/sim/rendering/static-lod/rough</max-property>
|
||||
|
||||
<min-property>/sim/rendering/static-lod/rough</min-property>
|
||||
-->
|
||||
<max-property>/sim/rendering/static-lod/bare</max-property>\n""")
|
||||
self.write("\t</animation>\n\n")
|
||||
|
||||
def lightrange(self, dist = 25000):
|
||||
s = """\
|
||||
<animation>
|
||||
<type>range</type>
|
||||
<min-m>0</min-m>
|
||||
<max-m>%s</max-m>
|
||||
</animation>\n\n"""
|
||||
self.write(s % dist)
|
||||
|
||||
|
||||
#==================================================================================================
|
||||
|
||||
|
||||
def Round(f, digits = 6):
|
||||
r = round(f, digits)
|
||||
if r == int(r):
|
||||
return str(int(r))
|
||||
else:
|
||||
return str(r)
|
||||
|
||||
|
||||
def serial(i, max = 0):
|
||||
if max == 1:
|
||||
return ""
|
||||
if max < 10:
|
||||
return "%d" % i
|
||||
if max < 100:
|
||||
return "%02d" % i
|
||||
if max < 1000:
|
||||
return "%03d" % i
|
||||
if max < 10000:
|
||||
return "%04d" % i
|
||||
return "%d" % i
|
||||
|
||||
|
||||
def needsObjects(objects, n):
|
||||
def error(e):
|
||||
raise Error("wrong number of selected mesh objects: please select " + e)
|
||||
if n < 0 and len(objects) < -n:
|
||||
if n == -1:
|
||||
error("at least one object")
|
||||
else:
|
||||
error("at least %d objects" % -n)
|
||||
elif n > 0 and len(objects) != n:
|
||||
if n == 1:
|
||||
error("exactly one object")
|
||||
else:
|
||||
error("exactly %d objects" % n)
|
||||
|
||||
|
||||
def checkName(name):
|
||||
""" check if name is already in use """
|
||||
try:
|
||||
Blender.Object.Get(name)
|
||||
raise Error("can't generate object '" + name + "'; name already in use")
|
||||
except AttributeError:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def selectedVertices(object):
|
||||
verts = []
|
||||
mat = object.getMatrix('worldspace')
|
||||
for v in object.getData().verts:
|
||||
if not v.sel:
|
||||
continue
|
||||
vec = Vector([v[0], v[1], v[2]])
|
||||
vec.resize4D()
|
||||
vec *= mat
|
||||
v[0], v[1], v[2] = vec[0], vec[1], vec[2]
|
||||
verts.append(v)
|
||||
return verts
|
||||
|
||||
|
||||
def createLightMesh(name, size = 1):
|
||||
mesh = Blender.NMesh.New(name + "mesh")
|
||||
mesh.mode = Blender.NMesh.Modes.NOVNORMALSFLIP
|
||||
mesh.verts.append(Blender.NMesh.Vert(-size, 0, -size))
|
||||
mesh.verts.append(Blender.NMesh.Vert(size, 0, -size))
|
||||
mesh.verts.append(Blender.NMesh.Vert(size, 0, size))
|
||||
mesh.verts.append(Blender.NMesh.Vert(-size, 0, size))
|
||||
|
||||
face1 = Blender.NMesh.Face()
|
||||
face1.v.append(mesh.verts[0])
|
||||
face1.v.append(mesh.verts[1])
|
||||
face1.v.append(mesh.verts[2])
|
||||
mesh.faces.append(face1)
|
||||
|
||||
face2 = Blender.NMesh.Face()
|
||||
face2.v.append(mesh.verts[0])
|
||||
face2.v.append(mesh.verts[2])
|
||||
face2.v.append(mesh.verts[3])
|
||||
mesh.faces.append(face2)
|
||||
|
||||
mat = Blender.Material.New(name + "mat")
|
||||
mat.setRGBCol(1, 1, 1)
|
||||
mat.setMirCol(1, 1, 1)
|
||||
mat.setAlpha(1)
|
||||
mat.setEmit(1)
|
||||
mat.setSpecCol(0, 0, 0)
|
||||
mat.setSpec(0)
|
||||
mat.setAmb(0)
|
||||
mesh.setMaterials([mat])
|
||||
return mesh
|
||||
|
||||
|
||||
def createLight(mesh, name):
|
||||
object = Blender.Object.New("Mesh", name)
|
||||
object.link(mesh)
|
||||
Blender.Scene.getCurrent().link(object)
|
||||
return object
|
||||
|
||||
|
||||
# modes ===========================================================================================
|
||||
|
||||
|
||||
class mode:
|
||||
def __init__(self, xml = None):
|
||||
self.cursor = Blender.Window.GetCursorPos()
|
||||
self.objects = [o for o in Blender.Object.GetSelected() if o.getType() == 'Mesh']
|
||||
if xml is not None:
|
||||
BlenderSetup()
|
||||
self.test()
|
||||
self.execute(xml)
|
||||
|
||||
def test(self):
|
||||
pass
|
||||
|
||||
|
||||
class translationFromOrigin(mode):
|
||||
def execute(self, xml):
|
||||
if self.cursor != ORIGIN:
|
||||
xml.translate("BlenderCursor", ORIGIN, self.cursor)
|
||||
|
||||
needsObjects(self.objects, 1)
|
||||
object = self.objects[0]
|
||||
verts = selectedVertices(object)
|
||||
if len(verts):
|
||||
xml.comment('[%s] translate from origin: "%s"' % (BASENAME, object.name), '\n')
|
||||
for i, v in enumerate(verts):
|
||||
xml.translate("X%d" % i, ORIGIN, v)
|
||||
|
||||
|
||||
class translationFromCursor(mode):
|
||||
def test(self):
|
||||
needsObjects(self.objects, 1)
|
||||
self.object = self.objects[0]
|
||||
self.verts = selectedVertices(self.object)
|
||||
if not len(self.verts):
|
||||
raise Error("no vertex selected")
|
||||
|
||||
def execute(self, xml):
|
||||
xml.comment('[%s] translate from cursor: "%s"' % (BASENAME, self.object.name), '\n')
|
||||
for i, v in enumerate(self.verts):
|
||||
xml.translate("X%d" % i, self.cursor, v)
|
||||
|
||||
|
||||
class rotation(mode):
|
||||
def test(self):
|
||||
needsObjects(self.objects, 1)
|
||||
self.object = self.objects[0]
|
||||
self.verts = selectedVertices(self.object)
|
||||
if len(self.verts) != 2:
|
||||
raise Error("you have to select two vertices that define the rotation axis!")
|
||||
|
||||
def execute(self, xml):
|
||||
xml.comment('[%s] rotate "%s"' % (BASENAME, self.object.name), '\n')
|
||||
if self.cursor == ORIGIN:
|
||||
Blender.Draw.PupMenu("The cursor is still at origin!%t|"\
|
||||
"But nevertheless, I pretend it is the rotation center.")
|
||||
xml.rotate(self.object.name, self.cursor, self.verts[0], self.verts[1])
|
||||
|
||||
|
||||
class levelOfDetail(mode):
|
||||
def test(self):
|
||||
needsObjects(self.objects, -1)
|
||||
|
||||
def execute(self, xml):
|
||||
xml.comment('[%s] level of detail' % BASENAME, '\n')
|
||||
xml.range(self.objects)
|
||||
|
||||
|
||||
class interpolation(mode):
|
||||
def test(self):
|
||||
needsObjects(self.objects, -2)
|
||||
|
||||
def execute(self, xml):
|
||||
print
|
||||
for i, o in enumerate(self.objects):
|
||||
print "%d: %s" % (i, o.name)
|
||||
|
||||
raise Error("this mode doesn't do anything useful yet")
|
||||
|
||||
|
||||
class texturedLights(mode):
|
||||
def test(self):
|
||||
needsObjects(self.objects, 1)
|
||||
self.object = self.objects[0]
|
||||
self.verts = selectedVertices(self.object)
|
||||
if not len(self.verts):
|
||||
raise Error("there are no vertices to put lights at")
|
||||
self.lightname = self.object.name + "X"
|
||||
|
||||
checkName(self.lightname)
|
||||
for i, v in enumerate(self.verts):
|
||||
checkName(self.lightname + serial(i, len(self.verts)))
|
||||
|
||||
def execute(self, xml):
|
||||
lightname = self.lightname
|
||||
verts = self.verts
|
||||
object = self.object
|
||||
|
||||
lightmesh = createLightMesh(lightname)
|
||||
|
||||
lights = []
|
||||
for i, v in enumerate(verts):
|
||||
lights.append(createLight(lightmesh, lightname + serial(i, len(verts))))
|
||||
|
||||
parent = object.getParent()
|
||||
if parent is not None:
|
||||
parent.makeParent(lights)
|
||||
|
||||
for l in lights:
|
||||
l.Layer = object.Layer
|
||||
|
||||
xml.lightrange()
|
||||
xml.write("\t<%sparams>\n" % lightname)
|
||||
xml.write("\t\t<light-near>%s</light-near>\n" % "0.4")
|
||||
xml.write("\t\t<light-med>%s</light-med>\n" % "0.8")
|
||||
xml.write("\t\t<light-far>%s</light-far>\n" % "10")
|
||||
xml.write("\t</%sparams>\n\n" % lightname)
|
||||
|
||||
if len(lights) == 1:
|
||||
xml.sunselect(lightname)
|
||||
xml.alphatest(lightname)
|
||||
else:
|
||||
xml.group(lightname + "Group", lights)
|
||||
xml.sunselect(lightname + "Group")
|
||||
xml.alphatest(lightname + "Group")
|
||||
|
||||
for i, l in enumerate(lights):
|
||||
xml.translate(l.name, ORIGIN, verts[i])
|
||||
|
||||
for l in lights:
|
||||
xml.billboard(l.name)
|
||||
|
||||
for l in lights:
|
||||
xml.distscale(l.name, lightname)
|
||||
|
||||
Blender.Redraw(-1)
|
||||
|
||||
|
||||
execute = {
|
||||
'LIGHTS' : texturedLights,
|
||||
'TRANS0' : translationFromOrigin,
|
||||
'TRANSC' : translationFromCursor,
|
||||
'ROTATE' : rotation,
|
||||
'RANGE' : levelOfDetail,
|
||||
'INTERPOL' : interpolation
|
||||
}
|
||||
|
||||
|
||||
|
||||
# main() ==========================================================================================
|
||||
|
||||
|
||||
def dofile(filename):
|
||||
try:
|
||||
xml = XMLExporter(filename)
|
||||
execute[MODE](xml)
|
||||
|
||||
except Error, e:
|
||||
xml.comment("ERROR: " + e.args[0], '\n')
|
||||
raise Error(e.args[0])
|
||||
|
||||
except IOError, (errno, strerror):
|
||||
raise Error(strerror)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
global FILENAME, INDENT
|
||||
reg = Blender.Registry.GetKey(REG_KEY, True)
|
||||
if reg:
|
||||
PATHNAME = reg['PATHNAME'] or ""
|
||||
INDENT = reg['INDENT'] or 0
|
||||
else:
|
||||
PATHNAME = ""
|
||||
INDENT = 0
|
||||
|
||||
if PATHNAME:
|
||||
FILENAME = Blender.sys.join(PATHNAME, FILENAME)
|
||||
|
||||
print 'writing to "' + FILENAME + '"'
|
||||
dofile(FILENAME)
|
||||
|
||||
except Error, e:
|
||||
print "ERROR: " + e.args[0]
|
||||
Blender.Draw.PupMenu("ERROR%t|" + e.args[0])
|
||||
|
||||
|
||||
|
||||
if MODE:
|
||||
main()
|
||||
|
||||
|
||||
134
utils/Modeller/photomodel.cxx
Normal file
134
utils/Modeller/photomodel.cxx
Normal file
@@ -0,0 +1,134 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WINDOWS_H
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // exit()
|
||||
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/math/sg_geodesy.hxx>
|
||||
|
||||
|
||||
int make_model( const char *texture,
|
||||
double ll_lon, double ll_lat,
|
||||
double lr_lon, double lr_lat,
|
||||
double ur_lon, double ur_lat,
|
||||
double ul_lon, double ul_lat,
|
||||
const char *model )
|
||||
{
|
||||
// open the output file
|
||||
FILE *out = fopen( model, "w" );
|
||||
if ( out == NULL ) {
|
||||
printf("Error: cannot open %s for writing\n", model);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// compute polygon boundaries in local XY projection
|
||||
double cen_lon = (ll_lon + lr_lon + ur_lon + ul_lon) / 4.0;
|
||||
double cen_lat = (ll_lat + lr_lat + ur_lat + ul_lat) / 4.0;
|
||||
|
||||
double az1, az2, dist;
|
||||
double angle;
|
||||
|
||||
geo_inverse_wgs_84( cen_lat, cen_lon, ll_lat, ll_lon, &az1, &az2, &dist );
|
||||
angle = az1 * SGD_DEGREES_TO_RADIANS;
|
||||
double llx = sin(angle) * dist;
|
||||
double lly = cos(angle) * dist;
|
||||
printf( "az1 = %.3f dx = %.3f dy = %.3f\n",
|
||||
az1, sin(angle) * dist, cos(angle) * dist );
|
||||
|
||||
geo_inverse_wgs_84( cen_lat, cen_lon, lr_lat, lr_lon, &az1, &az2, &dist );
|
||||
angle = az1 * SGD_DEGREES_TO_RADIANS;
|
||||
double lrx = sin(angle) * dist;
|
||||
double lry = cos(angle) * dist;
|
||||
printf( "az1 = %.3f dx = %.3f dy = %.3f\n",
|
||||
az1, sin(angle) * dist, cos(angle) * dist );
|
||||
|
||||
geo_inverse_wgs_84( cen_lat, cen_lon, ur_lat, ur_lon, &az1, &az2, &dist );
|
||||
angle = az1 * SGD_DEGREES_TO_RADIANS;
|
||||
double urx = sin(angle) * dist;
|
||||
double ury = cos(angle) * dist;
|
||||
printf( "az1 = %.3f dx = %.3f dy = %.3f\n",
|
||||
az1, sin(angle) * dist, cos(angle) * dist );
|
||||
|
||||
geo_inverse_wgs_84( cen_lat, cen_lon, ul_lat, ul_lon, &az1, &az2, &dist );
|
||||
angle = az1 * SGD_DEGREES_TO_RADIANS;
|
||||
double ulx = sin(angle) * dist;
|
||||
double uly = cos(angle) * dist;
|
||||
printf( "az1 = %.3f dx = %.3f dy = %.3f\n",
|
||||
az1, sin(angle) * dist, cos(angle) * dist );
|
||||
|
||||
fprintf( out, "AC3Db\n" );
|
||||
fprintf( out, "MATERIAL \"DefaultWhite\" rgb 1 1 1 amb 1 1 1 emis 0 0 0 spec 0.5 0.5 0.5 shi 64 trans 0\n" );
|
||||
fprintf( out, "OBJECT world\n" );
|
||||
fprintf( out, "kids 1\n" );
|
||||
fprintf( out, "OBJECT poly\n" );
|
||||
fprintf( out, "name \"terrain\"\n" );
|
||||
fprintf( out, "data 9\n" );
|
||||
fprintf( out, "Plane.073\n" );
|
||||
fprintf( out, "texture \"%s\"\n", texture );
|
||||
fprintf( out, "texrep 1 1\n" );
|
||||
fprintf( out, "crease 30.000000\n" );
|
||||
fprintf( out, "numvert 4\n" );
|
||||
fprintf( out, "%.3f 0 %.3f\n", lry, lrx );
|
||||
fprintf( out, "%.3f 0 %.3f\n", lly, llx );
|
||||
fprintf( out, "%.3f 0 %.3f\n", uly, ulx );
|
||||
fprintf( out, "%.3f 0 %.3f\n", ury, urx );
|
||||
fprintf( out, "numsurf 1\n" );
|
||||
fprintf( out, "SURF 0x00\n" );
|
||||
fprintf( out, "mat 0\n" );
|
||||
fprintf( out, "refs 4\n" );
|
||||
fprintf( out, "0 0.0 1.0\n" );
|
||||
fprintf( out, "3 0.0 0.0\n" );
|
||||
fprintf( out, "2 1.0 0.0\n" );
|
||||
fprintf( out, "1 1.0 1.0\n" );
|
||||
fprintf( out, "kids 0\n" );
|
||||
|
||||
fclose( out );
|
||||
|
||||
SGBucket b( cen_lon, cen_lat );
|
||||
|
||||
printf("\n");
|
||||
printf("Model is created as %s\n", model);
|
||||
printf("\n");
|
||||
printf("Now copy the .ac and .rgb file to your Model directory\n");
|
||||
printf("and add the following line to your .stg file:\n");
|
||||
printf("\n");
|
||||
printf(" %s/%s.stg\n",
|
||||
b.gen_base_path().c_str(), b.gen_index_str().c_str());
|
||||
printf("\n");
|
||||
printf("OBJECT_SHARED Models/MNUAV/%s %.6f %.6f ALT_IN_METERS 0.00\n",
|
||||
model, cen_lon, cen_lat);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void usage( char *prog ) {
|
||||
printf("Usage: %s photo.rgb ll_lon ll_lat lr_lon lr_lat ur_lon ur_lat ul_lon ul_lat model.ac\n", prog);
|
||||
printf("Usage: %s photo.rgb left_lon right_lon lower_lat upper_lat model.ac\n", prog);
|
||||
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
|
||||
int main( int argc, char **argv ) {
|
||||
if ( argc == 11 ) {
|
||||
make_model( argv[1],
|
||||
atof(argv[2]), atof(argv[3]), atof(argv[4]), atof(argv[5]),
|
||||
atof(argv[6]), atof(argv[7]), atof(argv[8]), atof(argv[9]),
|
||||
argv[10] );
|
||||
} else if ( argc == 7 ) {
|
||||
make_model( argv[1],
|
||||
atof(argv[2]), atof(argv[4]), atof(argv[3]), atof(argv[4]),
|
||||
atof(argv[3]), atof(argv[5]), atof(argv[2]), atof(argv[5]),
|
||||
argv[6] );
|
||||
} else {
|
||||
usage( argv[0] );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
174
utils/Modeller/uv_export_svg.py
Normal file
174
utils/Modeller/uv_export_svg.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!BPY
|
||||
|
||||
# """
|
||||
# Name: 'UV: Export to SVG'
|
||||
# Blender: 245
|
||||
# Group: 'Image'
|
||||
# Tooltip: 'Export selected objects to SVG file'
|
||||
# """
|
||||
|
||||
__author__ = "Melchior FRANZ < mfranz # aon : at >"
|
||||
__url__ = ["http://www.flightgear.org/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/uv_export_svg.py"]
|
||||
__version__ = "0.1"
|
||||
__bpydoc__ = """\
|
||||
Saves the UV mappings of all selected objects to an SVG file. The uv_import_svg.py
|
||||
script can be used to re-import such a file. Each object and each group of adjacent
|
||||
faces therein will be made a separate SVG group.
|
||||
"""
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
# Copyright (C) 2008 Melchior FRANZ < mfranz # aon : at >
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
FILL_COLOR = None # 'yellow' or '#ffa000' e.g. for uni-color, None for random color
|
||||
ID_SEPARATOR = '_.:._'
|
||||
|
||||
|
||||
import Blender, BPyMessages, sys, random
|
||||
|
||||
|
||||
class Abort(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
|
||||
class UVFaceGroups:
|
||||
def __init__(self, mesh):
|
||||
faces = dict([(f.index, f) for f in mesh.faces])
|
||||
self.groups = []
|
||||
while faces:
|
||||
self.groups.append(self.adjacent(faces))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.groups)
|
||||
|
||||
def __iter__(self):
|
||||
return self.groups.__iter__()
|
||||
|
||||
def adjacent(self, faces):
|
||||
uvcoords = {}
|
||||
face = faces.popitem()[1]
|
||||
group = [face]
|
||||
for c in face.uv:
|
||||
uvcoords[tuple(c)] = True
|
||||
|
||||
while True:
|
||||
found = []
|
||||
for face in faces.itervalues():
|
||||
for c in face.uv:
|
||||
if tuple(c) in uvcoords:
|
||||
for c in face.uv:
|
||||
uvcoords[tuple(c)] = True
|
||||
found.append(face)
|
||||
break
|
||||
if not found:
|
||||
return group
|
||||
for face in found:
|
||||
group.append(face)
|
||||
del faces[face.index]
|
||||
|
||||
|
||||
def stringcolor(string):
|
||||
random.seed(hash(string))
|
||||
c = [random.randint(220, 255), random.randint(120, 240), random.randint(120, 240)]
|
||||
random.shuffle(c)
|
||||
return "#%02x%02x%02x" % tuple(c)
|
||||
|
||||
|
||||
def write_svg(path):
|
||||
size = Blender.Draw.PupMenu("Image size%t|128|256|512|1024|2048|4096|8192")
|
||||
if size < 0:
|
||||
raise Abort('no image size chosen')
|
||||
size = 1 << (size + 6)
|
||||
|
||||
svg = open(path, "w")
|
||||
svg.write('<?xml version="1.0" standalone="no"?>\n')
|
||||
svg.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n\n')
|
||||
svg.write('<svg width="%spx" height="%spx" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg"' \
|
||||
' version="1.1" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">\n'
|
||||
% (size, size, size, size))
|
||||
svg.write("\t<desc>uv_export_svg.py: %s</desc>\n" % path);
|
||||
svg.write('\t<rect x="0" y="0" width="%d" height="%d" fill="none" stroke="blue" stroke-width="%f"/>\n'
|
||||
% (size, size, 1.0))
|
||||
|
||||
objects = {}
|
||||
for o in Blender.Scene.GetCurrent().objects.selected:
|
||||
if o.type != "Mesh":
|
||||
continue
|
||||
|
||||
mesh = o.getData(mesh = 1)
|
||||
if mesh.faceUV:
|
||||
objects[mesh.name] = (o.name, mesh)
|
||||
|
||||
for meshname, v in objects.iteritems():
|
||||
objname, mesh = v
|
||||
color = FILL_COLOR or stringcolor(meshname)
|
||||
|
||||
svg.write('\t<g style="fill:%s; stroke:black stroke-width:1px" inkscape:label="%s" ' \
|
||||
'id="%s">\n' % (color, objname, objname))
|
||||
|
||||
facegroups = UVFaceGroups(mesh)
|
||||
for faces in facegroups:
|
||||
indent = '\t\t'
|
||||
if len(facegroups) > 1:
|
||||
svg.write('\t\t<g>\n')
|
||||
indent = '\t\t\t'
|
||||
for f in faces:
|
||||
svg.write('%s<polygon id="%s%s%d" points="' % (indent, meshname, ID_SEPARATOR, f.index))
|
||||
for p in f.uv:
|
||||
svg.write('%.8f,%.8f ' % (p[0] * size, size - p[1] * size))
|
||||
svg.write('"/>\n')
|
||||
if len(facegroups) > 1:
|
||||
svg.write('\t\t</g>\n')
|
||||
|
||||
svg.write("\t</g>\n")
|
||||
|
||||
svg.write('</svg>\n')
|
||||
svg.close()
|
||||
|
||||
|
||||
def export(path):
|
||||
if not BPyMessages.Warning_SaveOver(path):
|
||||
return
|
||||
|
||||
editmode = Blender.Window.EditMode()
|
||||
if editmode:
|
||||
Blender.Window.EditMode(0)
|
||||
|
||||
try:
|
||||
write_svg(path)
|
||||
Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
|
||||
|
||||
except Abort, e:
|
||||
print "Error:", e.msg, " -> aborting ...\n"
|
||||
Blender.Draw.PupMenu("Error%t|" + e.msg)
|
||||
|
||||
if editmode:
|
||||
Blender.Window.EditMode(1)
|
||||
|
||||
|
||||
registry = Blender.Registry.GetKey("UVImportExportSVG", False)
|
||||
if registry and "path" in registry:
|
||||
path = registry["path"]
|
||||
else:
|
||||
active = Blender.Scene.GetCurrent().objects.active
|
||||
basename = Blender.sys.basename(Blender.sys.splitext(Blender.Get("filename"))[0])
|
||||
path = basename + "-" + active.name + ".svg"
|
||||
|
||||
Blender.Window.FileSelector(export, "Export to SVG", path)
|
||||
|
||||
308
utils/Modeller/uv_import_svg.py
Executable file
308
utils/Modeller/uv_import_svg.py
Executable file
@@ -0,0 +1,308 @@
|
||||
#!BPY
|
||||
|
||||
# """
|
||||
# Name: 'UV: (Re)Import UV from SVG'
|
||||
# Blender: 245
|
||||
# Group: 'Image'
|
||||
# Tooltip: 'Re-import UV layout from SVG file'
|
||||
# """
|
||||
|
||||
__author__ = "Melchior FRANZ < mfranz # aon : at >"
|
||||
__url__ = ["http://www.flightgear.org/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/uv_import_svg.py"]
|
||||
__version__ = "0.2"
|
||||
__bpydoc__ = """\
|
||||
Imports an SVG file containing UV maps, which has been saved by the
|
||||
uv_export.svg script. This allows to move, scale, and rotate object
|
||||
mappings in SVG editors like Inkscape. Note that all contained UV maps
|
||||
will be set, no matter which objects are actually selected at the moment.
|
||||
The choice has been made when the file was saved!
|
||||
"""
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
# Copyright (C) 2008 Melchior FRANZ < mfranz # aon : at >
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
ID_SEPARATOR = '_.:._'
|
||||
|
||||
|
||||
import Blender, BPyMessages, sys, math, re, xml.sax
|
||||
|
||||
|
||||
numwsp = re.compile('(?<=[\d.])\s+(?=[-+.\d])')
|
||||
commawsp = re.compile('\s+|\s*,\s*')
|
||||
istrans = re.compile('^\s*(skewX|skewY|scale|translate|rotate|matrix)\s*\(([^\)]*)\)\s*')
|
||||
isnumber = re.compile('^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$')
|
||||
|
||||
|
||||
class Abort(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
|
||||
class Matrix:
|
||||
def __init__(self, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0):
|
||||
self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
|
||||
|
||||
def __str__(self):
|
||||
return "[Matrix %f %f %f %f %f %f]" % (self.a, self.b, self.c, self.d, self.e, self.f)
|
||||
|
||||
def multiply(self, mat):
|
||||
a = mat.a * self.a + mat.c * self.b
|
||||
b = mat.b * self.a + mat.d * self.b
|
||||
c = mat.a * self.c + mat.c * self.d
|
||||
d = mat.b * self.c + mat.d * self.d
|
||||
e = mat.a * self.e + mat.c * self.f + mat.e
|
||||
f = mat.b * self.e + mat.d * self.f + mat.f
|
||||
self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
|
||||
|
||||
def transform(self, u, v):
|
||||
return u * self.a + v * self.c + self.e, u * self.b + v * self.d + self.f
|
||||
|
||||
def translate(self, dx, dy):
|
||||
self.multiply(Matrix(1, 0, 0, 1, dx, dy))
|
||||
|
||||
def scale(self, sx, sy):
|
||||
self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
|
||||
|
||||
def rotate(self, a):
|
||||
a *= math.pi / 180
|
||||
self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
|
||||
|
||||
def skewX(self, a):
|
||||
a *= math.pi / 180
|
||||
self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
|
||||
|
||||
def skewY(self, a):
|
||||
a *= math.pi / 180
|
||||
self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
|
||||
|
||||
|
||||
def parse_transform(s):
|
||||
matrix = Matrix()
|
||||
while True:
|
||||
match = istrans.match(s)
|
||||
if not match:
|
||||
break
|
||||
cmd = match.group(1)
|
||||
values = commawsp.split(match.group(2).strip())
|
||||
s = s[len(match.group(0)):]
|
||||
arg = []
|
||||
|
||||
for value in values:
|
||||
match = isnumber.match(value)
|
||||
if not match:
|
||||
raise Abort("bad transform value")
|
||||
|
||||
arg.append(float(match.group(0)))
|
||||
|
||||
num = len(arg)
|
||||
if cmd == "skewX":
|
||||
if num == 1:
|
||||
matrix.skewX(arg[0])
|
||||
continue
|
||||
|
||||
elif cmd == "skewY":
|
||||
if num == 1:
|
||||
matrix.skewY(arg[0])
|
||||
continue
|
||||
|
||||
elif cmd == "scale":
|
||||
if num == 1:
|
||||
matrix.scale(arg[0], arg[0])
|
||||
continue
|
||||
if num == 2:
|
||||
matrix.scale(arg[0], arg[1])
|
||||
continue
|
||||
|
||||
elif cmd == "translate":
|
||||
if num == 1:
|
||||
matrix.translate(arg[0], 0)
|
||||
continue
|
||||
if num == 2:
|
||||
matrix.translate(arg[0], arg[1])
|
||||
continue
|
||||
|
||||
elif cmd == "rotate":
|
||||
if num == 1:
|
||||
matrix.rotate(arg[0])
|
||||
continue
|
||||
if num == 3:
|
||||
matrix.translate(-arg[1], -arg[2])
|
||||
matrix.rotate(arg[0])
|
||||
matrix.translate(arg[1], arg[2])
|
||||
continue
|
||||
|
||||
elif cmd == "matrix":
|
||||
if num == 6:
|
||||
matrix.multiply(Matrix(*arg))
|
||||
continue
|
||||
|
||||
else:
|
||||
print "ERROR: unknown transform", cmd
|
||||
continue
|
||||
|
||||
print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
|
||||
|
||||
if len(s):
|
||||
print "ERROR: transform with trailing garbage (%s)" % s
|
||||
return matrix
|
||||
|
||||
|
||||
class import_svg(xml.sax.handler.ContentHandler):
|
||||
# err_handler
|
||||
def error(self, exception):
|
||||
raise Abort(str(exception))
|
||||
|
||||
def fatalError(self, exception):
|
||||
raise Abort(str(exception))
|
||||
|
||||
def warning(self, exception):
|
||||
print "WARNING: " + str(exception)
|
||||
|
||||
# doc_handler
|
||||
def setDocumentLocator(self, whatever):
|
||||
pass
|
||||
|
||||
def startDocument(self):
|
||||
self.verified = False
|
||||
self.scandesc = False
|
||||
self.matrices = [None]
|
||||
self.meshes = {}
|
||||
for o in Blender.Scene.GetCurrent().objects:
|
||||
if o.type != "Mesh":
|
||||
continue
|
||||
mesh = o.getData(mesh = 1)
|
||||
if not mesh.faceUV:
|
||||
continue
|
||||
if mesh.name in self.meshes:
|
||||
continue
|
||||
self.meshes[mesh.name] = mesh
|
||||
|
||||
def endDocument(self):
|
||||
pass
|
||||
|
||||
def characters(self, data):
|
||||
if not self.scandesc:
|
||||
return
|
||||
if data.startswith("uv_export_svg.py"):
|
||||
self.verified = True
|
||||
|
||||
def ignorableWhitespace(self, data, start, length):
|
||||
pass
|
||||
|
||||
def processingInstruction(self, target, data):
|
||||
pass
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
currmat = self.matrices[-1]
|
||||
if "transform" in attrs:
|
||||
m = parse_transform(attrs["transform"])
|
||||
if currmat is not None:
|
||||
m.multiply(currmat)
|
||||
self.matrices.append(m)
|
||||
else:
|
||||
self.matrices.append(currmat)
|
||||
|
||||
if name == "polygon":
|
||||
self.handlePolygon(attrs)
|
||||
elif name == "svg":
|
||||
if "viewBox" in attrs:
|
||||
x, y, w, h = commawsp.split(attrs["viewBox"], 4)
|
||||
if int(x) or int(y):
|
||||
raise Abort("bad viewBox")
|
||||
self.width = int(w)
|
||||
self.height = int(h)
|
||||
if self.width != self.height:
|
||||
raise Abort("viewBox isn't a square")
|
||||
else:
|
||||
raise Abort("no viewBox")
|
||||
elif name == "desc" and not self.verified:
|
||||
self.scandesc = True
|
||||
|
||||
def endElement(self, name):
|
||||
self.scandesc = False
|
||||
self.matrices.pop()
|
||||
|
||||
def handlePolygon(self, attrs):
|
||||
if not self.verified:
|
||||
raise Abort("this file wasn't written by uv_export_svg.py")
|
||||
ident = attrs.get("id", None)
|
||||
points = attrs.get("points", None)
|
||||
|
||||
if not ident or not points:
|
||||
print('bad polygon "%s"' % ident)
|
||||
return
|
||||
|
||||
try:
|
||||
meshname, num = ident.strip().split(ID_SEPARATOR, 2)
|
||||
except:
|
||||
print('broken id "%s"' % ident)
|
||||
return
|
||||
|
||||
if not meshname in self.meshes:
|
||||
print('unknown mesh "%s"' % meshname)
|
||||
return
|
||||
|
||||
#print 'mesh %s face %d: ' % (meshname, num)
|
||||
matrix = self.matrices[-1]
|
||||
transuv = []
|
||||
for p in numwsp.split(points.strip()):
|
||||
u, v = commawsp.split(p.strip(), 2)
|
||||
u = float(u)
|
||||
v = float(v)
|
||||
if matrix:
|
||||
u, v = matrix.transform(u, v)
|
||||
transuv.append((u / self.width, 1 - v / self.height))
|
||||
|
||||
for i, uv in enumerate(self.meshes[meshname].faces[int(num)].uv):
|
||||
uv[0] = transuv[i][0]
|
||||
uv[1] = transuv[i][1]
|
||||
|
||||
|
||||
def run_parser(path):
|
||||
if BPyMessages.Error_NoFile(path):
|
||||
return
|
||||
|
||||
editmode = Blender.Window.EditMode()
|
||||
if editmode:
|
||||
Blender.Window.EditMode(0)
|
||||
Blender.Window.WaitCursor(1)
|
||||
|
||||
try:
|
||||
xml.sax.parse(path, import_svg(), import_svg())
|
||||
Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
|
||||
|
||||
except Abort, e:
|
||||
print "Error:", e.msg, " -> aborting ...\n"
|
||||
Blender.Draw.PupMenu("Error%t|" + e.msg)
|
||||
|
||||
Blender.Window.RedrawAll()
|
||||
Blender.Window.WaitCursor(0)
|
||||
if editmode:
|
||||
Blender.Window.EditMode(1)
|
||||
|
||||
|
||||
registry = Blender.Registry.GetKey("UVImportExportSVG", False)
|
||||
if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
|
||||
path = registry["path"]
|
||||
else:
|
||||
path = ""
|
||||
|
||||
Blender.Window.FileSelector(run_parser, "Import SVG", path)
|
||||
|
||||
172
utils/Modeller/uv_pack.py
Normal file
172
utils/Modeller/uv_pack.py
Normal file
@@ -0,0 +1,172 @@
|
||||
#!BPY
|
||||
|
||||
# """
|
||||
# Name: 'Pack selected objects on a square'
|
||||
# Blender: 245
|
||||
# Group: 'UV'
|
||||
# Tooltip: 'Pack UV maps of all selected objects onto an empty square texture'
|
||||
# """
|
||||
|
||||
__author__ = "Melchior FRANZ < mfranz # aon : at >"
|
||||
__url__ = "http://members.aon.at/mfranz/flightgear/"
|
||||
__version__ = "0.1"
|
||||
__bpydoc__ = """\
|
||||
Script for mapping multiple objects onto one square texture.
|
||||
|
||||
Usage:
|
||||
(1) create new square texture in the UV editor
|
||||
(2) map all objects individually, choosing the most appropriate technique
|
||||
(3) scale each of the mappings to the appropriate size, relative to the
|
||||
other object mappings
|
||||
(4) select all objects and switch to edit mode (consider to use
|
||||
Select->Linked->Material or similar methods)
|
||||
(5) start this script with UVs->Scripts->Pack objects on a square
|
||||
|
||||
[now the texture image will first be erased, then colored rectangles
|
||||
will appear for each object]
|
||||
|
||||
(6) rescale and/or remap objects that you aren't happy with (the
|
||||
relative size of a mapping will be kept)
|
||||
|
||||
[continue with (5) until you like the result]
|
||||
|
||||
(7) export UV layout to SVG (UVs->Scripts->Save UV Face Layout)
|
||||
"""
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
# Copyright (C) 2008 Melchior FRANZ < mfranz # aon : at >
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
MARGIN = 10 # px
|
||||
GAP = 10 # px
|
||||
|
||||
|
||||
import Blender
|
||||
from random import randint as rand
|
||||
|
||||
|
||||
class Abort(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
|
||||
def pack():
|
||||
image = Blender.Image.GetCurrent()
|
||||
if not image:
|
||||
raise Abort('No texture image selected')
|
||||
|
||||
imgwidth, imgheight = image.getSize()
|
||||
if imgwidth != imgheight:
|
||||
Blender.Draw.PupMenu("Warning%t|Image isn't a square!")
|
||||
gap = (float(GAP) / imgwidth, float(GAP) / imgheight)
|
||||
margin = (float(MARGIN) / imgwidth - gap[0] * 0.5, float(MARGIN) / imgheight - gap[1] * 0.5)
|
||||
|
||||
def drawrect(x0, y0, x1, y1, color = (255, 255, 255, 255)):
|
||||
x0 *= imgwidth
|
||||
x1 *= imgwidth
|
||||
y0 *= imgheight
|
||||
y1 *= imgheight
|
||||
for u in range(int(x0 + 0.5), int(x1 - 0.5)):
|
||||
for v in range(int(y0 + 0.5), int(y1 - 0.5)):
|
||||
image.setPixelI(u, v, color)
|
||||
|
||||
|
||||
boxes = []
|
||||
unique_meshes = {}
|
||||
|
||||
BIG = 1<<30
|
||||
Blender.Window.DrawProgressBar(0.0, "packing")
|
||||
for o in Blender.Scene.GetCurrent().objects.selected:
|
||||
if o.type != "Mesh":
|
||||
continue
|
||||
|
||||
mesh = o.getData(mesh = 1)
|
||||
if not mesh.faceUV:
|
||||
continue
|
||||
if mesh.name in unique_meshes:
|
||||
#print "dropping duplicate mesh", mesh.name, "of object", o.name
|
||||
continue
|
||||
unique_meshes[mesh.name] = True
|
||||
|
||||
print "\tobject '%s'" % o.name
|
||||
xmin = ymin = BIG
|
||||
xmax = ymax = -BIG
|
||||
for f in mesh.faces:
|
||||
for p in f.uv:
|
||||
xmin = min(xmin, p[0])
|
||||
xmax = max(xmax, p[0])
|
||||
ymin = min(ymin, p[1])
|
||||
ymax = max(ymax, p[1])
|
||||
|
||||
width = xmax - xmin
|
||||
height = ymax - ymin
|
||||
boxes.append([0, 0, width + gap[0], height + gap[1], xmin, ymin, mesh])
|
||||
|
||||
if not boxes:
|
||||
raise Abort('No mesh objects selected')
|
||||
|
||||
|
||||
boxwidth, boxheight = Blender.Geometry.BoxPack2D(boxes)
|
||||
boxmax = max(boxwidth, boxheight)
|
||||
xscale = (1.0 - 2.0 * margin[0]) / boxmax
|
||||
yscale = (1.0 - 2.0 * margin[1]) / boxmax
|
||||
|
||||
image.reload()
|
||||
#drawrect(0, 0, 1, 1) # erase texture
|
||||
|
||||
for i, box in enumerate(boxes):
|
||||
Blender.Window.DrawProgressBar(float(i) * len(boxes), "Drawing")
|
||||
xmin = ymin = BIG
|
||||
xmax = ymax = -BIG
|
||||
for f in box[6].faces:
|
||||
for p in f.uv:
|
||||
p[0] = (p[0] - box[4] + box[0] + gap[0] * 0.5 + margin[0]) * xscale
|
||||
p[1] = (p[1] - box[5] + box[1] + gap[1] * 0.5 + margin[1]) * yscale
|
||||
|
||||
xmin = min(xmin, p[0])
|
||||
xmax = max(xmax, p[0])
|
||||
ymin = min(ymin, p[1])
|
||||
ymax = max(ymax, p[1])
|
||||
|
||||
drawrect(xmin, ymin, xmax, ymax, (rand(128, 255), rand(128, 255), rand(128, 255), 255))
|
||||
box[6].update()
|
||||
|
||||
Blender.Window.RedrawAll()
|
||||
Blender.Window.DrawProgressBar(1.0, "Finished")
|
||||
|
||||
|
||||
|
||||
editmode = Blender.Window.EditMode()
|
||||
if editmode:
|
||||
Blender.Window.EditMode(0)
|
||||
Blender.Window.WaitCursor(1)
|
||||
|
||||
try:
|
||||
print "box packing ..."
|
||||
pack()
|
||||
print "done\n"
|
||||
except Abort, e:
|
||||
print "Error:", e.msg, " -> aborting ...\n"
|
||||
Blender.Draw.PupMenu("Error%t|" + e.msg)
|
||||
|
||||
Blender.Window.WaitCursor(0)
|
||||
if editmode:
|
||||
Blender.Window.EditMode(1)
|
||||
|
||||
|
||||
827
utils/Modeller/yasim_import.py
Normal file
827
utils/Modeller/yasim_import.py
Normal file
@@ -0,0 +1,827 @@
|
||||
#!BPY
|
||||
|
||||
# """
|
||||
# Name: 'YASim (.xml)'
|
||||
# Blender: 245
|
||||
# Group: 'Import'
|
||||
# Tooltip: 'Loads and visualizes a YASim FDM geometry'
|
||||
# """
|
||||
|
||||
__author__ = "Melchior FRANZ < mfranz # aon : at >"
|
||||
__url__ = ["http://www.flightgear.org/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/yasim_import.py"]
|
||||
__version__ = "0.2"
|
||||
__bpydoc__ = """\
|
||||
yasim_import.py loads and visualizes a YASim FDM geometry
|
||||
=========================================================
|
||||
|
||||
It is recommended to load the model superimposed over a greyed out and immutable copy of the aircraft model:
|
||||
|
||||
(0) put this script into ~/.blender/scripts/
|
||||
(1) load or import aircraft model (menu -> "File" -> "Import" -> "AC3D (.ac) ...")
|
||||
(2) create new *empty* scene (menu -> arrow button left of "SCE:scene1" combobox -> "ADD NEW" -> "empty")
|
||||
(3) rename scene to yasim (not required)
|
||||
(4) link to scene1 (F10 -> "Output" tab in "Buttons Window" -> arrow button left of text entry "No Set Scene" -> "scene1")
|
||||
(5) now load the YASim config file (menu -> "File" -> "Import" -> "YASim (.xml) ...")
|
||||
|
||||
This is good enough for simple checks. But if you are working on the YASim configuration, then you need a
|
||||
quick and convenient way to reload the file. In that case continue after (4):
|
||||
|
||||
(5) switch the button area at the bottom of the blender screen to "Scripts Window" mode (green python snake icon)
|
||||
(6) load the YASim config file (menu -> "Scripts" -> "Import" -> "YASim (.xml) ...")
|
||||
(7) make the "Scripts Window" area as small as possible by dragging the area separator down
|
||||
(8) optionally split the "3D View" area and switch the right part to the "Outliner"
|
||||
(9) press the "Reload YASim" button in the script area to reload the file
|
||||
|
||||
|
||||
If the 3D model is displaced with respect to the FDM model, then the <offsets> values from the
|
||||
model animation XML file should be added as comment to the YASim config file, as a line all by
|
||||
itself, with no spaces surrounding the equal signs. Spaces elsewhere are allowed. For example:
|
||||
|
||||
<offsets>
|
||||
<x-m>3.45</x-m>
|
||||
<z-m>-0.4</z-m>
|
||||
<pitch-deg>5</pitch-deg>
|
||||
</offsets>
|
||||
|
||||
becomes:
|
||||
|
||||
<!-- offsets: x=3.45 z=-0.4 p=5 -->
|
||||
|
||||
Possible variables are:
|
||||
|
||||
x ... <x-m>
|
||||
y ... <y-m>
|
||||
z ... <z-m>
|
||||
h ... <heading-deg>
|
||||
p ... <pitch-deg>
|
||||
r ... <roll-deg>
|
||||
|
||||
Of course, absolute FDM coordinates can then no longer directly be read from Blender's 3D view.
|
||||
The cursor coordinates display in the script area, however, shows the coordinates in YASim space.
|
||||
Note that object names don't contain XML indices but element numbers. YASim_flap0#2 is the third
|
||||
flap0 in the whole file, not necessarily in its parent XML group. A floating point part in the
|
||||
object name (e.g. YASim_flap0#2.004) only means that the geometry has been reloaded that often.
|
||||
It's an unavoidable consequence of how Blender deals with meshes.
|
||||
|
||||
|
||||
Elements are displayed as follows:
|
||||
|
||||
cockpit -> monkey head
|
||||
fuselage -> blue "tube" (with only 12 sides for less clutter); center at "a"
|
||||
vstab -> red with yellow control surfaces (flap0, flap1, slat, spoiler)
|
||||
wing/mstab/hstab -> green with yellow control surfaces (which are always 20 cm deep);
|
||||
symmetric surfaces are only displayed on the left side, unless
|
||||
the "Mirror" button is active
|
||||
thrusters (jet/propeller/thruster) -> dashed line from center to actionpt;
|
||||
arrow from actionpt along thrust vector (always 1 m long);
|
||||
propeller circle
|
||||
rotor -> radius and rel_len_blade_start circle, normal and forward vector,
|
||||
one blade at phi0 with direction arrow near blade tip
|
||||
gear -> contact point and compression vector (no arrow head)
|
||||
tank -> magenta cube (10 cm side length)
|
||||
weight -> inverted cyan cone
|
||||
ballast -> yellow cylinder
|
||||
hitch -> hexagon (10 cm diameter)
|
||||
hook -> dashed line for up angle, T-line for down angle
|
||||
launchbar -> dashed line for up angles, T-line for down angles
|
||||
(launchbar and holdback each)
|
||||
|
||||
|
||||
The Mirror button complements symmetrical surfaces (wing/hstab/mstab) and control surfaces
|
||||
(flap0/flap1/slat/spoiler). This is useful for asymmetrical aircraft, but has the disadvantage
|
||||
that it moves the surfaces' object centers from their usual place, yasim's [x, y, z] value,
|
||||
to [0, 0, 0]. Turning mirroring off restores the object center.
|
||||
|
||||
|
||||
|
||||
Environment variable BLENDER_YASIM_IMPORT can be set to a space-separated list of options:
|
||||
|
||||
$ BLENDER_YASIM_IMPORT="mirror verbose" blender
|
||||
|
||||
whereby:
|
||||
|
||||
verbose ... enables verbose logs
|
||||
mirror ... enables mirroring of symmetric surfaces
|
||||
"""
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
# Copyright (C) 2009 Melchior FRANZ < mfranz # aon : at >
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
import Blender, BPyMessages, string, math, os
|
||||
from Blender.Mathutils import *
|
||||
from xml.sax import handler, make_parser
|
||||
|
||||
|
||||
CONFIG = string.split(os.getenv("BLENDER_YASIM_IMPORT") or "")
|
||||
YASIM_MATRIX = Matrix([-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1])
|
||||
ORIGIN = Vector(0, 0, 0)
|
||||
X = Vector(1, 0, 0)
|
||||
Y = Vector(0, 1, 0)
|
||||
Z = Vector(0, 0, 1)
|
||||
DEG2RAD = math.pi / 180
|
||||
RAD2DEG = 180 / math.pi
|
||||
|
||||
NO_EVENT = 0
|
||||
RELOAD_BUTTON = 1
|
||||
CURSOR_BUTTON = 2
|
||||
MIRROR_BUTTON = 3
|
||||
|
||||
|
||||
|
||||
class Global:
|
||||
verbose = "verbose" in CONFIG
|
||||
path = ""
|
||||
matrix = None
|
||||
data = None
|
||||
cursor = ORIGIN
|
||||
last_cursor = Vector(Blender.Window.GetCursorPos())
|
||||
mirror_button = Blender.Draw.Create("mirror" in CONFIG)
|
||||
|
||||
|
||||
|
||||
class Abort(Exception):
|
||||
def __init__(self, msg, term = None):
|
||||
self.msg = msg
|
||||
self.term = term
|
||||
|
||||
|
||||
|
||||
def log(msg):
|
||||
if Global.verbose:
|
||||
print(msg)
|
||||
|
||||
|
||||
|
||||
def draw_dashed_line(mesh, start, end):
|
||||
w = 0.04
|
||||
step = w * (end - start).normalize()
|
||||
n = len(mesh.verts)
|
||||
for i in range(int(1 + 0.5 * (end - start).length / w)):
|
||||
a = start + 2 * i * step
|
||||
b = a + step
|
||||
if (b - end).length < step.length:
|
||||
b = end
|
||||
mesh.verts.extend([a, b])
|
||||
mesh.edges.extend([n + 2 * i, n + 2 * i + 1])
|
||||
|
||||
|
||||
|
||||
def draw_arrow(mesh, start, end):
|
||||
v = end - start
|
||||
m = v.toTrackQuat('x', 'z').toMatrix().resize4x4() * TranslationMatrix(start)
|
||||
v = v.length * X
|
||||
n = len(mesh.verts)
|
||||
mesh.verts.extend([ORIGIN * m , v * m, (v - 0.05 * X + 0.05 * Y) * m, (v - 0.05 * X - 0.05 * Y) * m]) # head
|
||||
mesh.verts.extend([(ORIGIN + 0.05 * Y) * m, (ORIGIN - 0.05 * Y) * m]) # base
|
||||
mesh.edges.extend([[n, n + 1], [n + 1, n + 2], [n + 1, n + 3], [n + 4, n + 5]])
|
||||
|
||||
|
||||
|
||||
def draw_circle(mesh, numpoints, radius, matrix):
|
||||
n = len(mesh.verts)
|
||||
for i in range(numpoints):
|
||||
angle = 2.0 * math.pi * i / numpoints
|
||||
v = Vector(radius * math.cos(angle), radius * math.sin(angle), 0)
|
||||
mesh.verts.extend([v * matrix])
|
||||
for i in range(numpoints):
|
||||
i1 = (i + 1) % numpoints
|
||||
mesh.edges.extend([[n + i, n + i1]])
|
||||
|
||||
|
||||
|
||||
class Item:
|
||||
scene = Blender.Scene.GetCurrent()
|
||||
|
||||
def make_twosided(self, mesh):
|
||||
mesh.faceUV = True
|
||||
for f in mesh.faces:
|
||||
f.mode |= Blender.Mesh.FaceModes.TWOSIDE | Blender.Mesh.FaceModes.OBCOL
|
||||
|
||||
def set_color(self, obj, color):
|
||||
mat = Blender.Material.New()
|
||||
mat.setRGBCol(color[0], color[1], color[2])
|
||||
mat.setAlpha(color[3])
|
||||
mat.mode |= Blender.Material.Modes.ZTRANSP | Blender.Material.Modes.TRANSPSHADOW
|
||||
obj.transp = True
|
||||
|
||||
mesh = obj.getData(mesh = True)
|
||||
mesh.materials += [mat]
|
||||
|
||||
for f in mesh.faces:
|
||||
f.smooth = True
|
||||
mesh.calcNormals()
|
||||
|
||||
|
||||
|
||||
class Cockpit(Item):
|
||||
def __init__(self, center):
|
||||
mesh = Blender.Mesh.Primitives.Monkey()
|
||||
mesh.transform(ScaleMatrix(0.13, 4) * Euler(90, 0, 90).toMatrix().resize4x4() * TranslationMatrix(Vector(-0.1, 0, -0.032)))
|
||||
obj = self.scene.objects.new(mesh, "YASim_cockpit")
|
||||
obj.setMatrix(TranslationMatrix(center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Tank(Item):
|
||||
def __init__(self, name, center):
|
||||
mesh = Blender.Mesh.Primitives.Cube()
|
||||
mesh.transform(ScaleMatrix(0.05, 4))
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(TranslationMatrix(center) * Global.matrix)
|
||||
self.set_color(obj, [1, 0, 1, 0.5])
|
||||
|
||||
|
||||
|
||||
class Ballast(Item):
|
||||
def __init__(self, name, center):
|
||||
mesh = Blender.Mesh.Primitives.Cylinder()
|
||||
mesh.transform(ScaleMatrix(0.05, 4))
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(TranslationMatrix(center) * Global.matrix)
|
||||
self.set_color(obj, [1, 1, 0, 0.5])
|
||||
|
||||
|
||||
|
||||
class Weight(Item):
|
||||
def __init__(self, name, center):
|
||||
mesh = Blender.Mesh.Primitives.Cone()
|
||||
mesh.transform(ScaleMatrix(0.05, 4))
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(TranslationMatrix(center) * Global.matrix)
|
||||
self.set_color(obj, [0, 1, 1, 0.5])
|
||||
|
||||
|
||||
|
||||
class Gear(Item):
|
||||
def __init__(self, name, center, compression):
|
||||
mesh = Blender.Mesh.New()
|
||||
mesh.verts.extend([ORIGIN, compression])
|
||||
mesh.edges.extend([0, 1])
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(TranslationMatrix(center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Hook(Item):
|
||||
def __init__(self, name, center, length, up_angle, dn_angle):
|
||||
mesh = Blender.Mesh.New()
|
||||
up = ORIGIN - length * math.cos(up_angle * DEG2RAD) * X - length * math.sin(up_angle * DEG2RAD) * Z
|
||||
dn = ORIGIN - length * math.cos(dn_angle * DEG2RAD) * X - length * math.sin(dn_angle * DEG2RAD) * Z
|
||||
mesh.verts.extend([ORIGIN, dn, dn + 0.05 * Y, dn - 0.05 * Y])
|
||||
mesh.edges.extend([[0, 1], [2, 3]])
|
||||
draw_dashed_line(mesh, ORIGIN, up)
|
||||
draw_dashed_line(mesh, ORIGIN, dn)
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(TranslationMatrix(center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Launchbar(Item):
|
||||
def __init__(self, name, lb, lb_length, hb, hb_length, up_angle, dn_angle):
|
||||
mesh = Blender.Mesh.New()
|
||||
hb = hb - lb
|
||||
lb_tip = ORIGIN + lb_length * math.cos(dn_angle * DEG2RAD) * X - lb_length * math.sin(dn_angle * DEG2RAD) * Z
|
||||
hb_tip = hb - hb_length * math.cos(dn_angle * DEG2RAD) * X - hb_length * math.sin(dn_angle * DEG2RAD) * Z
|
||||
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])
|
||||
mesh.edges.extend([[0, 1], [1, 2], [2, 3], [4, 5], [6, 7]])
|
||||
draw_dashed_line(mesh, ORIGIN, lb_length * math.cos(up_angle * DEG2RAD) * X - lb_length * math.sin(up_angle * DEG2RAD) * Z)
|
||||
draw_dashed_line(mesh, hb, hb - hb_length * math.cos(up_angle * DEG2RAD) * X - hb_length * math.sin(up_angle * DEG2RAD) * Z)
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(TranslationMatrix(lb) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Hitch(Item):
|
||||
def __init__(self, name, center):
|
||||
mesh = Blender.Mesh.Primitives.Circle(6, 0.1)
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(RotationMatrix(90, 4, "x") * TranslationMatrix(center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Thrust:
|
||||
def set_actionpt(self, p):
|
||||
self.actionpt = p
|
||||
|
||||
def set_dir(self, d):
|
||||
self.thrustvector = d
|
||||
|
||||
|
||||
|
||||
class Thruster(Thrust, Item):
|
||||
def __init__(self, name, center, thrustvector):
|
||||
(self.name, self.center, self.actionpt, self.thrustvector) = (name, center, center, thrustvector)
|
||||
|
||||
def __del__(self):
|
||||
a = self.actionpt - self.center
|
||||
mesh = Blender.Mesh.New()
|
||||
draw_dashed_line(mesh, ORIGIN, a)
|
||||
draw_arrow(mesh, a, a + self.thrustvector.normalize())
|
||||
obj = self.scene.objects.new(mesh, self.name)
|
||||
obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Propeller(Thrust, Item):
|
||||
def __init__(self, name, center, radius):
|
||||
(self.name, self.center, self.radius, self.actionpt, self.thrustvector) = (name, center, radius, center, -X)
|
||||
|
||||
def __del__(self):
|
||||
a = self.actionpt - self.center
|
||||
matrix = self.thrustvector.toTrackQuat('z', 'x').toMatrix().resize4x4() * TranslationMatrix(a)
|
||||
|
||||
mesh = Blender.Mesh.New()
|
||||
mesh.verts.extend([ORIGIN * matrix, (ORIGIN + self.radius * X) * matrix])
|
||||
mesh.edges.extend([[0, 1]])
|
||||
draw_dashed_line(mesh, ORIGIN, a)
|
||||
draw_arrow(mesh, a, a + self.thrustvector.normalize())
|
||||
|
||||
draw_circle(mesh, 128, self.radius, matrix)
|
||||
obj = self.scene.objects.new(mesh, self.name)
|
||||
obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Jet(Thrust, Item):
|
||||
def __init__(self, name, center, rotate):
|
||||
(self.name, self.center, self.actionpt) = (name, center, center)
|
||||
self.thrustvector = -X * RotationMatrix(rotate, 4, "y")
|
||||
|
||||
def __del__(self):
|
||||
a = self.actionpt - self.center
|
||||
mesh = Blender.Mesh.New()
|
||||
draw_dashed_line(mesh, ORIGIN, a)
|
||||
draw_arrow(mesh, a, a + self.thrustvector.normalize())
|
||||
obj = self.scene.objects.new(mesh, self.name)
|
||||
obj.setMatrix(TranslationMatrix(self.center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Fuselage(Item):
|
||||
def __init__(self, name, a, b, width, taper, midpoint):
|
||||
numvert = 12
|
||||
angle = []
|
||||
for i in range(numvert):
|
||||
alpha = i * 2 * math.pi / float(numvert)
|
||||
angle.append([math.cos(alpha), math.sin(alpha)])
|
||||
|
||||
axis = b - a
|
||||
length = axis.length
|
||||
mesh = Blender.Mesh.New()
|
||||
|
||||
for i in range(numvert):
|
||||
mesh.verts.extend([[0, 0.5 * width * taper * angle[i][0], 0.5 * width * taper * angle[i][1]]])
|
||||
for i in range(numvert):
|
||||
mesh.verts.extend([[midpoint * length, 0.5 * width * angle[i][0], 0.5 * width * angle[i][1]]])
|
||||
for i in range(numvert):
|
||||
mesh.verts.extend([[length, 0.5 * width * taper * angle[i][0], 0.5 * width * taper * angle[i][1]]])
|
||||
for i in range(numvert):
|
||||
i1 = (i + 1) % numvert
|
||||
mesh.faces.extend([[i, i1, i1 + numvert, i + numvert]])
|
||||
mesh.faces.extend([[i + numvert, i1 + numvert, i1 + 2 * numvert, i + 2 * numvert]])
|
||||
|
||||
mesh.verts.extend([ORIGIN, length * X])
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(axis.toTrackQuat('x', 'y').toMatrix().resize4x4() * TranslationMatrix(a) * Global.matrix)
|
||||
self.set_color(obj, [0, 0, 0.5, 0.4])
|
||||
|
||||
|
||||
|
||||
class Rotor(Item):
|
||||
def __init__(self, name, center, up, fwd, numblades, radius, chord, twist, taper, rel_len_blade_start, phi0, ccw):
|
||||
matrix = RotationMatrix(phi0, 4, "z") * up.toTrackQuat('z', 'x').toMatrix().resize4x4()
|
||||
invert = matrix.copy().invert()
|
||||
direction = [-1, 1][ccw]
|
||||
twist *= DEG2RAD
|
||||
a = ORIGIN + rel_len_blade_start * radius * X
|
||||
b = ORIGIN + radius * X
|
||||
tw = 0.5 * chord * taper * math.cos(twist) * Y + 0.5 * direction * chord * taper * math.sin(twist) * Z
|
||||
|
||||
mesh = Blender.Mesh.New()
|
||||
mesh.verts.extend([ORIGIN, a, b, a + 0.5 * chord * Y, a - 0.5 * chord * Y, b + tw, b - tw])
|
||||
mesh.edges.extend([[0, 1], [1, 2], [1, 3], [1, 4], [3, 5], [4, 6], [5, 6]])
|
||||
draw_circle(mesh, 64, rel_len_blade_start * radius, Matrix())
|
||||
draw_circle(mesh, 128, radius, Matrix())
|
||||
draw_arrow(mesh, ORIGIN, up * invert)
|
||||
draw_arrow(mesh, ORIGIN, fwd * invert)
|
||||
b += 0.1 * X + direction * chord * Y
|
||||
draw_arrow(mesh, b, b + min(0.5 * radius, 1) * direction * Y)
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(matrix * TranslationMatrix(center) * Global.matrix)
|
||||
|
||||
|
||||
|
||||
class Wing(Item):
|
||||
def __init__(self, name, root, length, chord, incidence, twist, taper, sweep, dihedral):
|
||||
# <1--0--2
|
||||
# \ | /
|
||||
# 4-3-5
|
||||
self.is_symmetric = not name.startswith("YASim_vstab#")
|
||||
mesh = Blender.Mesh.New()
|
||||
mesh.verts.extend([ORIGIN, ORIGIN + 0.5 * chord * X, ORIGIN - 0.5 * chord * X])
|
||||
tip = ORIGIN + math.cos(sweep * DEG2RAD) * length * Y - math.sin(sweep * DEG2RAD) * length * X
|
||||
tipfore = tip + 0.5 * taper * chord * math.cos(twist * DEG2RAD) * X + 0.5 * taper * chord * math.sin(twist * DEG2RAD) * Z
|
||||
tipaft = tip + tip - tipfore
|
||||
mesh.verts.extend([tip, tipfore, tipaft])
|
||||
mesh.faces.extend([[0, 1, 4, 3], [2, 0, 3, 5]])
|
||||
|
||||
self.make_twosided(mesh)
|
||||
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
mesh.transform(Euler(dihedral, -incidence, 0).toMatrix().resize4x4())
|
||||
self.set_color(obj, [[0.5, 0.0, 0, 0.5], [0.0, 0.5, 0, 0.5]][self.is_symmetric])
|
||||
(self.obj, self.mesh) = (obj, mesh)
|
||||
|
||||
if self.is_symmetric and Global.mirror_button.val:
|
||||
mod = obj.modifiers.append(Blender.Modifier.Type.MIRROR)
|
||||
mod[Blender.Modifier.Settings.AXIS_X] = False
|
||||
mod[Blender.Modifier.Settings.AXIS_Y] = True
|
||||
mod[Blender.Modifier.Settings.AXIS_Z] = False
|
||||
mesh.transform(TranslationMatrix(root)) # must move object center to x axis
|
||||
obj.setMatrix(Global.matrix)
|
||||
else:
|
||||
obj.setMatrix(TranslationMatrix(root) * Global.matrix)
|
||||
|
||||
def add_flap(self, name, start, end):
|
||||
a = Vector(self.mesh.verts[2].co)
|
||||
b = Vector(self.mesh.verts[5].co)
|
||||
c = 0.2 * (Vector(self.mesh.verts[0].co - a)).normalize()
|
||||
m = self.obj.getMatrix()
|
||||
|
||||
mesh = Blender.Mesh.New()
|
||||
i0 = a + start * (b - a)
|
||||
i1 = a + end * (b - a)
|
||||
mesh.verts.extend([i0, i1, i0 + c, i1 + c])
|
||||
mesh.faces.extend([[0, 1, 3, 2]])
|
||||
|
||||
self.make_twosided(mesh)
|
||||
|
||||
obj = self.scene.objects.new(mesh, name)
|
||||
obj.setMatrix(m)
|
||||
self.set_color(obj, [0.8, 0.8, 0, 0.9])
|
||||
|
||||
if self.is_symmetric and Global.mirror_button.val:
|
||||
mod = obj.modifiers.append(Blender.Modifier.Type.MIRROR)
|
||||
mod[Blender.Modifier.Settings.AXIS_X] = False
|
||||
mod[Blender.Modifier.Settings.AXIS_Y] = True
|
||||
mod[Blender.Modifier.Settings.AXIS_Z] = False
|
||||
|
||||
|
||||
|
||||
class import_yasim(handler.ErrorHandler, handler.ContentHandler):
|
||||
ignored = ["cruise", "approach", "control-input", "control-output", "control-speed", \
|
||||
"control-setting", "stall", "airplane", "piston-engine", "turbine-engine", \
|
||||
"rotorgear", "tow", "winch", "solve-weight"]
|
||||
|
||||
|
||||
# err_handler
|
||||
def warning(self, exception):
|
||||
print((self.error_string("Warning", exception)))
|
||||
|
||||
def error(self, exception):
|
||||
print((self.error_string("Error", exception)))
|
||||
|
||||
def fatalError(self, exception):
|
||||
raise Abort(str(exception), self.error_string("Fatal", exception))
|
||||
|
||||
def error_string(self, tag, e):
|
||||
(column, line) = (e.getColumnNumber(), e.getLineNumber())
|
||||
return "%s: %s\n%s%s^" % (tag, str(e), Global.data[line - 1], column * ' ')
|
||||
|
||||
|
||||
# doc_handler
|
||||
def setDocumentLocator(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def startDocument(self):
|
||||
self.tags = []
|
||||
self.counter = {}
|
||||
self.items = [None]
|
||||
|
||||
def endDocument(self):
|
||||
for o in Item.scene.objects:
|
||||
o.sel = True
|
||||
|
||||
def startElement(self, tag, attrs):
|
||||
if len(self.tags) == 0 and tag != "airplane":
|
||||
raise Abort("this isn't a YASim config file (bad root tag at line %d)" % self.locator.getLineNumber())
|
||||
|
||||
self.tags.append(tag)
|
||||
path = string.join(self.tags, '/')
|
||||
item = Item()
|
||||
parent = self.items[-1]
|
||||
|
||||
if self.counter.has_key(tag):
|
||||
self.counter[tag] += 1
|
||||
else:
|
||||
self.counter[tag] = 0
|
||||
|
||||
if tag == "cockpit":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\033[31mcockpit x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
|
||||
item = Cockpit(c)
|
||||
|
||||
elif tag == "fuselage":
|
||||
a = Vector(float(attrs["ax"]), float(attrs["ay"]), float(attrs["az"]))
|
||||
b = Vector(float(attrs["bx"]), float(attrs["by"]), float(attrs["bz"]))
|
||||
width = float(attrs["width"])
|
||||
taper = float(attrs.get("taper", 1))
|
||||
midpoint = float(attrs.get("midpoint", 0.5))
|
||||
log("\033[32mfuselage ax=%f ay=%f az=%f bx=%f by=%f bz=%f width=%f taper=%f midpoint=%f\033[m" % \
|
||||
(a[0], a[1], a[2], b[0], b[1], b[2], width, taper, midpoint))
|
||||
item = Fuselage("YASim_%s#%d" % (tag, self.counter[tag]), a, b, width, taper, midpoint)
|
||||
|
||||
elif tag == "gear":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
compression = float(attrs.get("compression", 1))
|
||||
up = Z * compression
|
||||
if attrs.has_key("upx"):
|
||||
up = Vector(float(attrs["upx"]), float(attrs["upy"]), float(attrs["upz"])).normalize() * compression
|
||||
log("\033[35;1mgear x=%f y=%f z=%f compression=%f upx=%f upy=%f upz=%f\033[m" \
|
||||
% (c[0], c[1], c[2], compression, up[0], up[1], up[2]))
|
||||
item = Gear("YASim_gear#%d" % self.counter[tag], c, up)
|
||||
|
||||
elif tag == "jet":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
rotate = float(attrs.get("rotate", 0))
|
||||
log("\033[36;1mjet x=%f y=%f z=%f rotate=%f\033[m" % (c[0], c[1], c[2], rotate))
|
||||
item = Jet("YASim_jet#%d" % self.counter[tag], c, rotate)
|
||||
|
||||
elif tag == "propeller":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
radius = float(attrs["radius"])
|
||||
log("\033[36;1m%s x=%f y=%f z=%f radius=%f\033[m" % (tag, c[0], c[1], c[2], radius))
|
||||
item = Propeller("YASim_propeller#%d" % self.counter[tag], c, radius)
|
||||
|
||||
elif tag == "thruster":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
v = Vector(float(attrs["vx"]), float(attrs["vy"]), float(attrs["vz"]))
|
||||
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]))
|
||||
item = Thruster("YASim_thruster#%d" % self.counter[tag], c, v)
|
||||
|
||||
elif tag == "actionpt":
|
||||
if not isinstance(parent, Thrust):
|
||||
raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
|
||||
% (path, self.locator.getLineNumber()))
|
||||
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\t\033[36mactionpt x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
|
||||
parent.set_actionpt(c)
|
||||
|
||||
elif tag == "dir":
|
||||
if not isinstance(parent, Thrust):
|
||||
raise Abort("%s is not part of a thruster/propeller/jet at line %d" \
|
||||
% (path, self.locator.getLineNumber()))
|
||||
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\t\033[36mdir x=%f y=%f z=%f\033[m" % (c[0], c[1], c[2]))
|
||||
parent.set_dir(c)
|
||||
|
||||
elif tag == "tank":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\033[34;1m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
|
||||
item = Tank("YASim_tank#%d" % self.counter[tag], c)
|
||||
|
||||
elif tag == "ballast":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
|
||||
item = Ballast("YASim_ballast#%d" % self.counter[tag], c)
|
||||
|
||||
elif tag == "weight":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\033[34m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
|
||||
item = Weight("YASim_weight#%d" % self.counter[tag], c)
|
||||
|
||||
elif tag == "hook":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
length = float(attrs.get("length", 1))
|
||||
up_angle = float(attrs.get("up-angle", 0))
|
||||
down_angle = float(attrs.get("down-angle", 70))
|
||||
log("\033[35m%s x=%f y=%f z=%f length=%f up-angle=%f down-angle=%f\033[m" \
|
||||
% (tag, c[0], c[1], c[2], length, up_angle, down_angle))
|
||||
item = Hook("YASim_hook#%d" % self.counter[tag], c, length, up_angle, down_angle)
|
||||
|
||||
elif tag == "hitch":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
log("\033[35m%s x=%f y=%f z=%f\033[m" % (tag, c[0], c[1], c[2]))
|
||||
item = Hitch("YASim_hitch#%d" % self.counter[tag], c)
|
||||
|
||||
elif tag == "launchbar":
|
||||
c = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
length = float(attrs.get("length", 1))
|
||||
up_angle = float(attrs.get("up-angle", -45))
|
||||
down_angle = float(attrs.get("down-angle", 45))
|
||||
holdback = Vector(float(attrs.get("holdback-x", c[0])), float(attrs.get("holdback-y", c[1])), float(attrs.get("holdback-z", c[2])))
|
||||
holdback_length = float(attrs.get("holdback-length", 2))
|
||||
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" \
|
||||
% (tag, c[0], c[1], c[2], length, down_angle, up_angle, \
|
||||
holdback[0], holdback[1], holdback[2], holdback_length))
|
||||
item = Launchbar("YASim_launchbar#%d" % self.counter[tag], c, length, holdback, holdback_length, up_angle, down_angle)
|
||||
|
||||
elif tag == "wing" or tag == "hstab" or tag == "vstab" or tag == "mstab":
|
||||
root = Vector(float(attrs["x"]), float(attrs["y"]), float(attrs["z"]))
|
||||
length = float(attrs["length"])
|
||||
chord = float(attrs["chord"])
|
||||
incidence = float(attrs.get("incidence", 0))
|
||||
twist = float(attrs.get("twist", 0))
|
||||
taper = float(attrs.get("taper", 1))
|
||||
sweep = float(attrs.get("sweep", 0))
|
||||
dihedral = float(attrs.get("dihedral", [0, 90][tag == "vstab"]))
|
||||
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" \
|
||||
% (tag, root[0], root[1], root[2], length, chord, incidence, twist, taper, sweep, dihedral))
|
||||
item = Wing("YASim_%s#%d" % (tag, self.counter[tag]), root, length, chord, incidence, twist, taper, sweep, dihedral)
|
||||
|
||||
elif tag == "flap0" or tag == "flap1" or tag == "slat" or tag == "spoiler":
|
||||
if not isinstance(parent, Wing):
|
||||
raise Abort("%s is not part of a wing or stab at line %d" \
|
||||
% (path, self.locator.getLineNumber()))
|
||||
|
||||
start = float(attrs["start"])
|
||||
end = float(attrs["end"])
|
||||
log("\t\033[33m%s start=%f end=%f\033[m" % (tag, start, end))
|
||||
parent.add_flap("YASim_%s#%d" % (tag, self.counter[tag]), start, end)
|
||||
|
||||
elif tag == "rotor":
|
||||
c = Vector(float(attrs.get("x", 0)), float(attrs.get("y", 0)), float(attrs.get("z", 0)))
|
||||
norm = Vector(float(attrs.get("nx", 0)), float(attrs.get("ny", 0)), float(attrs.get("nz", 1)))
|
||||
fwd = Vector(float(attrs.get("fx", 1)), float(attrs.get("fy", 0)), float(attrs.get("fz", 0)))
|
||||
diameter = float(attrs.get("diameter", 10.2))
|
||||
numblades = int(attrs.get("numblades", 4))
|
||||
chord = float(attrs.get("chord", 0.3))
|
||||
twist = float(attrs.get("twist", 0))
|
||||
taper = float(attrs.get("taper", 1))
|
||||
rel_len_blade_start = float(attrs.get("rel-len-blade-start", 0))
|
||||
phi0 = float(attrs.get("phi0", 0))
|
||||
ccw = not not int(attrs.get("ccw", 0))
|
||||
|
||||
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 " \
|
||||
+ "chord=%f twist=%f taper=%f rel_len_blade_start=%f phi0=%f ccw=%d\033[m") \
|
||||
% (c[0], c[1], c[2], norm[0], norm[1], norm[2], fwd[0], fwd[1], fwd[2], numblades, \
|
||||
diameter, chord, twist, taper, rel_len_blade_start, phi0, ccw))
|
||||
item = Rotor("YASim_rotor#%d" % self.counter[tag], c, norm, fwd, numblades, 0.5 * diameter, chord, \
|
||||
twist, taper, rel_len_blade_start, phi0, ccw)
|
||||
|
||||
elif tag not in self.ignored:
|
||||
log("\033[30;1m%s\033[m" % path)
|
||||
|
||||
self.items.append(item)
|
||||
|
||||
def endElement(self, tag):
|
||||
self.tags.pop()
|
||||
self.items.pop()
|
||||
|
||||
|
||||
|
||||
def extract_matrix(filedata, tag):
|
||||
v = { 'x': 0.0, 'y': 0.0, 'z': 0.0, 'h': 0.0, 'p': 0.0, 'r': 0.0 }
|
||||
has_offsets = False
|
||||
for line in filedata:
|
||||
line = string.strip(line)
|
||||
if not line.startswith("<!--") or not line.endswith("-->"):
|
||||
continue
|
||||
line = string.strip(line[4:-3])
|
||||
if not string.lower(line).startswith("%s:" % tag):
|
||||
continue
|
||||
line = string.strip(line[len(tag) + 1:])
|
||||
for assignment in string.split(line):
|
||||
(key, value) = string.split(assignment, '=', 2)
|
||||
v[string.strip(key)] = float(string.strip(value))
|
||||
has_offsets = True
|
||||
|
||||
if not has_offsets:
|
||||
return None
|
||||
|
||||
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'])))
|
||||
return Euler(v['r'], v['p'], v['h']).toMatrix().resize4x4() * TranslationMatrix(Vector(v['x'], v['y'], v['z']))
|
||||
|
||||
|
||||
|
||||
def load_yasim_config(path):
|
||||
if BPyMessages.Error_NoFile(path):
|
||||
return
|
||||
|
||||
Blender.Window.WaitCursor(1)
|
||||
Blender.Window.EditMode(0)
|
||||
|
||||
print(("loading '%s'" % path))
|
||||
try:
|
||||
for o in Item.scene.objects:
|
||||
if o.name.startswith("YASim_"):
|
||||
Item.scene.objects.unlink(o)
|
||||
|
||||
try:
|
||||
f = open(path)
|
||||
Global.data = f.readlines()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
Global.path = path
|
||||
Global.matrix = YASIM_MATRIX
|
||||
matrix = extract_matrix(Global.data, "offsets")
|
||||
if matrix:
|
||||
Global.matrix *= matrix.invert()
|
||||
|
||||
Global.yasim.parse(path)
|
||||
Blender.Registry.SetKey("FGYASimImportExport", { "path": path }, False)
|
||||
Global.data = None
|
||||
|
||||
except Abort, e:
|
||||
print(("%s\nAborting ..." % (e.term or e.msg)))
|
||||
Blender.Draw.PupMenu("Error%t|" + e.msg)
|
||||
|
||||
Blender.Window.RedrawAll()
|
||||
Blender.Window.WaitCursor(0)
|
||||
|
||||
|
||||
|
||||
def gui_draw():
|
||||
from Blender import BGL, Draw
|
||||
(width, height) = Blender.Window.GetAreaSize()
|
||||
|
||||
BGL.glClearColor(0.4, 0.4, 0.45, 1)
|
||||
BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
|
||||
|
||||
BGL.glColor3f(1, 1, 1)
|
||||
BGL.glRasterPos2f(5, 55)
|
||||
Draw.Text("FlightGear YASim Import: '%s'" % Global.path)
|
||||
|
||||
Draw.PushButton("Reload", RELOAD_BUTTON, 5, 5, 80, 32, "reload YASim config file")
|
||||
Global.mirror_button = Draw.Toggle("Mirror", MIRROR_BUTTON, 100, 5, 50, 16, Global.mirror_button.val, \
|
||||
"show symmetric surfaces on both sides (reloads config)")
|
||||
Draw.PushButton("Update Cursor", CURSOR_BUTTON, width - 650, 5, 100, 32, "update cursor display (in YASim coordinate system)")
|
||||
|
||||
BGL.glRasterPos2f(width - 530 + Blender.Draw.GetStringWidth("Vector from last") - Blender.Draw.GetStringWidth("Current"), 24)
|
||||
Draw.Text("Current cursor pos: x = %+.3f y = %+.3f z = %+.3f" % tuple(Global.cursor))
|
||||
|
||||
c = Global.cursor - Global.last_cursor
|
||||
BGL.glRasterPos2f(width - 530, 7)
|
||||
Draw.Text("Vector from last cursor pos: x = %+.3f y = %+.3f z = %+.3f length = %.3f m" % (c[0], c[1], c[2], c.length))
|
||||
|
||||
|
||||
|
||||
def gui_event(ev, value):
|
||||
if ev == Blender.Draw.ESCKEY:
|
||||
Blender.Draw.Exit()
|
||||
|
||||
|
||||
|
||||
def gui_button(n):
|
||||
if n == NO_EVENT:
|
||||
return
|
||||
|
||||
elif n == RELOAD_BUTTON:
|
||||
load_yasim_config(Global.path)
|
||||
|
||||
elif n == CURSOR_BUTTON:
|
||||
Global.last_cursor = Global.cursor
|
||||
Global.cursor = Vector(Blender.Window.GetCursorPos()) * Global.matrix.invert()
|
||||
d = Global.cursor - Global.last_cursor
|
||||
print(("cursor: x=\"%f\" y=\"%f\" z=\"%f\" dx=%f dy=%f dz=%f length=%f" \
|
||||
% (Global.cursor[0], Global.cursor[1], Global.cursor[2], d[0], d[1], d[2], d.length)))
|
||||
|
||||
elif n == MIRROR_BUTTON:
|
||||
load_yasim_config(Global.path)
|
||||
|
||||
Blender.Draw.Redraw(1)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
log(6 * "\n")
|
||||
registry = Blender.Registry.GetKey("FGYASimImportExport", False)
|
||||
if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
|
||||
path = registry["path"]
|
||||
else:
|
||||
path = ""
|
||||
|
||||
xml_handler = import_yasim()
|
||||
Global.yasim = make_parser()
|
||||
Global.yasim.setContentHandler(xml_handler)
|
||||
Global.yasim.setErrorHandler(xml_handler)
|
||||
|
||||
if Blender.Window.GetScreenInfo(Blender.Window.Types.SCRIPT):
|
||||
Blender.Draw.Register(gui_draw, gui_event, gui_button)
|
||||
|
||||
Blender.Window.FileSelector(load_yasim_config, "Import YASim Configuration File", path)
|
||||
|
||||
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user