diff options
author | Arystanbek Dyussenov <arystan.d@gmail.com> | 2010-09-04 22:49:07 +0400 |
---|---|---|
committer | Arystanbek Dyussenov <arystan.d@gmail.com> | 2010-09-04 22:49:07 +0400 |
commit | 90b464d3728d9ed8ec26fdf59058d236b99dbcd9 (patch) | |
tree | e88cab4fb1358e962b19f658064ca8c9f8d29f5b /release/scripts/io | |
parent | 08d02dd04d836976b25793bb1d4c6a86b3f924c7 (diff) | |
parent | b0b787ef38f9947b3176642556f5282eb3518f69 (diff) |
COLLADA branch: merge from trunk -r 28015:31610.soc-2009-chingachgook
Diffstat (limited to 'release/scripts/io')
22 files changed, 1539 insertions, 2447 deletions
diff --git a/release/scripts/io/engine_render_pov.py b/release/scripts/io/engine_render_pov.py deleted file mode 100644 index e64e3bf05fb..00000000000 --- a/release/scripts/io/engine_render_pov.py +++ /dev/null @@ -1,964 +0,0 @@ -# ##### 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 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. -# -# ##### END GPL LICENSE BLOCK ##### - -# <pep8 compliant> - -import bpy - -from math import atan, pi, degrees -import subprocess -import os -import sys -import time - -import platform as pltfrm - -if pltfrm.architecture()[0] == '64bit': - bitness = 64 -else: - bitness = 32 - - -def write_pov(filename, scene=None, info_callback=None): - file = open(filename, 'w') - - # Only for testing - if not scene: - scene = bpy.data.scenes[0] - - render = scene.render - world = scene.world - - def uniqueName(name, nameSeq): - - if name not in nameSeq: - return name - - name_orig = name - i = 1 - while name in nameSeq: - name = '%s_%.3d' % (name_orig, i) - i += 1 - - return name - - def writeMatrix(matrix): - file.write('\tmatrix <%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f>\n' %\ - (matrix[0][0], matrix[0][1], matrix[0][2], matrix[1][0], matrix[1][1], matrix[1][2], matrix[2][0], matrix[2][1], matrix[2][2], matrix[3][0], matrix[3][1], matrix[3][2])) - - def writeObjectMaterial(material): - if material and material.transparency_method == 'RAYTRACE': - file.write('\tinterior { ior %.6f }\n' % material.raytrace_transparency.ior) - - # Other interior args - # fade_distance 2 - # fade_power [Value] - # fade_color - - # dispersion - # dispersion_samples - - materialNames = {} - DEF_MAT_NAME = 'Default' - - def writeMaterial(material): - # Assumes only called once on each material - - if material: - name_orig = material.name - else: - name_orig = DEF_MAT_NAME - - name = materialNames[name_orig] = uniqueName(bpy.utils.clean_name(name_orig), materialNames) - - file.write('#declare %s = finish {\n' % name) - - if material: - file.write('\tdiffuse %.3g\n' % material.diffuse_intensity) - file.write('\tspecular %.3g\n' % material.specular_intensity) - - file.write('\tambient %.3g\n' % material.ambient) - #file.write('\tambient rgb <%.3g, %.3g, %.3g>\n' % tuple([c*material.ambient for c in world.ambient_color])) # povray blends the global value - - # map hardness between 0.0 and 1.0 - roughness = ((1.0 - ((material.specular_hardness - 1.0) / 510.0))) - # scale from 0.0 to 0.1 - roughness *= 0.1 - # add a small value because 0.0 is invalid - roughness += (1 / 511.0) - - file.write('\troughness %.3g\n' % roughness) - - # 'phong 70.0 ' - - if material.raytrace_mirror.enabled: - raytrace_mirror = material.raytrace_mirror - if raytrace_mirror.reflect_factor: - file.write('\treflection {\n') - file.write('\t\trgb <%.3g, %.3g, %.3g>' % tuple(material.mirror_color)) - file.write('\t\tfresnel 1 falloff %.3g exponent %.3g metallic %.3g} ' % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_factor, raytrace_mirror.reflect_factor)) - - else: - file.write('\tdiffuse 0.8\n') - file.write('\tspecular 0.2\n') - - - # This is written into the object - ''' - if material and material.transparency_method=='RAYTRACE': - 'interior { ior %.3g} ' % material.raytrace_transparency.ior - ''' - - #file.write('\t\t\tcrand 1.0\n') # Sand granyness - #file.write('\t\t\tmetallic %.6f\n' % material.spec) - #file.write('\t\t\tphong %.6f\n' % material.spec) - #file.write('\t\t\tphong_size %.6f\n' % material.spec) - #file.write('\t\t\tbrilliance %.6f ' % (material.specular_hardness/256.0) # Like hardness - - file.write('}\n') - - def exportCamera(): - camera = scene.camera - matrix = camera.matrix - - # compute resolution - Qsize = float(render.resolution_x) / float(render.resolution_y) - - file.write('camera {\n') - file.write('\tlocation <0, 0, 0>\n') - file.write('\tlook_at <0, 0, -1>\n') - file.write('\tright <%s, 0, 0>\n' % - Qsize) - file.write('\tup <0, 1, 0>\n') - file.write('\tangle %f \n' % (360.0 * atan(16.0 / camera.data.lens) / pi)) - - file.write('\trotate <%.6f, %.6f, %.6f>\n' % tuple([degrees(e) for e in matrix.rotation_part().to_euler()])) - file.write('\ttranslate <%.6f, %.6f, %.6f>\n' % (matrix[3][0], matrix[3][1], matrix[3][2])) - file.write('}\n') - - def exportLamps(lamps): - # Get all lamps - for ob in lamps: - lamp = ob.data - - matrix = ob.matrix - - color = tuple([c * lamp.energy for c in lamp.color]) # Colour is modified by energy - - file.write('light_source {\n') - file.write('\t< 0,0,0 >\n') - file.write('\tcolor rgb<%.3g, %.3g, %.3g>\n' % color) - - if lamp.type == 'POINT': # Point Lamp - pass - elif lamp.type == 'SPOT': # Spot - file.write('\tspotlight\n') - - # Falloff is the main radius from the centre line - file.write('\tfalloff %.2f\n' % (degrees(lamp.spot_size) / 2.0)) # 1 TO 179 FOR BOTH - file.write('\tradius %.6f\n' % ((degrees(lamp.spot_size) / 2.0) * (1.0 - lamp.spot_blend))) - - # Blender does not have a tightness equivilent, 0 is most like blender default. - file.write('\ttightness 0\n') # 0:10f - - file.write('\tpoint_at <0, 0, -1>\n') - elif lamp.type == 'SUN': - file.write('\tparallel\n') - file.write('\tpoint_at <0, 0, -1>\n') # *must* be after 'parallel' - - elif lamp.type == 'AREA': - - size_x = lamp.size - samples_x = lamp.shadow_ray_samples_x - if lamp.shape == 'SQUARE': - size_y = size_x - samples_y = samples_x - else: - size_y = lamp.size_y - samples_y = lamp.shadow_ray_samples_y - - file.write('\tarea_light <%d,0,0>,<0,0,%d> %d, %d\n' % (size_x, size_y, samples_x, samples_y)) - if lamp.shadow_ray_sampling_method == 'CONSTANT_JITTERED': - if lamp.jitter: - file.write('\tjitter\n') - else: - file.write('\tadaptive 1\n') - file.write('\tjitter\n') - - if lamp.shadow_method == 'NOSHADOW': - file.write('\tshadowless\n') - - file.write('\tfade_distance %.6f\n' % lamp.distance) - file.write('\tfade_power %d\n' % 1) # Could use blenders lamp quad? - writeMatrix(matrix) - - file.write('}\n') - - def exportMeta(metas): - - # TODO - blenders 'motherball' naming is not supported. - - for ob in metas: - meta = ob.data - - file.write('blob {\n') - file.write('\t\tthreshold %.4g\n' % meta.threshold) - - try: - material = meta.materials[0] # lame! - blender cant do enything else. - except: - material = None - - for elem in meta.elements: - - if elem.type not in ('BALL', 'ELLIPSOID'): - continue # Not supported - - loc = elem.location - - stiffness = elem.stiffness - if elem.negative: - stiffness = - stiffness - - if elem.type == 'BALL': - - file.write('\tsphere { <%.6g, %.6g, %.6g>, %.4g, %.4g ' % (loc.x, loc.y, loc.z, elem.radius, stiffness)) - - # After this wecould do something simple like... - # "pigment {Blue} }" - # except we'll write the color - - elif elem.type == 'ELLIPSOID': - # location is modified by scale - file.write('\tsphere { <%.6g, %.6g, %.6g>, %.4g, %.4g ' % (loc.x / elem.size_x, loc.y / elem.size_y, loc.z / elem.size_z, elem.radius, stiffness)) - file.write('scale <%.6g, %.6g, %.6g> ' % (elem.size_x, elem.size_y, elem.size_z)) - - if material: - diffuse_color = material.diffuse_color - - if material.transparency and material.transparency_method == 'RAYTRACE': - trans = 1.0 - material.raytrace_transparency.filter - else: - trans = 0.0 - - file.write('pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} finish {%s} }\n' % \ - (diffuse_color[0], diffuse_color[1], diffuse_color[2], 1.0 - material.alpha, trans, materialNames[material.name])) - - else: - file.write('pigment {rgb<1 1 1>} finish {%s} }\n' % DEF_MAT_NAME) # Write the finish last. - - writeObjectMaterial(material) - - writeMatrix(ob.matrix) - - file.write('}\n') - - def exportMeshs(sel): - - ob_num = 0 - - for ob in sel: - ob_num += 1 - - if ob.type in ('LAMP', 'CAMERA', 'EMPTY', 'META', 'ARMATURE'): - continue - - me = ob.data - me_materials = me.materials - - me = ob.create_mesh(True, 'RENDER') - - if not me: - continue - - if info_callback: - info_callback('Object %2.d of %2.d (%s)' % (ob_num, len(sel), ob.name)) - - #if ob.type!='MESH': - # continue - # me = ob.data - - matrix = ob.matrix - try: - uv_layer = me.active_uv_texture.data - except: - uv_layer = None - - try: - vcol_layer = me.active_vertex_color.data - except: - vcol_layer = None - - faces_verts = [f.verts for f in me.faces] - faces_normals = [tuple(f.normal) for f in me.faces] - verts_normals = [tuple(v.normal) for v in me.verts] - - # quads incur an extra face - quadCount = len([f for f in faces_verts if len(f) == 4]) - - file.write('mesh2 {\n') - file.write('\tvertex_vectors {\n') - file.write('\t\t%s' % (len(me.verts))) # vert count - for v in me.verts: - file.write(',\n\t\t<%.6f, %.6f, %.6f>' % tuple(v.co)) # vert count - file.write('\n }\n') - - - # Build unique Normal list - uniqueNormals = {} - for fi, f in enumerate(me.faces): - fv = faces_verts[fi] - # [-1] is a dummy index, use a list so we can modify in place - if f.smooth: # Use vertex normals - for v in fv: - key = verts_normals[v] - uniqueNormals[key] = [-1] - else: # Use face normal - key = faces_normals[fi] - uniqueNormals[key] = [-1] - - file.write('\tnormal_vectors {\n') - file.write('\t\t%d' % len(uniqueNormals)) # vert count - idx = 0 - for no, index in uniqueNormals.items(): - file.write(',\n\t\t<%.6f, %.6f, %.6f>' % no) # vert count - index[0] = idx - idx += 1 - file.write('\n }\n') - - - # Vertex colours - vertCols = {} # Use for material colours also. - - if uv_layer: - # Generate unique UV's - uniqueUVs = {} - - for fi, uv in enumerate(uv_layer): - - if len(faces_verts[fi]) == 4: - uvs = uv.uv1, uv.uv2, uv.uv3, uv.uv4 - else: - uvs = uv.uv1, uv.uv2, uv.uv3 - - for uv in uvs: - uniqueUVs[tuple(uv)] = [-1] - - file.write('\tuv_vectors {\n') - #print unique_uvs - file.write('\t\t%s' % (len(uniqueUVs))) # vert count - idx = 0 - for uv, index in uniqueUVs.items(): - file.write(',\n\t\t<%.6f, %.6f>' % uv) - index[0] = idx - idx += 1 - ''' - else: - # Just add 1 dummy vector, no real UV's - file.write('\t\t1') # vert count - file.write(',\n\t\t<0.0, 0.0>') - ''' - file.write('\n }\n') - - - if me.vertex_colors: - - for fi, f in enumerate(me.faces): - material_index = f.material_index - material = me_materials[material_index] - - if material and material.vertex_color_paint: - - col = vcol_layer[fi] - - if len(faces_verts[fi]) == 4: - cols = col.color1, col.color2, col.color3, col.color4 - else: - cols = col.color1, col.color2, col.color3 - - for col in cols: - key = col[0], col[1], col[2], material_index # Material index! - vertCols[key] = [-1] - - else: - if material: - diffuse_color = tuple(material.diffuse_color) - key = diffuse_color[0], diffuse_color[1], diffuse_color[2], material_index - vertCols[key] = [-1] - - - else: - # No vertex colours, so write material colours as vertex colours - for i, material in enumerate(me_materials): - - if material: - diffuse_color = tuple(material.diffuse_color) - key = diffuse_color[0], diffuse_color[1], diffuse_color[2], i # i == f.mat - vertCols[key] = [-1] - - - # Vert Colours - file.write('\ttexture_list {\n') - file.write('\t\t%s' % (len(vertCols))) # vert count - idx = 0 - for col, index in vertCols.items(): - - if me_materials: - material = me_materials[col[3]] - material_finish = materialNames[material.name] - - if material.transparency and material.transparency_method == 'RAYTRACE': - trans = 1.0 - material.raytrace_transparency.filter - else: - trans = 0.0 - - else: - material_finish = DEF_MAT_NAME # not working properly, - trans = 0.0 - - #print material.apl - file.write(',\n\t\ttexture { pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} finish {%s}}' % - (col[0], col[1], col[2], 1.0 - material.alpha, trans, material_finish)) - - index[0] = idx - idx += 1 - - file.write('\n }\n') - - # Face indicies - file.write('\tface_indices {\n') - file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count - for fi, f in enumerate(me.faces): - fv = faces_verts[fi] - material_index = f.material_index - if len(fv) == 4: - indicies = (0, 1, 2), (0, 2, 3) - else: - indicies = ((0, 1, 2),) - - if vcol_layer: - col = vcol_layer[fi] - - if len(fv) == 4: - cols = col.color1, col.color2, col.color3, col.color4 - else: - cols = col.color1, col.color2, col.color3 - - - if not me_materials or me_materials[material_index] == None: # No materials - for i1, i2, i3 in indicies: - file.write(',\n\t\t<%d,%d,%d>' % (fv[i1], fv[i2], fv[i3])) # vert count - else: - material = me_materials[material_index] - for i1, i2, i3 in indicies: - if me.vertex_colors and material.vertex_color_paint: - # Colour per vertex - vertex colour - - col1 = cols[i1] - col2 = cols[i2] - col3 = cols[i3] - - ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0] - ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0] - ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0] - else: - # Colour per material - flat material colour - diffuse_color = material.diffuse_color - ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], diffuse_color[2], f.material_index][0] - - file.write(',\n\t\t<%d,%d,%d>, %d,%d,%d' % (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count - - - file.write('\n }\n') - - # normal_indices indicies - file.write('\tnormal_indices {\n') - file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count - for fi, fv in enumerate(faces_verts): - - if len(fv) == 4: - indicies = (0, 1, 2), (0, 2, 3) - else: - indicies = ((0, 1, 2),) - - for i1, i2, i3 in indicies: - if f.smooth: - file.write(',\n\t\t<%d,%d,%d>' %\ - (uniqueNormals[verts_normals[fv[i1]]][0],\ - uniqueNormals[verts_normals[fv[i2]]][0],\ - uniqueNormals[verts_normals[fv[i3]]][0])) # vert count - else: - idx = uniqueNormals[faces_normals[fi]][0] - file.write(',\n\t\t<%d,%d,%d>' % (idx, idx, idx)) # vert count - - file.write('\n }\n') - - if uv_layer: - file.write('\tuv_indices {\n') - file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count - for fi, fv in enumerate(faces_verts): - - if len(fv) == 4: - indicies = (0, 1, 2), (0, 2, 3) - else: - indicies = ((0, 1, 2),) - - uv = uv_layer[fi] - if len(faces_verts[fi]) == 4: - uvs = tuple(uv.uv1), tuple(uv.uv2), tuple(uv.uv3), tuple(uv.uv4) - else: - uvs = tuple(uv.uv1), tuple(uv.uv2), tuple(uv.uv3) - - for i1, i2, i3 in indicies: - file.write(',\n\t\t<%d,%d,%d>' %\ - (uniqueUVs[uvs[i1]][0],\ - uniqueUVs[uvs[i2]][0],\ - uniqueUVs[uvs[i2]][0])) # vert count - file.write('\n }\n') - - if me.materials: - material = me.materials[0] # dodgy - writeObjectMaterial(material) - - writeMatrix(matrix) - file.write('}\n') - - bpy.data.meshes.remove(me) - - def exportWorld(world): - if not world: - return - - mist = world.mist - - if mist.enabled: - file.write('fog {\n') - file.write('\tdistance %.6f\n' % mist.depth) - file.write('\tcolor rgbt<%.3g, %.3g, %.3g, %.3g>\n' % (tuple(world.horizon_color) + (1 - mist.intensity,))) - #file.write('\tfog_offset %.6f\n' % mist.start) - #file.write('\tfog_alt 5\n') - #file.write('\tturbulence 0.2\n') - #file.write('\tturb_depth 0.3\n') - file.write('\tfog_type 1\n') - file.write('}\n') - - def exportGlobalSettings(scene): - - file.write('global_settings {\n') - - if scene.pov_radio_enable: - file.write('\tradiosity {\n') - file.write("\t\tadc_bailout %.4g\n" % scene.pov_radio_adc_bailout) - file.write("\t\talways_sample %d\n" % scene.pov_radio_always_sample) - file.write("\t\tbrightness %.4g\n" % scene.pov_radio_brightness) - file.write("\t\tcount %d\n" % scene.pov_radio_count) - file.write("\t\terror_bound %.4g\n" % scene.pov_radio_error_bound) - file.write("\t\tgray_threshold %.4g\n" % scene.pov_radio_gray_threshold) - file.write("\t\tlow_error_factor %.4g\n" % scene.pov_radio_low_error_factor) - file.write("\t\tmedia %d\n" % scene.pov_radio_media) - file.write("\t\tminimum_reuse %.4g\n" % scene.pov_radio_minimum_reuse) - file.write("\t\tnearest_count %d\n" % scene.pov_radio_nearest_count) - file.write("\t\tnormal %d\n" % scene.pov_radio_normal) - file.write("\t\trecursion_limit %d\n" % scene.pov_radio_recursion_limit) - file.write('\t}\n') - - if world: - file.write("\tambient_light rgb<%.3g, %.3g, %.3g>\n" % tuple(world.ambient_color)) - - file.write('}\n') - - - # Convert all materials to strings we can access directly per vertex. - writeMaterial(None) # default material - - for material in bpy.data.materials: - writeMaterial(material) - - exportCamera() - #exportMaterials() - sel = scene.objects - exportLamps([l for l in sel if l.type == 'LAMP']) - exportMeta([l for l in sel if l.type == 'META']) - exportMeshs(sel) - exportWorld(scene.world) - exportGlobalSettings(scene) - - file.close() - - -def write_pov_ini(filename_ini, filename_pov, filename_image): - scene = bpy.data.scenes[0] - render = scene.render - - x = int(render.resolution_x * render.resolution_percentage * 0.01) - y = int(render.resolution_y * render.resolution_percentage * 0.01) - - file = open(filename_ini, 'w') - - file.write('Input_File_Name="%s"\n' % filename_pov) - file.write('Output_File_Name="%s"\n' % filename_image) - - file.write('Width=%d\n' % x) - file.write('Height=%d\n' % y) - - # Needed for border render. - ''' - file.write('Start_Column=%d\n' % part.x) - file.write('End_Column=%d\n' % (part.x+part.w)) - - file.write('Start_Row=%d\n' % (part.y)) - file.write('End_Row=%d\n' % (part.y+part.h)) - ''' - - file.write('Display=0\n') - file.write('Pause_When_Done=0\n') - file.write('Output_File_Type=T\n') # TGA, best progressive loading - file.write('Output_Alpha=1\n') - - if render.antialiasing: - aa_mapping = {'5': 2, '8': 3, '11': 4, '16': 5} # method 1 assumed - file.write('Antialias=1\n') - file.write('Antialias_Depth=%d\n' % aa_mapping[render.antialiasing_samples]) - else: - file.write('Antialias=0\n') - - file.close() - -# Radiosity panel, use in the scene for now. -FloatProperty = bpy.types.Scene.FloatProperty -IntProperty = bpy.types.Scene.IntProperty -BoolProperty = bpy.types.Scene.BoolProperty - -# Not a real pov option, just to know if we should write -BoolProperty(attr="pov_radio_enable", - name="Enable Radiosity", - description="Enable povrays radiosity calculation", - default=False) -BoolProperty(attr="pov_radio_display_advanced", - name="Advanced Options", - description="Show advanced options", - default=False) - -# Real pov options -FloatProperty(attr="pov_radio_adc_bailout", - name="ADC Bailout", - description="The adc_bailout for radiosity rays. Use adc_bailout = 0.01 / brightest_ambient_object for good results", - min=0.0, max=1000.0, soft_min=0.0, soft_max=1.0, default=0.01) - -BoolProperty(attr="pov_radio_always_sample", - name="Always Sample", - description="Only use the data from the pretrace step and not gather any new samples during the final radiosity pass", - default=True) - -FloatProperty(attr="pov_radio_brightness", - name="Brightness", - description="Amount objects are brightened before being returned upwards to the rest of the system", - min=0.0, max=1000.0, soft_min=0.0, soft_max=10.0, default=1.0) - -IntProperty(attr="pov_radio_count", - name="Ray Count", - description="Number of rays that are sent out whenever a new radiosity value has to be calculated", - min=1, max=1600, default=35) - -FloatProperty(attr="pov_radio_error_bound", - name="Error Bound", - description="One of the two main speed/quality tuning values, lower values are more accurate", - min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=1.8) - -FloatProperty(attr="pov_radio_gray_threshold", - name="Gray Threshold", - description="One of the two main speed/quality tuning values, lower values are more accurate", - min=0.0, max=1.0, soft_min=0, soft_max=1, default=0.0) - -FloatProperty(attr="pov_radio_low_error_factor", - name="Low Error Factor", - description="If you calculate just enough samples, but no more, you will get an image which has slightly blotchy lighting", - min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.5) - -# max_sample - not available yet -BoolProperty(attr="pov_radio_media", - name="Media", - description="Radiosity estimation can be affected by media", - default=False) - -FloatProperty(attr="pov_radio_minimum_reuse", - name="Minimum Reuse", - description="Fraction of the screen width which sets the minimum radius of reuse for each sample point (At values higher than 2% expect errors)", - min=0.0, max=1.0, soft_min=0.1, soft_max=0.1, default=0.015) - -IntProperty(attr="pov_radio_nearest_count", - name="Nearest Count", - description="Number of old ambient values blended together to create a new interpolated value", - min=1, max=20, default=5) - -BoolProperty(attr="pov_radio_normal", - name="Normals", - description="Radiosity estimation can be affected by normals", - default=False) - -IntProperty(attr="pov_radio_recursion_limit", - name="Recursion Limit", - description="how many recursion levels are used to calculate the diffuse inter-reflection", - min=1, max=20, default=3) - - -class PovrayRender(bpy.types.RenderEngine): - bl_idname = 'POVRAY_RENDER' - bl_label = "Povray" - DELAY = 0.02 - - def _export(self, scene): - import tempfile - - self._temp_file_in = tempfile.mktemp(suffix='.pov') - self._temp_file_out = tempfile.mktemp(suffix='.tga') - self._temp_file_ini = tempfile.mktemp(suffix='.ini') - ''' - self._temp_file_in = '/test.pov' - self._temp_file_out = '/test.tga' - self._temp_file_ini = '/test.ini' - ''' - - def info_callback(txt): - self.update_stats("", "POVRAY: " + txt) - - write_pov(self._temp_file_in, scene, info_callback) - - def _render(self): - - try: - os.remove(self._temp_file_out) # so as not to load the old file - except: - pass - - write_pov_ini(self._temp_file_ini, self._temp_file_in, self._temp_file_out) - - print ("***-STARTING-***") - - pov_binary = "povray" - - if sys.platform == 'win32': - import winreg - regKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\POV-Ray\\v3.6\\Windows') - - if bitness == 64: - pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine64' - else: - pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine' - - if 1: - # TODO, when povray isnt found this gives a cryptic error, would be nice to be able to detect if it exists - self._process = subprocess.Popen([pov_binary, self._temp_file_ini]) # stdout=subprocess.PIPE, stderr=subprocess.PIPE - else: - # This works too but means we have to wait until its done - os.system('%s %s' % (pov_binary, self._temp_file_ini)) - - print ("***-DONE-***") - - def _cleanup(self): - for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out): - try: - os.remove(f) - except: - pass - - self.update_stats("", "") - - def render(self, scene): - - self.update_stats("", "POVRAY: Exporting data from Blender") - self._export(scene) - self.update_stats("", "POVRAY: Parsing File") - self._render() - - r = scene.render - - # compute resolution - x = int(r.resolution_x * r.resolution_percentage * 0.01) - y = int(r.resolution_y * r.resolution_percentage * 0.01) - - # Wait for the file to be created - while not os.path.exists(self._temp_file_out): - if self.test_break(): - try: - self._process.terminate() - except: - pass - break - - if self._process.poll() != None: - self.update_stats("", "POVRAY: Failed") - break - - time.sleep(self.DELAY) - - if os.path.exists(self._temp_file_out): - - self.update_stats("", "POVRAY: Rendering") - - prev_size = -1 - - def update_image(): - result = self.begin_result(0, 0, x, y) - lay = result.layers[0] - # possible the image wont load early on. - try: - lay.load_from_file(self._temp_file_out) - except: - pass - self.end_result(result) - - # Update while povray renders - while True: - - # test if povray exists - if self._process.poll() is not None: - update_image() - break - - # user exit - if self.test_break(): - try: - self._process.terminate() - except: - pass - - break - - # Would be nice to redirect the output - # stdout_value, stderr_value = self._process.communicate() # locks - - - # check if the file updated - new_size = os.path.getsize(self._temp_file_out) - - if new_size != prev_size: - update_image() - prev_size = new_size - - time.sleep(self.DELAY) - - self._cleanup() - - -# Use some of the existing buttons. -import properties_render -properties_render.RENDER_PT_render.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_render.RENDER_PT_antialiasing.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_render.RENDER_PT_output.COMPAT_ENGINES.add('POVRAY_RENDER') -del properties_render - -# Use only a subset of the world panels -import properties_world -properties_world.WORLD_PT_preview.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_world.WORLD_PT_context_world.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_world.WORLD_PT_world.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_world.WORLD_PT_mist.COMPAT_ENGINES.add('POVRAY_RENDER') -del properties_world - -# Example of wrapping every class 'as is' -import properties_material -for member in dir(properties_material): - subclass = getattr(properties_material, member) - try: - subclass.COMPAT_ENGINES.add('POVRAY_RENDER') - except: - pass -del properties_material - - -class RenderButtonsPanel(bpy.types.Panel): - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = "render" - # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here - - def poll(self, context): - rd = context.scene.render - return (rd.use_game_engine == False) and (rd.engine in self.COMPAT_ENGINES) - - -class RENDER_PT_povray_radiosity(RenderButtonsPanel): - bl_label = "Radiosity" - COMPAT_ENGINES = {'POVRAY_RENDER'} - - def draw_header(self, context): - scene = context.scene - - self.layout.prop(scene, "pov_radio_enable", text="") - - def draw(self, context): - layout = self.layout - - scene = context.scene - rd = scene.render - - layout.active = scene.pov_radio_enable - - split = layout.split() - - col = split.column() - col.prop(scene, "pov_radio_count", text="Rays") - col.prop(scene, "pov_radio_recursion_limit", text="Recursions") - col = split.column() - col.prop(scene, "pov_radio_error_bound", text="Error") - - layout.prop(scene, "pov_radio_display_advanced") - - if scene.pov_radio_display_advanced: - split = layout.split() - - col = split.column() - col.prop(scene, "pov_radio_adc_bailout", slider=True) - col.prop(scene, "pov_radio_gray_threshold", slider=True) - col.prop(scene, "pov_radio_low_error_factor", slider=True) - - col = split.column() - col.prop(scene, "pov_radio_brightness") - col.prop(scene, "pov_radio_minimum_reuse", text="Min Reuse") - col.prop(scene, "pov_radio_nearest_count") - - split = layout.split() - - col = split.column() - col.label(text="Estimation Influence:") - col.prop(scene, "pov_radio_media") - col.prop(scene, "pov_radio_normal") - - col = split.column() - col.prop(scene, "pov_radio_always_sample") - - -classes = [ - PovrayRender, - RENDER_PT_povray_radiosity] - - -def register(): - register = bpy.types.register - for cls in classes: - register(cls) - - -def unregister(): - unregister = bpy.types.unregister - for cls in classes: - unregister(cls) - -if __name__ == "__main__": - register() diff --git a/release/scripts/io/export_3ds.py b/release/scripts/io/export_3ds.py index d46cc712e9a..706e7eb1516 100644 --- a/release/scripts/io/export_3ds.py +++ b/release/scripts/io/export_3ds.py @@ -74,15 +74,15 @@ import bpy # also used by X3D exporter # return a tuple (free, object list), free is True if memory should be freed later with free_derived_objects() -def create_derived_objects(ob): +def create_derived_objects(scene, ob): if ob.parent and ob.parent.dupli_type != 'NONE': return False, None if ob.dupli_type != 'NONE': - ob.create_dupli_list() + ob.create_dupli_list(scene) return True, [(dob.object, dob.matrix) for dob in ob.dupli_list] else: - return False, [(ob, ob.matrix)] + return False, [(ob, ob.matrix_world)] # also used by X3D exporter def free_derived_objects(ob): @@ -494,8 +494,7 @@ def make_material_texture_chunk(id, images): mat_sub = _3ds_chunk(id) def add_image(img): - filename = os.path.basename(image.filename) -# filename = image.filename.split('\\')[-1].split('/')[-1] + filename = os.path.basename(image.filepath) mat_sub_file = _3ds_chunk(MATMAPFILE) mat_sub_file.add_variable("mapfile", _3ds_string(sane_name(filename))) mat_sub.add_subchunk(mat_sub_file) @@ -565,14 +564,14 @@ def extract_triangles(mesh): img = None for i, face in enumerate(mesh.faces): - f_v = face.verts + f_v = face.vertices # f_v = face.v - uf = mesh.active_uv_texture.data[i] if do_uv else None + uf = mesh.uv_textures.active.data[i] if do_uv else None if do_uv: f_uv = uf.uv - # f_uv = (uf.uv1, uf.uv2, uf.uv3, uf.uv4) if face.verts[3] else (uf.uv1, uf.uv2, uf.uv3) + # f_uv = (uf.uv1, uf.uv2, uf.uv3, uf.uv4) if face.vertices[3] else (uf.uv1, uf.uv2, uf.uv3) # f_uv = face.uv img = uf.image if uf else None # img = face.image @@ -762,18 +761,18 @@ def make_mesh_chunk(mesh, materialDict): if len(mesh.uv_textures): # if mesh.faceUV: # Remove the face UVs and convert it to vertex UV: - vert_array, uv_array, tri_list = remove_face_uv(mesh.verts, tri_list) + vert_array, uv_array, tri_list = remove_face_uv(mesh.vertices, tri_list) else: # Add the vertices to the vertex array: vert_array = _3ds_array() - for vert in mesh.verts: + for vert in mesh.vertices: vert_array.add(_3ds_point_3d(vert.co)) # If the mesh has vertex UVs, create an array of UVs: if len(mesh.sticky): # if mesh.vertexUV: uv_array = _3ds_array() for uv in mesh.sticky: -# for vert in mesh.verts: +# for vert in mesh.vertices: uv_array.add(_3ds_point_uv(uv.co)) # uv_array.add(_3ds_point_uv(vert.uvco)) else: @@ -923,7 +922,7 @@ def make_kf_obj_node(obj, name_to_id): """ # import BPyMessages -def save_3ds(filename, context): +def write(filename, context): '''Save the Blender scene to a 3ds file.''' # Time the export @@ -942,7 +941,8 @@ def save_3ds(filename, context): sce = context.scene # sce= bpy.data.scenes.active - bpy.ops.object.mode_set(mode='OBJECT') + if context.object: + bpy.ops.object.mode_set(mode='OBJECT') # Initialize the main chunk (primary): primary = _3ds_chunk(PRIMARY) @@ -968,11 +968,12 @@ def save_3ds(filename, context): # each material is added once): materialDict = {} mesh_objects = [] - for ob in [ob for ob in context.scene.objects if ob.is_visible()]: + scene = context.scene + for ob in [ob for ob in scene.objects if ob.is_visible(scene)]: # for ob in sce.objects.context: # get derived objects - free, derived = create_derived_objects(ob) + free, derived = create_derived_objects(scene, ob) if derived == None: continue @@ -982,7 +983,7 @@ def save_3ds(filename, context): if ob.type not in ('MESH', 'CURVE', 'SURFACE', 'TEXT', 'META'): continue - data = ob_derived.create_mesh(True, 'PREVIEW') + data = ob_derived.create_mesh(scene, True, 'PREVIEW') # data = getMeshFromObject(ob_derived, None, True, False, sce) if data: data.transform(mat) @@ -997,7 +998,7 @@ def save_3ds(filename, context): if not mat_ls: mat = mat_name = None - for f, uf in zip(data.faces, data.active_uv_texture.data): + for f, uf in zip(data.faces, data.uv_textures.active.data): if mat_ls: mat_index = f.material_index # mat_index = f.mat @@ -1006,7 +1007,7 @@ def save_3ds(filename, context): mat = mat_ls[mat_index] if mat: mat_name = mat.name else: mat_name = None - # else there alredy set to none + # else there already set to none img = uf.image # img = f.image @@ -1064,7 +1065,7 @@ def save_3ds(filename, context): ''' if not blender_mesh.users: bpy.data.meshes.remove(blender_mesh) -# blender_mesh.verts = None +# blender_mesh.vertices = None i+=i @@ -1106,53 +1107,47 @@ def save_3ds(filename, context): #primary.dump() -# if __name__=='__main__': -# if struct: -# Blender.Window.FileSelector(save_3ds, "Export 3DS", Blender.sys.makename(ext='.3ds')) -# else: -# Blender.Draw.PupMenu("Error%t|This script requires a full python installation") -# # save_3ds('/test_b.3ds') +# # write('/test_b.3ds') from bpy.props import * class Export3DS(bpy.types.Operator): '''Export to 3DS file format (.3ds)''' bl_idname = "export.autodesk_3ds" bl_label = 'Export 3DS' - # List of operator properties, the attributes will be assigned - # to the class instance from the operator settings before calling. - - - # filename = StringProperty(name="File Name", description="File name used for exporting the 3DS file", maxlen= 1024, default= ""), - path = StringProperty(name="File Path", description="File path used for exporting the 3DS file", maxlen= 1024, default= "") + filepath = StringProperty(name="File Path", description="Filepath used for exporting the 3DS file", maxlen= 1024, default= "") check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) + @classmethod + def poll(cls, context): # Poll isnt working yet + return context.active_object != None + def execute(self, context): - save_3ds(self.properties.path, context) + filepath = self.properties.filepath + filepath = bpy.path.ensure_ext(filepath, ".3ds") + + write(filepath, context) return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) - return {'RUNNING_MODAL'} + import os + if not self.properties.is_property_set("filepath"): + self.properties.filepath = os.path.splitext(bpy.data.filepath)[0] + ".3ds" - def poll(self, context): # Poll isnt working yet - return context.active_object != None + context.manager.add_fileselect(self) + return {'RUNNING_MODAL'} # Add to a menu def menu_func(self, context): - default_path = bpy.data.filename.replace(".blend", ".3ds") - self.layout.operator(Export3DS.bl_idname, text="3D Studio (.3ds)").path = default_path + self.layout.operator(Export3DS.bl_idname, text="3D Studio (.3ds)") def register(): - bpy.types.register(Export3DS) bpy.types.INFO_MT_file_export.append(menu_func) + def unregister(): - bpy.types.unregister(Export3DS) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == "__main__": register() - diff --git a/release/scripts/io/export_fbx.py b/release/scripts/io/export_fbx.py index 9dd3a68f4da..486f7d8199b 100644 --- a/release/scripts/io/export_fbx.py +++ b/release/scripts/io/export_fbx.py @@ -54,50 +54,11 @@ import time import math # math.pi import shutil # for file copying - - - - - - - - - - - - - - - - - - - - - -# import Blender import bpy -import Mathutils - - - - - - - - - - - - - - - - - - +from mathutils import Vector, Euler, Matrix def copy_file(source, dest): + # XXX - remove, can use shutil file = open(source, 'rb') data = file.read() file.close() @@ -114,7 +75,7 @@ def copy_images(dest_dir, textures): image_paths = set() for tex in textures: - image_paths.add(Blender.sys.expandpath(tex.filename)) + image_paths.add(bpy.path.abspath(tex.filepath)) # Now copy images copyCount = 0 @@ -122,7 +83,7 @@ def copy_images(dest_dir, textures): if Blender.sys.exists(image_path): # Make a name for the target path. dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1] - if not Blender.sys.exists(dest_image_path): # Image isnt alredy there + if not Blender.sys.exists(dest_image_path): # Image isnt already there print('\tCopying "%s" > "%s"' % (image_path, dest_image_path)) try: copy_file(image_path, dest_image_path) @@ -135,7 +96,7 @@ def copy_images(dest_dir, textures): # I guess FBX uses degrees instead of radians (Arystan). # Call this function just before writing to FBX. def eulerRadToDeg(eul): - ret = Mathutils.Euler() + ret = Euler() ret.x = 180 / math.pi * eul[0] ret.y = 180 / math.pi * eul[1] @@ -143,22 +104,22 @@ def eulerRadToDeg(eul): return ret -mtx4_identity = Mathutils.Matrix() +mtx4_identity = Matrix() # testing -mtx_x90 = Mathutils.RotationMatrix( math.pi/2, 3, 'X') # used -#mtx_x90n = RotationMatrix(-90, 3, 'x') -#mtx_y90 = RotationMatrix( 90, 3, 'y') -#mtx_y90n = RotationMatrix(-90, 3, 'y') -#mtx_z90 = RotationMatrix( 90, 3, 'z') -#mtx_z90n = RotationMatrix(-90, 3, 'z') - -#mtx4_x90 = RotationMatrix( 90, 4, 'x') -mtx4_x90n = Mathutils.RotationMatrix(-math.pi/2, 4, 'X') # used -#mtx4_y90 = RotationMatrix( 90, 4, 'y') -mtx4_y90n = Mathutils.RotationMatrix(-math.pi/2, 4, 'Y') # used -mtx4_z90 = Mathutils.RotationMatrix( math.pi/2, 4, 'Z') # used -mtx4_z90n = Mathutils.RotationMatrix(-math.pi/2, 4, 'Z') # used +mtx_x90 = Matrix.Rotation( math.pi/2, 3, 'X') # used +#mtx_x90n = Matrix.Rotation(-90, 3, 'x') +#mtx_y90 = Matrix.Rotation( 90, 3, 'y') +#mtx_y90n = Matrix.Rotation(-90, 3, 'y') +#mtx_z90 = Matrix.Rotation( 90, 3, 'z') +#mtx_z90n = Matrix.Rotation(-90, 3, 'z') + +#mtx4_x90 = Matrix.Rotation( 90, 4, 'x') +mtx4_x90n = Matrix.Rotation(-math.pi/2, 4, 'X') # used +#mtx4_y90 = Matrix.Rotation( 90, 4, 'y') +mtx4_y90n = Matrix.Rotation(-math.pi/2, 4, 'Y') # used +mtx4_z90 = Matrix.Rotation( math.pi/2, 4, 'Z') # used +mtx4_z90n = Matrix.Rotation(-math.pi/2, 4, 'Z') # used # def strip_path(p): # return p.split('\\')[-1].split('/')[-1] @@ -215,7 +176,7 @@ def sane_name(data, dct): name = 'unnamed' # blank string, ASKING FOR TROUBLE! else: - name = bpy.utils.clean_name(name) # use our own + name = bpy.path.clean_name(name) # use our own while name in iter(dct.values()): name = increment_string(name) @@ -239,14 +200,14 @@ def sane_groupname(data): return sane_name(data, sane_name_mapping_group) # FORCE_CWD - dont use the basepath, just add a ./ to the filename. # use when we know the file will be in the basepath. # ''' -# fname = bpy.utils.expandpath(fname_orig) +# fname = bpy.path.abspath(fname_orig) # # fname = Blender.sys.expandpath(fname_orig) # fname_strip = os.path.basename(fname) # # fname_strip = strip_path(fname) # if FORCE_CWD: # fname_rel = '.' + os.sep + fname_strip # else: -# fname_rel = bpy.utils.relpath(fname, basepath) +# fname_rel = bpy.path.relpath(fname, basepath) # # fname_rel = Blender.sys.relpath(fname, basepath) # if fname_rel.startswith('//'): fname_rel = '.' + os.sep + fname_rel[2:] # return fname, fname_strip, fname_rel @@ -260,7 +221,7 @@ def mat4x4str(mat): def getVertsFromGroup(me, group_index): ret = [] - for i, v in enumerate(me.verts): + for i, v in enumerate(me.vertices): for g in v.groups: if g.group == group_index: ret.append((i, g.weight)) @@ -282,11 +243,11 @@ def BPyMesh_meshWeight2List(ob): if not len_groupNames: # no verts? return a vert aligned empty list - return [[] for i in range(len(me.verts))], [] + return [[] for i in range(len(me.vertices))], [] else: - vWeightList= [[0.0]*len_groupNames for i in range(len(me.verts))] + vWeightList= [[0.0]*len_groupNames for i in range(len(me.vertices))] - for i, v in enumerate(me.verts): + for i, v in enumerate(me.vertices): for g in v.groups: vWeightList[i][g.group] = g.weight @@ -333,7 +294,7 @@ def write(filename, batch_objects = None, \ EXP_CAMERA = True, EXP_EMPTY = True, EXP_IMAGE_COPY = False, - GLOBAL_MATRIX = Mathutils.Matrix(), + GLOBAL_MATRIX = Matrix(), ANIM_ENABLE = True, ANIM_OPTIMIZE = True, ANIM_OPTIMIZE_PRECISSION = 6, @@ -344,7 +305,8 @@ def write(filename, batch_objects = None, \ BATCH_OWN_DIR = False ): - bpy.ops.object.mode_set(mode='OBJECT') + if bpy.context.object: + bpy.ops.object.mode_set(mode='OBJECT') # ----------------- Batch support! if BATCH_ENABLE: @@ -392,13 +354,13 @@ def write(filename, batch_objects = None, \ new_fbxpath = fbxpath # own dir option modifies, we need to keep an original for data in data_seq: # scene or group - newname = BATCH_FILE_PREFIX + bpy.utils.clean_name(data.name) -# newname = BATCH_FILE_PREFIX + BPySys.bpy.utils.clean_name(data.name) + newname = BATCH_FILE_PREFIX + bpy.path.clean_name(data.name) +# newname = BATCH_FILE_PREFIX + BPySys.bpy.path.clean_name(data.name) if BATCH_OWN_DIR: new_fbxpath = fbxpath + newname + os.sep - # path may alredy exist + # path may already exist # TODO - might exist but be a file. unlikely but should probably account for it. if bpy.utils.exists(new_fbxpath) == 0: @@ -430,7 +392,7 @@ def write(filename, batch_objects = None, \ # Call self with modified args - # Dont pass batch options since we alredy usedt them + # Dont pass batch options since we already usedt them write(filename, data.objects, context, False, @@ -567,7 +529,7 @@ def write(filename, batch_objects = None, \ self.fbxGroupNames = [] self.fbxParent = None # set later on IF the parent is in the selection. if matrixWorld: self.matrixWorld = GLOBAL_MATRIX * matrixWorld - else: self.matrixWorld = GLOBAL_MATRIX * ob.matrix + else: self.matrixWorld = GLOBAL_MATRIX * ob.matrix_world # else: self.matrixWorld = ob.matrixWorld * GLOBAL_MATRIX self.__anim_poselist = {} # we should only access this @@ -578,8 +540,7 @@ def write(filename, batch_objects = None, \ return self.matrixWorld def setPoseFrame(self, f): - self.__anim_poselist[f] = self.blenObject.matrix.copy() -# self.__anim_poselist[f] = self.blenObject.matrixWorld.copy() + self.__anim_poselist[f] = self.blenObject.matrix_world.copy() def getAnimParRelMatrix(self, frame): if self.fbxParent: @@ -600,8 +561,8 @@ def write(filename, batch_objects = None, \ matrix_rot = matrix_rot * mtx_x90 elif type =='CAMERA': # elif ob and type =='Camera': - y = matrix_rot * Mathutils.Vector(0,1,0) - matrix_rot = Mathutils.RotationMatrix(math.pi/2, 3, y) * matrix_rot + y = matrix_rot * Vector((0.0, 1.0, 0.0)) + matrix_rot = Matrix.Rotation(math.pi/2, 3, y) * matrix_rot return matrix_rot @@ -685,7 +646,7 @@ def write(filename, batch_objects = None, \ else: # This is bad because we need the parent relative matrix from the fbx parent (if we have one), dont use anymore - #if ob and not matrix: matrix = ob.matrixWorld * GLOBAL_MATRIX + #if ob and not matrix: matrix = ob.matrix_world * GLOBAL_MATRIX if ob and not matrix: raise Exception("error: this should never happen!") matrix_rot = matrix @@ -702,8 +663,8 @@ def write(filename, batch_objects = None, \ matrix_rot = matrix_rot * mtx_x90 rot = tuple(matrix_rot.to_euler()) elif ob and ob.type =='Camera': - y = matrix_rot * Mathutils.Vector(0,1,0) - matrix_rot = Mathutils.RotationMatrix(math.pi/2, 3, y) * matrix_rot + y = matrix_rot * Vector((0.0, 1.0, 0.0)) + matrix_rot = Matrix.Rotation(math.pi/2, 3, y) * matrix_rot rot = tuple(matrix_rot.to_euler()) else: rot = tuple(matrix_rot.to_euler()) @@ -987,10 +948,7 @@ def write(filename, batch_objects = None, \ render = scene.render width = render.resolution_x height = render.resolution_y -# render = scene.render -# width = render.sizeX -# height = render.sizeY - aspect = float(width)/height + aspect = width / height data = my_cam.blenObject.data @@ -1002,11 +960,9 @@ def write(filename, batch_objects = None, \ file.write('\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",%.6f' % math.degrees(data.angle)) file.write('\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1') file.write('\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1') - file.write('\n\t\t\tProperty: "FocalLength", "Real", "A+",14.0323972702026') + # file.write('\n\t\t\tProperty: "FocalLength", "Real", "A+",14.0323972702026') file.write('\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",%.6f' % data.shift_x) # not sure if this is in the correct units? -# file.write('\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",%.6f' % data.shiftX) # not sure if this is in the correct units? file.write('\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",%.6f' % data.shift_y) # ditto -# file.write('\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",%.6f' % data.shiftY) # ditto file.write('\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0,0,0') file.write('\n\t\t\tProperty: "TurnTable", "Real", "A+",0') file.write('\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1') @@ -1015,7 +971,7 @@ def write(filename, batch_objects = None, \ file.write('\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1') file.write('\n\t\t\tProperty: "ResolutionMode", "enum", "",0') file.write('\n\t\t\tProperty: "ApertureMode", "enum", "",2') - file.write('\n\t\t\tProperty: "GateFit", "enum", "",0') + file.write('\n\t\t\tProperty: "GateFit", "enum", "",2') file.write('\n\t\t\tProperty: "CameraFormat", "enum", "",0') file.write('\n\t\t\tProperty: "AspectW", "double", "",%i' % width) file.write('\n\t\t\tProperty: "AspectH", "double", "",%i' % height) @@ -1088,8 +1044,8 @@ def write(filename, batch_objects = None, \ file.write('\n\t\tTypeFlags: "Camera"') file.write('\n\t\tGeometryVersion: 124') file.write('\n\t\tPosition: %.6f,%.6f,%.6f' % loc) - file.write('\n\t\tUp: %.6f,%.6f,%.6f' % tuple(matrix_rot * Mathutils.Vector(0,1,0)) ) - file.write('\n\t\tLookAt: %.6f,%.6f,%.6f' % tuple(matrix_rot * Mathutils.Vector(0,0,-1)) ) + file.write('\n\t\tUp: %.6f,%.6f,%.6f' % tuple(matrix_rot * Vector((0.0, 1.0, 0.0)))) + file.write('\n\t\tLookAt: %.6f,%.6f,%.6f' % tuple(matrix_rot * Vector((0.0, 0.0, -1.0)))) #file.write('\n\t\tUp: 0,0,0' ) #file.write('\n\t\tLookAt: 0,0,0' ) @@ -1126,7 +1082,7 @@ def write(filename, batch_objects = None, \ else: do_shadow = 0 - if light.only_shadow or (not light.diffuse and not light.specular): + if light.use_only_shadow or (not light.diffuse and not light.specular): # if mode & Blender.Lamp.Modes.OnlyShadow or (mode & Blender.Lamp.Modes.NoDiffuse and mode & Blender.Lamp.Modes.NoSpecular): do_light = 0 else: @@ -1234,7 +1190,7 @@ def write(filename, batch_objects = None, \ # mat_spec = mat.spec/2.0 mat_alpha = mat.alpha mat_emit = mat.emit - mat_shadeless = mat.shadeless + mat_shadeless = mat.use_shadeless # mat_shadeless = mat.mode & Blender.Material.Modes.SHADELESS if mat_shadeless: mat_shader = 'Lambert' @@ -1294,7 +1250,7 @@ def write(filename, batch_objects = None, \ file.write('\n\t}') def copy_image(image): - fn = bpy.utils.expandpath(image.filename) + fn = bpy.path.abspath(image.filepath) fn_strip = os.path.basename(fn) if EXP_IMAGE_COPY: @@ -1321,7 +1277,7 @@ def write(filename, batch_objects = None, \ Property: "Height", "int", "",0''') if tex: fname_rel, fname_strip = copy_image(tex) -# fname, fname_strip, fname_rel = derived_paths(tex.filename, basepath, EXP_IMAGE_COPY) +# fname, fname_strip, fname_rel = derived_paths(tex.filepath, basepath, EXP_IMAGE_COPY) else: fname = fname_strip = fname_rel = '' @@ -1371,9 +1327,9 @@ def write(filename, batch_objects = None, \ Property: "CurrentMappingType", "enum", "",0 Property: "UVSwap", "bool", "",0''') - file.write('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.clamp_x) + file.write('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.use_clamp_x) # file.write('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.clampX) - file.write('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.clamp_y) + file.write('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.use_clamp_y) # file.write('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.clampY) file.write(''' @@ -1386,7 +1342,7 @@ def write(filename, batch_objects = None, \ if tex: fname_rel, fname_strip = copy_image(tex) -# fname, fname_strip, fname_rel = derived_paths(tex.filename, basepath, EXP_IMAGE_COPY) +# fname, fname_strip, fname_rel = derived_paths(tex.filepath, basepath, EXP_IMAGE_COPY) else: fname = fname_strip = fname_rel = '' @@ -1442,7 +1398,7 @@ def write(filename, batch_objects = None, \ # TODO - this is a bit lazy, we could have a simple write loop # for this case because all weights are 1.0 but for now this is ok # Parent Bones arent used all that much anyway. - vgroup_data = [(j, 1.0) for j in range(len(my_mesh.blenData.verts))] + vgroup_data = [(j, 1.0) for j in range(len(my_mesh.blenData.vertices))] else: # This bone is not a parent of this mesh object, no weights vgroup_data = [] @@ -1531,7 +1487,7 @@ def write(filename, batch_objects = None, \ file.write('\n\t\tVertices: ') i=-1 - for v in me.verts: + for v in me.vertices: if i==-1: file.write('%.6f,%.6f,%.6f' % tuple(v.co)); i=0 else: @@ -1543,17 +1499,14 @@ def write(filename, batch_objects = None, \ file.write('\n\t\tPolygonVertexIndex: ') i=-1 for f in me.faces: - fi = [v for v in f.verts] - # fi = [v_index for j, v_index in enumerate(f.verts) if v_index != 0 or j != 3] -# fi = [v.index for v in f] + fi = f.vertices[:] - # flip the last index, odd but it looks like - # this is how fbx tells one face from another - fi[-1] = -(fi[-1]+1) + # last index XORd w. -1 indicates end of face + fi[-1] = fi[-1] ^ -1 fi = tuple(fi) + if i==-1: if len(fi) == 3: file.write('%i,%i,%i' % fi ) -# if len(f) == 3: file.write('%i,%i,%i' % fi ) else: file.write('%i,%i,%i,%i' % fi ) i=0 else: @@ -1561,22 +1514,38 @@ def write(filename, batch_objects = None, \ file.write('\n\t\t') i=0 if len(fi) == 3: file.write(',%i,%i,%i' % fi ) -# if len(f) == 3: file.write(',%i,%i,%i' % fi ) else: file.write(',%i,%i,%i,%i' % fi ) i+=1 + # write loose edges as faces. + for ed in me.edges: + if ed.is_loose: + ed_val = ed.vertices[:] + ed_val = ed_val[0], ed_val[-1] ^ -1 + + if i==-1: + file.write('%i,%i' % ed_val) + i=0 + else: + if i==13: + file.write('\n\t\t') + i=0 + file.write(',%i,%i' % ed_val) + i+=1 + + file.write('\n\t\tEdges: ') i=-1 for ed in me.edges: if i==-1: - file.write('%i,%i' % (ed.verts[0], ed.verts[1])) + file.write('%i,%i' % (ed.vertices[0], ed.vertices[1])) # file.write('%i,%i' % (ed.v1.index, ed.v2.index)) i=0 else: if i==13: file.write('\n\t\t') i=0 - file.write(',%i,%i' % (ed.verts[0], ed.verts[1])) + file.write(',%i,%i' % (ed.vertices[0], ed.vertices[1])) # file.write(',%i,%i' % (ed.v1.index, ed.v2.index)) i+=1 @@ -1591,7 +1560,7 @@ def write(filename, batch_objects = None, \ Normals: ''') i=-1 - for v in me.verts: + for v in me.vertices: if i==-1: file.write('%.15f,%.15f,%.15f' % tuple(v.normal)); i=0 # file.write('%.15f,%.15f,%.15f' % tuple(v.no)); i=0 @@ -1615,11 +1584,11 @@ def write(filename, batch_objects = None, \ i=-1 for f in me.faces: if i==-1: - file.write('%i' % f.smooth); i=0 + file.write('%i' % f.use_smooth); i=0 else: if i==54: file.write('\n '); i=0 - file.write(',%i' % f.smooth) + file.write(',%i' % f.use_smooth) i+=1 file.write('\n\t\t}') @@ -1633,27 +1602,23 @@ def write(filename, batch_objects = None, \ ReferenceInformationType: "Direct" Smoothing: ''') -# SHARP = Blender.Mesh.EdgeFlags.SHARP i=-1 for ed in me.edges: if i==-1: - file.write('%i' % (ed.sharp)); i=0 -# file.write('%i' % ((ed.flag&SHARP)!=0)); i=0 + file.write('%i' % (ed.use_edge_sharp)); i=0 else: if i==54: file.write('\n '); i=0 - file.write(',%i' % (ed.sharp)) -# file.write(',%i' % ((ed.flag&SHARP)!=0)) + file.write(',%i' % (ed.use_edge_sharp)) i+=1 file.write('\n\t\t}') -# del SHARP # small utility function # returns a slice of data depending on number of face verts # data is either a MeshTextureFace or MeshColor def face_data(data, face): - totvert = len(f.verts) + totvert = len(f.vertices) return data[:totvert] @@ -1665,7 +1630,7 @@ def write(filename, batch_objects = None, \ # if me.vertexColors: collayers = me.vertex_colors # collayers = me.getColorLayerNames() - collayer_orig = me.active_vertex_color + collayer_orig = me.vertex_colors.active # collayer_orig = me.activeColorLayer for colindex, collayer in enumerate(collayers): # me.activeColorLayer = collayer @@ -1735,7 +1700,7 @@ def write(filename, batch_objects = None, \ if do_uvs: uvlayers = me.uv_textures # uvlayers = me.getUVLayerNames() - uvlayer_orig = me.active_uv_texture + uvlayer_orig = me.uv_textures.active # uvlayer_orig = me.activeUVLayer for uvindex, uvlayer in enumerate(me.uv_textures): # for uvindex, uvlayer in enumerate(uvlayers): @@ -1869,8 +1834,8 @@ def write(filename, batch_objects = None, \ mats = my_mesh.blenMaterialList - if me.active_uv_texture: - uv_faces = me.active_uv_texture.data + if me.uv_textures.active: + uv_faces = me.uv_textures.active.data else: uv_faces = [None] * len(me.faces) @@ -2037,9 +2002,8 @@ def write(filename, batch_objects = None, \ if ob_arms_orig_rest: for ob_base in bpy.data.objects: - #if ob_base.type == 'Armature': - ob_base.make_display_list() -# ob_base.makeDisplayList() + if ob_base.type == 'ARMATURE': + ob_base.update(scene) # This causes the makeDisplayList command to effect the mesh scene.set_frame(scene.frame_current) @@ -2052,9 +2016,9 @@ def write(filename, batch_objects = None, \ if ob_base.parent and ob_base.parent.dupli_type != 'NONE': continue - obs = [(ob_base, ob_base.matrix)] + obs = [(ob_base, ob_base.matrix_world)] if ob_base.dupli_type != 'NONE': - ob_base.create_dupli_list() + ob_base.create_dupli_list(scene) obs = [(dob.object, dob.matrix) for dob in ob_base.dupli_list] for ob, mtx in obs: @@ -2083,7 +2047,7 @@ def write(filename, batch_objects = None, \ if tmp_ob_type != 'MESH': # if tmp_ob_type != 'Mesh': # me = bpy.data.meshes.new() - try: me = ob.create_mesh(True, 'PREVIEW') + try: me = ob.create_mesh(scene, True, 'PREVIEW') # try: me.getFromObject(ob) except: me = None if me: @@ -2094,7 +2058,7 @@ def write(filename, batch_objects = None, \ # Mesh Type! if EXP_MESH_APPLY_MOD: # me = bpy.data.meshes.new() - me = ob.create_mesh(True, 'PREVIEW') + me = ob.create_mesh(scene, True, 'PREVIEW') # me.getFromObject(ob) # so we keep the vert groups @@ -2104,7 +2068,7 @@ def write(filename, batch_objects = None, \ # ob.copy().link(me) # # If new mesh has no vgroups we can try add if verts are teh same # if not me.getVertGroupNames(): # vgroups were not kept by the modifier -# if len(me.verts) == len(orig_mesh.verts): +# if len(me.vertices) == len(orig_mesh.vertices): # groupNames, vWeightDict = BPyMesh.meshWeight2Dict(orig_mesh) # BPyMesh.dict2MeshWeight(me, groupNames, vWeightDict) @@ -2139,7 +2103,7 @@ def write(filename, batch_objects = None, \ material_mapping_local = {} if len(me.uv_textures) > 0: # if me.faceUV: - uvlayer_orig = me.active_uv_texture + uvlayer_orig = me.uv_textures.active # uvlayer_orig = me.activeUVLayer for uvlayer in me.uv_textures: # for uvlayer in me.getUVLayerNames(): @@ -2213,9 +2177,7 @@ def write(filename, batch_objects = None, \ if ob_arms_orig_rest: for ob_base in bpy.data.objects: if ob_base.type == 'ARMATURE': -# if ob_base.type == 'Armature': - ob_base.make_display_list() -# ob_base.makeDisplayList() + ob_base.update(scene) # This causes the makeDisplayList command to effect the mesh scene.set_frame(scene.frame_current) # Blender.Set('curframe', Blender.Get('curframe')) @@ -2793,7 +2755,7 @@ Takes: {''') act_end = end else: # use existing name - if blenAction == blenActionDefault: # have we alredy got the name + if blenAction == blenActionDefault: # have we already got the name file.write('\n\tTake: "%s" {' % sane_name_mapping_take[blenAction.name]) else: file.write('\n\tTake: "%s" {' % sane_takename(blenAction)) @@ -2812,7 +2774,7 @@ Takes: {''') # Set the action active for my_bone in ob_arms: - if blenAction in my_bone.blenActionList: + if ob.animation_data and blenAction in my_bone.blenActionList: ob.animation_data.action = blenAction # print '\t\tSetting Action!', blenAction # scene.update(1) @@ -2948,7 +2910,7 @@ Takes: {''') for val, frame in context_bone_anim_keys: if frame != context_bone_anim_keys[0][1]: # not the first file.write(',') - # frame is alredy one less then blenders frame + # frame is already one less then blenders frame file.write('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame), val )) if i==0: file.write('\n\t\t\t\t\t\tColor: 1,0,0') @@ -2970,7 +2932,8 @@ Takes: {''') # end action loop. set original actions # do this after every loop incase actions effect eachother. for my_bone in ob_arms: - my_bone.blenObject.animation_data.action = my_bone.blenAction + if my_bone.blenObject.animation_data: + my_bone.blenObject.animation_data.action = my_bone.blenAction file.write('\n}') @@ -2994,13 +2957,12 @@ Takes: {''') # Clear mesh data Only when writing with modifiers applied for me in meshes_to_clear: bpy.data.meshes.remove(me) -# me.verts = None +# me.vertices = None # --------------------------- Footer if world: m = world.mist - has_mist = m.enabled -# has_mist = world.mode & 1 + has_mist = m.use_mist mist_intense = m.intensity mist_start = m.start mist_end = m.depth @@ -3068,7 +3030,7 @@ Takes: {''') # -------------------------------------------- # UI Function - not a part of the exporter. -# this is to seperate the user interface from the rest of the exporter. +# this is to separate the user interface from the rest of the exporter. # from Blender import Draw, Window EVENT_NONE = 0 EVENT_EXIT = 1 @@ -3365,7 +3327,7 @@ class ExportFBX(bpy.types.Operator): # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for exporting the FBX file", maxlen= 1024, default="") + filepath = StringProperty(name="File Path", description="Filepath used for exporting the FBX file", maxlen= 1024, default="") check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) EXP_OBS_SELECTED = BoolProperty(name="Selected Objects", description="Export selected objects on visible layers", default=True) @@ -3395,12 +3357,16 @@ class ExportFBX(bpy.types.Operator): BATCH_FILE_PREFIX = StringProperty(name="Prefix", description="Prefix each file with this name", maxlen=1024, default="") - def poll(self, context): + @classmethod + def poll(cls, context): return context.active_object def execute(self, context): - if not self.properties.path: - raise Exception("path not set") + if not self.properties.filepath: + raise Exception("filepath not set") + + filepath = self.properties.filepath + filepath = bpy.path.ensure_ext(filepath, ".fbx") GLOBAL_MATRIX = mtx4_identity GLOBAL_MATRIX[0][0] = GLOBAL_MATRIX[1][1] = GLOBAL_MATRIX[2][2] = self.properties.TX_SCALE @@ -3408,7 +3374,7 @@ class ExportFBX(bpy.types.Operator): if self.properties.TX_YROT90: GLOBAL_MATRIX = mtx4_y90n * GLOBAL_MATRIX if self.properties.TX_ZROT90: GLOBAL_MATRIX = mtx4_z90n * GLOBAL_MATRIX - write(self.properties.path, + write(filepath, None, # XXX context, self.properties.EXP_OBS_SELECTED, @@ -3428,25 +3394,29 @@ class ExportFBX(bpy.types.Operator): self.properties.BATCH_ENABLE, self.properties.BATCH_GROUP, self.properties.BATCH_FILE_PREFIX, - self.properties.BATCH_OWN_DIR) + self.properties.BATCH_OWN_DIR, + ) return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) + import os + if not self.properties.is_property_set("filepath"): + self.properties.filepath = os.path.splitext(bpy.data.filepath)[0] + ".fbx" + + context.manager.add_fileselect(self) return {'RUNNING_MODAL'} # if __name__ == "__main__": -# bpy.ops.EXPORT_OT_ply(filename="/tmp/test.ply") +# bpy.ops.EXPORT_OT_ply(filepath="/tmp/test.ply") # NOTES (all line numbers correspond to original export_fbx.py (under release/scripts) # - Draw.PupMenu alternative in 2.5?, temporarily replaced PupMenu with print -# - get rid of bpy.utils.clean_name somehow +# - get rid of bpy.path.clean_name somehow # + fixed: isinstance(inst, bpy.types.*) doesn't work on RNA objects: line 565 # + get rid of BPyObject_getObjectArmature, move it in RNA? # - BATCH_ENABLE and BATCH_GROUP options: line 327 @@ -3461,24 +3431,21 @@ class ExportFBX(bpy.types.Operator): # - bpy.data.remove_scene: line 366 # - bpy.sys.time move to bpy.sys.util? # - new scene creation, activation: lines 327-342, 368 -# - uses bpy.utils.expandpath, *.relpath - replace at least relpath +# - uses bpy.path.abspath, *.relpath - replace at least relpath # SMALL or COSMETICAL # - find a way to get blender version, and put it in bpy.util?, old was Blender.Get('version') def menu_func(self, context): - default_path = bpy.data.filename.replace(".blend", ".fbx") - self.layout.operator(ExportFBX.bl_idname, text="Autodesk FBX (.fbx)").path = default_path + self.layout.operator(ExportFBX.bl_idname, text="Autodesk FBX (.fbx)") def register(): - bpy.types.register(ExportFBX) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): - bpy.types.unregister(ExportFBX) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == "__main__": diff --git a/release/scripts/io/export_mdd.py b/release/scripts/io/export_mdd.py index be3ddbc73bd..b2eda13fc8f 100644 --- a/release/scripts/io/export_mdd.py +++ b/release/scripts/io/export_mdd.py @@ -48,7 +48,7 @@ Be sure not to use modifiers that change the number or order of verts in the mes # ***** END GPL LICENCE BLOCK ***** import bpy -import Mathutils +import mathutils from struct import pack @@ -65,7 +65,7 @@ def check_vertcount(mesh, vertcount): ''' check and make sure the vertcount is consistent throughout the frame range ''' - if len(mesh.verts) != vertcount: + if len(mesh.vertices) != vertcount: raise Exception('Error, number of verts has changed during animation, cannot export') f.close() zero_file(filepath) @@ -84,17 +84,17 @@ def write(filename, sce, ob, PREF_STARTFRAME, PREF_ENDFRAME, PREF_FPS): orig_frame = sce.frame_current sce.set_frame(PREF_STARTFRAME) - me = ob.create_mesh(True, 'PREVIEW') + me = ob.create_mesh(sce, True, 'PREVIEW') #Flip y and z - mat_flip = Mathutils.Matrix(\ + mat_flip = mathutils.Matrix(\ [1.0, 0.0, 0.0, 0.0],\ [0.0, 0.0, 1.0, 0.0],\ [0.0, 1.0, 0.0, 0.0],\ [0.0, 0.0, 0.0, 1.0],\ ) - numverts = len(me.verts) + numverts = len(me.vertices) numframes = PREF_ENDFRAME - PREF_STARTFRAME + 1 PREF_FPS = float(PREF_FPS) @@ -113,8 +113,8 @@ def write(filename, sce, ob, PREF_STARTFRAME, PREF_ENDFRAME, PREF_FPS): """ check_vertcount(me, numverts) - me.transform(mat_flip * ob.matrix) - f.write(pack(">%df" % (numverts * 3), *[axis for v in me.verts for axis in v.co])) + me.transform(mat_flip * ob.matrix_world) + f.write(pack(">%df" % (numverts * 3), *[axis for v in me.vertices for axis in v.co])) for frame in range(PREF_STARTFRAME, PREF_ENDFRAME + 1):#in order to start at desired frame """ @@ -123,15 +123,15 @@ def write(filename, sce, ob, PREF_STARTFRAME, PREF_ENDFRAME, PREF_FPS): """ sce.set_frame(frame) - me = ob.create_mesh(True, 'PREVIEW') + me = ob.create_mesh(sce, True, 'PREVIEW') check_vertcount(me, numverts) - me.transform(mat_flip * ob.matrix) + me.transform(mat_flip * ob.matrix_world) # Write the vertex data - f.write(pack(">%df" % (numverts * 3), *[axis for v in me.verts for axis in v.co])) + f.write(pack(">%df" % (numverts * 3), *[axis for v in me.vertices for axis in v.co])) """ - me_tmp.verts= None + me_tmp.vertices= None """ f.close() @@ -159,41 +159,49 @@ class ExportMDD(bpy.types.Operator): # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for exporting the MDD file", maxlen=1024) + filepath = StringProperty(name="File Path", description="Filepath used for exporting the MDD file", maxlen=1024) check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) fps = IntProperty(name="Frames Per Second", description="Number of frames/second", min=minfps, max=maxfps, default=25) frame_start = IntProperty(name="Start Frame", description="Start frame for baking", min=minframe, max=maxframe, default=1) frame_end = IntProperty(name="End Frame", description="End frame for baking", min=minframe, max=maxframe, default=250) - def poll(self, context): + @classmethod + def poll(cls, context): ob = context.active_object return (ob and ob.type == 'MESH') def execute(self, context): - if not self.properties.path: - raise Exception("filename not set") - write(self.properties.path, context.scene, context.active_object, - self.properties.frame_start, self.properties.frame_end, self.properties.fps) + filepath = self.properties.filepath + filepath = bpy.path.ensure_ext(filepath, ".mdd") + + write(filepath, + context.scene, + context.active_object, + self.properties.frame_start, + self.properties.frame_end, + self.properties.fps, + ) + return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) + import os + if not self.properties.is_property_set("filepath"): + self.properties.filepath = os.path.splitext(bpy.data.filepath)[0] + ".mdd" + + context.manager.add_fileselect(self) return {'RUNNING_MODAL'} def menu_func(self, context): - default_path = bpy.data.filename.replace(".blend", ".mdd") - self.layout.operator(ExportMDD.bl_idname, text="Lightwave Point Cache (.mdd)").path = default_path + self.layout.operator(ExportMDD.bl_idname, text="Lightwave Point Cache (.mdd)") def register(): - bpy.types.register(ExportMDD) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): - bpy.types.unregister(ExportMDD) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == "__main__": diff --git a/release/scripts/io/export_obj.py b/release/scripts/io/export_obj.py index c10a698c9d3..37c6059d173 100644 --- a/release/scripts/io/export_obj.py +++ b/release/scripts/io/export_obj.py @@ -18,13 +18,6 @@ # <pep8 compliant> -""" -Name: 'Wavefront (.obj)...' -Blender: 248 -Group: 'Export' -Tooltip: 'Save a Wavefront OBJ File' -""" - __author__ = "Campbell Barton, Jiri Hnidek, Paolo Ciccone" __url__ = ['http://wiki.blender.org/index.php/Scripts/Manual/Export/wavefront_obj', 'www.blender.org', 'blenderartists.org'] __version__ = "1.21" @@ -47,17 +40,7 @@ import time import shutil import bpy -import Mathutils - - -# Returns a tuple - path,extension. -# 'hello.obj' > ('hello', '.obj') -def splitExt(path): - dotidx = path.rfind('.') - if dotidx == -1: - return path, '' - else: - return path[:dotidx], path[dotidx:] +import mathutils def fixName(name): if name == None: @@ -65,15 +48,15 @@ def fixName(name): else: return name.replace(' ', '_') -def write_mtl(scene, filename, copy_images, mtl_dict): +def write_mtl(scene, filepath, copy_images, mtl_dict): world = scene.world worldAmb = world.ambient_color - dest_dir = os.path.dirname(filename) + dest_dir = os.path.dirname(filepath) def copy_image(image): - fn = bpy.utils.expandpath(image.filename) + fn = bpy.path.abspath(image.filepath) fn_strip = os.path.basename(fn) if copy_images: rel = fn_strip @@ -86,9 +69,9 @@ def write_mtl(scene, filename, copy_images, mtl_dict): return rel - file = open(filename, "w") + file = open(filepath, "w") # XXX -# file.write('# Blender MTL File: %s\n' % Blender.Get('filename').split('\\')[-1].split('/')[-1]) +# file.write('# Blender MTL File: %s\n' % Blender.Get('filepath').split('\\')[-1].split('/')[-1]) file.write('# Material Count: %i\n' % len(mtl_dict)) # Write material/image combinations we have used. for key, (mtl_mat_name, mat, img) in mtl_dict.items(): @@ -100,7 +83,7 @@ def write_mtl(scene, filename, copy_images, mtl_dict): if mat: file.write('Ns %.6f\n' % ((mat.specular_hardness-1) * 1.9607843137254901) ) # Hardness, convert blenders 1-511 to MTL's - file.write('Ka %.6f %.6f %.6f\n' % tuple([c*mat.ambient for c in worldAmb]) ) # Ambient, uses mirror colour, + file.write('Ka %.6f %.6f %.6f\n' % tuple([c*mat.ambient for c in worldAmb]) ) # Ambient, uses mirror colour, file.write('Kd %.6f %.6f %.6f\n' % tuple([c*mat.diffuse_intensity for c in mat.diffuse_color]) ) # Diffuse file.write('Ks %.6f %.6f %.6f\n' % tuple([c*mat.specular_intensity for c in mat.specular_color]) ) # Specular if hasattr(mat, "ior"): @@ -110,7 +93,7 @@ def write_mtl(scene, filename, copy_images, mtl_dict): file.write('d %.6f\n' % mat.alpha) # Alpha (obj uses 'd' for dissolve) # 0 to disable lighting, 1 for ambient & diffuse only (specular color set to black), 2 for full lighting. - if mat.shadeless: + if mat.use_shadeless: file.write('illum 0\n') # ignore lighting elif mat.specular_intensity == 0: file.write('illum 1\n') # no specular. @@ -120,26 +103,26 @@ def write_mtl(scene, filename, copy_images, mtl_dict): else: #write a dummy material here? file.write('Ns 0\n') - file.write('Ka %.6f %.6f %.6f\n' % tuple([c for c in worldAmb]) ) # Ambient, uses mirror colour, + file.write('Ka %.6f %.6f %.6f\n' % tuple([c for c in worldAmb]) ) # Ambient, uses mirror colour, file.write('Kd 0.8 0.8 0.8\n') file.write('Ks 0.8 0.8 0.8\n') file.write('d 1\n') # No alpha file.write('illum 2\n') # light normaly # Write images! - if img: # We have an image on the face! + if img: # We have an image on the face! # write relative image path rel = copy_image(img) file.write('map_Kd %s\n' % rel) # Diffuse mapping image -# file.write('map_Kd %s\n' % img.filename.split('\\')[-1].split('/')[-1]) # Diffuse mapping image +# file.write('map_Kd %s\n' % img.filepath.split('\\')[-1].split('/')[-1]) # Diffuse mapping image elif mat: # No face image. if we havea material search for MTex image. for mtex in mat.texture_slots: if mtex and mtex.texture.type == 'IMAGE': try: - filename = copy_image(mtex.texture.image) -# filename = mtex.texture.image.filename.split('\\')[-1].split('/')[-1] - file.write('map_Kd %s\n' % filename) # Diffuse mapping image + filepath = copy_image(mtex.texture.image) +# filepath = mtex.texture.image.filepath.split('\\')[-1].split('/')[-1] + file.write('map_Kd %s\n' % filepath) # Diffuse mapping image break except: # Texture has no image though its an image type, best ignore. @@ -164,8 +147,8 @@ def copy_file(source, dest): def copy_images(dest_dir): if dest_dir[-1] != os.sep: dest_dir += os.sep -# if dest_dir[-1] != sys.sep: -# dest_dir += sys.sep +# if dest_dir[-1] != sys.sep: +# dest_dir += sys.sep # Get unique image names uniqueImages = {} @@ -188,63 +171,61 @@ def copy_images(dest_dir): # Now copy images copyCount = 0 -# for bImage in uniqueImages.values(): -# image_path = bpy.utils.expandpath(bImage.filename) -# if bpy.sys.exists(image_path): -# # Make a name for the target path. -# dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1] -# if not bpy.utils.exists(dest_image_path): # Image isnt alredy there -# print('\tCopying "%s" > "%s"' % (image_path, dest_image_path)) -# copy_file(image_path, dest_image_path) -# copyCount+=1 +# for bImage in uniqueImages.values(): +# image_path = bpy.path.abspath(bImage.filepath) +# if bpy.sys.exists(image_path): +# # Make a name for the target path. +# dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1] +# if not bpy.utils.exists(dest_image_path): # Image isnt already there +# print('\tCopying "%s" > "%s"' % (image_path, dest_image_path)) +# copy_file(image_path, dest_image_path) +# copyCount+=1 -# paths= bpy.util.copy_images(uniqueImages.values(), dest_dir) +# paths= bpy.util.copy_images(uniqueImages.values(), dest_dir) print('\tCopied %d images' % copyCount) -# print('\tCopied %d images' % copyCount) -# XXX not converted + def test_nurbs_compat(ob): - if ob.type != 'Curve': + if ob.type != 'CURVE': return False - for nu in ob.data: - if (not nu.knotsV) and nu.type != 1: # not a surface and not bezier + for nu in ob.data.splines: + if nu.point_count_v == 1 and nu.type != 'BEZIER': # not a surface and not bezier return True return False -# XXX not converted def write_nurb(file, ob, ob_mat): tot_verts = 0 cu = ob.data # use negative indices - Vector = Blender.Mathutils.Vector - for nu in cu: - - if nu.type==0: DEG_ORDER_U = 1 - else: DEG_ORDER_U = nu.orderU-1 # Tested to be correct + for nu in cu.splines: + if nu.type == 'POLY': + DEG_ORDER_U = 1 + else: + DEG_ORDER_U = nu.order_u - 1 # odd but tested to be correct - if nu.type==1: + if nu.type == 'BEZIER': print("\tWarning, bezier curve:", ob.name, "only poly and nurbs curves supported") continue - if nu.knotsV: + if nu.point_count_v > 1: print("\tWarning, surface:", ob.name, "only poly and nurbs curves supported") continue - if len(nu) <= DEG_ORDER_U: - print("\tWarning, orderU is lower then vert count, skipping:", ob.name) + if len(nu.points) <= DEG_ORDER_U: + print("\tWarning, order_u is lower then vert count, skipping:", ob.name) continue pt_num = 0 - do_closed = (nu.flagU & 1) - do_endpoints = (do_closed==0) and (nu.flagU & 2) + do_closed = nu.use_cyclic_u + do_endpoints = (do_closed == 0) and nu.use_endpoint_u - for pt in nu: - pt = Vector(pt[0], pt[1], pt[2]) * ob_mat + for pt in nu.points: + pt = ob_mat * pt.co.copy().resize3D() file.write('v %.6f %.6f %.6f\n' % (pt[0], pt[1], pt[2])) pt_num += 1 tot_verts += pt_num @@ -264,7 +245,7 @@ def write_nurb(file, ob, ob_mat): pt_num += DEG_ORDER_U curve_ls = curve_ls + curve_ls[0:DEG_ORDER_U] - file.write('curv 0.0 1.0 %s\n' % (' '.join( [str(i) for i in curve_ls] ))) # Blender has no U and V values for the curve + file.write('curv 0.0 1.0 %s\n' % (' '.join([str(i) for i in curve_ls]))) # Blender has no U and V values for the curve # 'parm' keyword tot_parm = (DEG_ORDER_U + 1) + pt_num @@ -282,7 +263,7 @@ def write_nurb(file, ob, ob_mat): return tot_verts -def write(filename, objects, scene, +def write_file(filepath, objects, scene, EXPORT_TRI=False, EXPORT_EDGES=False, EXPORT_NORMALS=False, @@ -299,7 +280,7 @@ def write(filename, objects, scene, EXPORT_POLYGROUPS=False, EXPORT_CURVE_AS_NURBS=True): ''' - Basic write function. The context and options must be alredy set + Basic write function. The context and options must be already set This can be accessed externaly eg. write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options. @@ -325,10 +306,10 @@ def write(filename, objects, scene, of vertices is the face's group """ weightDict = {} - for vert_index in face.verts: -# for vert in face: + for vert_index in face.vertices: +# for vert in face: vWeights = vWeightMap[vert_index] -# vWeights = vWeightMap[vert] +# vWeights = vWeightMap[vert] for vGroupName, weight in vWeights: weightDict[vGroupName] = weightDict.get(vGroupName, 0) + weight @@ -343,7 +324,7 @@ def write(filename, objects, scene, def getVertsFromGroup(me, group_index): ret = [] - for i, v in enumerate(me.verts): + for i, v in enumerate(me.vertices): for g in v.groups: if g.group == group_index: ret.append((i, g.weight)) @@ -351,26 +332,26 @@ def write(filename, objects, scene, return ret - print('OBJ Export path: "%s"' % filename) + print('OBJ Export path: "%s"' % filepath) temp_mesh_name = '~tmp-mesh' time1 = time.clock() -# time1 = sys.time() -# scn = Scene.GetCurrent() +# time1 = sys.time() +# scn = Scene.GetCurrent() - file = open(filename, "w") + file = open(filepath, "w") # Write Header - file.write('# Blender v%s OBJ File: %s\n' % (bpy.app.version_string, bpy.data.filename.split('/')[-1].split('\\')[-1] )) + file.write('# Blender v%s OBJ File: %s\n' % (bpy.app.version_string, bpy.data.filepath.split('/')[-1].split('\\')[-1] )) file.write('# www.blender.org\n') # Tell the obj file what material file to use. if EXPORT_MTL: - mtlfilename = '%s.mtl' % '.'.join(filename.split('.')[:-1]) - file.write('mtllib %s\n' % ( mtlfilename.split('\\')[-1].split('/')[-1] )) + mtlfilepath = '%s.mtl' % '.'.join(filepath.split('.')[:-1]) + file.write('mtllib %s\n' % ( mtlfilepath.split('\\')[-1].split('/')[-1] )) if EXPORT_ROTX90: - mat_xrot90= Mathutils.RotationMatrix(-math.pi/2, 4, 'X') + mat_xrot90= mathutils.Matrix.Rotation(-math.pi/2, 4, 'X') # Initialize totals, these are updated each object totverts = totuvco = totno = 1 @@ -396,48 +377,48 @@ def write(filename, objects, scene, if ob_main.dupli_type != 'NONE': # XXX print('creating dupli_list on', ob_main.name) - ob_main.create_dupli_list() + ob_main.create_dupli_list(scene) obs = [(dob.object, dob.matrix) for dob in ob_main.dupli_list] # XXX debug print print(ob_main.name, 'has', len(obs), 'dupli children') else: - obs = [(ob_main, ob_main.matrix)] + obs = [(ob_main, ob_main.matrix_world)] for ob, ob_mat in obs: - # XXX postponed -# # Nurbs curve support -# if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob): -# if EXPORT_ROTX90: -# ob_mat = ob_mat * mat_xrot90 - -# totverts += write_nurb(file, ob, ob_mat) - -# continue -# end nurbs + # Nurbs curve support + if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob): + if EXPORT_ROTX90: + ob_mat = ob_mat * mat_xrot90 + totverts += write_nurb(file, ob, ob_mat) + continue + # END NURBS if ob.type != 'MESH': continue - me = ob.create_mesh(EXPORT_APPLY_MODIFIERS, 'PREVIEW') + me = ob.create_mesh(scene, EXPORT_APPLY_MODIFIERS, 'PREVIEW') if EXPORT_ROTX90: me.transform(mat_xrot90 * ob_mat) else: me.transform(ob_mat) -# # Will work for non meshes now! :) -# me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, EXPORT_POLYGROUPS, scn) -# if not me: -# continue +# # Will work for non meshes now! :) +# me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, EXPORT_POLYGROUPS, scn) +# if not me: +# continue if EXPORT_UV: faceuv = len(me.uv_textures) > 0 + uv_layer = me.uv_textures.active.data[:] else: faceuv = False + me_verts = me.vertices[:] + # XXX - todo, find a better way to do triangulation # ...removed convert_to_triface because it relies on editmesh ''' @@ -446,7 +427,7 @@ def write(filename, objects, scene, # Add a dummy object to it. has_quads = False for f in me.faces: - if f.verts[3] != 0: + if f.vertices[3] != 0: has_quads = True break @@ -468,7 +449,7 @@ def write(filename, objects, scene, else: edges = [] - if not (len(face_index_pairs)+len(edges)+len(me.verts)): # Make sure there is somthing to write + if not (len(face_index_pairs)+len(edges)+len(me.vertices)): # Make sure there is somthing to write # clean up bpy.data.meshes.remove(me) @@ -479,13 +460,13 @@ def write(filename, objects, scene, # High Quality Normals if EXPORT_NORMALS and face_index_pairs: me.calc_normals() -# if EXPORT_NORMALS_HQ: -# BPyMesh.meshCalcNormals(me) -# else: -# # transforming normals is incorrect -# # when the matrix is scaled, -# # better to recalculate them -# me.calcNormals() +# if EXPORT_NORMALS_HQ: +# BPyMesh.meshCalcNormals(me) +# else: +# # transforming normals is incorrect +# # when the matrix is scaled, +# # better to recalculate them +# me.calcNormals() materials = me.materials @@ -510,29 +491,24 @@ def write(filename, objects, scene, if EXPORT_KEEP_VERT_ORDER: pass elif faceuv: - # XXX update - tface = me.active_uv_texture.data - - face_index_pairs.sort(key=lambda a: (a[0].material_index, hash(tface[a[1]].image), a[0].smooth)) + face_index_pairs.sort(key=lambda a: (a[0].material_index, hash(uv_layer[a[1]].image), a[0].use_smooth)) elif len(materials) > 1: - face_index_pairs.sort(key = lambda a: (a[0].material_index, a[0].smooth)) + face_index_pairs.sort(key = lambda a: (a[0].material_index, a[0].use_smooth)) else: # no materials - face_index_pairs.sort(key = lambda a: a[0].smooth) -# if EXPORT_KEEP_VERT_ORDER: -# pass -# elif faceuv: -# try: faces.sort(key = lambda a: (a.mat, a.image, a.smooth)) -# except: faces.sort(lambda a,b: cmp((a.mat, a.image, a.smooth), (b.mat, b.image, b.smooth))) -# elif len(materials) > 1: -# try: faces.sort(key = lambda a: (a.mat, a.smooth)) -# except: faces.sort(lambda a,b: cmp((a.mat, a.smooth), (b.mat, b.smooth))) -# else: -# # no materials -# try: faces.sort(key = lambda a: a.smooth) -# except: faces.sort(lambda a,b: cmp(a.smooth, b.smooth)) - - faces = [pair[0] for pair in face_index_pairs] + face_index_pairs.sort(key = lambda a: a[0].use_smooth) +# if EXPORT_KEEP_VERT_ORDER: +# pass +# elif faceuv: +# try: faces.sort(key = lambda a: (a.mat, a.image, a.use_smooth)) +# except: faces.sort(lambda a,b: cmp((a.mat, a.image, a.use_smooth), (b.mat, b.image, b.use_smooth))) +# elif len(materials) > 1: +# try: faces.sort(key = lambda a: (a.mat, a.use_smooth)) +# except: faces.sort(lambda a,b: cmp((a.mat, a.use_smooth), (b.mat, b.use_smooth))) +# else: +# # no materials +# try: faces.sort(key = lambda a: a.use_smooth) +# except: faces.sort(lambda a,b: cmp(a.use_smooth, b.use_smooth)) # Set the default mat to no material and no image. contextMat = (0, 0) # Can never be this, so we will label a new material teh first chance we get. @@ -553,28 +529,17 @@ def write(filename, objects, scene, # Vert - for v in me.verts: + for v in me_verts: file.write('v %.6f %.6f %.6f\n' % tuple(v.co)) # UV if faceuv: - uv_face_mapping = [[0,0,0,0] for f in faces] # a bit of a waste for tri's :/ + uv_face_mapping = [[0,0,0,0] for i in range(len(face_index_pairs))] # a bit of a waste for tri's :/ uv_dict = {} # could use a set() here - uv_layer = me.active_uv_texture + uv_layer = me.uv_textures.active.data for f, f_index in face_index_pairs: - - tface = uv_layer.data[f_index] - - # workaround, since tface.uv iteration is wrong atm - uvs = tface.uv - # uvs = [tface.uv1, tface.uv2, tface.uv3] - - # # add another UV if it's a quad - # if len(f.verts) == 4: - # uvs.append(tface.uv4) - - for uv_index, uv in enumerate(uvs): + for uv_index, uv in enumerate(uv_layer[f_index].uv): uvkey = veckey2d(uv) try: uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] @@ -582,27 +547,16 @@ def write(filename, objects, scene, uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] = len(uv_dict) file.write('vt %.6f %.6f\n' % tuple(uv)) -# uv_dict = {} # could use a set() here -# for f_index, f in enumerate(faces): - -# for uv_index, uv in enumerate(f.uv): -# uvkey = veckey2d(uv) -# try: -# uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] -# except: -# uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] = len(uv_dict) -# file.write('vt %.6f %.6f\n' % tuple(uv)) - uv_unique_count = len(uv_dict) -# del uv, uvkey, uv_dict, f_index, uv_index +# del uv, uvkey, uv_dict, f_index, uv_index # Only need uv_unique_count and uv_face_mapping # NORMAL, Smooth/Non smoothed. if EXPORT_NORMALS: - for f in faces: - if f.smooth: - for vIdx in f.verts: - v = me.verts[vIdx] + for f, f_index in face_index_pairs: + if f.use_smooth: + for v_idx in f.vertices: + v = me_verts[v_idx] noKey = veckey3d(v.normal) if noKey not in globalNormals: globalNormals[noKey] = totno @@ -622,66 +576,66 @@ def write(filename, objects, scene, # XXX if EXPORT_POLYGROUPS: # Retrieve the list of vertex groups -# vertGroupNames = me.getVertGroupNames() +# vertGroupNames = me.getVertGroupNames() currentVGroup = '' # Create a dictionary keyed by face id and listing, for each vertex, the vertex groups it belongs to - vgroupsMap = [[] for _i in range(len(me.verts))] -# vgroupsMap = [[] for _i in xrange(len(me.verts))] + vgroupsMap = [[] for _i in range(len(me_verts))] +# vgroupsMap = [[] for _i in xrange(len(me_verts))] for g in ob.vertex_groups: -# for vertexGroupName in vertGroupNames: - for vIdx, vWeight in getVertsFromGroup(me, g.index): -# for vIdx, vWeight in me.getVertsFromGroup(vertexGroupName, 1): - vgroupsMap[vIdx].append((g.name, vWeight)) +# for vertexGroupName in vertGroupNames: + for v_idx, vWeight in getVertsFromGroup(me, g.index): +# for v_idx, vWeight in me.getVertsFromGroup(vertexGroupName, 1): + vgroupsMap[v_idx].append((g.name, vWeight)) - for f_index, f in enumerate(faces): - f_v = [{"index": index, "vertex": me.verts[index]} for index in f.verts] + for f, f_index in face_index_pairs: + f_v = [me_verts[v_idx] for v_idx in f.vertices] - # if f.verts[3] == 0: - # f_v.pop() + # if f.vertices[3] == 0: + # f_v.pop() -# f_v= f.v - f_smooth= f.smooth +# f_v= f.v + f_smooth= f.use_smooth f_mat = min(f.material_index, len(materialNames)-1) -# f_mat = min(f.mat, len(materialNames)-1) +# f_mat = min(f.mat, len(materialNames)-1) if faceuv: - tface = me.active_uv_texture.data[face_index_pairs[f_index][1]] + tface = uv_layer[f_index] f_image = tface.image f_uv = tface.uv # f_uv= [tface.uv1, tface.uv2, tface.uv3] - # if len(f.verts) == 4: - # f_uv.append(tface.uv4) -# f_image = f.image -# f_uv= f.uv + # if len(f.vertices) == 4: + # f_uv.append(tface.uv4) +# f_image = f.image +# f_uv= f.uv # MAKE KEY if faceuv and f_image: # Object is always true. - key = materialNames[f_mat], f_image.name + key = materialNames[f_mat], f_image.name else: - key = materialNames[f_mat], None # No image, use None instead. + key = materialNames[f_mat], None # No image, use None instead. # Write the vertex group if EXPORT_POLYGROUPS: if len(ob.vertex_groups): # find what vertext group the face belongs to theVGroup = findVertexGroupName(f,vgroupsMap) - if theVGroup != currentVGroup: + if theVGroup != currentVGroup: currentVGroup = theVGroup file.write('g %s\n' % theVGroup) -# # Write the vertex group -# if EXPORT_POLYGROUPS: -# if vertGroupNames: -# # find what vertext group the face belongs to -# theVGroup = findVertexGroupName(f,vgroupsMap) -# if theVGroup != currentVGroup: -# currentVGroup = theVGroup -# file.write('g %s\n' % theVGroup) +# # Write the vertex group +# if EXPORT_POLYGROUPS: +# if vertGroupNames: +# # find what vertext group the face belongs to +# theVGroup = findVertexGroupName(f,vgroupsMap) +# if theVGroup != currentVGroup: +# currentVGroup = theVGroup +# file.write('g %s\n' % theVGroup) # CHECK FOR CONTEXT SWITCH if key == contextMat: - pass # Context alredy switched, dont do anything + pass # Context already switched, dont do anything else: if key[0] == None and key[1] == None: # Write a null material, since we know the context has changed. @@ -725,21 +679,21 @@ def write(filename, objects, scene, if f_smooth: # Smoothed, use vertex normals for vi, v in enumerate(f_v): file.write( ' %d/%d/%d' % \ - (v["index"] + totverts, + (v.index + totverts, totuvco + uv_face_mapping[f_index][vi], - globalNormals[ veckey3d(v["vertex"].normal) ]) ) # vert, uv, normal + globalNormals[ veckey3d(v.normal) ]) ) # vert, uv, normal else: # No smoothing, face normals no = globalNormals[ veckey3d(f.normal) ] for vi, v in enumerate(f_v): file.write( ' %d/%d/%d' % \ - (v["index"] + totverts, + (v.index + totverts, totuvco + uv_face_mapping[f_index][vi], no) ) # vert, uv, normal else: # No Normals for vi, v in enumerate(f_v): file.write( ' %d/%d' % (\ - v["index"] + totverts,\ + v.index + totverts,\ totuvco + uv_face_mapping[f_index][vi])) # vert, uv face_vert_index += len(f_v) @@ -749,25 +703,25 @@ def write(filename, objects, scene, if f_smooth: # Smoothed, use vertex normals for v in f_v: file.write( ' %d//%d' % - (v["index"] + totverts, globalNormals[ veckey3d(v["vertex"].normal) ]) ) + (v.index + totverts, globalNormals[ veckey3d(v.normal) ]) ) else: # No smoothing, face normals no = globalNormals[ veckey3d(f.normal) ] for v in f_v: - file.write( ' %d//%d' % (v["index"] + totverts, no) ) + file.write( ' %d//%d' % (v.index + totverts, no) ) else: # No Normals for v in f_v: - file.write( ' %d' % (v["index"] + totverts) ) + file.write( ' %d' % (v.index + totverts) ) file.write('\n') # Write edges. if EXPORT_EDGES: for ed in edges: - if ed.loose: - file.write('f %d %d\n' % (ed.verts[0] + totverts, ed.verts[1] + totverts)) + if ed.is_loose: + file.write('f %d %d\n' % (ed.vertices[0] + totverts, ed.vertices[1] + totverts)) # Make the indicies global rather then per mesh - totverts += len(me.verts) + totverts += len(me_verts) if faceuv: totuvco += uv_unique_count @@ -782,53 +736,53 @@ def write(filename, objects, scene, # Now we have all our materials, save them if EXPORT_MTL: - write_mtl(scene, mtlfilename, EXPORT_COPY_IMAGES, mtl_dict) -# if EXPORT_COPY_IMAGES: -# dest_dir = os.path.basename(filename) -# # dest_dir = filename -# # # Remove chars until we are just the path. -# # while dest_dir and dest_dir[-1] not in '\\/': -# # dest_dir = dest_dir[:-1] -# if dest_dir: -# copy_images(dest_dir, mtl_dict) -# else: -# print('\tError: "%s" could not be used as a base for an image path.' % filename) + write_mtl(scene, mtlfilepath, EXPORT_COPY_IMAGES, mtl_dict) +# if EXPORT_COPY_IMAGES: +# dest_dir = os.path.basename(filepath) +# # dest_dir = filepath +# # # Remove chars until we are just the path. +# # while dest_dir and dest_dir[-1] not in '\\/': +# # dest_dir = dest_dir[:-1] +# if dest_dir: +# copy_images(dest_dir, mtl_dict) +# else: +# print('\tError: "%s" could not be used as a base for an image path.' % filepath) print("OBJ Export time: %.2f" % (time.clock() - time1)) -# print "OBJ Export time: %.2f" % (sys.time() - time1) - -def do_export(filename, context, - EXPORT_APPLY_MODIFIERS = True, # not used - EXPORT_ROTX90 = True, # wrong - EXPORT_TRI = False, # ok - EXPORT_EDGES = False, - EXPORT_NORMALS = False, # not yet - EXPORT_NORMALS_HQ = False, # not yet - EXPORT_UV = True, # ok - EXPORT_MTL = True, - EXPORT_SEL_ONLY = True, # ok - EXPORT_ALL_SCENES = False, # XXX not working atm - EXPORT_ANIMATION = False, - EXPORT_COPY_IMAGES = False, - EXPORT_BLEN_OBS = True, - EXPORT_GROUP_BY_OB = False, - EXPORT_GROUP_BY_MAT = False, - EXPORT_KEEP_VERT_ORDER = False, - EXPORT_POLYGROUPS = False, - EXPORT_CURVE_AS_NURBS = True): + +def write(filepath, context, + EXPORT_TRI, # ok + EXPORT_EDGES, + EXPORT_NORMALS, # not yet + EXPORT_NORMALS_HQ, # not yet + EXPORT_UV, # ok + EXPORT_MTL, + EXPORT_COPY_IMAGES, + EXPORT_APPLY_MODIFIERS, # ok + EXPORT_ROTX90, # wrong + EXPORT_BLEN_OBS, + EXPORT_GROUP_BY_OB, + EXPORT_GROUP_BY_MAT, + EXPORT_KEEP_VERT_ORDER, + EXPORT_POLYGROUPS, + EXPORT_CURVE_AS_NURBS, + EXPORT_SEL_ONLY, # ok + EXPORT_ALL_SCENES, # XXX not working atm + EXPORT_ANIMATION): # Not used - base_name, ext = splitExt(filename) + base_name, ext = os.path.splitext(filepath) context_name = [base_name, '', '', ext] # Base name, scene name, frame number, extension orig_scene = context.scene # Exit edit mode before exporting, so current object states are exported properly. - bpy.ops.object.mode_set(mode='OBJECT') + if context.object: + bpy.ops.object.mode_set(mode='OBJECT') -# if EXPORT_ALL_SCENES: -# export_scenes = bpy.data.scenes -# else: -# export_scenes = [orig_scene] +# if EXPORT_ALL_SCENES: +# export_scenes = bpy.data.scenes +# else: +# export_scenes = [orig_scene] # XXX only exporting one scene atm since changing # current scene is not possible. @@ -837,49 +791,58 @@ def do_export(filename, context, export_scenes = [orig_scene] # Export all scenes. - for scn in export_scenes: - # scn.makeCurrent() # If already current, this is not slow. - # context = scn.getRenderingContext() - orig_frame = scn.frame_current + for scene in export_scenes: + # scene.makeCurrent() # If already current, this is not slow. + # context = scene.getRenderingContext() + orig_frame = scene.frame_current if EXPORT_ALL_SCENES: # Add scene name into the context_name - context_name[1] = '_%s' % bpy.utils.clean_name(scn.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied. + context_name[1] = '_%s' % bpy.path.clean_name(scene.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied. # Export an animation? if EXPORT_ANIMATION: - scene_frames = range(scn.frame_start, context.frame_end + 1) # Up to and including the end frame. + scene_frames = range(scene.frame_start, context.frame_end + 1) # Up to and including the end frame. else: scene_frames = [orig_frame] # Dont export an animation. # Loop through all frames in the scene and export. for frame in scene_frames: - if EXPORT_ANIMATION: # Add frame to the filename. + if EXPORT_ANIMATION: # Add frame to the filepath. context_name[2] = '_%.6d' % frame - scn.frame_current = frame + scene.frame_current = frame if EXPORT_SEL_ONLY: - export_objects = context.selected_objects + objects = context.selected_objects else: - export_objects = scn.objects + objects = scene.objects full_path= ''.join(context_name) # erm... bit of a problem here, this can overwrite files when exporting frames. not too bad. # EXPORT THE FILE. - write(full_path, export_objects, scn, - EXPORT_TRI, EXPORT_EDGES, EXPORT_NORMALS, - EXPORT_NORMALS_HQ, EXPORT_UV, EXPORT_MTL, - EXPORT_COPY_IMAGES, EXPORT_APPLY_MODIFIERS, - EXPORT_ROTX90, EXPORT_BLEN_OBS, - EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER, - EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS) - - - scn.frame_current = orig_frame + write_file(full_path, objects, scene, + EXPORT_TRI, + EXPORT_EDGES, + EXPORT_NORMALS, + EXPORT_NORMALS_HQ, + EXPORT_UV, + EXPORT_MTL, + EXPORT_COPY_IMAGES, + EXPORT_APPLY_MODIFIERS, + EXPORT_ROTX90, + EXPORT_BLEN_OBS, + EXPORT_GROUP_BY_OB, + EXPORT_GROUP_BY_MAT, + EXPORT_KEEP_VERT_ORDER, + EXPORT_POLYGROUPS, + EXPORT_CURVE_AS_NURBS) + + + scene.frame_current = orig_frame # Restore old active scene. -# orig_scene.makeCurrent() -# Window.WaitCursor(0) +# orig_scene.makeCurrent() +# Window.WaitCursor(0) ''' @@ -900,28 +863,28 @@ class ExportOBJ(bpy.types.Operator): # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for exporting the OBJ file", maxlen= 1024, default= "") + filepath = StringProperty(name="File Path", description="Filepath used for exporting the OBJ file", maxlen= 1024, default= "") check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) # context group - use_selection = BoolProperty(name="Selection Only", description="", default= False) + use_selection = BoolProperty(name="Selection Only", description="Export selected objects only", default= False) use_all_scenes = BoolProperty(name="All Scenes", description="", default= False) - use_animation = BoolProperty(name="All Animation", description="", default= False) + use_animation = BoolProperty(name="Animation", description="", default= False) # object group - use_modifiers = BoolProperty(name="Apply Modifiers", description="", default= True) + use_modifiers = BoolProperty(name="Apply Modifiers", description="Apply modifiers (preview resolution)", default= True) use_rotate90 = BoolProperty(name="Rotate X90", description="", default= True) # extra data group - use_edges = BoolProperty(name="Edges", description="", default= True) - use_normals = BoolProperty(name="Normals", description="", default= False) - use_hq_normals = BoolProperty(name="High Quality Normals", description="", default= True) + use_edges = BoolProperty(name="Edges", description="", default=True) + use_normals = BoolProperty(name="Normals", description="", default=False) + use_hq_normals = BoolProperty(name="High Quality Normals", description="", default=True) use_uvs = BoolProperty(name="UVs", description="", default= True) - use_materials = BoolProperty(name="Materials", description="", default= True) - copy_images = BoolProperty(name="Copy Images", description="", default= False) - use_triangles = BoolProperty(name="Triangulate", description="", default= False) - use_vertex_groups = BoolProperty(name="Polygroups", description="", default= False) - use_nurbs = BoolProperty(name="Nurbs", description="", default= False) + use_materials = BoolProperty(name="Materials", description="", default=True) + copy_images = BoolProperty(name="Copy Images", description="", default=False) + use_triangles = BoolProperty(name="Triangulate", description="", default=False) + use_vertex_groups = BoolProperty(name="Polygroups", description="", default=False) + use_nurbs = BoolProperty(name="Nurbs", description="", default=False) # grouping group use_blen_objects = BoolProperty(name="Objects as OBJ Objects", description="", default= True) @@ -932,48 +895,48 @@ class ExportOBJ(bpy.types.Operator): def execute(self, context): - path = self.properties.path - if not path.lower().endswith(".obj"): - path += ".obj" - - do_export(path, context, - EXPORT_TRI=self.properties.use_triangles, - EXPORT_EDGES=self.properties.use_edges, - EXPORT_NORMALS=self.properties.use_normals, - EXPORT_NORMALS_HQ=self.properties.use_hq_normals, - EXPORT_UV=self.properties.use_uvs, - EXPORT_MTL=self.properties.use_materials, - EXPORT_COPY_IMAGES=self.properties.copy_images, - EXPORT_APPLY_MODIFIERS=self.properties.use_modifiers, - EXPORT_ROTX90=self.properties.use_rotate90, - EXPORT_BLEN_OBS=self.properties.use_blen_objects, - EXPORT_GROUP_BY_OB=self.properties.group_by_object, - EXPORT_GROUP_BY_MAT=self.properties.group_by_material, - EXPORT_KEEP_VERT_ORDER=self.properties.keep_vertex_order, - EXPORT_POLYGROUPS=self.properties.use_vertex_groups, - EXPORT_CURVE_AS_NURBS=self.properties.use_nurbs, - EXPORT_SEL_ONLY=self.properties.use_selection, - EXPORT_ALL_SCENES=self.properties.use_all_scenes) + filepath = self.properties.filepath + filepath = bpy.path.ensure_ext(filepath, ".obj") + + write(filepath, context, + EXPORT_TRI=self.properties.use_triangles, + EXPORT_EDGES=self.properties.use_edges, + EXPORT_NORMALS=self.properties.use_normals, + EXPORT_NORMALS_HQ=self.properties.use_hq_normals, + EXPORT_UV=self.properties.use_uvs, + EXPORT_MTL=self.properties.use_materials, + EXPORT_COPY_IMAGES=self.properties.copy_images, + EXPORT_APPLY_MODIFIERS=self.properties.use_modifiers, + EXPORT_ROTX90=self.properties.use_rotate90, + EXPORT_BLEN_OBS=self.properties.use_blen_objects, + EXPORT_GROUP_BY_OB=self.properties.group_by_object, + EXPORT_GROUP_BY_MAT=self.properties.group_by_material, + EXPORT_KEEP_VERT_ORDER=self.properties.keep_vertex_order, + EXPORT_POLYGROUPS=self.properties.use_vertex_groups, + EXPORT_CURVE_AS_NURBS=self.properties.use_nurbs, + EXPORT_SEL_ONLY=self.properties.use_selection, + EXPORT_ALL_SCENES=self.properties.use_all_scenes, + EXPORT_ANIMATION=self.properties.use_animation) return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) + import os + if not self.properties.is_property_set("filepath"): + self.properties.filepath = os.path.splitext(bpy.data.filepath)[0] + ".obj" + + context.manager.add_fileselect(self) return {'RUNNING_MODAL'} def menu_func(self, context): - default_path = bpy.data.filename.replace(".blend", ".obj") - self.layout.operator(ExportOBJ.bl_idname, text="Wavefront (.obj)").path = default_path + self.layout.operator(ExportOBJ.bl_idname, text="Wavefront (.obj)") def register(): - bpy.types.register(ExportOBJ) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): - bpy.types.unregister(ExportOBJ) bpy.types.INFO_MT_file_export.remove(menu_func) @@ -986,4 +949,3 @@ def unregister(): if __name__ == "__main__": register() - diff --git a/release/scripts/io/export_ply.py b/release/scripts/io/export_ply.py index 23a3eb8b9ee..946be68ec41 100644 --- a/release/scripts/io/export_ply.py +++ b/release/scripts/io/export_ply.py @@ -99,11 +99,12 @@ def write(filename, scene, ob, \ Window.WaitCursor(1) """ - bpy.ops.object.mode_set(mode='OBJECT') + if scene.objects.active: + bpy.ops.object.mode_set(mode='OBJECT') #mesh = BPyMesh.getMeshFromObject(ob, None, EXPORT_APPLY_MODIFIERS, False, scn) # XXX if EXPORT_APPLY_MODIFIERS: - mesh = ob.create_mesh(True, 'PREVIEW') + mesh = ob.create_mesh(scene, True, 'PREVIEW') else: mesh = ob.data @@ -111,7 +112,7 @@ def write(filename, scene, ob, \ raise ("Error, could not get mesh data from active object") return - # mesh.transform(ob.matrixWorld) # XXX + # mesh.transform(ob.matrix_world) # XXX faceUV = (len(mesh.uv_textures) > 0) vertexUV = (len(mesh.sticky) > 0) @@ -128,7 +129,7 @@ def write(filename, scene, ob, \ vertexColors = False if faceUV: - active_uv_layer = mesh.active_uv_texture + active_uv_layer = mesh.uv_textures.active if not active_uv_layer: EXPORT_UV = False faceUV = None @@ -136,7 +137,7 @@ def write(filename, scene, ob, \ active_uv_layer = active_uv_layer.data if vertexColors: - active_col_layer = mesh.active_vertex_color + active_col_layer = mesh.vertex_colors.active if not active_col_layer: EXPORT_COLORS = False vertexColors = None @@ -146,7 +147,7 @@ def write(filename, scene, ob, \ # incase color = uvcoord = uvcoord_key = normal = normal_key = None - mesh_verts = mesh.verts # save a lookup + mesh_verts = mesh.vertices # save a lookup ply_verts = [] # list of dictionaries # vdict = {} # (index, normal, uv) -> new index vdict = [{} for i in range(len(mesh_verts))] @@ -155,7 +156,7 @@ def write(filename, scene, ob, \ for i, f in enumerate(mesh.faces): - smooth = f.smooth + smooth = f.use_smooth if not smooth: normal = tuple(f.normal) normal_key = rvec3d(normal) @@ -167,7 +168,7 @@ def write(filename, scene, ob, \ col = active_col_layer[i] col = col.color1, col.color2, col.color3, col.color4 - f_verts = f.verts + f_verts = f.vertices pf = ply_faces[i] for j, vidx in enumerate(f_verts): @@ -203,7 +204,7 @@ def write(filename, scene, ob, \ file.write('ply\n') file.write('format ascii 1.0\n') - file.write('comment Created by Blender %s - www.blender.org, source file: %s\n' % (bpy.app.version_string, bpy.data.filename.split('/')[-1].split('\\')[-1])) + file.write('comment Created by Blender %s - www.blender.org, source file: %s\n' % (bpy.app.version_string, bpy.data.filepath.split('/')[-1].split('\\')[-1])) file.write('element vertex %d\n' % len(ply_verts)) @@ -267,23 +268,22 @@ class ExportPLY(bpy.types.Operator): # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for exporting the PLY file", maxlen=1024, default="") + filepath = StringProperty(name="File Path", description="Filepath used for exporting the PLY file", maxlen=1024, default="") check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) use_modifiers = BoolProperty(name="Apply Modifiers", description="Apply Modifiers to the exported mesh", default=True) use_normals = BoolProperty(name="Normals", description="Export Normals for smooth and hard shaded faces", default=True) use_uvs = BoolProperty(name="UVs", description="Exort the active UV layer", default=True) use_colors = BoolProperty(name="Vertex Colors", description="Exort the active vertex color layer", default=True) - def poll(self, context): + @classmethod + def poll(cls, context): return context.active_object != None def execute(self, context): - # print("Selected: " + context.active_object.name) + filepath = self.properties.filepath + filepath = bpy.path.ensure_ext(filepath, ".ply") - if not self.properties.path: - raise Exception("filename not set") - - write(self.properties.path, context.scene, context.active_object,\ + write(filepath, context.scene, context.active_object,\ EXPORT_APPLY_MODIFIERS=self.properties.use_modifiers, EXPORT_NORMALS=self.properties.use_normals, EXPORT_UV=self.properties.use_uvs, @@ -293,8 +293,11 @@ class ExportPLY(bpy.types.Operator): return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) + import os + if not self.properties.is_property_set("filepath"): + self.properties.filepath = os.path.splitext(bpy.data.filepath)[0] + ".ply" + + context.manager.add_fileselect(self) return {'RUNNING_MODAL'} def draw(self, context): @@ -310,17 +313,14 @@ class ExportPLY(bpy.types.Operator): def menu_func(self, context): - default_path = bpy.data.filename.replace(".blend", ".ply") - self.layout.operator(ExportPLY.bl_idname, text="Stanford (.ply)").path = default_path + self.layout.operator(ExportPLY.bl_idname, text="Stanford (.ply)") def register(): - bpy.types.register(ExportPLY) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): - bpy.types.unregister(ExportPLY) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == "__main__": diff --git a/release/scripts/io/export_x3d.py b/release/scripts/io/export_x3d.py index dccc5808bc5..12fe63f5296 100644 --- a/release/scripts/io/export_x3d.py +++ b/release/scripts/io/export_x3d.py @@ -69,7 +69,7 @@ import math import os import bpy -import Mathutils +import mathutils from export_3ds import create_derived_objects, free_derived_objects @@ -81,7 +81,7 @@ from export_3ds import create_derived_objects, free_derived_objects # DEG2RAD=0.017453292519943295 -MATWORLD= Mathutils.RotationMatrix(-90, 4, 'X') +MATWORLD= mathutils.Matrix.Rotation(-90, 4, 'X') #################################### # Global Variables @@ -237,7 +237,7 @@ class x3d_class: lens = min(lens, math.pi) # get the camera location, subtract 90 degress from X to orient like X3D does - # mat = ob.matrixWorld - mat is now passed! + # mat = ob.matrix_world - mat is now passed! loc = self.rotatePointForVRML(mat.translation_part()) rot = mat.to_euler() @@ -258,12 +258,9 @@ class x3d_class: def writeFog(self, world): if world: - mtype = world.mist.falloff - # mtype = world.getMistype() - mparam = world.mist - # mparam = world.getMist() + mtype = world.mist_settings.falloff + mparam = world.mist_settings grd = world.horizon_color - # grd = world.getHor() grd0, grd1, grd2 = grd[0], grd[1], grd[2] else: return @@ -300,7 +297,7 @@ class x3d_class: # note -dz seems to equal om[3][1] # note dy seems to equal om[3][2] - #location=(ob.matrixWorld*MATWORLD).translation_part() # now passed + #location=(ob.matrix_world*MATWORLD).translation_part() # now passed location=(mtx*MATWORLD).translation_part() radius = lamp.distance*math.cos(beamWidth) @@ -346,7 +343,7 @@ class x3d_class: ambi = 0 ambientIntensity = 0 - # location=(ob.matrixWorld*MATWORLD).translation_part() # now passed + # location=(ob.matrix_world*MATWORLD).translation_part() # now passed location= (mtx*MATWORLD).translation_part() self.file.write("<PointLight DEF=\"%s\" " % safeName) @@ -364,7 +361,7 @@ class x3d_class: return else: dx,dy,dz = self.computeDirection(mtx) - # location=(ob.matrixWorld*MATWORLD).translation_part() + # location=(ob.matrix_world*MATWORLD).translation_part() location=(mtx*MATWORLD).translation_part() self.writeIndented("<%s\n" % obname,1) self.writeIndented("direction=\"%s %s %s\"\n" % (round(dx,3),round(dy,3),round(dz,3))) @@ -402,17 +399,17 @@ class x3d_class: if len(mesh.faces) == 0: return mode = [] # mode = 0 - if mesh.active_uv_texture: + if mesh.uv_textures.active: # if mesh.faceUV: - for face in mesh.active_uv_texture.data: + for face in mesh.uv_textures.active.data: # for face in mesh.faces: - if face.halo and 'HALO' not in mode: + if face.use_halo and 'HALO' not in mode: mode += ['HALO'] - if face.billboard and 'BILLBOARD' not in mode: + if face.use_billboard and 'BILLBOARD' not in mode: mode += ['BILLBOARD'] - if face.object_color and 'OBJECT_COLOR' not in mode: + if face.use_object_color and 'OBJECT_COLOR' not in mode: mode += ['OBJECT_COLOR'] - if face.collision and 'COLLISION' not in mode: + if face.use_collision and 'COLLISION' not in mode: mode += ['COLLISION'] # mode |= face.mode @@ -445,7 +442,7 @@ class x3d_class: else: bTwoSided=0 - # mtx = ob.matrixWorld * MATWORLD # mtx is now passed + # mtx = ob.matrix_world * MATWORLD # mtx is now passed mtx = mtx * MATWORLD loc= mtx.translation_part() @@ -461,16 +458,16 @@ class x3d_class: self.writeIndented("<Shape>\n",1) maters=mesh.materials hasImageTexture=0 - issmooth=0 + is_smooth = False - if len(maters) > 0 or mesh.active_uv_texture: + if len(maters) > 0 or mesh.uv_textures.active: # if len(maters) > 0 or mesh.faceUV: self.writeIndented("<Appearance>\n", 1) # right now this script can only handle a single material per mesh. if len(maters) >= 1: mat=maters[0] # matFlags = mat.getMode() - if not mat.face_texture: + if not mat.use_face_texture: # if not matFlags & Blender.Material.Modes['TEXFACE']: self.writeMaterial(mat, self.cleanStr(mat.name,''), world) # self.writeMaterial(mat, self.cleanStr(maters[0].name,''), world) @@ -480,9 +477,9 @@ class x3d_class: #-- textures face = None - if mesh.active_uv_texture: + if mesh.uv_textures.active: # if mesh.faceUV: - for face in mesh.active_uv_texture.data: + for face in mesh.uv_textures.active.data: # for face in mesh.faces: if face.image: # if (hasImageTexture == 0) and (face.image): @@ -516,16 +513,16 @@ class x3d_class: self.file.write("solid=\"true\" ") for face in mesh.faces: - if face.smooth: - issmooth=1 - break - if issmooth==1: - creaseAngle=(mesh.autosmooth_angle)*(math.pi/180.0) + if face.use_smooth: + is_smooth = True + break + if is_smooth == True: + creaseAngle=(mesh.auto_smooth_angle)*(math.pi/180.0) # creaseAngle=(mesh.degr)*(math.pi/180.0) self.file.write("creaseAngle=\"%s\" " % (round(creaseAngle,self.cp))) #--- output textureCoordinates if UV texture used - if mesh.active_uv_texture: + if mesh.uv_textures.active: # if mesh.faceUV: if self.matonly == 1 and self.share == 1: self.writeFaceColors(mesh) @@ -540,7 +537,7 @@ class x3d_class: self.writeCoordinates(ob, mesh, meshName, EXPORT_TRI) #--- output textureCoordinates if UV texture used - if mesh.active_uv_texture: + if mesh.uv_textures.active: # if mesh.faceUV: if hasImageTexture == 1: self.writeTextureCoordinates(mesh) @@ -581,7 +578,7 @@ class x3d_class: if self.writingcoords == 0: self.file.write('coordIndex="') for face in mesh.faces: - fv = face.verts + fv = face.vertices # fv = face.v if len(fv)==3: @@ -601,10 +598,10 @@ class x3d_class: self.file.write("\">\n") else: #-- vertices - # mesh.transform(ob.matrixWorld) + # mesh.transform(ob.matrix_world) self.writeIndented("<Coordinate DEF=\"%s%s\" \n" % ("coord_",meshName), 1) self.file.write("\t\t\t\tpoint=\"") - for v in mesh.verts: + for v in mesh.vertices: self.file.write("%.6f %.6f %.6f, " % tuple(v.co)) self.file.write("\" />") self.writeIndented("\n", -1) @@ -614,11 +611,11 @@ class x3d_class: texIndexList=[] j=0 - for face in mesh.active_uv_texture.data: + for face in mesh.uv_textures.active.data: # for face in mesh.faces: # workaround, since tface.uv iteration is wrong atm uvs = face.uv - # uvs = [face.uv1, face.uv2, face.uv3, face.uv4] if face.verts[3] else [face.uv1, face.uv2, face.uv3] + # uvs = [face.uv1, face.uv2, face.uv3, face.uv4] if face.vertices[3] else [face.uv1, face.uv2, face.uv3] for uv in uvs: # for uv in face.uv: @@ -646,10 +643,10 @@ class x3d_class: def writeFaceColors(self, mesh): if self.writingcolor == 0: self.file.write("colorPerVertex=\"false\" ") - elif mesh.active_vertex_color: + elif mesh.vertex_colors.active: # else: self.writeIndented("<Color color=\"", 1) - for face in mesh.active_vertex_color.data: + for face in mesh.vertex_colors.active.data: c = face.color1 if self.verbose > 2: print("Debug: face.col r=%d g=%d b=%d" % (c[0], c[1], c[2])) @@ -699,7 +696,7 @@ class x3d_class: # specB = (mat.specCol[2]+0.001)/(1.25/(mat.spec+0.001)) transp = 1-mat.alpha # matFlags = mat.getMode() - if mat.shadeless: + if mat.use_shadeless: # if matFlags & Blender.Material.Modes['SHADELESS']: ambient = 1 shine = 1 @@ -717,7 +714,7 @@ class x3d_class: def writeImageTexture(self, image): name = image.name - filename = image.filename.split('/')[-1].split('\\')[-1] + filename = image.filepath.split('/')[-1].split('\\')[-1] if name in self.texNames: self.writeIndented("<ImageTexture USE=\"%s\" />\n" % self.cleanStr(name)) self.texNames[name] += 1 @@ -731,7 +728,7 @@ class x3d_class: def writeBackground(self, world, alltextures): if world: worldname = world.name else: return - blending = (world.blend_sky, world.paper_sky, world.real_sky) + blending = (world.use_sky_blend, world.use_sky_paper, world.use_sky_real) # blending = world.getSkytype() grd = world.horizon_color # grd = world.getHor() @@ -794,28 +791,28 @@ class x3d_class: pic = tex.image # using .expandpath just in case, os.path may not expect // - basename = os.path.basename(bpy.utils.expandpath(pic.filename)) + basename = os.path.basename(bpy.path.abspath(pic.filepath)) pic = alltextures[i].image # pic = alltextures[i].getImage() if (namemat == "back") and (pic != None): self.file.write("\n\tbackUrl=\"%s\" " % basename) - # self.file.write("\n\tbackUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1]) + # self.file.write("\n\tbackUrl=\"%s\" " % pic.filepath.split('/')[-1].split('\\')[-1]) elif (namemat == "bottom") and (pic != None): self.writeIndented("bottomUrl=\"%s\" " % basename) - # self.writeIndented("bottomUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1]) + # self.writeIndented("bottomUrl=\"%s\" " % pic.filepath.split('/')[-1].split('\\')[-1]) elif (namemat == "front") and (pic != None): self.writeIndented("frontUrl=\"%s\" " % basename) - # self.writeIndented("frontUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1]) + # self.writeIndented("frontUrl=\"%s\" " % pic.filepath.split('/')[-1].split('\\')[-1]) elif (namemat == "left") and (pic != None): self.writeIndented("leftUrl=\"%s\" " % basename) - # self.writeIndented("leftUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1]) + # self.writeIndented("leftUrl=\"%s\" " % pic.filepath.split('/')[-1].split('\\')[-1]) elif (namemat == "right") and (pic != None): self.writeIndented("rightUrl=\"%s\" " % basename) - # self.writeIndented("rightUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1]) + # self.writeIndented("rightUrl=\"%s\" " % pic.filepath.split('/')[-1].split('\\')[-1]) elif (namemat == "top") and (pic != None): self.writeIndented("topUrl=\"%s\" " % basename) - # self.writeIndented("topUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1]) + # self.writeIndented("topUrl=\"%s\" " % pic.filepath.split('/')[-1].split('\\')[-1]) self.writeIndented("/>\n\n") ########################################################## @@ -852,10 +849,10 @@ class x3d_class: # -------------------------- - for ob_main in [o for o in scene.objects if o.is_visible()]: + for ob_main in [o for o in scene.objects if o.is_visible(scene)]: # for ob_main in scene.objects.context: - free, derived = create_derived_objects(ob_main) + free, derived = create_derived_objects(scene, ob_main) if derived == None: continue @@ -871,7 +868,7 @@ class x3d_class: # elif objType in ("Mesh", "Curve", "Surf", "Text") : if EXPORT_APPLY_MODIFIERS or objType != 'MESH': # if EXPORT_APPLY_MODIFIERS or objType != 'Mesh': - me = ob.create_mesh(EXPORT_APPLY_MODIFIERS, 'PREVIEW') + me = ob.create_mesh(scene, EXPORT_APPLY_MODIFIERS, 'PREVIEW') # me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, False, scene) else: me = ob.data @@ -912,7 +909,7 @@ class x3d_class: # if EXPORT_APPLY_MODIFIERS: # if containerMesh: - # containerMesh.verts = None + # containerMesh.vertices = None self.cleanup() @@ -961,16 +958,11 @@ class x3d_class: faceMap={} nFaceIndx=0 - if mesh.active_uv_texture: + if mesh.uv_textures.active: # if mesh.faceUV: - for face in mesh.active_uv_texture.data: - # for face in mesh.faces: - sidename=''; - if face.twoside: - # if face.mode & Mesh.FaceModes.TWOSIDE: - sidename='two' - else: - sidename='one' + for face in mesh.uv_textures.active.data: + # for face in mesh.faces + sidename = "two" if face.use_twoside else "one" if sidename in sided: sided[sidename]+=1 @@ -1003,8 +995,8 @@ class x3d_class: if face.mode & Mesh.FaceModes.TWOSIDE: print("Debug: face.mode twosided") - print("Debug: face.transp=0x%x (enum)" % face.transp) - if face.transp == Mesh.FaceTranspModes.SOLID: + print("Debug: face.transp=0x%x (enum)" % face.blend_type) + if face.blend_type == Mesh.FaceTranspModes.SOLID: print("Debug: face.transp.SOLID") if face.image: @@ -1030,7 +1022,7 @@ class x3d_class: # print("Debug: mesh.faceUV=%d" % mesh.faceUV) print("Debug: mesh.hasVertexColours=%d" % (len(mesh.vertex_colors) > 0)) # print("Debug: mesh.hasVertexColours=%d" % mesh.hasVertexColours()) - print("Debug: mesh.verts=%d" % len(mesh.verts)) + print("Debug: mesh.vertices=%d" % len(mesh.vertices)) print("Debug: mesh.faces=%d" % len(mesh.faces)) print("Debug: mesh.materials=%d" % len(mesh.materials)) @@ -1140,7 +1132,7 @@ class x3d_class: # Callbacks, needed before Main ########################################################## -def x3d_export(filename, +def write(filename, context, EXPORT_APPLY_MODIFIERS=False, EXPORT_TRI=False, @@ -1156,8 +1148,9 @@ def x3d_export(filename, scene = context.scene world = scene.world - - bpy.ops.object.mode_set(mode='OBJECT') + + if scene.objects.active: + bpy.ops.object.mode_set(mode='OBJECT') # XXX these are global textures while .Get() returned only scene's? alltextures = bpy.data.textures @@ -1174,50 +1167,6 @@ def x3d_export(filename, ) -def x3d_export_ui(filename): - if not filename.endswith(extension): - filename += extension - #if _safeOverwrite and sys.exists(filename): - # result = Draw.PupMenu("File Already Exists, Overwrite?%t|Yes%x1|No%x0") - #if(result != 1): - # return - - # Get user options - EXPORT_APPLY_MODIFIERS = Draw.Create(1) - EXPORT_TRI = Draw.Create(0) - EXPORT_GZIP = Draw.Create( filename.lower().endswith('.x3dz') ) - - # Get USER Options - pup_block = [\ - ('Apply Modifiers', EXPORT_APPLY_MODIFIERS, 'Use transformed mesh data from each object.'),\ - ('Triangulate', EXPORT_TRI, 'Triangulate quads.'),\ - ('Compress', EXPORT_GZIP, 'GZip the resulting file, requires a full python install'),\ - ] - - if not Draw.PupBlock('Export...', pup_block): - return - - Blender.Window.EditMode(0) - Blender.Window.WaitCursor(1) - - x3d_export(filename,\ - EXPORT_APPLY_MODIFIERS = EXPORT_APPLY_MODIFIERS.val,\ - EXPORT_TRI = EXPORT_TRI.val,\ - EXPORT_GZIP = EXPORT_GZIP.val\ - ) - - Blender.Window.WaitCursor(0) - - - -######################################################### -# main routine -######################################################### - - -# if __name__ == '__main__': -# Blender.Window.FileSelector(x3d_export_ui,"Export X3D", Blender.Get('filename').replace('.blend', '.x3d')) - from bpy.props import * class ExportX3D(bpy.types.Operator): @@ -1227,7 +1176,7 @@ class ExportX3D(bpy.types.Operator): # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for exporting the X3D file", maxlen= 1024, default= "") + filepath = StringProperty(name="File Path", description="Filepath used for exporting the X3D file", maxlen= 1024, default= "") check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) apply_modifiers = BoolProperty(name="Apply Modifiers", description="Use transformed mesh data from each object", default=True) @@ -1235,26 +1184,35 @@ class ExportX3D(bpy.types.Operator): compress = BoolProperty(name="Compress", description="GZip the resulting file, requires a full python install", default=False) def execute(self, context): - x3d_export(self.properties.path, context, self.properties.apply_modifiers, self.properties.triangulate, self.properties.compress) + filepath = self.properties.filepath + filepath = bpy.path.ensure_ext(filepath, ".x3d") + + write(filepath, + context, + self.properties.apply_modifiers, + self.properties.triangulate, + self.properties.compress, + ) + return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) + import os + if not self.properties.is_property_set("filepath"): + self.properties.filepath = os.path.splitext(bpy.data.filepath)[0] + ".x3d" + + context.manager.add_fileselect(self) return {'RUNNING_MODAL'} def menu_func(self, context): - default_path = bpy.data.filename.replace(".blend", ".x3d") - self.layout.operator(ExportX3D.bl_idname, text="X3D Extensible 3D (.x3d)").path = default_path + self.layout.operator(ExportX3D.bl_idname, text="X3D Extensible 3D (.x3d)") def register(): - bpy.types.register(ExportX3D) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): - bpy.types.unregister(ExportX3D) bpy.types.INFO_MT_file_export.remove(menu_func) # NOTES @@ -1262,4 +1220,3 @@ def unregister(): if __name__ == "__main__": register() - diff --git a/release/scripts/io/import_anim_bvh.py b/release/scripts/io/import_anim_bvh.py index e6142a2db8a..04cae915a49 100644 --- a/release/scripts/io/import_anim_bvh.py +++ b/release/scripts/io/import_anim_bvh.py @@ -22,8 +22,8 @@ import math from math import radians import bpy -import Mathutils -from Mathutils import Vector, Euler, Matrix, RotationMatrix, TranslationMatrix +import mathutils +from mathutils import Vector, Euler, Matrix class bvh_node_class(object): @@ -78,12 +78,12 @@ MATRIX_IDENTITY_4x4 = Matrix([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, def eulerRotate(x, y, z, rot_order): # Clamp all values between 0 and 360, values outside this raise an error. - mats = [RotationMatrix(x, 3, 'X'), RotationMatrix(y, 3, 'Y'), RotationMatrix(z, 3, 'Z')] + mats = [Matrix.Rotation(x, 3, 'X'), Matrix.Rotation(y, 3, 'Y'), Matrix.Rotation(z, 3, 'Z')] return (MATRIX_IDENTITY_3x3 * mats[rot_order[0]] * (mats[rot_order[1]] * (mats[rot_order[2]]))).to_euler() # Should work but doesnt! ''' - eul = Euler(x,y,z) + eul = Euler((x, y, z)) eul.order = "XYZ"[rot_order[0]] + "XYZ"[rot_order[1]] + "XYZ"[rot_order[2]] return tuple(eul.to_matrix().to_euler()) ''' @@ -136,7 +136,7 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0): #print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1]) lineIdx += 2 # Incriment to the next line (Offset) - rest_head_local = Vector(float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3])) * GLOBAL_SCALE + rest_head_local = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * GLOBAL_SCALE lineIdx += 1 # Incriment to the next line (Channels) # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation] @@ -188,7 +188,7 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0): # Account for an end node if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site': # There is somtimes a name after 'End Site' but we will ignore it. lineIdx += 2 # Incriment to the next line (Offset) - rest_tail = Vector(float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3])) * GLOBAL_SCALE + rest_tail = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * GLOBAL_SCALE bvh_nodes_serial[-1].rest_tail_world = bvh_nodes_serial[-1].rest_head_world + rest_tail bvh_nodes_serial[-1].rest_tail_local = bvh_nodes_serial[-1].rest_head_local + rest_tail @@ -267,8 +267,8 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0): # raise 'error, bvh node has no end and no children. bad file' # Removed temp for now - rest_tail_world = Vector(0.0, 0.0, 0.0) - rest_tail_local = Vector(0.0, 0.0, 0.0) + rest_tail_world = Vector((0.0, 0.0, 0.0)) + rest_tail_local = Vector((0.0, 0.0, 0.0)) for bvh_node_child in bvh_node.children: rest_tail_world += bvh_node_child.rest_head_world rest_tail_local += bvh_node_child.rest_head_local @@ -328,7 +328,7 @@ def bvh_node_dict2objects(context, bvh_nodes, IMPORT_START_FRAME=1, IMPORT_LOOP= lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current] rest_head_local = bvh_node.rest_head_local - bvh_node.temp.loc = rest_head_local + Vector(lx, ly, lz) + bvh_node.temp.loc = rest_head_local + Vector((lx, ly, lz)) bvh_node.temp.rot = rx, ry, rz @@ -347,7 +347,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM scn = context.scene #XXX scn.objects.selected = [] for ob in scn.objects: - ob.selected = False + ob.select = False scn.set_frame(IMPORT_START_FRAME) @@ -356,7 +356,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM scn.objects.link(arm_ob) - arm_ob.selected = True + arm_ob.select = True scn.objects.active = arm_ob print(scn.objects.active) @@ -426,7 +426,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM bvh_node.parent and\ bvh_node.parent.temp.name not in ZERO_AREA_BONES and\ bvh_node.parent.rest_tail_local == bvh_node.rest_head_local: - bvh_node.temp.connected = True + bvh_node.temp.use_connect = True # Replace the editbone with the editbone name, # to avoid memory errors accessing the editbone outside editmode @@ -458,13 +458,11 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM pose_bone = pose_bones[bone_name] pose_bone.rotation_mode = eul_order_lookup[tuple(bvh_node.rot_order)] - elif ROT_MODE == 'XYZ': - print(2) + elif ROT_MODE != 'QUATERNION': for pose_bone in pose_bones: - pose_bone.rotation_mode = 'XYZ' + pose_bone.rotation_mode = ROT_MODE else: # Quats default - print(3) pass context.scene.update() @@ -520,18 +518,18 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current + 1] if bvh_node.has_rot: - bone_rotation_matrix = Euler(rx, ry, rz).to_matrix().resize4x4() + bone_rotation_matrix = Euler((rx, ry, rz)).to_matrix().resize4x4() bone_rotation_matrix = bone_rest_matrix_inv * bone_rotation_matrix * bone_rest_matrix if ROT_MODE == 'QUATERNION': pose_bone.rotation_quaternion = bone_rotation_matrix.to_quat() else: - euler = bone_rotation_matrix.to_euler('XYZ', prev_euler[i]) # pose_bone.rotation_mode # TODO, XYZ default for now + euler = bone_rotation_matrix.to_euler(pose_bone.rotation_mode, prev_euler[i]) pose_bone.rotation_euler = euler prev_euler[i] = euler if bvh_node.has_loc: - pose_bone.location = (bone_rest_matrix_inv * TranslationMatrix(Vector(lx, ly, lz) - bvh_node.rest_head_local)).translation_part() + pose_bone.location = (bone_rest_matrix_inv * Matrix.Translation(Vector((lx, ly, lz)) - bvh_node.rest_head_local)).translation_part() if bvh_node.has_loc: pose_bone.keyframe_insert("location") @@ -563,23 +561,23 @@ class BvhImporter(bpy.types.Operator): bl_idname = "import_anim.bvh" bl_label = "Import BVH" - path = StringProperty(name="File Path", description="File path used for importing the OBJ file", maxlen=1024, default="") + filepath = StringProperty(name="File Path", description="Filepath used for importing the OBJ file", maxlen=1024, default="") scale = FloatProperty(name="Scale", description="Scale the BVH by this value", min=0.0001, max=1000000.0, soft_min=0.001, soft_max=100.0, default=0.1) frame_start = IntProperty(name="Start Frame", description="Starting frame for the animation", default=1) loop = BoolProperty(name="Loop", description="Loop the animation playback", default=False) rotate_mode = EnumProperty(items=( ('QUATERNION', "Quaternion", "Convert rotations to quaternions"), - # ('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"), + ('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"), ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"), - # ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"), - # ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"), - # ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"), - # ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"), - # ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX")), + ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"), + ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"), + ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"), + ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"), + ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"), ), name="Rotation", description="Rotation conversion.", - default='QUATERNION') + default='NATIVE') def execute(self, context): # print("Selected: " + context.active_object.name) @@ -587,7 +585,7 @@ class BvhImporter(bpy.types.Operator): t1 = time.time() print('\tparsing bvh...', end="") - bvh_nodes = read_bvh(context, self.properties.path, + bvh_nodes = read_bvh(context, self.properties.filepath, ROT_MODE=self.properties.rotate_mode, GLOBAL_SCALE=self.properties.scale) @@ -614,12 +612,10 @@ def menu_func(self, context): def register(): - bpy.types.register(BvhImporter) bpy.types.INFO_MT_file_import.append(menu_func) def unregister(): - bpy.types.unregister(BvhImporter) bpy.types.INFO_MT_file_import.remove(menu_func) if __name__ == "__main__": diff --git a/release/scripts/io/import_scene_3ds.py b/release/scripts/io/import_scene_3ds.py index 19bb734028b..d34b5ad0723 100644 --- a/release/scripts/io/import_scene_3ds.py +++ b/release/scripts/io/import_scene_3ds.py @@ -141,10 +141,10 @@ import os import time import struct -from import_scene_obj import unpack_face_list, load_image +from import_scene_obj import load_image import bpy -import Mathutils +import mathutils BOUNDS_3DS = [] @@ -266,12 +266,10 @@ def read_string(file): s += struct.unpack('<c', file.read(1))[0] #print 'string: ',s + #remove the null character from the string s = str(s[:-1], 'ASCII') # print("read string", s) - - #remove the null character from the string return s -# return s[:-1] ###################################################### # IMPORT @@ -300,7 +298,6 @@ def add_texture_to_material(image, texture, material, mapto): if image: texture.image = image -# if image: texture.setImage(image) # double check its an image. material.add_texture(texture, "UV", mapto) @@ -310,12 +307,12 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): contextObName = None contextLamp = [None, None] # object, Data contextMaterial = None - contextMatrix_rot = None # Blender.Mathutils.Matrix(); contextMatrix.identity() - #contextMatrix_tx = None # Blender.Mathutils.Matrix(); contextMatrix.identity() - contextMesh_vertls = None + contextMatrix_rot = None # Blender.mathutils.Matrix(); contextMatrix.identity() + #contextMatrix_tx = None # Blender.mathutils.Matrix(); contextMatrix.identity() + contextMesh_vertls = None # flat array: (verts * 3) contextMesh_facels = None contextMeshMaterials = {} # matname:[face_idxs] - contextMeshUV = None + contextMeshUV = None # flat array (verts * 2) TEXTURE_DICT = {} MATDICT = {} @@ -333,115 +330,71 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): # print STRUCT_SIZE_4x3MAT, ' STRUCT_SIZE_4x3MAT' def putContextMesh(myContextMesh_vertls, myContextMesh_facels, myContextMeshMaterials): - - materialFaces = set() # faces that have a material. Can optimize? - - # Now make copies with assigned materils. - - def makeMeshMaterialCopy(matName, faces): - ''' - Make a new mesh with only face the faces that use this material. - faces can be any iterable object - containing ints. - ''' - - faceVertUsers = [False] * len(myContextMesh_vertls) - ok = 0 - for fIdx in faces: - for vindex in myContextMesh_facels[fIdx]: - faceVertUsers[vindex] = True - if matName != None: # if matName is none then this is a set(), meaning we are using the untextured faces and do not need to store textured faces. - materialFaces.add(fIdx) - ok = 1 - - if not ok: - return - - myVertMapping = {} - vertMappingIndex = 0 - - vertsToUse = [i for i in range(len(myContextMesh_vertls)) if faceVertUsers[i]] - myVertMapping = dict( [ (ii, i) for i, ii in enumerate(vertsToUse) ] ) - - tempName= '%s_%s' % (contextObName, matName) # matName may be None. - bmesh = bpy.data.meshes.new(tempName) - - if matName == None: - img = None + + bmesh = bpy.data.meshes.new(contextObName) + if myContextMesh_vertls: + + bmesh.vertices.add(len(myContextMesh_vertls)//3) + bmesh.faces.add(len(myContextMesh_facels)) + bmesh.vertices.foreach_set("co", myContextMesh_vertls) + + eekadoodle_faces = [] + for v1, v2, v3 in myContextMesh_facels: + eekadoodle_faces.extend([v3, v1, v2, 0] if v3 == 0 else [v1, v2, v3, 0]) + bmesh.faces.foreach_set("vertices_raw", eekadoodle_faces) + + if bmesh.faces and contextMeshUV: + bmesh.uv_textures.new() + uv_faces = bmesh.uv_textures.active.data[:] else: - bmat = MATDICT[matName][1] - bmesh.add_material(bmat) -# bmesh.materials = [bmat] - try: img = TEXTURE_DICT[bmat.name] - except: img = None - -# bmesh_verts = bmesh.verts - if len(vertsToUse): - bmesh.add_geometry(len(vertsToUse), 0, len(faces)) - - # XXX why add extra vertex? -# bmesh_verts.extend( [Vector()] ) - bmesh.verts.foreach_set("co", [x for tup in [myContextMesh_vertls[i] for i in vertsToUse] for x in tup]) -# bmesh_verts.extend( [myContextMesh_vertls[i] for i in vertsToUse] ) - - # +1 because of DUMMYVERT - bmesh.faces.foreach_set("verts_raw", unpack_face_list([[myVertMapping[vindex] for vindex in myContextMesh_facels[fIdx]] for fIdx in faces])) -# face_mapping = bmesh.faces.extend( [ [ bmesh_verts[ myVertMapping[vindex]+1] for vindex in myContextMesh_facels[fIdx]] for fIdx in faces ], indexList=True ) - - if bmesh.faces and (contextMeshUV or img): - bmesh.add_uv_texture() - for ii, i in enumerate(faces): - - # Mapped index- faces may have not been added- if so, then map to the correct index - # BUGGY API - face_mapping is not always the right length -# map_index = face_mapping[ii] - - if 1: -# if map_index != None: - targetFace = bmesh.faces[ii] -# targetFace = bmesh.faces[map_index] - - uf = bmesh.active_uv_texture.data[ii] - - if contextMeshUV: - # v.index-1 because of the DUMMYVERT - uvs = [contextMeshUV[vindex] for vindex in myContextMesh_facels[i]] - - if len(myContextMesh_facels[i]) == 3: - uf.uv1, uf.uv2, uf.uv3, uf.uv4 = uvs + [(0.0, 0.0)] - else: - uf.uv1, uf.uv2, uf.uv3, uf.uv4 = uvs -# targetFace.uv = [contextMeshUV[vindex] for vindex in myContextMesh_facels[i]] - if img: - uf.image = img - - # to get this image to show up in 'Textured' shading mode - uf.tex = True - - # bmesh.transform(contextMatrix) - ob = bpy.data.objects.new(tempName, bmesh) - SCN.objects.link(ob) -# ob = SCN_OBJECTS.new(bmesh, tempName) - ''' - if contextMatrix_tx: - ob.setMatrix(contextMatrix_tx) - ''' - - if contextMatrix_rot: - # ob.matrix = [x for row in contextMatrix_rot for x in row] - ob.matrix = contextMatrix_rot -# ob.setMatrix(contextMatrix_rot) - - importedObjects.append(ob) - bmesh.update() -# bmesh.calcNormals() - - for matName, faces in myContextMeshMaterials.items(): - makeMeshMaterialCopy(matName, faces) - - if len(materialFaces) != len(myContextMesh_facels): - # Invert material faces. - makeMeshMaterialCopy(None, set(range(len( myContextMesh_facels ))) - materialFaces) - #raise 'Some UnMaterialed faces', len(contextMesh.faces) + uv_faces = None + + for mat_idx, (matName, faces) in enumerate(myContextMeshMaterials.items()): + if matName is None: + bmat = None + else: + bmat = MATDICT[matName][1] + img = TEXTURE_DICT.get(bmat.name) + + bmesh.materials.link(bmat) # can be None + + if uv_faces and img: + for fidx in faces: + bmesh.faces[fidx].material_index = mat_idx + uf = uv_faces[fidx] + uf.image = img + uf.use_image = True + else: + for fidx in faces: + bmesh.faces[fidx].material_index = mat_idx + + if uv_faces: + for fidx, uf in enumerate(uv_faces): + face = myContextMesh_facels[fidx] + v1, v2, v3 = face + + # eekadoodle + if v3 == 0: + v1, v2, v3 = v3, v1, v2 + + uf.uv1 = contextMeshUV[v1 * 2:(v1 * 2) + 2] + uf.uv2 = contextMeshUV[v2 * 2:(v2 * 2) + 2] + uf.uv3 = contextMeshUV[v3 * 2:(v3 * 2) + 2] + # always a tri + + ob = bpy.data.objects.new(tempName, bmesh) + SCN.objects.link(ob) + + ''' + if contextMatrix_tx: + ob.setMatrix(contextMatrix_tx) + ''' + + if contextMatrix_rot: + ob.matrix_world = contextMatrix_rot + + importedObjects.append(ob) + bmesh.update() #a spare chunk new_chunk = chunk() @@ -460,9 +413,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): return [float(col)/255 for col in struct.unpack('<3B', temp_data)] # data [0,1,2] == rgb def read_texture(new_chunk, temp_chunk, name, mapto): - new_texture = bpy.data.textures.new(name) - new_texture.type = 'IMAGE' - new_texture = new_texture.recast_type() + new_texture = bpy.data.textures.new(name, type='IMAGE') img = None while (new_chunk.bytes_read < new_chunk.length): @@ -669,14 +620,10 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): new_chunk.bytes_read += 2 # print 'number of verts: ', num_verts - def getvert(): - temp_data = struct.unpack('<3f', file.read(STRUCT_SIZE_3FLOAT)) - new_chunk.bytes_read += STRUCT_SIZE_3FLOAT #12: 3 floats x 4 bytes each - return temp_data - - #contextMesh.verts.extend( [Vector(),] ) # DUMMYVERT! - remove when blenders internals are fixed. - contextMesh_vertls = [getvert() for i in range(num_verts)] - + contextMesh_vertls = struct.unpack('<%df' % (num_verts * 3), file.read(STRUCT_SIZE_3FLOAT * num_verts)) + new_chunk.bytes_read += STRUCT_SIZE_3FLOAT * num_verts + # dummyvert is not used atm! + #print 'object verts: bytes read: ', new_chunk.bytes_read elif (new_chunk.ID == OBJECT_FACES): @@ -686,15 +633,11 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): new_chunk.bytes_read += 2 #print 'number of faces: ', num_faces - def getface(): - # print '\ngetting a face' - temp_data = file.read(STRUCT_SIZE_4UNSIGNED_SHORT) - new_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each - v1,v2,v3,dummy = struct.unpack('<4H', temp_data) - return v1, v2, v3 - - contextMesh_facels = [ getface() for i in range(num_faces) ] - + # print '\ngetting a face' + temp_data = file.read(STRUCT_SIZE_4UNSIGNED_SHORT * num_faces) + new_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT * num_faces #4 short ints x 2 bytes each + contextMesh_facels = struct.unpack('<%dH' % (num_faces * 4), temp_data) + contextMesh_facels = [contextMesh_facels[i - 3:i] for i in range(3, (num_faces * 4) + 3, 4)] elif (new_chunk.ID == OBJECT_MATERIAL): # print 'elif (new_chunk.ID == OBJECT_MATERIAL):' @@ -705,12 +648,11 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): num_faces_using_mat = struct.unpack('<H', temp_data)[0] new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT - def getmat(): - temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT) - new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT - return struct.unpack('<H', temp_data)[0] + + temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT * num_faces_using_mat) + new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT * num_faces_using_mat - contextMeshMaterials[material_name]= [ getmat() for i in range(num_faces_using_mat) ] + contextMeshMaterials[material_name]= struct.unpack("<%dH" % (num_faces_using_mat), temp_data) #look up the material in all the materials @@ -719,12 +661,9 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): num_uv = struct.unpack('<H', temp_data)[0] new_chunk.bytes_read += 2 - def getuv(): - temp_data = file.read(STRUCT_SIZE_2FLOAT) - new_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each - return Mathutils.Vector( struct.unpack('<2f', temp_data) ) - - contextMeshUV = [ getuv() for i in range(num_uv) ] + temp_data = file.read(STRUCT_SIZE_2FLOAT * num_uv) + new_chunk.bytes_read += STRUCT_SIZE_2FLOAT * num_uv + contextMeshUV = struct.unpack('<%df' % (num_uv * 2), temp_data) elif (new_chunk.ID == OBJECT_TRANS_MATRIX): # How do we know the matrix size? 54 == 4x4 48 == 4x3 @@ -732,7 +671,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): data = list( struct.unpack('<ffffffffffff', temp_data) ) new_chunk.bytes_read += STRUCT_SIZE_4x3MAT - contextMatrix_rot = Mathutils.Matrix(\ + contextMatrix_rot = mathutils.Matrix(\ data[:3] + [0],\ data[3:6] + [0],\ data[6:9] + [0],\ @@ -740,7 +679,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): ''' - contextMatrix_rot = Blender.Mathutils.Matrix(\ + contextMatrix_rot = Blender.mathutils.Matrix(\ data[:3] + [0],\ data[3:6] + [0],\ data[6:9] + [0],\ @@ -748,14 +687,14 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): ''' ''' - contextMatrix_rot = Blender.Mathutils.Matrix(\ + contextMatrix_rot = Blender.mathutils.Matrix(\ data[:3] ,\ data[3:6],\ data[6:9]) ''' ''' - contextMatrix_rot = Blender.Mathutils.Matrix() + contextMatrix_rot = Blender.mathutils.Matrix() m = 0 for j in xrange(4): for i in xrange(3): @@ -773,7 +712,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): #print contextMatrix_rot contextMatrix_rot.invert() #print contextMatrix_rot - #contextMatrix_tx = Blender.Mathutils.TranslationMatrix(0.5 * Blender.Mathutils.Vector(data[9:])) + #contextMatrix_tx = mathutils.Matrix.Translation(0.5 * Blender.mathutils.Vector(data[9:])) #contextMatrix_tx.invert() #tx.invert() @@ -808,7 +747,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): # FINISHED LOOP # There will be a number of objects still not added - if contextMesh_facels != None: + if CreateBlenderObject: putContextMesh(contextMesh_vertls, contextMesh_facels, contextMeshMaterials) def load_3ds(filename, context, IMPORT_CONSTRAIN_BOUNDS=10.0, IMAGE_SEARCH=True, APPLY_MATRIX=False): @@ -885,14 +824,12 @@ def load_3ds(filename, context, IMPORT_CONSTRAIN_BOUNDS=10.0, IMAGE_SEARCH=True, # REMOVE DUMMYVERT, - remove this in the next release when blenders internal are fixed. - -# for ob in importedObjects: -# if ob.type == 'MESH': -# # if ob.type=='Mesh': -# me = ob.getData(mesh=1) -# me.verts.delete([me.verts[0],]) -# if not APPLY_MATRIX: -# me.transform(ob.matrixWorld.copy().invert()) + for ob in importedObjects: + if ob.type == 'MESH': + me = ob.data +# me.vertices.delete([me.vertices[0],]) # XXX, todo + if not APPLY_MATRIX: + me.transform(ob.matrix_world.copy().invert()) # Done DUMMYVERT """ @@ -946,11 +883,11 @@ def load_3ds(filename, context, IMPORT_CONSTRAIN_BOUNDS=10.0, IMAGE_SEARCH=True, SCALE/=10 # SCALE Matrix - SCALE_MAT = Mathutils.Matrix([SCALE,0,0,0],[0,SCALE,0,0],[0,0,SCALE,0],[0,0,0,1]) -# SCALE_MAT = Blender.Mathutils.Matrix([SCALE,0,0,0],[0,SCALE,0,0],[0,0,SCALE,0],[0,0,0,1]) + SCALE_MAT = mathutils.Matrix([SCALE,0,0,0],[0,SCALE,0,0],[0,0,SCALE,0],[0,0,0,1]) +# SCALE_MAT = Blender.mathutils.Matrix([SCALE,0,0,0],[0,SCALE,0,0],[0,0,SCALE,0],[0,0,0,1]) for ob in importedObjects: - ob.setMatrix(ob.matrixWorld * SCALE_MAT) + ob.matrix_world = ob.matrix_world * SCALE_MAT # Done constraining to bounds. @@ -1011,17 +948,19 @@ class IMPORT_OT_autodesk_3ds(bpy.types.Operator): # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. + filepath = StringProperty(name="File Path", description="Filepath used for importing the 3DS file", maxlen= 1024, default= "") - path = StringProperty(name="File Path", description="File path used for importing the 3DS file", maxlen= 1024, default= "") - filename = StringProperty(name="File Name", description="Name of the file.") - directory = StringProperty(name="Directory", description="Directory of the file.") - -# size_constraint = FloatProperty(name="Size Constraint", description="Scale the model by 10 until it reacehs the size constraint. Zero Disables.", min=0.0, max=1000.0, soft_min=0.0, soft_max=1000.0, default=10.0), -# search_images = BoolProperty(name="Image Search", description="Search subdirectories for any assosiated images (Warning, may be slow)", default=True), -# apply_matrix = BoolProperty(name="Transform Fix", description="Workaround for object transformations importing incorrectly", default=False), + constrain_size = FloatProperty(name="Size Constraint", description="Scale the model by 10 until it reacehs the size constraint. Zero Disables.", min=0.0, max=1000.0, soft_min=0.0, soft_max=1000.0, default=10.0) + search_images = BoolProperty(name="Image Search", description="Search subdirectories for any assosiated images (Warning, may be slow)", default=True) + apply_transform = BoolProperty(name="Apply Transform", description="Workaround for object transformations importing incorrectly", default=False) def execute(self, context): - load_3ds(self.properties.path, context, 0.0, False, False) + load_3ds(self.properties.filepath, + context, + IMPORT_CONSTRAIN_BOUNDS=self.properties.constrain_size, + IMAGE_SEARCH=self.properties.search_images, + APPLY_MATRIX=self.properties.apply_transform) + return {'FINISHED'} def invoke(self, context, event): @@ -1034,11 +973,9 @@ def menu_func(self, context): self.layout.operator(IMPORT_OT_autodesk_3ds.bl_idname, text="3D Studio (.3ds)") def register(): - bpy.types.register(IMPORT_OT_autodesk_3ds) bpy.types.INFO_MT_file_import.append(menu_func) def unregister(): - bpy.types.unregister(IMPORT_OT_autodesk_3ds) bpy.types.INFO_MT_file_import.remove(menu_func) # NOTES: diff --git a/release/scripts/io/import_scene_obj.py b/release/scripts/io/import_scene_obj.py index 9a147b06b07..88caaf7eeb5 100644 --- a/release/scripts/io/import_scene_obj.py +++ b/release/scripts/io/import_scene_obj.py @@ -30,62 +30,11 @@ Run this script from "File->Import" menu and then load the desired OBJ file. Note, This loads mesh objects and materials only, nurbs and curves are not supported. """ -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# Script copyright (C) Campbell J Barton 2007 -# -# 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. -# -# ***** END GPL LICENCE BLOCK ***** -# -------------------------------------------------------------------------- - import os import time import bpy -import Mathutils -import Geometry - -# from Blender import Mesh, Draw, Window, Texture, Material, sys -# # import BPyMesh -# import BPyImage -# import BPyMessages - -# try: import os -# except: os= False - -# Generic path functions -def stripFile(path): - '''Return directory, where the file is''' - lastSlash= max(path.rfind('\\'), path.rfind('/')) - if lastSlash != -1: - path= path[:lastSlash] - return '%s%s' % (path, os.sep) -# return '%s%s' % (path, sys.sep) - -def stripPath(path): - '''Strips the slashes from the back of a string''' - return path.split('/')[-1].split('\\')[-1] - -def stripExt(name): # name is a string - '''Strips the prefix off the name before writing''' - index= name.rfind('.') - if index != -1: - return name[ : index ] - else: - return name -# end path funcs +import mathutils +from geometry import PolyFill def unpack_list(list_of_tuples): l = [] @@ -95,23 +44,21 @@ def unpack_list(list_of_tuples): # same as above except that it adds 0 for triangle faces def unpack_face_list(list_of_tuples): - l = [] - for t in list_of_tuples: - face = [i for i in t] - - if len(face) != 3 and len(face) != 4: - raise RuntimeError("{0} vertices in face.".format(len(face))) + # allocate the entire list + flat_ls = [0] * (len(list_of_tuples) * 4) + i = 0 - # rotate indices if the 4th is 0 - if len(face) == 4 and face[3] == 0: - face = [face[3], face[0], face[1], face[2]] - - if len(face) == 3: - face.append(0) + for t in list_of_tuples: + if len(t) == 3: + if t[2] == 0: + t = t[1], t[2], t[0] + else: # assuem quad + if t[3] == 0 or t[2] == 0: + t = t[2], t[3], t[0], t[1] - l.extend(face) - - return l + flat_ls[i:i + len(t)] = t + i += 4 + return flat_ls def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True): ''' @@ -127,7 +74,7 @@ def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True): if not set: # Need sets for this, otherwise do a normal fill. PREF_FIX_LOOPS= False - Vector= Mathutils.Vector + Vector= mathutils.Vector if not indices: return [] @@ -152,13 +99,13 @@ def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True): if type(from_data) in (tuple, list): verts= [Vector(from_data[i]) for ii, i in enumerate(indices)] else: - verts= [from_data.verts[i].co for ii, i in enumerate(indices)] + verts= [from_data.vertices[i].co for ii, i in enumerate(indices)] for i in range(len(verts)-1, 0, -1): # same as reversed(xrange(1, len(verts))): if verts[i][1]==verts[i-1][0]: verts.pop(i-1) - fill= Geometry.PolyFill([verts]) + fill= PolyFill([verts]) else: ''' @@ -169,7 +116,7 @@ def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True): if type(from_data) in (tuple, list): verts= [vert_treplet(Vector(from_data[i]), ii) for ii, i in enumerate(indices)] else: - verts= [vert_treplet(from_data.verts[i].co, ii) for ii, i in enumerate(indices)] + verts= [vert_treplet(from_data.vertices[i].co, ii) for ii, i in enumerate(indices)] edges= [(i, i-1) for i in range(len(verts))] if edges: @@ -266,7 +213,7 @@ def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True): vert_map[i+ii]= vert[2] ii+=len(verts) - fill= Geometry.PolyFill([ [v[0] for v in loop] for loop in loop_list ]) + fill= PolyFill([ [v[0] for v in loop] for loop in loop_list ]) #draw_loops(loop_list) #raise 'done loop' # map to original indicies @@ -318,24 +265,28 @@ def load_image(imagepath, dirname): if os.path.exists(imagepath): return bpy.data.images.load(imagepath) - variants = [os.path.join(dirname, imagepath), os.path.join(dirname, os.path.basename(imagepath))] + variants = [imagepath, os.path.join(dirname, imagepath), os.path.join(dirname, os.path.basename(imagepath))] - for path in variants: - if os.path.exists(path): - return bpy.data.images.load(path) - else: - print(path, "doesn't exist") + for filepath in variants: + for nfilepath in (filepath, bpy.path.resolve_ncase(filepath)): + if os.path.exists(nfilepath): + return bpy.data.images.load(nfilepath) # TODO comprehensiveImageLoad also searched in bpy.config.textureDir return None def obj_image_load(imagepath, DIR, IMAGE_SEARCH): - if '_' in imagepath: image= load_image(imagepath.replace('_', ' '), DIR) - if image: return image + if image: + return image - return load_image(imagepath, DIR) + image = load_image(imagepath, DIR) + if image: + return image + + print("failed to load '%s' doesn't exist", imagepath) + return None # def obj_image_load(imagepath, DIR, IMAGE_SEARCH): # ''' @@ -360,25 +311,22 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_ Create all the used materials in this obj, assign colors and images to the materials from all referenced material libs ''' - DIR= stripFile(filepath) + DIR= os.path.dirname(filepath) #==================================================================================# # This function sets textures defined in .mtl file # #==================================================================================# def load_material_image(blender_material, context_material_name, imagepath, type): - texture= bpy.data.textures.new(type) - texture.type= 'IMAGE' - texture = texture.recast_type() # Workaround for limitation in rna api. -# texture= bpy.data.textures.new(type) -# texture.setType('Image') + texture= bpy.data.textures.new(name=type, type='IMAGE') # Absolute path - c:\.. etc would work here - image= obj_image_load(imagepath, DIR, IMAGE_SEARCH) - has_data = image.has_data if image else False + image = obj_image_load(imagepath, DIR, IMAGE_SEARCH) + has_data = False if image: texture.image = image + has_data = image.has_data # Adds textures for materials (rendering) if type == 'Kd': @@ -386,38 +334,32 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_ # Image has alpha # XXX bitmask won't work? - blender_material.add_texture(texture, "UV", ("COLOR", "ALPHA")) + blender_material.add_texture(texture, 'UV', {'COLOR', 'ALPHA'}) texture.mipmap = True texture.interpolation = True texture.use_alpha = True - blender_material.z_transparency = True + blender_material.use_transparency = True blender_material.alpha = 0.0 - -# blender_material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL | Texture.MapTo.ALPHA) -# texture.setImageFlags('MipMap', 'InterPol', 'UseAlpha') -# blender_material.mode |= Material.Modes.ZTRANSP -# blender_material.alpha = 0.0 else: - blender_material.add_texture(texture, "UV", "COLOR") -# blender_material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL) + blender_material.add_texture(texture, 'UV', 'COLOR') # adds textures to faces (Textured/Alt-Z mode) # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func. unique_material_images[context_material_name]= image, has_data # set the texface image elif type == 'Ka': - blender_material.add_texture(texture, "UV", "AMBIENT") + blender_material.add_texture(texture, 'UV', 'AMBIENT') # blender_material.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.CMIR) # TODO- Add AMB to BPY API elif type == 'Ks': - blender_material.add_texture(texture, "UV", "SPECULARITY") + blender_material.add_texture(texture, 'UV', 'SPECULARITY') # blender_material.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC) elif type == 'Bump': - blender_material.add_texture(texture, "UV", "NORMAL") + blender_material.add_texture(texture, 'UV', 'NORMAL') # blender_material.setTexture(3, texture, Texture.TexCo.UV, Texture.MapTo.NOR) elif type == 'D': - blender_material.add_texture(texture, "UV", "ALPHA") + blender_material.add_texture(texture, 'UV', 'ALPHA') blender_material.z_transparency = True blender_material.alpha = 0.0 # blender_material.setTexture(4, texture, Texture.TexCo.UV, Texture.MapTo.ALPHA) @@ -426,15 +368,14 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_ # Todo, unset deffuse material alpha if it has an alpha channel elif type == 'refl': - blender_material.add_texture(texture, "UV", "REFLECTION") + blender_material.add_texture(texture, 'UV', 'REFLECTION') # blender_material.setTexture(5, texture, Texture.TexCo.UV, Texture.MapTo.REF) # Add an MTL with the same name as the obj if no MTLs are spesified. - temp_mtl= stripExt(stripPath(filepath))+ '.mtl' + temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + '.mtl' - if os.path.exists(DIR + temp_mtl) and temp_mtl not in material_libs: -# if sys.exists(DIR + temp_mtl) and temp_mtl not in material_libs: + if os.path.exists(os.path.join(DIR, temp_mtl)) and temp_mtl not in material_libs: material_libs.append( temp_mtl ) del temp_mtl @@ -448,11 +389,9 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_ unique_material_images[None]= None, False for libname in material_libs: - mtlpath= DIR + libname + mtlpath= os.path.join(DIR, libname) if not os.path.exists(mtlpath): -# if not sys.exists(mtlpath): - #print '\tError Missing MTL: "%s"' % mtlpath - pass + print ("\tError Missing MTL: '%s'" % mtlpath) else: #print '\t\tloading mtl: "%s"' % mtlpath context_material= None @@ -471,22 +410,16 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_ line_lower= line.lower().lstrip() if line_lower.startswith('ka'): context_material.mirror_color = (float(line_split[1]), float(line_split[2]), float(line_split[3])) -# context_material.setMirCol((float(line_split[1]), float(line_split[2]), float(line_split[3]))) elif line_lower.startswith('kd'): context_material.diffuse_color = (float(line_split[1]), float(line_split[2]), float(line_split[3])) -# context_material.setRGBCol((float(line_split[1]), float(line_split[2]), float(line_split[3]))) elif line_lower.startswith('ks'): context_material.specular_color = (float(line_split[1]), float(line_split[2]), float(line_split[3])) -# context_material.setSpecCol((float(line_split[1]), float(line_split[2]), float(line_split[3]))) elif line_lower.startswith('ns'): context_material.specular_hardness = int((float(line_split[1])*0.51)) -# context_material.setHardness( int((float(line_split[1])*0.51)) ) elif line_lower.startswith('ni'): # Refraction index - context_material.raytrace_transparency.ior = max(1, min(float(line_split[1]), 3)) -# context_material.setIOR( max(1, min(float(line_split[1]), 3))) # Between 1 and 3 + context_material.raytrace_transparency.ior = max(1, min(float(line_split[1]), 3)) # Between 1 and 3 elif line_lower.startswith('d') or line_lower.startswith('tr'): context_material.alpha = float(line_split[1]) -# context_material.setAlpha(float(line_split[1])) elif line_lower.startswith('map_ka'): img_filepath= line_value(line.split()) if img_filepath: @@ -517,49 +450,32 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_ -def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, SPLIT_MATERIALS): +def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP): ''' - Takes vert_loc and faces, and seperates into multiple sets of + Takes vert_loc and faces, and separates into multiple sets of (verts_loc, faces, unique_materials, dataname) ''' - filename = stripExt(stripPath(filepath)) + filename = os.path.splitext((os.path.basename(filepath)))[0] - if not SPLIT_OB_OR_GROUP and not SPLIT_MATERIALS: + if not SPLIT_OB_OR_GROUP: # use the filename for the object name since we arnt chopping up the mesh. return [(verts_loc, faces, unique_materials, filename)] - def key_to_name(key): # if the key is a tuple, join it to make a string - if type(key) == tuple: - return '%s_%s' % key - elif not key: + if not key: return filename # assume its a string. make sure this is true if the splitting code is changed else: return key # Return a key that makes the faces unique. - if SPLIT_OB_OR_GROUP and not SPLIT_MATERIALS: - def face_key(face): - return face[4] # object - - elif not SPLIT_OB_OR_GROUP and SPLIT_MATERIALS: - def face_key(face): - return face[2] # material - - else: # Both - def face_key(face): - return face[4], face[2] # object,material - - face_split_dict= {} oldkey= -1 # initialize to a value that will never match the key for face in faces: - - key= face_key(face) + key= face[4] if oldkey != key: # Check the key has changed. @@ -584,7 +500,6 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, vert_remap[i]= new_index # set the new remapped index so we only add once and can reference next time. face_vert_loc_indicies[enum] = new_index # remap to the local index verts_split.append( verts_loc[i] ) # add the vert to the local verts - else: face_vert_loc_indicies[enum] = vert_remap[i] # remap to the local index @@ -594,12 +509,11 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, faces_split.append(face) - # remove one of the itemas and reorder return [(value[0], value[1], value[2], key_to_name(key)) for key, value in list(face_split_dict.items())] -def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc, verts_tex, faces, unique_materials, unique_material_images, unique_smooth_groups, vertex_groups, dataname): +def create_mesh(new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc, verts_tex, faces, unique_materials, unique_material_images, unique_smooth_groups, vertex_groups, dataname): ''' Takes all the data gathered and generates a mesh, adding the new object to new_objects deals with fgons, sharp edges and assigning materials @@ -609,7 +523,7 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l if unique_smooth_groups: sharp_edges= {} - smooth_group_users= dict([ (context_smooth_group, {}) for context_smooth_group in list(unique_smooth_groups.keys()) ]) + smooth_group_users = {context_smooth_group: {} for context_smooth_group in list(unique_smooth_groups.keys())} context_smooth_group_old= -1 # Split fgons into tri's @@ -663,14 +577,14 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l if has_ngons and len_face_vert_loc_indicies > 4: ngon_face_indices= BPyMesh_ngon(verts_loc, face_vert_loc_indicies) - faces.extend(\ - [(\ - [face_vert_loc_indicies[ngon[0]], face_vert_loc_indicies[ngon[1]], face_vert_loc_indicies[ngon[2]] ],\ - [face_vert_tex_indicies[ngon[0]], face_vert_tex_indicies[ngon[1]], face_vert_tex_indicies[ngon[2]] ],\ - context_material,\ - context_smooth_group,\ - context_object)\ - for ngon in ngon_face_indices]\ + faces.extend( + [( + [face_vert_loc_indicies[ngon[0]], face_vert_loc_indicies[ngon[1]], face_vert_loc_indicies[ngon[2]] ], + [face_vert_tex_indicies[ngon[0]], face_vert_tex_indicies[ngon[1]], face_vert_tex_indicies[ngon[2]] ], + context_material, + context_smooth_group, + context_object) + for ngon in ngon_face_indices] ) # edges to make fgons @@ -704,7 +618,7 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l # map the material names to an index - material_mapping= dict([(name, i) for i, name in enumerate(unique_materials)]) # enumerate over unique_materials keys() + material_mapping = {name: i for i, name in enumerate(unique_materials)} # enumerate over unique_materials keys() materials= [None] * len(unique_materials) @@ -715,30 +629,25 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l # make sure the list isnt too big for material in materials: - me.add_material(material) - #me.verts.extend([(0,0,0)]) # dummy vert + me.materials.link(material) - me.add_geometry(len(verts_loc), 0, len(faces)) + me.vertices.add(len(verts_loc)) + me.faces.add(len(faces)) # verts_loc is a list of (x, y, z) tuples - me.verts.foreach_set("co", unpack_list(verts_loc)) -# me.verts.extend(verts_loc) + me.vertices.foreach_set("co", unpack_list(verts_loc)) # faces is a list of (vert_indices, texco_indices, ...) tuples # XXX faces should contain either 3 or 4 verts # XXX no check for valid face indices - me.faces.foreach_set("verts_raw", unpack_face_list([f[0] for f in faces])) -# face_mapping= me.faces.extend([f[0] for f in faces], indexList=True) + me.faces.foreach_set("vertices_raw", unpack_face_list([f[0] for f in faces])) if verts_tex and me.faces: - me.add_uv_texture() -# me.faceUV= 1 - # TEXMODE= Mesh.FaceModes['TEX'] + me.uv_textures.new() context_material_old= -1 # avoid a dict lookup mat= 0 # rare case it may be un-initialized. me_faces= me.faces -# ALPHA= Mesh.FaceTranspModes.ALPHA for i, face in enumerate(faces): if len(face[0]) < 2: @@ -747,14 +656,8 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l if CREATE_EDGES: edges.append(face[0]) else: -# face_index_map= face_mapping[i] - - # since we use foreach_set to add faces, all of them are added - if 1: -# if face_index_map!=None: # None means the face wasnt added blender_face = me.faces[i] -# blender_face= me_faces[face_index_map] face_vert_loc_indicies,\ face_vert_tex_indicies,\ @@ -765,7 +668,7 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l if context_smooth_group: - blender_face.smooth= True + blender_face.use_smooth = True if context_material: if context_material_old is not context_material: @@ -781,14 +684,12 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l blender_tface= me.uv_textures[0].data[i] if context_material: - image, has_data= unique_material_images[context_material] + image, has_data = unique_material_images[context_material] if image: # Can be none if the material dosnt have an image. - blender_tface.image= image -# blender_face.image= image - if has_data: -# if has_data and image.depth == 32: - blender_tface.transp = 'ALPHA' -# blender_face.transp |= ALPHA + blender_tface.image = image + blender_tface.use_image = True + if has_data and image.depth == 32: + blender_tface.blend_type = 'ALPHA' # BUG - Evil eekadoodle problem where faces that have vert index 0 location at 3 or 4 are shuffled. if len(face_vert_loc_indicies)==4: @@ -814,10 +715,10 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l if CREATE_EDGES: - me.add_geometry(0, len(edges), 0) + me.edges.add(len(edges)) # edges should be a list of (a, b) tuples - me.edges.foreach_set("verts", unpack_list(edges)) + me.edges.foreach_set("vertices", unpack_list(edges)) # me_edges.extend( edges ) # del me_edges @@ -832,8 +733,8 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l # if CREATE_FGONS and fgon_edges: # for fgon_edge in fgon_edges.keys(): # for ed in me.edges: -# if edges_match(fgon_edge, ed.verts): -# ed.fgon = True +# if edges_match(fgon_edge, ed.vertices): +# ed.is_fgon = True # if CREATE_FGONS and fgon_edges: # FGON= Mesh.EdgeFlags.FGON @@ -846,8 +747,8 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l # if unique_smooth_groups and sharp_edges: # for sharp_edge in sharp_edges.keys(): # for ed in me.edges: -# if edges_match(sharp_edge, ed.verts): -# ed.sharp = True +# if edges_match(sharp_edge, ed.vertices): +# ed.use_edge_sharp = True # if unique_smooth_groups and sharp_edges: # SHARP= Mesh.EdgeFlags.SHARP @@ -860,21 +761,18 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l # me.calcNormals() ob= bpy.data.objects.new("Mesh", me) - scn.objects.link(ob) new_objects.append(ob) # Create the vertex groups. No need to have the flag passed here since we test for the # content of the vertex_groups. If the user selects to NOT have vertex groups saved then # the following test will never run for group_name, group_indicies in vertex_groups.items(): - group= ob.add_vertex_group(group_name) -# me.addVertGroup(group_name) + group= ob.vertex_groups.new(group_name) for vertex_index in group_indicies: - ob.add_vertex_to_group(vertex_index, group, 1.0, 'REPLACE') -# me.assignVertsToGroup(group_name, group_indicies, 1.00, Mesh.AssignModes.REPLACE) + ob.vertex_groups.assign(vertex_index, group, 1.0, 'REPLACE') -def create_nurbs(scn, context_nurbs, vert_loc, new_objects): +def create_nurbs(context_nurbs, vert_loc, new_objects): ''' Add nurbs object to blender, only support one type at the moment ''' @@ -899,21 +797,14 @@ def create_nurbs(scn, context_nurbs, vert_loc, new_objects): print('\tWarning, surfaces not supported') return - cu = bpy.data.curves.new(name, 'Curve') - cu.flag |= 1 # 3D curve + cu = bpy.data.curves.new(name, 'CURVE') + cu.dimensions = '3D' - nu = None - for pt in curv_idx: + nu = cu.splines.new('NURBS') + nu.points.add(len(curv_idx) - 1) # a point is added to start with + nu.points.foreach_set("co", [co_axis for vt_idx in curv_idx for co_axis in (vert_loc[vt_idx] + (1.0,))]) - pt = vert_loc[pt] - pt = (pt[0], pt[1], pt[2], 1.0) - - if nu == None: - nu = cu.appendNurb(pt) - else: - nu.append(pt) - - nu.orderU = deg[0]+1 + nu.order_u = deg[0] + 1 # get for endpoint flag from the weighting if curv_range and len(parm_u) > deg[0]+1: @@ -932,7 +823,7 @@ def create_nurbs(scn, context_nurbs, vert_loc, new_objects): do_endpoints = False if do_endpoints: - nu.flagU |= 2 + nu.use_endpoint_u = True # close @@ -947,10 +838,11 @@ def create_nurbs(scn, context_nurbs, vert_loc, new_objects): break if do_closed: - nu.flagU |= 1 + nu.use_cyclic_u = True ''' + + ob= bpy.data.objects.new("Nurb", cu) - ob = scn.objects.new(cu) new_objects.append(ob) @@ -1271,11 +1163,9 @@ def load_obj(filepath, verts_loc[:] = [(v[0], v[2], -v[1]) for v in verts_loc] # deselect all -# if context.selected_objects: -# bpy.ops.OBJECT_OT_select_all() + bpy.ops.object.select_all(action='DESELECT') scene = context.scene -# scn = bpy.data.scenes.active # scn.objects.selected = [] new_objects= [] # put new objects here @@ -1284,13 +1174,20 @@ def load_obj(filepath, if SPLIT_OBJECTS or SPLIT_GROUPS: SPLIT_OB_OR_GROUP = True else: SPLIT_OB_OR_GROUP = False - for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, SPLIT_MATERIALS): + for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP): # Create meshes from the data, warning 'vertex_groups' wont support splitting - create_mesh(scene, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc_split, verts_tex, faces_split, unique_materials_split, unique_material_images, unique_smooth_groups, vertex_groups, dataname) + create_mesh(new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc_split, verts_tex, faces_split, unique_materials_split, unique_material_images, unique_smooth_groups, vertex_groups, dataname) # nurbs support -# for context_nurbs in nurbs: -# create_nurbs(scn, context_nurbs, verts_loc, new_objects) + for context_nurbs in nurbs: + create_nurbs(context_nurbs, verts_loc, new_objects) + + # Create new obj + for obj in new_objects: + base = scene.objects.link(obj) + base.select = True + + scene.update() axis_min= [ 1000000000]*3 @@ -1333,14 +1230,13 @@ def load_obj_ui(filepath, BATCH_LOAD= False): if BPyMessages.Error_NoFile(filepath): return - global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, CLAMP_SIZE, IMAGE_SEARCH, POLYGROUPS, KEEP_VERT_ORDER, ROTATE_X90 + global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, CLAMP_SIZE, IMAGE_SEARCH, POLYGROUPS, KEEP_VERT_ORDER, ROTATE_X90 CREATE_SMOOTH_GROUPS= Draw.Create(0) CREATE_FGONS= Draw.Create(1) CREATE_EDGES= Draw.Create(1) SPLIT_OBJECTS= Draw.Create(0) SPLIT_GROUPS= Draw.Create(0) - SPLIT_MATERIALS= Draw.Create(0) CLAMP_SIZE= Draw.Create(10.0) IMAGE_SEARCH= Draw.Create(1) POLYGROUPS= Draw.Create(0) @@ -1359,7 +1255,6 @@ def load_obj_ui(filepath, BATCH_LOAD= False): 'Separate objects from obj...',\ ('Object', SPLIT_OBJECTS, 'Import OBJ Objects into Blender Objects'),\ ('Group', SPLIT_GROUPS, 'Import OBJ Groups into Blender Objects'),\ - ('Split Materials', SPLIT_MATERIALS, 'Import each material into a seperate mesh'),\ 'Options...',\ ('Keep Vert Order', KEEP_VERT_ORDER, 'Keep vert and face order, disables some other options.'),\ ('Clamp Scale:', CLAMP_SIZE, 0.0, 1000.0, 'Clamp the size to this maximum (Zero to Disable)'),\ @@ -1372,7 +1267,6 @@ def load_obj_ui(filepath, BATCH_LOAD= False): if KEEP_VERT_ORDER.val: SPLIT_OBJECTS.val = False SPLIT_GROUPS.val = False - SPLIT_MATERIALS.val = False ''' @@ -1394,25 +1288,25 @@ def load_obj_ui(filepath, BATCH_LOAD= False): GLOBALS['EVENT'] = e def do_split(e,v): - global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER, POLYGROUPS - if SPLIT_OBJECTS.val or SPLIT_GROUPS.val or SPLIT_MATERIALS.val: + global SPLIT_OBJECTS, SPLIT_GROUPS, KEEP_VERT_ORDER, POLYGROUPS + if SPLIT_OBJECTS.val or SPLIT_GROUPS.val: KEEP_VERT_ORDER.val = 0 POLYGROUPS.val = 0 else: KEEP_VERT_ORDER.val = 1 def do_vertorder(e,v): - global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER + global SPLIT_OBJECTS, SPLIT_GROUPS, KEEP_VERT_ORDER if KEEP_VERT_ORDER.val: - SPLIT_OBJECTS.val = SPLIT_GROUPS.val = SPLIT_MATERIALS.val = 0 + SPLIT_OBJECTS.val = SPLIT_GROUPS.val = 0 else: - if not (SPLIT_OBJECTS.val or SPLIT_GROUPS.val or SPLIT_MATERIALS.val): + if not (SPLIT_OBJECTS.val or SPLIT_GROUPS.val): KEEP_VERT_ORDER.val = 1 def do_polygroups(e,v): - global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER, POLYGROUPS + global SPLIT_OBJECTS, SPLIT_GROUPS, KEEP_VERT_ORDER, POLYGROUPS if POLYGROUPS.val: - SPLIT_OBJECTS.val = SPLIT_GROUPS.val = SPLIT_MATERIALS.val = 0 + SPLIT_OBJECTS.val = SPLIT_GROUPS.val = 0 def do_help(e,v): url = __url__[0] @@ -1432,7 +1326,7 @@ def load_obj_ui(filepath, BATCH_LOAD= False): ui_x -= 165 ui_y -= 90 - global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, CLAMP_SIZE, IMAGE_SEARCH, POLYGROUPS, KEEP_VERT_ORDER, ROTATE_X90 + global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, CLAMP_SIZE, IMAGE_SEARCH, POLYGROUPS, KEEP_VERT_ORDER, ROTATE_X90 Draw.Label('Import...', ui_x+9, ui_y+159, 220, 21) Draw.BeginAlign() @@ -1445,7 +1339,6 @@ def load_obj_ui(filepath, BATCH_LOAD= False): Draw.BeginAlign() SPLIT_OBJECTS = Draw.Toggle('Object', EVENT_REDRAW, ui_x+9, ui_y+89, 55, 21, SPLIT_OBJECTS.val, 'Import OBJ Objects into Blender Objects', do_split) SPLIT_GROUPS = Draw.Toggle('Group', EVENT_REDRAW, ui_x+64, ui_y+89, 55, 21, SPLIT_GROUPS.val, 'Import OBJ Groups into Blender Objects', do_split) - SPLIT_MATERIALS = Draw.Toggle('Split Materials', EVENT_REDRAW, ui_x+119, ui_y+89, 60, 21, SPLIT_MATERIALS.val, 'Import each material into a seperate mesh', do_split) Draw.EndAlign() # Only used for user feedback @@ -1495,7 +1388,7 @@ def load_obj_ui(filepath, BATCH_LOAD= False): return for f in files: - scn= bpy.data.scenes.new( stripExt(f) ) + scn= bpy.data.scenes.new(os.path.splitext(f)[0]) scn.makeCurrent() load_obj(sys.join(filepath, f),\ @@ -1505,7 +1398,6 @@ def load_obj_ui(filepath, BATCH_LOAD= False): CREATE_EDGES.val,\ SPLIT_OBJECTS.val,\ SPLIT_GROUPS.val,\ - SPLIT_MATERIALS.val,\ ROTATE_X90.val,\ IMAGE_SEARCH.val,\ POLYGROUPS.val @@ -1519,7 +1411,6 @@ def load_obj_ui(filepath, BATCH_LOAD= False): CREATE_EDGES.val,\ SPLIT_OBJECTS.val,\ SPLIT_GROUPS.val,\ - SPLIT_MATERIALS.val,\ ROTATE_X90.val,\ IMAGE_SEARCH.val,\ POLYGROUPS.val @@ -1576,14 +1467,13 @@ class IMPORT_OT_obj(bpy.types.Operator): # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for importing the OBJ file", maxlen= 1024, default= "") + filepath = StringProperty(name="File Path", description="Filepath used for importing the OBJ file", maxlen= 1024, default= "") CREATE_SMOOTH_GROUPS = BoolProperty(name="Smooth Groups", description="Surround smooth groups by sharp edges", default= True) CREATE_FGONS = BoolProperty(name="NGons as FGons", description="Import faces with more then 4 verts as fgons", default= True) CREATE_EDGES = BoolProperty(name="Lines as Edges", description="Import lines and faces with 2 verts as edge", default= True) SPLIT_OBJECTS = BoolProperty(name="Object", description="Import OBJ Objects into Blender Objects", default= True) SPLIT_GROUPS = BoolProperty(name="Group", description="Import OBJ Groups into Blender Objects", default= True) - SPLIT_MATERIALS = BoolProperty(name="Split Materials", description="Import each material into a seperate mesh", default= False) # old comment: only used for user feedback # disabled this option because in old code a handler for it disabled SPLIT* params, it's not passed to load_obj # KEEP_VERT_ORDER = BoolProperty(name="Keep Vert Order", description="Keep vert and face order, disables split options, enable for morph targets", default= True) @@ -1596,7 +1486,7 @@ class IMPORT_OT_obj(bpy.types.Operator): def execute(self, context): # print("Selected: " + context.active_object.name) - load_obj(self.properties.path, + load_obj(self.properties.filepath, context, self.properties.CLAMP_SIZE, self.properties.CREATE_FGONS, @@ -1604,7 +1494,6 @@ class IMPORT_OT_obj(bpy.types.Operator): self.properties.CREATE_EDGES, self.properties.SPLIT_OBJECTS, self.properties.SPLIT_GROUPS, - self.properties.SPLIT_MATERIALS, self.properties.ROTATE_X90, self.properties.IMAGE_SEARCH, self.properties.POLYGROUPS) @@ -1612,8 +1501,7 @@ class IMPORT_OT_obj(bpy.types.Operator): return {'FINISHED'} def invoke(self, context, event): - wm = context.manager - wm.add_fileselect(self) + context.manager.add_fileselect(self) return {'RUNNING_MODAL'} @@ -1622,11 +1510,9 @@ def menu_func(self, context): def register(): - bpy.types.register(IMPORT_OT_obj) bpy.types.INFO_MT_file_import.append(menu_func) def unregister(): - bpy.types.unregister(IMPORT_OT_obj) bpy.types.INFO_MT_file_import.remove(menu_func) @@ -1634,13 +1520,11 @@ def unregister(): # check later: line 489 # can convert now: edge flags, edges: lines 508-528 # ngon (uses python module BPyMesh): 384-414 -# nurbs: 947- # NEXT clamp size: get bound box with RNA # get back to l 140 (here) # search image in bpy.config.textureDir - load_image # replaced BPyImage.comprehensiveImageLoad with a simplified version that only checks additional directory specified, but doesn't search dirs recursively (obj_image_load) # bitmask won't work? - 132 -# uses operator bpy.ops.OBJECT_OT_select_all() to deselect all (not necessary?) # uses bpy.sys.time() if __name__ == "__main__": diff --git a/release/scripts/io/import_shape_mdd.py b/release/scripts/io/import_shape_mdd.py index 884fa92ddd0..c7b199918a2 100644 --- a/release/scripts/io/import_shape_mdd.py +++ b/release/scripts/io/import_shape_mdd.py @@ -12,7 +12,7 @@ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** @@ -66,7 +66,7 @@ def mdd_import(filepath, ob, scene, PREF_START_FRAME=0, PREF_JUMP=1): ob.active_shape_key_index = len(ob.data.shape_keys.keys)-1 index = len(ob.data.shape_keys.keys)-1 - ob.shape_key_lock = True + ob.show_shape_key = True verts = ob.data.shape_keys.keys[len(ob.data.shape_keys.keys)-1].data @@ -74,7 +74,7 @@ def mdd_import(filepath, ob, scene, PREF_START_FRAME=0, PREF_JUMP=1): for v in verts: # 12 is the size of 3 floats v.co[:] = unpack('>3f', file.read(12)) #me.update() - ob.shape_key_lock = False + ob.show_shape_key = False # insert keyframes @@ -104,7 +104,7 @@ from bpy.props import * class importMDD(bpy.types.Operator): '''Import MDD vertex keyframe file to shape keys''' - bl_idname = "import.mdd" + bl_idname = "import_shape.mdd" bl_label = "Import MDD" # get first scene to get min and max properties for frames, fps @@ -116,19 +116,20 @@ class importMDD(bpy.types.Operator): # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - path = StringProperty(name="File Path", description="File path used for importing the MDD file", maxlen=1024) + filepath = StringProperty(name="File Path", description="Filepath used for importing the MDD file", maxlen=1024) #fps = IntProperty(name="Frames Per Second", description="Number of frames/second", min=minfps, max=maxfps, default=25) frame_start = IntProperty(name="Start Frame", description="Start frame for inserting animation", min=minframe, max=maxframe, default=0) - def poll(self, context): + @classmethod + def poll(cls, context): ob = context.active_object return (ob and ob.type == 'MESH') def execute(self, context): - if not self.properties.path: + if not self.properties.filepath: raise Exception("filename not set") - mdd_import(self.properties.path, bpy.context.active_object, context.scene, self.properties.frame_start, 1) + mdd_import(self.properties.filepath, bpy.context.active_object, context.scene, self.properties.frame_start, 1) return {'FINISHED'} @@ -143,12 +144,10 @@ def menu_func(self, context): def register(): - bpy.types.register(importMDD) bpy.types.INFO_MT_file_import.append(menu_func) def unregister(): - bpy.types.unregister(importMDD) bpy.types.INFO_MT_file_import.remove(menu_func) if __name__ == "__main__": diff --git a/release/scripts/io/netrender/__init__.py b/release/scripts/io/netrender/__init__.py index e06d061f173..5a705e95aa8 100644 --- a/release/scripts/io/netrender/__init__.py +++ b/release/scripts/io/netrender/__init__.py @@ -18,15 +18,31 @@ # This directory is a Python package. -from netrender import model -from netrender import operators -from netrender import client -from netrender import slave -from netrender import master -from netrender import master_html -from netrender import utils -from netrender import balancing -from netrender import ui +# To support reload properly, try to access a package var, if it's there, reload everything +try: + init_data + + reload(model) + reload(operators) + reload(client) + reload(slave) + reload(master) + reload(master_html) + reload(utils) + reload(balancing) + reload(ui) + reload(repath) +except: + from netrender import model + from netrender import operators + from netrender import client + from netrender import slave + from netrender import master + from netrender import master_html + from netrender import utils + from netrender import balancing + from netrender import ui + from netrender import repath jobs = [] slaves = [] @@ -37,11 +53,10 @@ init_data = True init_address = True def register(): - pass # TODO + ui.addProperties() + def unregister(): import bpy - bpy.types.unregister(ui.NetRenderJob) - bpy.types.unregister(ui.NetRenderSettings) - bpy.types.unregister(ui.NetRenderSlave) + bpy.types.Scene.RemoveProperty("network_render") diff --git a/release/scripts/io/netrender/client.py b/release/scripts/io/netrender/client.py index 3acc0e1c2e1..6f0f6460ae1 100644 --- a/release/scripts/io/netrender/client.py +++ b/release/scripts/io/netrender/client.py @@ -41,7 +41,7 @@ def addFluidFiles(job, path): job.addFile(path + fluid_file, current_frame, current_frame) def addPointCache(job, ob, point_cache, default_path): - if not point_cache.disk_cache: + if not point_cache.use_disk_cache: return @@ -49,7 +49,7 @@ def addPointCache(job, ob, point_cache, default_path): if name == "": name = "".join(["%02X" % ord(c) for c in ob.name]) - cache_path = bpy.utils.expandpath(point_cache.filepath) if point_cache.external else default_path + cache_path = bpy.path.abspath(point_cache.filepath) if point_cache.use_external else default_path index = "%02i" % point_cache.index @@ -101,7 +101,7 @@ def clientSendJob(conn, scene, anim = False): else: job.addFrame(scene.frame_current) - filename = bpy.data.filename + filename = bpy.data.filepath job.addFile(filename) job_name = netsettings.job_name @@ -113,7 +113,7 @@ def clientSendJob(conn, scene, anim = False): # LIBRARIES ########################### for lib in bpy.data.libraries: - file_path = bpy.utils.expandpath(lib.filename) + file_path = bpy.path.abspath(lib.filepath) if os.path.exists(file_path): job.addFile(file_path) @@ -122,9 +122,13 @@ def clientSendJob(conn, scene, anim = False): ########################### for image in bpy.data.images: if image.source == "FILE" and not image.packed_file: - file_path = bpy.utils.expandpath(image.filename) + file_path = bpy.path.abspath(image.filepath) if os.path.exists(file_path): job.addFile(file_path) + + tex_path = os.path.splitext(file_path)[0] + ".tex" + if os.path.exists(tex_path): + job.addFile(tex_path) ########################### # FLUID + POINT CACHE @@ -135,15 +139,18 @@ def clientSendJob(conn, scene, anim = False): for object in bpy.data.objects: for modifier in object.modifiers: if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - addFluidFiles(job, bpy.utils.expandpath(modifier.settings.path)) + addFluidFiles(job, bpy.path.abspath(modifier.settings.path)) elif modifier.type == "CLOTH": addPointCache(job, object, modifier.point_cache, default_path) elif modifier.type == "SOFT_BODY": addPointCache(job, object, modifier.point_cache, default_path) elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": addPointCache(job, object, modifier.domain_settings.point_cache_low, default_path) - if modifier.domain_settings.highres: + if modifier.domain_settings.use_high_resolution: addPointCache(job, object, modifier.domain_settings.point_cache_high, default_path) + elif modifier.type == "MULTIRES" and modifier.is_external: + file_path = bpy.path.abspath(modifier.filepath) + job.addFile(file_path) # particles modifier are stupid and don't contain data # we have to go through the object property @@ -164,6 +171,7 @@ def clientSendJob(conn, scene, anim = False): # try to send path first conn.request("POST", "/job", repr(job.serialize())) response = conn.getresponse() + response.read() job_id = response.getheader("job-id") @@ -174,6 +182,7 @@ def clientSendJob(conn, scene, anim = False): conn.request("PUT", fileURL(job_id, rfile.index), f) f.close() response = conn.getresponse() + response.read() # server will reply with ACCEPTED until all files are found @@ -182,7 +191,6 @@ def clientSendJob(conn, scene, anim = False): def requestResult(conn, job_id, frame): conn.request("GET", renderURL(job_id, frame)) -@rnaType class NetworkRenderEngine(bpy.types.RenderEngine): bl_idname = 'NET_RENDER' bl_label = "Network Render" @@ -202,7 +210,7 @@ class NetworkRenderEngine(bpy.types.RenderEngine): address = "" if netsettings.server_address == "[default]" else netsettings.server_address - master.runMaster((address, netsettings.server_port), netsettings.master_broadcast, netsettings.master_clear, netsettings.path, self.update_stats, self.test_break) + master.runMaster((address, netsettings.server_port), netsettings.master_broadcast, netsettings.use_master_clear, netsettings.path, self.update_stats, self.test_break) def render_slave(self, scene): @@ -230,6 +238,7 @@ class NetworkRenderEngine(bpy.types.RenderEngine): requestResult(conn, job_id, scene.frame_current) response = conn.getresponse() + response.read() if response.status == http.client.NO_CONTENT: new_job = True @@ -238,16 +247,19 @@ class NetworkRenderEngine(bpy.types.RenderEngine): requestResult(conn, job_id, scene.frame_current) response = conn.getresponse() + response.read() while response.status == http.client.ACCEPTED and not self.test_break(): time.sleep(1) requestResult(conn, job_id, scene.frame_current) response = conn.getresponse() + response.read() # cancel new jobs (animate on network) on break if self.test_break() and new_job: conn.request("POST", cancelURL(job_id)) response = conn.getresponse() + response.read() print( response.status, response.reason ) netsettings.job_id = 0 @@ -259,7 +271,7 @@ class NetworkRenderEngine(bpy.types.RenderEngine): x= int(r.resolution_x*r.resolution_percentage*0.01) y= int(r.resolution_y*r.resolution_percentage*0.01) - f = open(netsettings.path + "output.exr", "wb") + f = open(os.path.join(netsettings.path, "output.exr"), "wb") buf = response.read(1024) while buf: @@ -269,7 +281,7 @@ class NetworkRenderEngine(bpy.types.RenderEngine): f.close() result = self.begin_result(0, 0, x, y) - result.load_from_file(netsettings.path + "output.exr") + result.load_from_file(os.path.join(netsettings.path, "output.exr")) self.end_result(result) conn.close() @@ -284,3 +296,6 @@ def compatible(module): #compatible("properties_render") compatible("properties_world") compatible("properties_material") +compatible("properties_data_mesh") +compatible("properties_data_camera") +compatible("properties_texture") diff --git a/release/scripts/io/netrender/master.py b/release/scripts/io/netrender/master.py index 019f33047d8..6deb925420b 100644 --- a/release/scripts/io/netrender/master.py +++ b/release/scripts/io/netrender/master.py @@ -19,6 +19,7 @@ import sys, os import http, http.client, http.server, urllib, socket, socketserver, threading import subprocess, shutil, time, hashlib +import pickle import select # for select.error from netrender.utils import * @@ -27,12 +28,16 @@ import netrender.balancing import netrender.master_html class MRenderFile(netrender.model.RenderFile): - def __init__(self, filepath, index, start, end): - super().__init__(filepath, index, start, end) + def __init__(self, filepath, index, start, end, signature): + super().__init__(filepath, index, start, end, signature) self.found = False def test(self): self.found = os.path.exists(self.filepath) + if self.found: + found_signature = hashFile(self.filepath) + self.found = self.signature == found_signature + return self.found @@ -74,7 +79,7 @@ class MRenderJob(netrender.model.RenderJob): # special server properties self.last_update = 0 self.save_path = "" - self.files = [MRenderFile(rfile.filepath, rfile.index, rfile.start, rfile.end) for rfile in job_info.files] + self.files = [MRenderFile(rfile.filepath, rfile.index, rfile.start, rfile.end, rfile.signature) for rfile in job_info.files] self.resolution = None @@ -84,7 +89,7 @@ class MRenderJob(netrender.model.RenderJob): def save(self): if self.save_path: - f = open(self.save_path + "job.txt", "w") + f = open(os.path.join(self.save_path, "job.txt"), "w") f.write(repr(self.serialize())) f.close() @@ -129,8 +134,8 @@ class MRenderJob(netrender.model.RenderJob): self.status = JOB_QUEUED def addLog(self, frames): - log_name = "_".join(("%04d" % f for f in frames)) + ".log" - log_path = self.save_path + log_name + log_name = "_".join(("%06d" % f for f in frames)) + ".log" + log_path = os.path.join(self.save_path, log_name) for number in frames: frame = self[number] @@ -190,6 +195,11 @@ pause_pattern = re.compile("/pause_([a-zA-Z0-9]+)") edit_pattern = re.compile("/edit_([a-zA-Z0-9]+)") class RenderHandler(http.server.BaseHTTPRequestHandler): + def log_message(self, format, *args): + # override because the original calls self.address_string(), which + # is extremely slow due to some timeout.. + sys.stderr.write("[%s] %s\n" % (self.log_date_time_string(), format%args)) + def send_head(self, code = http.client.OK, headers = {}, content = "application/octet-stream"): self.send_response(code) self.send_header("Content-type", content) @@ -250,7 +260,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): elif frame.status == DONE: self.server.stats("", "Sending result to client") - filename = job.save_path + "%04d" % frame_number + ".exr" + filename = os.path.join(job.save_path, "%06d.exr" % frame_number) f = open(filename, 'rb') self.send_head(content = "image/x-exr") @@ -284,7 +294,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): if frame.status in (QUEUED, DISPATCHED): self.send_head(http.client.ACCEPTED) elif frame.status == DONE: - filename = job.save_path + "%04d" % frame_number + ".exr" + filename = os.path.join(job.save_path, "%06d.exr" % frame_number) thumbname = thumbnail(filename) @@ -706,12 +716,12 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): if file_index > 0: file_path = prefixPath(job.save_path, render_file.filepath, main_path) else: - file_path = job.save_path + main_name + file_path = os.path.join(job.save_path, main_name) buf = self.rfile.read(length) # add same temp file + renames as slave - + f = open(file_path, "wb") f.write(buf) f.close() @@ -762,7 +772,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): if job_result == DONE: length = int(self.headers['content-length']) buf = self.rfile.read(length) - f = open(job.save_path + "%04d" % job_frame + ".exr", 'wb') + f = open(os.path.join(job.save_path, "%06d.exr" % job_frame), 'wb') f.write(buf) f.close() @@ -812,13 +822,12 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): if job.type == netrender.model.JOB_BLENDER: length = int(self.headers['content-length']) buf = self.rfile.read(length) - f = open(job.save_path + "%04d" % job_frame + ".jpg", 'wb') + f = open(os.path.join(job.save_path, "%06d.jpg" % job_frame), 'wb') f.write(buf) f.close() del buf - self.send_head() else: # frame not found self.send_head(http.client.NO_CONTENT) else: # job not found @@ -861,16 +870,20 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): self.send_head(http.client.NO_CONTENT) class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): - def __init__(self, address, handler_class, path): + def __init__(self, address, handler_class, path, subdir=True): super().__init__(address, handler_class) self.jobs = [] self.jobs_map = {} self.slaves = [] self.slaves_map = {} self.job_id = 0 - self.path = path + "master_" + str(os.getpid()) + os.sep - self.slave_timeout = 30 # 30 mins: need a parameter for that + if subdir: + self.path = os.path.join(path, "master_" + str(os.getpid())) + else: + self.path = path + + self.slave_timeout = 5 # 5 mins: need a parameter for that self.balancer = netrender.balancing.Balancer() self.balancer.addRule(netrender.balancing.RatingUsageByCategory(self.getJobs)) @@ -883,6 +896,22 @@ class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): if not os.path.exists(self.path): os.mkdir(self.path) + def restore(self, jobs, slaves, balancer = None): + self.jobs = jobs + self.jobs_map = {} + + for job in self.jobs: + self.jobs_map[job.id] = job + self.job_id = max(self.job_id, int(job.id)) + + self.slaves = slaves + for slave in self.slaves: + self.slaves_map[slave.id] = slave + + if balancer: + self.balancer = balancer + + def nextJobID(self): self.job_id += 1 return str(self.job_id) @@ -977,7 +1006,7 @@ class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): self.jobs_map[job.id] = job # create job directory - job.save_path = self.path + "job_" + job.id + os.sep + job.save_path = os.path.join(self.path, "job_" + job.id) if not os.path.exists(job.save_path): os.mkdir(job.save_path) @@ -1001,8 +1030,29 @@ class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): def clearMaster(path): shutil.rmtree(path) +def createMaster(address, clear, path): + filepath = os.path.join(path, "blender_master.data") + + if not clear and os.path.exists(filepath): + print("loading saved master:", filepath) + with open(filepath, 'rb') as f: + path, jobs, slaves = pickle.load(f) + + httpd = RenderMasterServer(address, RenderHandler, path, subdir=False) + httpd.restore(jobs, slaves) + + return httpd + + return RenderMasterServer(address, RenderHandler, path) + +def saveMaster(path, httpd): + filepath = os.path.join(path, "blender_master.data") + + with open(filepath, 'wb') as f: + pickle.dump((httpd.path, httpd.jobs, httpd.slaves), f, pickle.HIGHEST_PROTOCOL) + def runMaster(address, broadcast, clear, path, update_stats, test_break): - httpd = RenderMasterServer(address, RenderHandler, path) + httpd = createMaster(address, clear, path) httpd.timeout = 1 httpd.stats = update_stats @@ -1010,7 +1060,7 @@ def runMaster(address, broadcast, clear, path, update_stats, test_break): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - start_time = time.time() + start_time = time.time() - 2 while not test_break(): try: @@ -1018,7 +1068,7 @@ def runMaster(address, broadcast, clear, path, update_stats, test_break): except select.error: pass - if time.time() - start_time >= 10: # need constant here + if time.time() - start_time >= 2: # need constant here httpd.timeoutSlaves() httpd.updateUsage() @@ -1031,3 +1081,6 @@ def runMaster(address, broadcast, clear, path, update_stats, test_break): httpd.server_close() if clear: clearMaster(httpd.path) + else: + saveMaster(path, httpd) + diff --git a/release/scripts/io/netrender/master_html.py b/release/scripts/io/netrender/master_html.py index e5feac86e12..74155f6bd66 100644 --- a/release/scripts/io/netrender/master_html.py +++ b/release/scripts/io/netrender/master_html.py @@ -27,8 +27,10 @@ def get(handler): def output(text): handler.wfile.write(bytes(text, encoding='utf8')) - def head(title): + def head(title, refresh = False): output("<html><head>") + if refresh: + output("<meta http-equiv='refresh' content=5>") output("<script src='/html/netrender.js' type='text/javascript'></script>") # output("<script src='/html/json2.js' type='text/javascript'></script>") output("<title>") @@ -103,54 +105,7 @@ def get(handler): f.close() elif handler.path == "/html" or handler.path == "/": handler.send_head(content = "text/html") - head("NetRender") - - output("<h2>Master</h2>") - - output("""<button title="remove all jobs" onclick="clear_jobs();">CLEAR JOB LIST</button>""") - - startTable(caption = "Rules", class_style = "rules") - - headerTable("type", "enabled", "description", "limit") - - for rule in handler.server.balancer.rules: - rowTable( - "rating", - checkbox("", rule.enabled, "balance_enable('%i', '%s')" % (id(rule), str(not rule.enabled))), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%i', '%s');">edit</button>""" % (id(rule), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - for rule in handler.server.balancer.priorities: - rowTable( - "priority", - checkbox("", rule.enabled, "balance_enable('%i', '%s')" % (id(rule), str(not rule.enabled))), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%i', '%s');">edit</button>""" % (id(rule), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - for rule in handler.server.balancer.exceptions: - rowTable( - "exception", - checkbox("", rule.enabled, "balance_enable('%i', '%s')" % (id(rule), str(not rule.enabled))), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%i', '%s');">edit</button>""" % (id(rule), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - endTable() - - output("<h2>Slaves</h2>") - - startTable() - headerTable("name", "address", "last seen", "stats", "job") - - for slave in handler.server.slaves: - rowTable(slave.name, slave.address[0], time.ctime(slave.last_seen), slave.stats, link(slave.job.name, "/html/job" + slave.job.id) if slave.job else "None") - - endTable() + head("NetRender", refresh = True) output("<h2>Jobs</h2>") @@ -203,6 +158,53 @@ def get(handler): ) endTable() + + output("<h2>Slaves</h2>") + + startTable() + headerTable("name", "address", "last seen", "stats", "job") + + for slave in handler.server.slaves: + rowTable(slave.name, slave.address[0], time.ctime(slave.last_seen), slave.stats, link(slave.job.name, "/html/job" + slave.job.id) if slave.job else "None") + + endTable() + + output("<h2>Configuration</h2>") + + output("""<button title="remove all jobs" onclick="clear_jobs();">CLEAR JOB LIST</button>""") + + startTable(caption = "Rules", class_style = "rules") + + headerTable("type", "enabled", "description", "limit") + + for rule in handler.server.balancer.rules: + rowTable( + "rating", + checkbox("", rule.enabled, "balance_enable('%i', '%s')" % (id(rule), str(not rule.enabled))), + rule, + rule.str_limit() + + """<button title="edit limit" onclick="balance_edit('%i', '%s');">edit</button>""" % (id(rule), str(rule.limit)) if hasattr(rule, "limit") else " " + ) + + for rule in handler.server.balancer.priorities: + rowTable( + "priority", + checkbox("", rule.enabled, "balance_enable('%i', '%s')" % (id(rule), str(not rule.enabled))), + rule, + rule.str_limit() + + """<button title="edit limit" onclick="balance_edit('%i', '%s');">edit</button>""" % (id(rule), str(rule.limit)) if hasattr(rule, "limit") else " " + ) + + for rule in handler.server.balancer.exceptions: + rowTable( + "exception", + checkbox("", rule.enabled, "balance_enable('%i', '%s')" % (id(rule), str(not rule.enabled))), + rule, + rule.str_limit() + + """<button title="edit limit" onclick="balance_edit('%i', '%s');">edit</button>""" % (id(rule), str(rule.limit)) if hasattr(rule, "limit") else " " + ) + + endTable() output("</body></html>") @@ -234,13 +236,17 @@ def get(handler): tot_cache = 0 tot_fluid = 0 + rowTable(job.files[0].filepath) + rowTable("Other Files", class_style = "toggle", extra = "onclick='toggleDisplay(".other", "none", "table-row")'") + for file in job.files: if file.filepath.endswith(".bphys"): tot_cache += 1 elif file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): tot_fluid += 1 else: - rowTable(file.filepath) + if file != job.files[0]: + rowTable(file.filepath, class_style = "other") if tot_cache > 0: rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(".cache", "none", "table-row")'") @@ -256,9 +262,9 @@ def get(handler): endTable() - output("<h2>Blacklist</h2>") - if job.blacklist: + output("<h2>Blacklist</h2>") + startTable() headerTable("name", "address") @@ -267,8 +273,6 @@ def get(handler): rowTable(slave.name, slave.address[0]) endTable() - else: - output("<i>Empty</i>") output("<h2>Frames</h2>") diff --git a/release/scripts/io/netrender/model.py b/release/scripts/io/netrender/model.py index 8b0f50ba848..e7656f498b4 100644 --- a/release/scripts/io/netrender/model.py +++ b/release/scripts/io/netrender/model.py @@ -103,8 +103,10 @@ JOB_TYPES = { } class RenderFile: - def __init__(self, filepath = "", index = 0, start = -1, end = -1): + def __init__(self, filepath = "", index = 0, start = -1, end = -1, signature=0): self.filepath = filepath + self.original_path = filepath + self.signature = signature self.index = index self.start = start self.end = end @@ -112,9 +114,11 @@ class RenderFile: def serialize(self): return { "filepath": self.filepath, + "original_path": self.original_path, "index": self.index, "start": self.start, - "end": self.end + "end": self.end, + "signature": self.signature } @staticmethod @@ -122,7 +126,8 @@ class RenderFile: if not data: return None - rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"]) + rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"], data["signature"]) + rfile.original_path = data["original_path"] return rfile @@ -153,7 +158,8 @@ class RenderJob: self.blacklist = job_info.blacklist def addFile(self, file_path, start=-1, end=-1): - self.files.append(RenderFile(file_path, len(self.files), start, end)) + signature = hashFile(file_path) + self.files.append(RenderFile(file_path, len(self.files), start, end, signature)) def addFrame(self, frame_number, command = ""): frame = RenderFrame(frame_number, command) diff --git a/release/scripts/io/netrender/netrender.css b/release/scripts/io/netrender/netrender.css index cc8a93bb9a7..0c54690e002 100644 --- a/release/scripts/io/netrender/netrender.css +++ b/release/scripts/io/netrender/netrender.css @@ -68,6 +68,10 @@ button { display: none; } +.other { + display: none; +} + .rules { width: 60em; text-align: left; diff --git a/release/scripts/io/netrender/operators.py b/release/scripts/io/netrender/operators.py index 4c027436560..252b1146b67 100644 --- a/release/scripts/io/netrender/operators.py +++ b/release/scripts/io/netrender/operators.py @@ -26,20 +26,20 @@ from netrender.utils import * import netrender.client as client import netrender.model -@rnaType class RENDER_OT_netslave_bake(bpy.types.Operator): '''NEED DESCRIPTION''' bl_idname = "render.netslavebake" bl_label = "Bake all in file" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): scene = context.scene netsettings = scene.network_render - filename = bpy.data.filename + filename = bpy.data.filepath path, name = os.path.split(filename) root, ext = os.path.splitext(name) default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that @@ -52,45 +52,45 @@ class RENDER_OT_netslave_bake(bpy.types.Operator): modifier.settings.path = relative_path bpy.ops.fluid.bake({"active_object": object, "scene": scene}) elif modifier.type == "CLOTH": - modifier.point_cache.step = 1 - modifier.point_cache.disk_cache = True - modifier.point_cache.external = False + modifier.point_cache.frame_step = 1 + modifier.point_cache.use_disk_cache = True + modifier.point_cache.use_external = False elif modifier.type == "SOFT_BODY": - modifier.point_cache.step = 1 - modifier.point_cache.disk_cache = True - modifier.point_cache.external = False + modifier.point_cache.frame_step = 1 + modifier.point_cache.use_disk_cache = True + modifier.point_cache.use_external = False elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - modifier.domain_settings.point_cache_low.step = 1 - modifier.domain_settings.point_cache_low.disk_cache = True - modifier.domain_settings.point_cache_low.external = False - modifier.domain_settings.point_cache_high.step = 1 - modifier.domain_settings.point_cache_high.disk_cache = True - modifier.domain_settings.point_cache_high.external = False + modifier.domain_settings.point_cache_low.use_step = 1 + modifier.domain_settings.point_cache_low.use_disk_cache = True + modifier.domain_settings.point_cache_low.use_external = False + modifier.domain_settings.point_cache_high.use_step = 1 + modifier.domain_settings.point_cache_high.use_disk_cache = True + modifier.domain_settings.point_cache_high.use_external = False # particles modifier are stupid and don't contain data # we have to go through the object property for psys in object.particle_systems: - psys.point_cache.step = 1 - psys.point_cache.disk_cache = True - psys.point_cache.external = False + psys.point_cache.use_step = 1 + psys.point_cache.use_disk_cache = True + psys.point_cache.use_external = False psys.point_cache.filepath = relative_path bpy.ops.ptcache.bake_all() - #bpy.ops.wm.save_mainfile(path = path + os.sep + root + "_baked.blend") + #bpy.ops.wm.save_mainfile(filepath = path + os.sep + root + "_baked.blend") return {'FINISHED'} def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientanim(bpy.types.Operator): '''Start rendering an animation on network''' bl_idname = "render.netclientanim" bl_label = "Animation on network" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -111,13 +111,13 @@ class RENDER_OT_netclientanim(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientrun(bpy.types.Operator): '''Start network rendering service''' bl_idname = "render.netclientstart" bl_label = "Start Service" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -128,13 +128,13 @@ class RENDER_OT_netclientrun(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientsend(bpy.types.Operator): '''Send Render Job to the Network''' bl_idname = "render.netclientsend" bl_label = "Send job" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -158,13 +158,43 @@ class RENDER_OT_netclientsend(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType +class RENDER_OT_netclientsendframe(bpy.types.Operator): + '''Send Render Job with current frame to the Network''' + bl_idname = "render.netclientsendframe" + bl_label = "Send current frame job" + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + scene = context.scene + netsettings = scene.network_render + + try: + conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) + + if conn: + # Sending file + scene.network_render.job_id = client.clientSendJob(conn, scene, False) + conn.close() + self.report('INFO', "Job sent to master") + except Exception as err: + self.report('ERROR', str(err)) + + + return {'FINISHED'} + + def invoke(self, context, event): + return self.execute(context) + class RENDER_OT_netclientstatus(bpy.types.Operator): '''Refresh the status of the current jobs''' bl_idname = "render.netclientstatus" bl_label = "Client Status" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -175,6 +205,7 @@ class RENDER_OT_netclientstatus(bpy.types.Operator): conn.request("GET", "/status") response = conn.getresponse() + response.read() print( response.status, response.reason ) jobs = (netrender.model.RenderJob.materialize(j) for j in eval(str(response.read(), encoding='utf8'))) @@ -198,13 +229,13 @@ class RENDER_OT_netclientstatus(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientblacklistslave(bpy.types.Operator): '''Operator documentation text, will be used for the operator tooltip and python docs.''' bl_idname = "render.netclientblacklistslave" bl_label = "Client Blacklist Slave" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -228,13 +259,13 @@ class RENDER_OT_netclientblacklistslave(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientwhitelistslave(bpy.types.Operator): '''Operator documentation text, will be used for the operator tooltip and python docs.''' bl_idname = "render.netclientwhitelistslave" bl_label = "Client Whitelist Slave" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -259,13 +290,13 @@ class RENDER_OT_netclientwhitelistslave(bpy.types.Operator): return self.execute(context) -@rnaType class RENDER_OT_netclientslaves(bpy.types.Operator): '''Refresh status about available Render slaves''' bl_idname = "render.netclientslaves" bl_label = "Client Slaves" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -276,6 +307,7 @@ class RENDER_OT_netclientslaves(bpy.types.Operator): conn.request("GET", "/slaves") response = conn.getresponse() + response.read() print( response.status, response.reason ) slaves = (netrender.model.RenderSlave.materialize(s) for s in eval(str(response.read(), encoding='utf8'))) @@ -304,13 +336,13 @@ class RENDER_OT_netclientslaves(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientcancel(bpy.types.Operator): '''Cancel the selected network rendering job.''' bl_idname = "render.netclientcancel" bl_label = "Client Cancel" - def poll(self, context): + @classmethod + def poll(cls, context): netsettings = context.scene.network_render return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 @@ -324,6 +356,7 @@ class RENDER_OT_netclientcancel(bpy.types.Operator): conn.request("POST", cancelURL(job.id)) response = conn.getresponse() + response.read() print( response.status, response.reason ) netsettings.jobs.remove(netsettings.active_job_index) @@ -333,13 +366,13 @@ class RENDER_OT_netclientcancel(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class RENDER_OT_netclientcancelall(bpy.types.Operator): '''Cancel all running network rendering jobs.''' bl_idname = "render.netclientcancelall" bl_label = "Client Cancel All" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -350,6 +383,7 @@ class RENDER_OT_netclientcancelall(bpy.types.Operator): conn.request("POST", "/clear") response = conn.getresponse() + response.read() print( response.status, response.reason ) while(len(netsettings.jobs) > 0): @@ -360,13 +394,13 @@ class RENDER_OT_netclientcancelall(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class netclientdownload(bpy.types.Operator): '''Download render results from the network''' bl_idname = "render.netclientdownload" bl_label = "Client Download" - def poll(self, context): + @classmethod + def poll(cls, context): netsettings = context.scene.network_render return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 @@ -382,6 +416,7 @@ class netclientdownload(bpy.types.Operator): for frame in job.frames: client.requestResult(conn, job.id, frame.number) response = conn.getresponse() + response.read() if response.status != http.client.OK: print("missing", frame.number) @@ -389,7 +424,7 @@ class netclientdownload(bpy.types.Operator): print("got back", frame.number) - f = open(netsettings.path + "%06d" % frame.number + ".exr", "wb") + f = open(os.path.join(netsettings.path, "%06d.exr" % frame.number), "wb") buf = response.read(1024) while buf: @@ -405,13 +440,13 @@ class netclientdownload(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class netclientscan(bpy.types.Operator): '''Listen on network for master server broadcasting its address and port.''' bl_idname = "render.netclientscan" bl_label = "Client Scan" - def poll(self, context): + @classmethod + def poll(cls, context): return True def execute(self, context): @@ -428,13 +463,13 @@ class netclientscan(bpy.types.Operator): def invoke(self, context, event): return self.execute(context) -@rnaType class netclientweb(bpy.types.Operator): '''Open new window with information about running rendering jobs''' bl_idname = "render.netclientweb" bl_label = "Open Master Monitor" - def poll(self, context): + @classmethod + def poll(cls, context): netsettings = context.scene.network_render return netsettings.server_address != "[default]" diff --git a/release/scripts/io/netrender/repath.py b/release/scripts/io/netrender/repath.py new file mode 100644 index 00000000000..3ac9636b628 --- /dev/null +++ b/release/scripts/io/netrender/repath.py @@ -0,0 +1,150 @@ +# ##### 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 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. +# +# ##### END GPL LICENSE BLOCK ##### + +import sys, os +import subprocess + +import bpy + +from netrender.utils import * +import netrender.model + +BLENDER_PATH = sys.argv[0] + +def reset(job): + main_file = job.files[0] + + job_full_path = main_file.filepath + + if os.path.exists(job_full_path + ".bak"): + os.remove(job_full_path) # repathed file + os.renames(job_full_path + ".bak", job_full_path) + +def update(job): + paths = [] + + main_file = job.files[0] + + job_full_path = main_file.filepath + + + path, ext = os.path.splitext(job_full_path) + + new_path = path + ".remap" + ext + + # Disable for now. Partial repath should work anyway + #all = main_file.filepath != main_file.original_path + all = False + + for rfile in job.files[1:]: + if all or rfile.original_path != rfile.filepath: + paths.append(rfile.original_path) + paths.append(rfile.filepath) + + # Only update if needed + if paths: + process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-P", __file__, "--", new_path] + paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + process.wait() + + os.renames(job_full_path, job_full_path + ".bak") + os.renames(new_path, job_full_path) + +def process(paths): + def processPointCache(point_cache): + point_cache.use_external = False + + def processFluid(fluid): + new_path = path_map.get(fluid.filepath, None) + if new_path: + fluid.path = new_path + + path_map = {} + for i in range(0, len(paths), 2): + # special case for point cache + if paths[i].endswith(".bphys"): + pass # Don't need them in the map, they all use the default external path + # NOTE: This is probably not correct all the time, need to be fixed. + # special case for fluids + elif paths[i].endswith(".bobj.gz"): + path_map[os.path.split(paths[i])[0]] = os.path.split(paths[i+1])[0] + else: + path_map[os.path.split(paths[i])[1]] = paths[i+1] + + # TODO original paths aren't really the orignal path (they are the normalized path + # so we repath using the filenames only. + + ########################### + # LIBRARIES + ########################### + for lib in bpy.data.libraries: + file_path = bpy.path.abspath(lib.filepath) + new_path = path_map.get(os.path.split(file_path)[1], None) + if new_path: + lib.filepath = new_path + + ########################### + # IMAGES + ########################### + for image in bpy.data.images: + if image.source == "FILE" and not image.packed_file: + file_path = bpy.path.abspath(image.filepath) + new_path = path_map.get(os.path.split(file_path)[1], None) + if new_path: + image.filepath = new_path + + + ########################### + # FLUID + POINT CACHE + ########################### + for object in bpy.data.objects: + for modifier in object.modifiers: + if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": + processFluid(settings) + elif modifier.type == "CLOTH": + processPointCache(modifier.point_cache) + elif modifier.type == "SOFT_BODY": + processPointCache(modifier.point_cache) + elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": + processPointCache(modifier.domain_settings.point_cache_low) + if modifier.domain_settings.use_high_resolution: + processPointCache(modifier.domain_settings.point_cache_high) + elif modifier.type == "MULTIRES" and modifier.is_external: + file_path = bpy.path.abspath(modifier.filepath) + new_path = path_map.get(file_path, None) + if new_path: + modifier.filepath = new_path + + # particles modifier are stupid and don't contain data + # we have to go through the object property + for psys in object.particle_systems: + processPointCache(psys.point_cache) + + +if __name__ == "__main__": + try: + i = sys.argv.index("--") + except: + i = 0 + + if i: + new_path = sys.argv[i+1] + args = sys.argv[i+2:] + + process(args) + + bpy.ops.wm.save_as_mainfile(filepath=new_path, check_existing=False) diff --git a/release/scripts/io/netrender/slave.py b/release/scripts/io/netrender/slave.py index d24490385fb..b383481824b 100644 --- a/release/scripts/io/netrender/slave.py +++ b/release/scripts/io/netrender/slave.py @@ -22,6 +22,7 @@ import subprocess, time from netrender.utils import * import netrender.model +import netrender.repath BLENDER_PATH = sys.argv[0] @@ -58,18 +59,30 @@ def slave_Info(): def testCancel(conn, job_id, frame_number): conn.request("HEAD", "/status", headers={"job-id":job_id, "job-frame": str(frame_number)}) - # cancelled if job isn't found anymore - if conn.getresponse().status == http.client.NO_CONTENT: + # canceled if job isn't found anymore + if responseStatus(conn) == http.client.NO_CONTENT: return True else: return False -def testFile(conn, job_id, slave_id, file_index, JOB_PREFIX, file_path, main_path = None): - job_full_path = prefixPath(JOB_PREFIX, file_path, main_path) - - if not os.path.exists(job_full_path): - temp_path = JOB_PREFIX + "slave.temp.blend" - conn.request("GET", fileURL(job_id, file_index), headers={"slave-id":slave_id}) +def testFile(conn, job_id, slave_id, rfile, JOB_PREFIX, main_path = None): + job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path) + + found = os.path.exists(job_full_path) + + if found: + found_signature = hashFile(job_full_path) + found = found_signature == rfile.signature + + if not found: + print("Found file %s at %s but signature mismatch!" % (rfile.filepath, job_full_path)) + job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True) + + if not found: + # Force prefix path if not found + job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True) + temp_path = os.path.join(JOB_PREFIX, "slave.temp") + conn.request("GET", fileURL(job_id, rfile.index), headers={"slave-id":slave_id}) response = conn.getresponse() if response.status != http.client.OK: @@ -85,6 +98,8 @@ def testFile(conn, job_id, slave_id, file_index, JOB_PREFIX, file_path, main_pat f.close() os.renames(temp_path, job_full_path) + + rfile.filepath = job_full_path return job_full_path @@ -98,13 +113,16 @@ def render_slave(engine, netsettings, threads): if conn: conn.request("POST", "/slave", repr(slave_Info().serialize())) response = conn.getresponse() + response.read() slave_id = response.getheader("slave-id") - NODE_PREFIX = netsettings.path + "slave_" + slave_id + os.sep + NODE_PREFIX = os.path.join(netsettings.path, "slave_" + slave_id) if not os.path.exists(NODE_PREFIX): os.mkdir(NODE_PREFIX) + engine.update_stats("", "Network render connected to master, waiting for jobs") + while not engine.test_break(): conn.request("GET", "/job", headers={"slave-id":slave_id}) response = conn.getresponse() @@ -113,8 +131,9 @@ def render_slave(engine, netsettings, threads): timeout = 1 # reset timeout on new job job = netrender.model.RenderJob.materialize(eval(str(response.read(), encoding='utf8'))) + engine.update_stats("", "Network render processing job from master") - JOB_PREFIX = NODE_PREFIX + "job_" + job.id + os.sep + JOB_PREFIX = os.path.join(NODE_PREFIX, "job_" + job.id) if not os.path.exists(JOB_PREFIX): os.mkdir(JOB_PREFIX) @@ -123,19 +142,23 @@ def render_slave(engine, netsettings, threads): job_path = job.files[0].filepath # path of main file main_path, main_file = os.path.split(job_path) - job_full_path = testFile(conn, job.id, slave_id, 0, JOB_PREFIX, job_path) + job_full_path = testFile(conn, job.id, slave_id, job.files[0], JOB_PREFIX) print("Fullpath", job_full_path) print("File:", main_file, "and %i other files" % (len(job.files) - 1,)) - engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) for rfile in job.files[1:]: + testFile(conn, job.id, slave_id, rfile, JOB_PREFIX, main_path) print("\t", rfile.filepath) - testFile(conn, job.id, slave_id, rfile.index, JOB_PREFIX, rfile.filepath, main_path) + + netrender.repath.update(job) + + engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) # announce log to master logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames]) conn.request("POST", "/log", bytes(repr(logfile.serialize()), encoding='utf8')) response = conn.getresponse() + response.read() first_frame = job.frames[0].number @@ -151,7 +174,7 @@ def render_slave(engine, netsettings, threads): frame_args += ["-f", str(frame.number)] val = SetErrorMode() - process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-t", str(threads), "-o", JOB_PREFIX + "######", "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-t", str(threads), "-o", os.path.join(JOB_PREFIX, "######"), "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) RestoreErrorMode(val) elif job.type == netrender.model.JOB_PROCESS: command = job.frames[0].command @@ -175,6 +198,11 @@ def render_slave(engine, netsettings, threads): # (only need to update on one frame, they are linked conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) response = conn.getresponse() + response.read() + + # Also output on console + if netsettings.use_slave_output_log: + print(str(stdout, encoding='utf8'), end="") stdout = bytes() @@ -182,15 +210,32 @@ def render_slave(engine, netsettings, threads): if testCancel(conn, job.id, first_frame): cancelled = True + if job.type == netrender.model.JOB_BLENDER: + netrender.repath.reset(job) + # read leftovers if needed stdout += process.stdout.read() if cancelled: # kill process if needed if process.poll() == None: - process.terminate() + try: + process.terminate() + except OSError: + pass continue # to next frame + # flush the rest of the logs + if stdout: + # Also output on console + if netsettings.use_slave_thumb: + print(str(stdout, encoding='utf8'), end="") + + # (only need to update on one frame, they are linked + conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) + if responseStatus(conn) == http.client.NO_CONTENT: + continue + total_t = time.time() - start_t avg_t = total_t / len(job.frames) @@ -199,13 +244,6 @@ def render_slave(engine, netsettings, threads): print("status", status) - # flush the rest of the logs - if stdout: - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - if conn.getresponse().status == http.client.NO_CONTENT: - continue - headers = {"job-id":job.id, "slave-id":slave_id, "job-time":str(avg_t)} @@ -216,26 +254,27 @@ def render_slave(engine, netsettings, threads): if job.type == netrender.model.JOB_BLENDER: # send image back to server - filename = JOB_PREFIX + "%06d" % frame.number + ".exr" + filename = os.path.join(JOB_PREFIX, "%06d.exr" % frame.number) # thumbnail first - if netsettings.slave_thumb: + if netsettings.use_slave_thumb: thumbname = thumbnail(filename) f = open(thumbname, 'rb') conn.request("PUT", "/thumb", f, headers=headers) f.close() - conn.getresponse() + responseStatus(conn) + f = open(filename, 'rb') conn.request("PUT", "/render", f, headers=headers) f.close() - if conn.getresponse().status == http.client.NO_CONTENT: + if responseStatus(conn) == http.client.NO_CONTENT: continue elif job.type == netrender.model.JOB_PROCESS: conn.request("PUT", "/render", headers=headers) - if conn.getresponse().status == http.client.NO_CONTENT: + if responseStatus(conn) == http.client.NO_CONTENT: continue else: headers["job-result"] = str(ERROR) @@ -243,8 +282,10 @@ def render_slave(engine, netsettings, threads): headers["job-frame"] = str(frame.number) # send error result back to server conn.request("PUT", "/render", headers=headers) - if conn.getresponse().status == http.client.NO_CONTENT: + if responseStatus(conn) == http.client.NO_CONTENT: continue + + engine.update_stats("", "Network render connected to master, waiting for jobs") else: if timeout < MAX_TIMEOUT: timeout += INCREMENT_TIMEOUT @@ -256,7 +297,7 @@ def render_slave(engine, netsettings, threads): conn.close() - if netsettings.slave_clear: + if netsettings.use_slave_clear: clearSlave(NODE_PREFIX) if __name__ == "__main__": diff --git a/release/scripts/io/netrender/ui.py b/release/scripts/io/netrender/ui.py index f8b29fdd326..c2d943f63f8 100644 --- a/release/scripts/io/netrender/ui.py +++ b/release/scripts/io/netrender/ui.py @@ -36,9 +36,14 @@ DISPATCHED = 1 DONE = 2 ERROR = 3 +def base_poll(cls, context): + rd = context.scene.render + return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES) + + def init_file(): - if netrender.init_file != bpy.data.filename: - netrender.init_file = bpy.data.filename + if netrender.init_file != bpy.data.filepath: + netrender.init_file = bpy.data.filepath netrender.init_data = True netrender.init_address = True @@ -76,22 +81,26 @@ def verify_address(netsettings): else: netsettings.server_address = "[default]" -class RenderButtonsPanel(bpy.types.Panel): +class RenderButtonsPanel(): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here - - def poll(self, context): + + @classmethod + def poll(cls, context): rd = context.scene.render - return (rd.use_game_engine==False) and (rd.engine in self.COMPAT_ENGINES) + return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES) # Setting panel, use in the scene for now. -@rnaType -class RENDER_PT_network_settings(RenderButtonsPanel): +class RENDER_PT_network_settings(bpy.types.Panel, RenderButtonsPanel): bl_label = "Network Settings" COMPAT_ENGINES = {'NET_RENDER'} + @classmethod + def poll(cls, context): + return super(RENDER_PT_network_settings, cls).poll(context) + def draw(self, context): layout = self.layout @@ -122,15 +131,14 @@ class RENDER_PT_network_settings(RenderButtonsPanel): layout.operator("render.netclientweb", icon='QUESTION') -@rnaType -class RENDER_PT_network_slave_settings(RenderButtonsPanel): +class RENDER_PT_network_slave_settings(bpy.types.Panel, RenderButtonsPanel): bl_label = "Slave Settings" COMPAT_ENGINES = {'NET_RENDER'} - def poll(self, context): + @classmethod + def poll(cls, context): scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_SLAVE") + return super(RENDER_PT_network_slave_settings, cls).poll(context) and scene.network_render.mode == "RENDER_SLAVE" def draw(self, context): layout = self.layout @@ -139,22 +147,23 @@ class RENDER_PT_network_slave_settings(RenderButtonsPanel): rd = scene.render netsettings = scene.network_render - layout.prop(netsettings, "slave_clear") - layout.prop(netsettings, "slave_thumb") + layout.prop(netsettings, "use_slave_clear") + layout.prop(netsettings, "use_slave_thumb") + layout.prop(netsettings, "use_slave_output_log") layout.label(text="Threads:") layout.prop(rd, "threads_mode", expand=True) sub = layout.column() sub.enabled = rd.threads_mode == 'FIXED' sub.prop(rd, "threads") -@rnaType -class RENDER_PT_network_master_settings(RenderButtonsPanel): + +class RENDER_PT_network_master_settings(bpy.types.Panel, RenderButtonsPanel): bl_label = "Master Settings" COMPAT_ENGINES = {'NET_RENDER'} - def poll(self, context): + @classmethod + def poll(cls, context): scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_MASTER") + return super(RENDER_PT_network_master_settings, cls).poll(context) and scene.network_render.mode == "RENDER_MASTER" def draw(self, context): layout = self.layout @@ -162,18 +171,17 @@ class RENDER_PT_network_master_settings(RenderButtonsPanel): scene = context.scene netsettings = scene.network_render - layout.prop(netsettings, "master_broadcast") - layout.prop(netsettings, "master_clear") + layout.prop(netsettings, "use_master_broadcast") + layout.prop(netsettings, "use_master_clear") -@rnaType -class RENDER_PT_network_job(RenderButtonsPanel): +class RENDER_PT_network_job(bpy.types.Panel, RenderButtonsPanel): bl_label = "Job Settings" COMPAT_ENGINES = {'NET_RENDER'} - def poll(self, context): + @classmethod + def poll(cls, context): scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT") + return super(RENDER_PT_network_job, cls).poll(context) and scene.network_render.mode == "RENDER_CLIENT" def draw(self, context): layout = self.layout @@ -186,6 +194,7 @@ class RENDER_PT_network_job(RenderButtonsPanel): if netsettings.server_address != "[default]": layout.operator("render.netclientanim", icon='RENDER_ANIMATION') layout.operator("render.netclientsend", icon='FILE_BLEND') + layout.operator("render.netclientsendframe", icon='RENDER_STILL') if netsettings.job_id: row = layout.row() row.operator("render.render", text="Get Image", icon='RENDER_STILL') @@ -205,18 +214,18 @@ class RENDER_PT_network_job(RenderButtonsPanel): row.prop(netsettings, "priority") row.prop(netsettings, "chunks") -@rnaType -class RENDER_PT_network_slaves(RenderButtonsPanel): +class RENDER_PT_network_slaves(bpy.types.Panel, RenderButtonsPanel): bl_label = "Slaves Status" COMPAT_ENGINES = {'NET_RENDER'} - def poll(self, context): + @classmethod + def poll(cls, context): scene = context.scene netsettings = scene.network_render + if netsettings.mode != "RENDER_CLIENT": + return False verify_address(netsettings) - return (super().poll(context) - and netsettings.mode == "RENDER_CLIENT" - and netsettings.server_address != "[default]") + return super(RENDER_PT_network_slaves, cls).poll(context) and netsettings.server_address != "[default]" def draw(self, context): layout = self.layout @@ -243,18 +252,18 @@ class RENDER_PT_network_slaves(RenderButtonsPanel): layout.label(text="Seen: " + time.ctime(slave.last_seen)) layout.label(text="Stats: " + slave.stats) -@rnaType -class RENDER_PT_network_slaves_blacklist(RenderButtonsPanel): +class RENDER_PT_network_slaves_blacklist(bpy.types.Panel, RenderButtonsPanel): bl_label = "Slaves Blacklist" COMPAT_ENGINES = {'NET_RENDER'} - def poll(self, context): + @classmethod + def poll(cls, context): scene = context.scene netsettings = scene.network_render + if netsettings.mode != "RENDER_CLIENT": + return False verify_address(netsettings) - return (super().poll(context) - and netsettings.mode == "RENDER_CLIENT" - and netsettings.server_address != "[default]") + return super(RENDER_PT_network_slaves_blacklist, cls).poll(context) and netsettings.server_address != "[default]" def draw(self, context): layout = self.layout @@ -280,18 +289,18 @@ class RENDER_PT_network_slaves_blacklist(RenderButtonsPanel): layout.label(text="Seen: " + time.ctime(slave.last_seen)) layout.label(text="Stats: " + slave.stats) -@rnaType -class RENDER_PT_network_jobs(RenderButtonsPanel): +class RENDER_PT_network_jobs(bpy.types.Panel, RenderButtonsPanel): bl_label = "Jobs" COMPAT_ENGINES = {'NET_RENDER'} - def poll(self, context): + @classmethod + def poll(cls, context): scene = context.scene netsettings = scene.network_render + if netsettings.mode != "RENDER_CLIENT": + return False verify_address(netsettings) - return (super().poll(context) - and netsettings.mode == "RENDER_CLIENT" - and netsettings.server_address != "[default]") + return super(RENDER_PT_network_jobs, cls).poll(context) and netsettings.server_address != "[default]" def draw(self, context): layout = self.layout @@ -320,145 +329,148 @@ class RENDER_PT_network_jobs(RenderButtonsPanel): layout.label(text="Done: %04i" % job.results[DONE]) layout.label(text="Error: %04i" % job.results[ERROR]) -@rnaType class NetRenderSettings(bpy.types.IDPropertyGroup): pass -@rnaType class NetRenderSlave(bpy.types.IDPropertyGroup): pass -@rnaType class NetRenderJob(bpy.types.IDPropertyGroup): pass -bpy.types.Scene.PointerProperty(attr="network_render", type=NetRenderSettings, name="Network Render", description="Network Render Settings") - -NetRenderSettings.StringProperty( attr="server_address", - name="Server address", - description="IP or name of the master render server", - maxlen = 128, - default = "[default]") - -NetRenderSettings.IntProperty( attr="server_port", - name="Server port", - description="port of the master render server", - default = 8000, - min=1, - max=65535) - -NetRenderSettings.BoolProperty( attr="master_broadcast", - name="Broadcast", - description="broadcast master server address on local network", - default = True) - -NetRenderSettings.BoolProperty( attr="slave_clear", - name="Clear on exit", - description="delete downloaded files on exit", - default = True) - -NetRenderSettings.BoolProperty( attr="slave_thumb", - name="Generate thumbnails", - description="Generate thumbnails on slaves instead of master", - default = False) - -NetRenderSettings.BoolProperty( attr="master_clear", - name="Clear on exit", - description="delete saved files on exit", - default = False) - -default_path = os.environ.get("TEMP") - -if not default_path: - if os.name == 'nt': - default_path = "c:/tmp/" - else: - default_path = "/tmp/" -elif not default_path.endswith(os.sep): - default_path += os.sep - -NetRenderSettings.StringProperty( attr="path", - name="Path", - description="Path for temporary files", - maxlen = 128, - default = default_path, - subtype='FILE_PATH') - -NetRenderSettings.StringProperty( attr="job_name", - name="Job name", - description="Name of the job", - maxlen = 128, - default = "[default]") - -NetRenderSettings.StringProperty( attr="job_category", - name="Job category", - description="Category of the job", - maxlen = 128, - default = "") - -NetRenderSettings.IntProperty( attr="chunks", - name="Chunks", - description="Number of frame to dispatch to each slave in one chunk", - default = 5, - min=1, - max=65535) - -NetRenderSettings.IntProperty( attr="priority", - name="Priority", - description="Priority of the job", - default = 1, - min=1, - max=10) - -NetRenderSettings.StringProperty( attr="job_id", - name="Network job id", - description="id of the last sent render job", - maxlen = 64, - default = "") - -NetRenderSettings.IntProperty( attr="active_slave_index", - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) - -NetRenderSettings.IntProperty( attr="active_blacklisted_slave_index", - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) - -NetRenderSettings.IntProperty( attr="active_job_index", - name="Index of the active job", - description="", - default = -1, - min= -1, - max=65535) - -NetRenderSettings.EnumProperty(attr="mode", - items=( - ("RENDER_CLIENT", "Client", "Act as render client"), - ("RENDER_MASTER", "Master", "Act as render master"), - ("RENDER_SLAVE", "Slave", "Act as render slave"), - ), - name="Network mode", - description="Mode of operation of this instance", - default="RENDER_CLIENT") - -NetRenderSettings.CollectionProperty(attr="slaves", type=NetRenderSlave, name="Slaves", description="") -NetRenderSettings.CollectionProperty(attr="slaves_blacklist", type=NetRenderSlave, name="Slaves Blacklist", description="") -NetRenderSettings.CollectionProperty(attr="jobs", type=NetRenderJob, name="Job List", description="") - -NetRenderSlave.StringProperty( attr="name", - name="Name of the slave", - description="", - maxlen = 64, - default = "") - -NetRenderJob.StringProperty( attr="name", - name="Name of the job", - description="", - maxlen = 128, - default = "") +def addProperties(): + bpy.types.Scene.PointerProperty(attr="network_render", type=NetRenderSettings, name="Network Render", description="Network Render Settings") + + NetRenderSettings.StringProperty( attr="server_address", + name="Server address", + description="IP or name of the master render server", + maxlen = 128, + default = "[default]") + + NetRenderSettings.IntProperty( attr="server_port", + name="Server port", + description="port of the master render server", + default = 8000, + min=1, + max=65535) + + NetRenderSettings.BoolProperty( attr="use_master_broadcast", + name="Broadcast", + description="broadcast master server address on local network", + default = True) + + NetRenderSettings.BoolProperty( attr="use_slave_clear", + name="Clear on exit", + description="delete downloaded files on exit", + default = True) + + NetRenderSettings.BoolProperty( attr="use_slave_thumb", + name="Generate thumbnails", + description="Generate thumbnails on slaves instead of master", + default = False) + + NetRenderSettings.BoolProperty( attr="use_slave_output_log", + name="Output render log on console", + description="Output render text log to console as well as sending it to the master", + default = True) + + NetRenderSettings.BoolProperty( attr="use_master_clear", + name="Clear on exit", + description="delete saved files on exit", + default = False) + + default_path = os.environ.get("TEMP") + + if not default_path: + if os.name == 'nt': + default_path = "c:/tmp/" + else: + default_path = "/tmp/" + elif not default_path.endswith(os.sep): + default_path += os.sep + + NetRenderSettings.StringProperty( attr="path", + name="Path", + description="Path for temporary files", + maxlen = 128, + default = default_path, + subtype='FILE_PATH') + + NetRenderSettings.StringProperty( attr="job_name", + name="Job name", + description="Name of the job", + maxlen = 128, + default = "[default]") + + NetRenderSettings.StringProperty( attr="job_category", + name="Job category", + description="Category of the job", + maxlen = 128, + default = "") + + NetRenderSettings.IntProperty( attr="chunks", + name="Chunks", + description="Number of frame to dispatch to each slave in one chunk", + default = 5, + min=1, + max=65535) + + NetRenderSettings.IntProperty( attr="priority", + name="Priority", + description="Priority of the job", + default = 1, + min=1, + max=10) + + NetRenderSettings.StringProperty( attr="job_id", + name="Network job id", + description="id of the last sent render job", + maxlen = 64, + default = "") + + NetRenderSettings.IntProperty( attr="active_slave_index", + name="Index of the active slave", + description="", + default = -1, + min= -1, + max=65535) + + NetRenderSettings.IntProperty( attr="active_blacklisted_slave_index", + name="Index of the active slave", + description="", + default = -1, + min= -1, + max=65535) + + NetRenderSettings.IntProperty( attr="active_job_index", + name="Index of the active job", + description="", + default = -1, + min= -1, + max=65535) + + NetRenderSettings.EnumProperty(attr="mode", + items=( + ("RENDER_CLIENT", "Client", "Act as render client"), + ("RENDER_MASTER", "Master", "Act as render master"), + ("RENDER_SLAVE", "Slave", "Act as render slave"), + ), + name="Network mode", + description="Mode of operation of this instance", + default="RENDER_CLIENT") + + NetRenderSettings.CollectionProperty(attr="slaves", type=NetRenderSlave, name="Slaves", description="") + NetRenderSettings.CollectionProperty(attr="slaves_blacklist", type=NetRenderSlave, name="Slaves Blacklist", description="") + NetRenderSettings.CollectionProperty(attr="jobs", type=NetRenderJob, name="Job List", description="") + + NetRenderSlave.StringProperty( attr="name", + name="Name of the slave", + description="", + maxlen = 64, + default = "") + + NetRenderJob.StringProperty( attr="name", + name="Name of the job", + description="", + maxlen = 128, + default = "") diff --git a/release/scripts/io/netrender/utils.py b/release/scripts/io/netrender/utils.py index 37787732f09..81617ac0d30 100644 --- a/release/scripts/io/netrender/utils.py +++ b/release/scripts/io/netrender/utils.py @@ -19,7 +19,7 @@ import sys, os import re import http, http.client, http.server, urllib, socket -import subprocess, shutil, time, hashlib +import subprocess, shutil, time, hashlib, zlib import netrender.model @@ -28,7 +28,7 @@ try: except: bpy = None -VERSION = bytes("0.8", encoding='utf8') +VERSION = bytes("0.9", encoding='utf8') # Jobs status JOB_WAITING = 0 # before all data has been entered @@ -57,9 +57,10 @@ FRAME_STATUS_TEXT = { ERROR: "Error" } -def rnaType(rna_type): - if bpy: bpy.types.register(rna_type) - return rna_type +def responseStatus(conn): + response = conn.getresponse() + response.read() + return response.status def reporting(report, message, errorType = None): if errorType: @@ -154,22 +155,37 @@ def renderURL(job_id, frame_number): def cancelURL(job_id): return "/cancel_%s" % (job_id) -def prefixPath(prefix_directory, file_path, prefix_path): +def hashFile(path): + f = open(path, "rb") + value = hashData(f.read()) + f.close() + return value + +def hashData(data): + m = hashlib.md5() + m.update(data) + return m.hexdigest() + + +def prefixPath(prefix_directory, file_path, prefix_path, force = False): if os.path.isabs(file_path): # if an absolute path, make sure path exists, if it doesn't, use relative local path full_path = file_path - if not os.path.exists(full_path): - p, n = os.path.split(full_path) + if force or not os.path.exists(full_path): + p, n = os.path.split(os.path.normpath(full_path)) if prefix_path and p.startswith(prefix_path): - directory = prefix_directory + p[len(prefix_path):] - full_path = directory + os.sep + n - if not os.path.exists(directory): - os.mkdir(directory) + if len(prefix_path) < len(p): + directory = os.path.join(prefix_directory, p[len(prefix_path)+1:]) # +1 to remove separator + if not os.path.exists(directory): + os.mkdir(directory) + else: + directory = prefix_directory + full_path = os.path.join(directory, n) else: - full_path = prefix_directory + n + full_path = os.path.join(prefix_directory, n) else: - full_path = prefix_directory + file_path + full_path = (prefix_directory, file_path) return full_path @@ -200,7 +216,7 @@ def thumbnail(filename): scene = bpy.data.scenes[0] # FIXME, this is dodgy! scene.render.file_format = "JPEG" scene.render.file_quality = 90 - bpy.ops.image.open(path = filename) + bpy.ops.image.open(filepath=filename) img = bpy.data.images[imagename] img.save_render(thumbname, scene=scene) |