Node Wrangler: Improved accuracy on Align Nodes operator #104551

Open
quackarooni wants to merge 18 commits from quackarooni/blender-addons:nw_rework_align_nodes into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
2 changed files with 44 additions and 53 deletions
Showing only changes of commit 1767393035 - Show all commits

View File

@ -23,8 +23,7 @@ bl_info = {
"cameras, lamps & animation", "cameras, lamps & animation",
"warning": "Images must be in file folder, " "warning": "Images must be in file folder, "
"filenames are limited to DOS 8.3 format", "filenames are limited to DOS 8.3 format",
"doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_3ds.html",
"Scripts/Import-Export/Autodesk_3DS",
"category": "Import-Export", "category": "Import-Export",
} }

View File

@ -7,6 +7,7 @@ from the lib3ds project (http://lib3ds.sourceforge.net/) sourcecode.
""" """
import bpy import bpy
import time
import math import math
import struct import struct
import mathutils import mathutils
@ -332,7 +333,7 @@ class _3ds_rgb_color(object):
class _3ds_face(object): class _3ds_face(object):
"""Class representing a face for a 3ds file.""" """Class representing a face for a 3ds file."""
__slots__ = ("vindex", "flag") __slots__ = ("vindex", "flag", )
def __init__(self, vindex, flag): def __init__(self, vindex, flag):
self.vindex = vindex self.vindex = vindex
@ -530,6 +531,10 @@ def make_percent_subchunk(chunk_id, percent):
pcti = _3ds_chunk(PCT) pcti = _3ds_chunk(PCT)
pcti.add_variable("percent", _3ds_ushort(int(round(percent * 100, 0)))) pcti.add_variable("percent", _3ds_ushort(int(round(percent * 100, 0))))
pct_sub.add_subchunk(pcti) pct_sub.add_subchunk(pcti)
# optional:
# pctf = _3ds_chunk(PCTF)
# pctf.add_variable("pctfloat", _3ds_float(round(percent, 6)))
# pct_sub.add_subchunk(pctf)
return pct_sub return pct_sub
@ -557,6 +562,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct):
Paint slots are optionally used as image source if no nodes are Paint slots are optionally used as image source if no nodes are
used. No additional filtering for mapping modes is done, all used. No additional filtering for mapping modes is done, all
slots are written "as is".""" slots are written "as is"."""
# Add texture percentage value # Add texture percentage value
mat_sub = make_percent_subchunk(chunk_id, pct) mat_sub = make_percent_subchunk(chunk_id, pct)
has_entry = False has_entry = False
@ -572,19 +578,26 @@ def make_material_texture_chunk(chunk_id, texslots, pct):
socket = link.from_socket.identifier socket = link.from_socket.identifier
mat_sub_mapflags = _3ds_chunk(MAT_MAP_TILING) mat_sub_mapflags = _3ds_chunk(MAT_MAP_TILING)
"""Control bit flags, where 0x1 activates decaling, 0x2 activates mirror,
0x8 activates inversion, 0x10 deactivates tiling, 0x20 activates summed area sampling,
0x40 activates alpha source, 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint.
Bits 0x80, 0x100, and 0x200 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks.
0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk must be accompanied with a tint bit,
either 0x100 or 0x200, tintcolor will be processed if colorchunks are present"""
mapflags = 0 mapflags = 0
# no perfect mapping for mirror modes - 3DS only has uniform mirror w. repeat=2 # no perfect mapping for mirror modes - 3DS only has uniform mirror w. repeat=2
if texslot.extension == 'EXTEND': # decal flag if texslot.extension == 'EXTEND':
mapflags |= 0x1 mapflags |= 0x1
# CLIP maps to 3DS' decal flag
if texslot.extension == 'CLIP': # no wrap if texslot.extension == 'CLIP':
mapflags |= 0x10 mapflags |= 0x10
if socket == 'Alpha': if socket == 'Alpha':
mapflags |= 0x40 # summed area sampling 0x20 mapflags |= 0x40
if texslot.socket_dst.identifier in {'Base Color', 'Specular'}: if texslot.socket_dst.identifier in {'Base Color', 'Specular'}:
mapflags |= 0x80 if image.colorspace_settings.name=='Non-Color' else 0x200 # RGB tint mapflags |= 0x80 if image.colorspace_settings.name=='Non-Color' else 0x200
mat_sub_mapflags.add_variable("mapflags", _3ds_ushort(mapflags)) mat_sub_mapflags.add_variable("mapflags", _3ds_ushort(mapflags))
mat_sub.add_subchunk(mat_sub_mapflags) mat_sub.add_subchunk(mat_sub_mapflags)
@ -635,6 +648,7 @@ def make_material_chunk(material, image):
"""Make a material chunk out of a blender material. """Make a material chunk out of a blender material.
Shading method is required for 3ds max, 0 for wireframe. Shading method is required for 3ds max, 0 for wireframe.
0x1 for flat, 0x2 for gouraud, 0x3 for phong and 0x4 for metal.""" 0x1 for flat, 0x2 for gouraud, 0x3 for phong and 0x4 for metal."""
material_chunk = _3ds_chunk(MATERIAL) material_chunk = _3ds_chunk(MATERIAL)
name = _3ds_chunk(MATNAME) name = _3ds_chunk(MATNAME)
shading = _3ds_chunk(MATSHADING) shading = _3ds_chunk(MATSHADING)
@ -727,7 +741,7 @@ def make_material_chunk(material, image):
diffuse = [] diffuse = []
for link in wrap.material.node_tree.links: for link in wrap.material.node_tree.links:
if link.from_node.type == 'TEX_IMAGE' and link.to_node.type == 'MIX_RGB': if link.from_node.type == 'TEX_IMAGE' and link.to_node.type in {'MIX', 'MIX_RGB'}:
diffuse = [link.from_node.image] diffuse = [link.from_node.image]
if diffuse: if diffuse:
@ -987,16 +1001,6 @@ def make_uv_chunk(uv_array):
return uv_chunk return uv_chunk
'''
def make_matrix_4x3_chunk(matrix):
matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
for vec in matrix.col:
for f in vec[:3]:
matrix_chunk.add_variable("matrix_f", _3ds_float(f))
return matrix_chunk
'''
def make_mesh_chunk(ob, mesh, matrix, materialDict, translation): def make_mesh_chunk(ob, mesh, matrix, materialDict, translation):
"""Make a chunk out of a Blender mesh.""" """Make a chunk out of a Blender mesh."""
@ -1027,14 +1031,12 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation):
if uv_array: if uv_array:
mesh_chunk.add_subchunk(make_uv_chunk(uv_array)) mesh_chunk.add_subchunk(make_uv_chunk(uv_array))
# mesh_chunk.add_subchunk(make_matrix_4x3_chunk(matrix))
# create transformation matrix chunk # create transformation matrix chunk
matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX) matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
obj_matrix = matrix.transposed().to_3x3() obj_matrix = matrix.transposed().to_3x3()
if ob.parent is None: if ob.parent is None or ob.parent.name not in translation:
obj_translate = translation[ob.name] obj_translate = matrix.to_translation()
else: # Calculate child matrix translation relative to parent else: # Calculate child matrix translation relative to parent
obj_translate = translation[ob.name].cross(-1 * translation[ob.parent.name]) obj_translate = translation[ob.name].cross(-1 * translation[ob.parent.name])
@ -1185,14 +1187,13 @@ def save(operator,
global_matrix=None, global_matrix=None,
): ):
import time
# from bpy_extras.io_utils import create_derived_objects, free_derived_objects
"""Save the Blender scene to a 3ds file.""" """Save the Blender scene to a 3ds file."""
# Time the export # Time the export
duration = time.time() duration = time.time()
# Blender.Window.WaitCursor(1)
scene = context.scene
layer = context.view_layer
depsgraph = context.evaluated_depsgraph_get()
if global_matrix is None: if global_matrix is None:
global_matrix = mathutils.Matrix() global_matrix = mathutils.Matrix()
@ -1200,12 +1201,9 @@ def save(operator,
if bpy.ops.object.mode_set.poll(): if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
scene = context.scene
layer = context.view_layer
depsgraph = context.evaluated_depsgraph_get()
# Initialize the main chunk (primary): # Initialize the main chunk (primary):
primary = _3ds_chunk(PRIMARY) primary = _3ds_chunk(PRIMARY)
# Add version chunk: # Add version chunk:
version_chunk = _3ds_chunk(VERSION) version_chunk = _3ds_chunk(VERSION)
version_chunk.add_variable("version", _3ds_uint(3)) version_chunk.add_variable("version", _3ds_uint(3))
@ -1241,16 +1239,16 @@ def save(operator,
mesh_objects = [] mesh_objects = []
if use_selection: if use_selection:
objects = [ob for ob in scene.objects if not ob.hide_viewport and ob.select_get(view_layer=layer)] objects = [ob for ob in scene.objects if ob.visible_get(view_layer=layer) and ob.select_get(view_layer=layer)]
else: else:
objects = [ob for ob in scene.objects if not ob.hide_viewport] objects = [ob for ob in scene.objects if ob.visible_get(view_layer=layer)]
empty_objects = [ob for ob in objects if ob.type == 'EMPTY']
light_objects = [ob for ob in objects if ob.type == 'LIGHT'] light_objects = [ob for ob in objects if ob.type == 'LIGHT']
camera_objects = [ob for ob in objects if ob.type == 'CAMERA'] camera_objects = [ob for ob in objects if ob.type == 'CAMERA']
for ob in objects: for ob in objects:
# get derived objects # get derived objects
# free, derived = create_derived_objects(scene, ob)
derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob]) derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob])
derived = derived_dict.get(ob) derived = derived_dict.get(ob)
@ -1302,25 +1300,24 @@ def save(operator,
if f.material_index >= ma_ls_len: if f.material_index >= ma_ls_len:
f.material_index = 0 f.material_index = 0
# ob_derived_eval.to_mesh_clear()
# if free:
# free_derived_objects(ob)
# Make material chunks for all materials used in the meshes: # Make material chunks for all materials used in the meshes:
for ma_image in materialDict.values(): for ma_image in materialDict.values():
object_info.add_subchunk(make_material_chunk(ma_image[0], ma_image[1])) object_info.add_subchunk(make_material_chunk(ma_image[0], ma_image[1]))
# Collect translation for transformation matrix
translation = {}
# Give all objects a unique ID and build a dictionary from object name to object id: # Give all objects a unique ID and build a dictionary from object name to object id:
translation = {} # collect translation for transformation matrix
# name_to_id = {} # name_to_id = {}
for ob, data, matrix in mesh_objects: for ob, data, matrix in mesh_objects:
translation[ob.name] = ob.location translation[ob.name] = ob.location
# name_to_id[ob.name]= len(name_to_id) # name_to_id[ob.name]= len(name_to_id)
"""
#for ob in empty_objects: for ob in empty_objects:
translation[ob.name] = ob.location
# name_to_id[ob.name]= len(name_to_id) # name_to_id[ob.name]= len(name_to_id)
"""
# Create object chunks for all meshes: # Create object chunks for all meshes:
i = 0 i = 0
@ -1347,10 +1344,6 @@ def save(operator,
kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
''' '''
# if not blender_mesh.users:
# bpy.data.meshes.remove(blender_mesh)
# blender_mesh.vertices = None
i += i i += i
# Create chunks for all empties: # Create chunks for all empties:
@ -1425,9 +1418,9 @@ def save(operator,
''' '''
# At this point, the chunk hierarchy is completely built. # At this point, the chunk hierarchy is completely built.
# Check the size: # Check the size:
primary.get_size() primary.get_size()
# Open the file for writing: # Open the file for writing:
file = open(filepath, 'wb') file = open(filepath, 'wb')
@ -1442,7 +1435,6 @@ def save(operator,
name_mapping.clear() name_mapping.clear()
# Debugging only: report the exporting time: # Debugging only: report the exporting time:
# Blender.Window.WaitCursor(0)
print("3ds export time: %.2f" % (time.time() - duration)) print("3ds export time: %.2f" % (time.time() - duration))
# Debugging only: dump the chunk hierarchy: # Debugging only: dump the chunk hierarchy: