Code: Select all
#!/usr/bin/python
# -*- coding: utf-8 -*-
# ##### BEGIN GPL LICENSE BLOCK #####
#
# 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 3
# 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.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "CSV instance transform exporter",
"author": "Matej Moderc, Issa",
"version": (1,0,1),
"blender": (2, 6, 3),
"location": "Toolbar",
"description": "Writes a .csv file containing instance transforms of selected duplicator object",
"warning": "testing",
"wiki_url": "",
"tracker_url": "",
"category": "Import-Export"}
from math import radians
import os
import bpy
import bpy.path as bpath
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import StringProperty, EnumProperty, PointerProperty
from mathutils import Matrix, Quaternion, Vector
VERSION = ".".join(str(v) for v in bl_info["version"])
def info(msg):
print("INFO: %s" % msg)
def warning(msg):
print("WARNING: %s" % msg)
def error(msg):
print("ERROR: %s" % msg)
class CSVExportException(Exception):
def __init__(self, msg):
self._msg = msg
def __str__(self):
return repr(self._msg)
########################
# EXPORT FUNCTIONS #
########################
def writeTransform(t, fh):
"""Writes first three matrix rows to the file handle"""
vals = ["%.6f" % v for v in tuple(t[0]) + tuple(t[1]) + tuple(t[2])]
fh.write(" ".join(vals) + "\n")
def exportDuplis(context, emitter, t_world, t_world_inv):
"""Exports dupli objects (dupliverts / duplifaces) for the specified emitter"""
info("Exporting dupli objects for '%s'" % emitter.name)
export_dir = bpath.abspath(context.scene.csv_exporter_options.export_dir)
emitter.dupli_list_create(context.scene)
lst = emitter.dupli_list
if len(lst) == 0: # if not on active layers, dupli list = empty
return
try:
fh = open(export_dir + emitter.name + ".csv", "w")
for duplicate in lst.values():
t = duplicate.matrix.copy()
writeTransform(t_world * t * t_world_inv, fh)
fh.close()
except IOError as err:
msg = "IOError during file handling '{0}'".format(err)
error(msg)
raise CSVExportException(msg)
emitter.dupli_list_clear()
def exportMeshDuplis(context, obj, users, t_world, t_world_inv):
"""Exports the transforms of Alt+D duplicated objects"""
export_dir = bpath.abspath(context.scene.csv_exporter_options.export_dir)
csv_filename = export_dir + obj.data.name + ".csv"
info("Saving transforms for '%s' mesh-dupli objects into file '%s' " % (obj.data.name, csv_filename))
try:
fh = open(csv_filename, "w")
for o in users:
t = o.matrix_world.copy()
writeTransform(t_world * t * t_world_inv, fh)
fh.close()
except IOError as err:
msg = "IOError during file handling '{0}'".format(err)
error(msg)
raise CSVExportException(msg)
def exportParticles(context, emitter, psys, t_world, t_world_inv):
"""Exports the particle system transforms for the specified emitter"""
export_dir = bpath.abspath(context.scene.csv_exporter_options.export_dir)
pset = psys.settings
infostr = "Exporting PS '%s' (%s) on emitter '%s'" % (psys.name, pset.type, emitter.name)
particles = [p for p in psys.particles] if pset.type == 'HAIR' else [p for p in psys.particles if p.alive_state == 'ALIVE']
if pset.render_type != "OBJECT":
warning("Invalid PS visualization type '%s'" % pset.render_type)
return
if not pset.use_rotation_dupli:
warning("'Use object rotation' should be on. Rotations wont conform to Blender veiwport")
try:
fh = open(export_dir + psys.name + ".csv", "w")
for p in particles:
#if pset.type == 'HAIR' or not p.alive_state == 'DEAD':
rot = Quaternion.to_matrix(p.rotation).to_4x4()
if (pset.type == "HAIR"):
h1 = p.hair_keys[0].co
h2 = p.hair_keys[-1].co
loc = Matrix.Translation(h1)
scale = Matrix.Scale((h2 - h1).length, 4)
emitter_rot = emitter.matrix_world.decompose()[1].to_matrix().to_4x4()
rot = emitter_rot.inverted() * rot
else:
loc = Matrix.Translation(p.location.copy())
scale = Matrix.Scale(p.size, 4)
t = loc * rot * scale
t = emitter.matrix_world * t if pset.type == "HAIR" else t
writeTransform(t_world * t * t_world_inv, fh)
fh.close()
except IOError as err:
msg = "IOError during file handling '{0}'".format(err)
error(msg)
raise CSVExportException(msg)
###################
# GUI #
###################
### GLOBAL OPTIONS OF THE EXPORTER ###
class CSVExporterOptions(PropertyGroup):
export_dir = StringProperty(
name = "",
default = "/tmp",
description = "Define the export path for the .csv file",
subtype = "DIR_PATH"
)
world_space = EnumProperty(
name="World space options",
items=(('Y Up', 'Y Up', 'Writes transforms in Octane world space'),
('Z Up', 'Z Up', 'Writes transforms in Blender native space')),
default='Y Up',
#update=callback
)
### EXPORTER PANEL ###
class CSVExporterPanel(Panel):
"""Exports instance transforms to a .csv file"""
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_label = "CSV Exporter" #+ VERSION
def draw_header(self, context):
layout = self.layout
layout.label(text="", icon="PARTICLES")
def draw(self, context):
sce = context.scene
opts = sce.csv_exporter_options
layout = self.layout
col = layout.column()
col.label(text="Select export path")
col.prop(opts, "export_dir")
col.label(text="Select up axis")
row = layout.row()
row.prop(opts, "world_space", expand=True)
col = layout.column(align=True)
col.operator("ops.csv_write_transforms", icon="RENDER_STILL")
col = layout.column(align=True)
col.operator("ops.csv_write_animation", icon="RENDER_ANIMATION")
### WRITE TRANSFORMS BUTTON ###
class CSVWriteTransforms(Operator):
"""Writes instance transforms to .csv file. Note that a duplicator object must be selected."""
bl_idname = "ops.csv_write_transforms"
bl_label = "Export transforms"
def execute(self, context):
info("CSV instance transforms exporter")
sce = context.scene
with_warnings = False
msg = None
t_scale = Matrix.Scale(1, 4) #TODO: check for aplicable scaling
t_world = Matrix.Rotation(radians(-90.0), 4, 'X') * t_scale
t_world_inv = Matrix.Rotation(radians(90.0), 4, 'X') * t_scale.inverted()
world_space_tag = sce.csv_exporter_options.world_space
info("Exporting in %s world space" % (world_space_tag))
if world_space_tag == "Z Up":
t_world = Matrix.Identity(4)
t_world_inv = Matrix.Identity(4)
# utility func to send warnings through Blender GUI
def report_warning(msg):
self.report({'WARNING'}, msg)
nonlocal with_warnings
with_warnings = True
ob = context.selected_objects[0]
if ob.is_duplicator: # and obj.is_visible(sce) and not obj.hide_render:
info("Found duplicator '%s' of type '%s'" % (ob.name, ob.dupli_type))
if ob.dupli_type in ('FACES', 'VERTS'):
exportDuplis(context, ob, t_world, t_world_inv)
elif ob.dupli_type == 'NONE' and len(ob.particle_systems) > 0:
# even if NONE for dupliverts, its possible PS
if len(ob.particle_systems) > 0:
for ps in ob.particle_systems:
pset = ps.settings
if pset.render_type == "OBJECT":
exportParticles(context, ob, ps, t_world, t_world_inv)
else:
report_warning("Unsupported PS vizualisation type '%s'" % pset.render_type)
else:
report_warning("Object '%s' has no particle systems attached" % ob.name)
else:
report_warning("Unsupported duplicator type '%s'" % ob.dupli_type)
# if not duplicator, possible dupliverts
elif ob.type == "MESH" and ob.data.users > 1:
# find all users of the mesh of this object, aka. duplicates
# meshes = bpy.data.meshes
me = ob.data
users = [o for o in bpy.data.objects if o.type == "MESH" and o.data == me]
info("Found %i users for dupli-mesh '%s'" % (len(users), me.name))
exportMeshDuplis(context, ob, users, t_world, t_world_inv)
else:
report_warning("The selected object '%s' is not an instance emitter or mesh-dupli" % ob.name)
if with_warnings:
msg = "Script ended with warnings, look into console"
self.report({'WARNING'}, msg)
else:
msg = "Export complete. CSV file(s) written in '%s'" % (sce.csv_exporter_options.export_dir)
info(msg)
self.report({'INFO'}, msg)
return {"FINISHED"}
# button is active only when selected object is a duplicator
@classmethod
def poll(self, context):
obs = context.selected_objects
if len(obs) < 1:
return False
ob = obs[0]
return ob.is_duplicator or (ob.type == "MESH" and ob.data.users > 1)
### WRITE ANIMATION BUTTON ###
class CSVWriteAnimation(Operator):
"""Writes instance transforms to .csv file. Note that a duplicator object must be selected."""
bl_idname = "ops.csv_write_animation"
bl_label = "Export animation"
# button is active only when selected object is a duplicator
@classmethod
def poll(self, context):
obs = context.selected_objects
if len(obs) < 1:
return False
ob = obs[0]
return ob.is_duplicator or (ob.type == "MESH" and ob.data.users > 1)
def execute(self, context):
sce = context.scene
###Export !
curFilePath = sce.csv_exporter_options.export_dir
if not os.path.isdir(curFilePath):
curFilePath = os.path.split(curFilePath)[0]+"\\"
RANGE = range(sce.frame_start, sce.frame_end+1)
for frame in RANGE:
#sce.frame_current =
bpy.context.scene.frame_set(frame)
bpy.data.particles.update()
sce.update()
sce.csv_exporter_options.export_dir = curFilePath + ("%06d" % (sce.frame_current,)) + "_"
bpy.ops.ops.csv_write_transforms()
###
return {"FINISHED"}
def register():
bpy.utils.register_class(CSVWriteTransforms)
bpy.utils.register_class(CSVWriteAnimation)
bpy.utils.register_class(CSVExporterPanel)
bpy.utils.register_class(CSVExporterOptions)
#bpy.utils.register_module(__name__)
bpy.types.Scene.csv_exporter_options = PointerProperty(type=CSVExporterOptions)
def unregister():
bpy.utils.unregister_class(CSVWriteTransforms)
bpy.utils.unregister_class(CSVWriteAnimation)
bpy.utils.unregister_class(CSVExporterPanel)
bpy.utils.unregister_class(CSVExporterOptions)
#bpy.utils.unregister_module(__name__)
del bpy.types.Scene.csv_exporter_options
if __name__=="__main__":
register()