From e98a2bf101ab14ade9340730bed2a2de8f99682d Mon Sep 17 00:00:00 2001 From: Vladimir Spivak Date: Sat, 12 Mar 2022 13:42:19 +0200 Subject: Fix T96342: Add Curve Extra Objects addon fails when adding Curvy Curve --- add_curve_extra_objects/add_curve_curly.py | 6 +++--- add_curve_extra_objects/beveltaper_curve.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/add_curve_extra_objects/add_curve_curly.py b/add_curve_extra_objects/add_curve_curly.py index d49cb8fb..5d07a149 100644 --- a/add_curve_extra_objects/add_curve_curly.py +++ b/add_curve_extra_objects/add_curve_curly.py @@ -5,7 +5,7 @@ bl_info = { "name": "Curly Curves", "author": "Cmomoney", - "version": (1, 1, 9), + "version": (1, 2, 0), "blender": (2, 80, 0), "location": "View3D > Add > Curve > Curly Curve", "description": "Adds a new Curly Curve", @@ -402,7 +402,7 @@ def make_curve(self, context, verts, lh, rh): for p in range(len(verts)): c = 0 newSpline = Curve.data.splines.new(type='BEZIER') # newSpline - newSpline.bezier_points.add(len(verts[p]) / 3 - 1) + newSpline.bezier_points.add(int(len(verts[p]) / 3 - 1)) newSpline.bezier_points.foreach_set('co', verts[p]) for bp in newSpline.bezier_points: @@ -424,7 +424,7 @@ def make_curve(self, context, verts, lh, rh): for p in range(len(verts)): c = 0 newSpline = dataCurve.splines.new(type='BEZIER') # newSpline - newSpline.bezier_points.add(len(verts[p]) / 3 - 1) + newSpline.bezier_points.add(int(len(verts[p]) / 3 - 1)) newSpline.bezier_points.foreach_set('co', verts[p]) for bp in newSpline.bezier_points: diff --git a/add_curve_extra_objects/beveltaper_curve.py b/add_curve_extra_objects/beveltaper_curve.py index 5b71cf00..476f8e4e 100644 --- a/add_curve_extra_objects/beveltaper_curve.py +++ b/add_curve_extra_objects/beveltaper_curve.py @@ -20,7 +20,7 @@ bl_info = { "name": "Bevel/Taper Curve", "author": "Cmomoney", - "version": (1, 2), + "version": (1, 2, 1), "blender": (2, 80, 0), "location": "View3D > Object > Bevel/Taper", "description": "Adds bevel and/or taper curve to active curve", @@ -219,7 +219,7 @@ def make_curve(self, context, verts, lh, rh): c = 0 spline = curve_data.splines.new(type='BEZIER') spline.use_cyclic_u = True - spline.bezier_points.add(len(verts[p]) / 3 - 1) + spline.bezier_points.add(int(len(verts[p]) / 3 - 1)) spline.bezier_points.foreach_set('co', verts[p]) for bp in spline.bezier_points: -- cgit v1.2.3 From 0f6165054bb2c802156e8fcd1d2d55c501455bc2 Mon Sep 17 00:00:00 2001 From: Jay Jasper Date: Sat, 26 Mar 2022 17:35:31 +1100 Subject: STL Export: add "global_space" matrix Added an option to allow the user to export STLs using an arbitrary (custom) coordinate-space. This is not exposed in the UI and is intended for script authors exporting content. Ref D11517 --- io_mesh_stl/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py index 56276e5f..3d1aec5c 100644 --- a/io_mesh_stl/__init__.py +++ b/io_mesh_stl/__init__.py @@ -43,6 +43,7 @@ from bpy.props import ( CollectionProperty, EnumProperty, FloatProperty, + FloatVectorProperty, ) from bpy_extras.io_utils import ( ImportHelper, @@ -227,6 +228,12 @@ class ExportSTL(Operator, ExportHelper): ('OBJECT', "Object", "Each object as a file"), ), ) + global_space: FloatVectorProperty( + name="Global Space", + description="Export in this reference space", + subtype='MATRIX', + size=(4, 4), + ) @property def check_extension(self): @@ -249,7 +256,8 @@ class ExportSTL(Operator, ExportHelper): "filter_glob", "use_scene_unit", "use_mesh_modifiers", - "batch_mode" + "batch_mode", + "global_space", ), ) @@ -269,6 +277,9 @@ class ExportSTL(Operator, ExportHelper): to_up=self.axis_up, ).to_4x4() @ Matrix.Scale(global_scale, 4) + if self.properties.is_property_set("global_space"): + global_matrix = global_matrix @ self.global_space.inverted() + if self.batch_mode == 'OFF': faces = itertools.chain.from_iterable( blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers) -- cgit v1.2.3 From d700a6888ecd93092c0c2f00ce7c2815a813613b Mon Sep 17 00:00:00 2001 From: Samuli Raivio Date: Mon, 28 Mar 2022 09:36:27 +0200 Subject: Add "Triangulate Faces" option to FBX export I noticed the FBX export was missing a triangulation option seen in other software and other Blender exporters such as .obj. This feature is rather useful for example for ensuring consistent normal map baking in third party software, where tangent space gets easily messed up with tools using mikktspace that depends on triangulation choices. This patch adds a "Triangulate Faces" option in the export options similarly to what the Wavefront OBJ exporter has. Diff-wise it's rather simple by reusing the temporary mesh creation from "Apply Modifiers". Reviewed By: mont29 Differential Revision: https://developer.blender.org/D14352 --- io_scene_fbx/__init__.py | 8 +++++++- io_scene_fbx/export_fbx_bin.py | 16 ++++++++++++++-- io_scene_fbx/fbx_utils.py | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index 0abafc33..9933322b 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -5,7 +5,7 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (4, 34, 2), + "version": (4, 35, 0), "blender": (3, 2, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", @@ -477,6 +477,11 @@ class ExportFBX(bpy.types.Operator, ExportHelper): "(will only work correctly with tris/quads only meshes!)", default=False, ) + use_triangles: BoolProperty( + name="Triangulate Faces", + description="Convert all faces to triangles", + default=False, + ) use_custom_props: BoolProperty( name="Custom Properties", description="Export custom properties", @@ -754,6 +759,7 @@ class FBX_PT_export_geometry(bpy.types.Panel): #sub.enabled = operator.use_mesh_modifiers and False # disabled in 2.8... #sub.prop(operator, "use_mesh_modifiers_render") layout.prop(operator, "use_mesh_edges") + layout.prop(operator, "use_triangles") sub = layout.row() #~ sub.enabled = operator.mesh_smooth_type in {'OFF'} sub.prop(operator, "use_tspace") diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index 50aefe03..b017a400 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -2266,12 +2266,14 @@ def fbx_data_from_scene(scene, depsgraph, settings): is_ob_material = any(ms.link == 'OBJECT' for ms in ob.material_slots) - if settings.use_mesh_modifiers or ob.type in BLENDER_OTHER_OBJECT_TYPES or is_ob_material: + if settings.use_mesh_modifiers or settings.use_triangles or ob.type in BLENDER_OTHER_OBJECT_TYPES or is_ob_material: # We cannot use default mesh in that case, or material would not be the right ones... use_org_data = not (is_ob_material or ob.type in BLENDER_OTHER_OBJECT_TYPES) backup_pose_positions = [] tmp_mods = [] if use_org_data and ob.type == 'MESH': + if settings.use_triangles: + use_org_data = False # No need to create a new mesh in this case, if no modifier is active! last_subsurf = None for mod in ob.modifiers: @@ -2316,6 +2318,14 @@ def fbx_data_from_scene(scene, depsgraph, settings): # free them afterwards. Not ideal but ensures correct ownerwhip. tmp_me = bpy.data.meshes.new_from_object( ob_to_convert, preserve_all_data_layers=True, depsgraph=depsgraph) + # Triangulate the mesh if requested + if settings.use_triangles: + import bmesh + bm = bmesh.new() + bm.from_mesh(tmp_me) + bmesh.ops.triangulate(bm, faces=bm.faces) + bm.to_mesh(tmp_me) + bm.free() data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True) # Change armatures back. for armature, pose_position in backup_pose_positions: @@ -3008,6 +3018,7 @@ def save_single(operator, scene, depsgraph, filepath="", path_mode='AUTO', use_mesh_edges=True, use_tspace=True, + use_triangles=False, embed_textures=False, use_custom_props=False, bake_space_transform=False, @@ -3074,7 +3085,7 @@ def save_single(operator, scene, depsgraph, filepath="", operator.report, (axis_up, axis_forward), global_matrix, global_scale, apply_unit_scale, unit_scale, bake_space_transform, global_matrix_inv, global_matrix_inv_transposed, context_objects, object_types, use_mesh_modifiers, use_mesh_modifiers_render, - mesh_smooth_type, use_subsurf, use_mesh_edges, use_tspace, + mesh_smooth_type, use_subsurf, use_mesh_edges, use_tspace, use_triangles, armature_nodetype, use_armature_deform_only, add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv, bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions, @@ -3148,6 +3159,7 @@ def defaults_unity3d(): "mesh_smooth_type": 'FACE', "use_subsurf": False, "use_tspace": False, # XXX Why? Unity is expected to support tspace import... + "use_triangles": False, "use_armature_deform_only": True, diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 4fbff329..680ee387 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -1210,7 +1210,7 @@ FBXExportSettings = namedtuple("FBXExportSettings", ( "report", "to_axes", "global_matrix", "global_scale", "apply_unit_scale", "unit_scale", "bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed", "context_objects", "object_types", "use_mesh_modifiers", "use_mesh_modifiers_render", - "mesh_smooth_type", "use_subsurf", "use_mesh_edges", "use_tspace", + "mesh_smooth_type", "use_subsurf", "use_mesh_edges", "use_tspace", "use_triangles", "armature_nodetype", "use_armature_deform_only", "add_leaf_bones", "bone_correction_matrix", "bone_correction_matrix_inv", "bake_anim", "bake_anim_use_all_bones", "bake_anim_use_nla_strips", "bake_anim_use_all_actions", -- cgit v1.2.3 From 1d45d7e10631bc57f563f3e6702592c7a8565416 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 28 Mar 2022 10:41:43 +0200 Subject: Fix various UI messages issues. --- io_scene_gltf2/__init__.py | 4 ++-- object_print3d_utils/operators.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index fd0687b9..c2704ffd 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -375,8 +375,8 @@ class ExportGLTF2_Base: optimize_animation_size: BoolProperty( name='Optimize Animation Size', description=( - "Reduces exported filesize by removing duplicate keyframes" - "Can cause problems with stepped animation" + "Reduce exported file-size by removing duplicate keyframes" + "(can cause problems with stepped animation)" ), default=True ) diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py index 50988163..39142dde 100644 --- a/object_print3d_utils/operators.py +++ b/object_print3d_utils/operators.py @@ -781,9 +781,9 @@ class MESH_OT_print3d_align_to_xy(Operator): for name in skip_invalid: print(f"Align to XY: Skipping object {name}. No faces selected.") if len(skip_invalid) == 1: - self.report({'WARNING'}, f"Skipping object {skip_invalid[0]}. No faces selected.") + self.report({'WARNING'}, "Skipping object. No faces selected" % skip_invalid[0]) else: - self.report({'WARNING'}, f"Skipping some objects. No faces selected. See terminal.") + self.report({'WARNING'}, "Skipping some objects. No faces selected. See terminal") return {'FINISHED'} def invoke(self, context, event): -- cgit v1.2.3 From ff99cb3cf86ff2824262541b1e6d981be76ea2ac Mon Sep 17 00:00:00 2001 From: Max Schlecht Date: Tue, 29 Mar 2022 09:56:43 +0200 Subject: Fix .X3D/.WRL importer crashing with empty IndexedFaceSets When importing a .x3d file containing an empty IndexedFaceSet, like this for example: ``` Shape { geometry IndexedFaceSet { coord Coordinate { point [ ] } coordIndex [ ] } } ``` `import_x3d.py` throws an exception, because `x_min`, `x_max`, ... are not initialized/still set to `None`. This fixes the issue by initializing the mentioned variables to `inf`/`-inf` respectively and also further simplifies the code by utlizing the `min` and `max` builtins. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D14470 --- io_scene_x3d/import_x3d.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/io_scene_x3d/import_x3d.py b/io_scene_x3d/import_x3d.py index 8299faf1..fbde81ad 100644 --- a/io_scene_x3d/import_x3d.py +++ b/io_scene_x3d/import_x3d.py @@ -2007,23 +2007,18 @@ def importMesh_IndexedFaceSet(geom, ancestry): for v in f for co in tex_coord_points[v]] else: - x_min = x_max = y_min = y_max = z_min = z_max = None + x_min = y_min = z_min = math.inf + x_max = y_max = z_max = -math.inf for f in faces: # Unused vertices don't participate in size; X3DOM does so for v in f: (x, y, z) = points[v] - if x_min is None or x < x_min: - x_min = x - if x_max is None or x > x_max: - x_max = x - if y_min is None or y < y_min: - y_min = y - if y_max is None or y > y_max: - y_max = y - if z_min is None or z < z_min: - z_min = z - if z_max is None or z > z_max: - z_max = z + x_min = min(x_min, x) + x_max = max(x_max, x) + y_min = min(y_min, y) + y_max = max(y_max, y) + z_min = min(z_min, z) + z_max = max(z_max, z) mins = (x_min, y_min, z_min) deltas = (x_max - x_min, y_max - y_min, z_max - z_min) -- cgit v1.2.3 From 1d5c8b54ee99433ff5c3ef416f1f9bc1fd8780ef Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 29 Mar 2022 17:39:41 +0200 Subject: glTF exporter: Fix T96517 better instance management when no modifiers --- io_scene_gltf2/__init__.py | 2 +- .../blender/exp/gltf2_blender_gather_mesh.py | 1 - .../blender/exp/gltf2_blender_gather_nodes.py | 47 ++++++++++++---------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index c2704ffd..11dbdce6 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 15), + "version": (3, 2, 16), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py index c8987127..fd334cb3 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py @@ -12,7 +12,6 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions -@cached def get_mesh_cache_key(blender_mesh, blender_object, vertex_groups, diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py index 25784960..b0b2d4b8 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -194,28 +194,31 @@ def __gather_mesh(vnode, blender_object, export_settings): if len(modifiers) == 0: modifiers = None - # TODO for objects without any modifiers, we can keep original mesh_data - # It will instance mesh in glTF - if export_settings[gltf2_blender_export_keys.APPLY]: - armature_modifiers = {} - if export_settings[gltf2_blender_export_keys.SKINS]: - # temporarily disable Armature modifiers if exporting skins - for idx, modifier in enumerate(blender_object.modifiers): - if modifier.type == 'ARMATURE': - armature_modifiers[idx] = modifier.show_viewport - modifier.show_viewport = False - - depsgraph = bpy.context.evaluated_depsgraph_get() - blender_mesh_owner = blender_object.evaluated_get(depsgraph) - blender_mesh = blender_mesh_owner.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph) - for prop in blender_object.data.keys(): - blender_mesh[prop] = blender_object.data[prop] - skip_filter = True - if export_settings[gltf2_blender_export_keys.SKINS]: - # restore Armature modifiers - for idx, show_viewport in armature_modifiers.items(): - blender_object.modifiers[idx].show_viewport = show_viewport + if export_settings[gltf2_blender_export_keys.APPLY]: + if modifiers is None: # If no modifier, use original mesh, it will instance all shared mesh in a single glTF mesh + blender_mesh = blender_object.data + skip_filter = False + else: + armature_modifiers = {} + if export_settings[gltf2_blender_export_keys.SKINS]: + # temporarily disable Armature modifiers if exporting skins + for idx, modifier in enumerate(blender_object.modifiers): + if modifier.type == 'ARMATURE': + armature_modifiers[idx] = modifier.show_viewport + modifier.show_viewport = False + + depsgraph = bpy.context.evaluated_depsgraph_get() + blender_mesh_owner = blender_object.evaluated_get(depsgraph) + blender_mesh = blender_mesh_owner.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph) + for prop in blender_object.data.keys(): + blender_mesh[prop] = blender_object.data[prop] + skip_filter = True + + if export_settings[gltf2_blender_export_keys.SKINS]: + # restore Armature modifiers + for idx, show_viewport in armature_modifiers.items(): + blender_object.modifiers[idx].show_viewport = show_viewport else: blender_mesh = blender_object.data skip_filter = False @@ -249,7 +252,7 @@ def __gather_mesh(vnode, blender_object, export_settings): None, export_settings) - if export_settings[gltf2_blender_export_keys.APPLY]: + if export_settings[gltf2_blender_export_keys.APPLY] and modifiers is not None: blender_mesh_owner.to_mesh_clear() return result -- cgit v1.2.3 From 564fdcdf7159013bc7087c9d12f2a034cfa3a945 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 29 Mar 2022 17:44:36 +0200 Subject: glTF exporter: Manage skinning when some vertices are not weights at all --- io_scene_gltf2/__init__.py | 2 +- .../blender/exp/gltf2_blender_extract.py | 19 +++++- io_scene_gltf2/blender/exp/gltf2_blender_gather.py | 2 + .../blender/exp/gltf2_blender_gather_skins.py | 3 + .../blender/exp/gltf2_blender_gather_tree.py | 69 ++++++++++++++++++++++ io_scene_gltf2/io/imp/gltf2_io_binary.py | 31 ++++++++++ 6 files changed, 122 insertions(+), 4 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 11dbdce6..4ff7ff69 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 16), + "version": (3, 2, 17), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py index d81bd706..98e2ac19 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -82,7 +82,14 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings) if skin: - vert_bones, num_joint_sets = __get_bone_data(blender_mesh, skin, blender_vertex_groups) + vert_bones, num_joint_sets, need_neutral_bone = __get_bone_data(blender_mesh, skin, blender_vertex_groups) + if need_neutral_bone is True: + # Need to create a fake joint at root of armature + # In order to assign not assigned vertices to it + # But for now, this is not yet possible, we need to wait the armature node is created + # Just store this, to be used later + armature_uuid = export_settings['vtree'].nodes[uuid_for_skined_data].armature + export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True # In Blender there is both per-vert data, like position, and also per-loop # (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert @@ -535,6 +542,9 @@ def __get_colors(blender_mesh, color_i): def __get_bone_data(blender_mesh, skin, blender_vertex_groups): + + need_neutral_bone = False + joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)} group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups] @@ -557,7 +567,10 @@ def __get_bone_data(blender_mesh, skin, blender_vertex_groups): continue bones.append((joint, weight)) bones.sort(key=lambda x: x[1], reverse=True) - if not bones: bones = ((0, 1.0),) # HACK for verts with zero weight (#308) + if not bones: + # Is not assign to any bone + bones = ((len(skin.joints), 1.0),) # Assign to a joint that will be created later + need_neutral_bone = True vert_bones.append(bones) if len(bones) > max_num_influences: max_num_influences = len(bones) @@ -565,7 +578,7 @@ def __get_bone_data(blender_mesh, skin, blender_vertex_groups): # How many joint sets do we need? 1 set = 4 influences num_joint_sets = (max_num_influences + 3) // 4 - return vert_bones, num_joint_sets + return vert_bones, num_joint_sets, need_neutral_bone def __zup2yup(array): diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py index f515da8c..b3f4fd2a 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py @@ -68,6 +68,8 @@ def __gather_scene(blender_scene, export_settings): if node is not None: scene.nodes.append(node) + vtree.add_neutral_bones() + export_user_extensions('gather_scene_hook', export_settings, scene, blender_scene) return scene diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py index 136d654d..3e4673e1 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py @@ -73,6 +73,9 @@ def __gather_inverse_bind_matrices(armature_uuid, export_settings): axis_basis_change = 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))) + # store matrix_world of armature in case we need to add a neutral bone + export_settings['vtree'].nodes[armature_uuid].matrix_world_armature = blender_armature_object.matrix_world.copy() + bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid) def __collect_matrices(bone): inverse_bind_matrix = ( diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py index 643cbea0..3a7b0fa5 100644 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py @@ -3,10 +3,17 @@ import bpy import uuid +import numpy as np from . import gltf2_blender_export_keys from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions from mathutils import Quaternion, Matrix +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.imp.gltf2_io_binary import BinaryData +from io_scene_gltf2.io.com import gltf2_io_constants +from .gltf2_blender_gather_primitive_attributes import array_to_accessor +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors class VExportNode: @@ -375,6 +382,68 @@ class VExportTree: n.armature = candidates[0].uuid del n.armature_needed + def add_neutral_bones(self): + for n in [n for n in self.nodes.values() if n.armature is not None and n.blender_type == VExportNode.OBJECT and hasattr(self.nodes[n.armature], "need_neutral_bone")]: #all skin meshes objects where neutral bone is needed + # First add a new node + + axis_basis_change = Matrix.Identity(4) + if self.export_settings[gltf2_blender_export_keys.YUP]: + axis_basis_change = 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))) + + trans, rot, sca = axis_basis_change.decompose() + translation, rotation, scale = (None, None, None) + if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0: + translation = [trans[0], trans[1], trans[2]] + if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0: + rotation = [rot[1], rot[2], rot[3], rot[0]] + if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0: + scale = [sca[0], sca[1], sca[2]] + neutral_bone = gltf2_io.Node( + camera=None, + children=None, + extensions=None, + extras=None, + matrix=None, + mesh=None, + name='neutral_bone', + rotation=rotation, + scale=scale, + skin=None, + translation=translation, + weights=None + ) + # Add it to child list of armature + self.nodes[n.armature].node.children.append(neutral_bone) + # Add it to joint list + n.node.skin.joints.append(neutral_bone) + + # Need to add an InverseBindMatrix + array = BinaryData.decode_accessor_internal(n.node.skin.inverse_bind_matrices) + + axis_basis_change = Matrix.Identity(4) + if self.export_settings[gltf2_blender_export_keys.YUP]: + axis_basis_change = 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))) + + inverse_bind_matrix = ( + axis_basis_change @ self.nodes[n.armature].matrix_world_armature).inverted_safe() + + matrix = [] + for column in range(0, 4): + for row in range(0, 4): + matrix.append(inverse_bind_matrix[row][column]) + + array = np.append(array, np.array([matrix]), axis=0) + binary_data = gltf2_io_binary_data.BinaryData.from_list(array.flatten(), gltf2_io_constants.ComponentType.Float) + n.node.skin.inverse_bind_matrices = gltf2_blender_gather_accessors.gather_accessor( + binary_data, + gltf2_io_constants.ComponentType.Float, + len(array.flatten()) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Mat4), + None, + None, + gltf2_io_constants.DataType.Mat4, + self.export_settings + ) def get_unused_skins(self): from .gltf2_blender_gather_skins import gather_skin skins = [] diff --git a/io_scene_gltf2/io/imp/gltf2_io_binary.py b/io_scene_gltf2/io/imp/gltf2_io_binary.py index 21bbb41b..995fd3c9 100755 --- a/io_scene_gltf2/io/imp/gltf2_io_binary.py +++ b/io_scene_gltf2/io/imp/gltf2_io_binary.py @@ -77,6 +77,37 @@ class BinaryData(): return array + + @staticmethod + def decode_accessor_internal(accessor): + # Is use internally when accessor binary data is not yet in a glTF buffer_view + # MAT2/3 have special alignment requirements that aren't handled. But it + # doesn't matter because nothing uses them. + assert accessor.type not in ['MAT2', 'MAT3'] + + dtype = ComponentType.to_numpy_dtype(accessor.component_type) + component_nb = DataType.num_elements(accessor.type) + + buffer_data = accessor.buffer_view.data + + accessor_offset = accessor.byte_offset or 0 + buffer_data = buffer_data[accessor_offset:] + + bytes_per_elem = dtype(1).nbytes + default_stride = bytes_per_elem * component_nb + stride = default_stride + + array = np.frombuffer( + buffer_data, + dtype=np.dtype(dtype).newbyteorder('<'), + count=accessor.count * component_nb, + ) + array = array.reshape(accessor.count, component_nb) + + return array + + + @staticmethod def decode_accessor_obj(gltf, accessor): # MAT2/3 have special alignment requirements that aren't handled. But it -- cgit v1.2.3 From 4c1df0f54ce02754b720d89c3bcf6f94361d5fcb Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 29 Mar 2022 17:45:51 +0200 Subject: glTF exporter: Fix transmission export --- io_scene_gltf2/__init__.py | 2 +- .../blender/exp/gltf2_blender_gather_materials.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 4ff7ff69..8a2e85f0 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 17), + "version": (3, 2, 18), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index 402e06fa..3805e9bd 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -248,9 +248,10 @@ def __gather_extensions(blender_material, export_settings): # KHR_materials_transmission - transmission_extension = __gather_transmission_extension(blender_material, export_settings) + transmission_extension, use_actives_uvmap_transmission = __gather_transmission_extension(blender_material, export_settings) if transmission_extension: extensions["KHR_materials_transmission"] = transmission_extension + actives_uvmaps.extend(use_actives_uvmap_transmission) return extensions, actives_uvmaps if extensions else None @@ -429,17 +430,19 @@ def __gather_transmission_extension(blender_material, export_settings): transmission_extension['transmissionFactor'] = transmission_socket.default_value transmission_enabled = transmission_extension['transmissionFactor'] > 0 elif __has_image_node_from_socket(transmission_socket): - transmission_extension['transmissionFactor'] = 1 + transmission_extension['transmissionFactor'] = 1.0 has_transmission_texture = True transmission_enabled = True if not transmission_enabled: - return None + return None, None # Pack transmission channel (R). if has_transmission_texture: transmission_slots = (transmission_socket,) + use_actives_uvmaps = [] + if len(transmission_slots) > 0: combined_texture, use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info( transmission_socket, @@ -448,8 +451,10 @@ def __gather_transmission_extension(blender_material, export_settings): ) if has_transmission_texture: transmission_extension['transmissionTexture'] = combined_texture + if use_active_uvmap: + use_actives_uvmaps.append("transmissionTexture") - return Extension('KHR_materials_transmission', transmission_extension, False), ["transmissionTexture"] if use_active_uvmap else [] + return Extension('KHR_materials_transmission', transmission_extension, False), use_actives_uvmaps def __gather_material_unlit(blender_material, active_uvmap_index, export_settings): -- cgit v1.2.3 From 374c730236d226e016e1928608f80e4a719cc7e9 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 29 Mar 2022 17:47:47 +0200 Subject: glTF exporter: Manage not initial buffers --- io_scene_gltf2/__init__.py | 2 +- io_scene_gltf2/io/exp/gltf2_io_buffer.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 8a2e85f0..9d8ccca2 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 18), + "version": (3, 2, 19), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/io/exp/gltf2_io_buffer.py b/io_scene_gltf2/io/exp/gltf2_io_buffer.py index 0129e85a..5fae3834 100755 --- a/io_scene_gltf2/io/exp/gltf2_io_buffer.py +++ b/io_scene_gltf2/io/exp/gltf2_io_buffer.py @@ -10,8 +10,10 @@ from io_scene_gltf2.io.exp import gltf2_io_binary_data class Buffer: """Class representing binary data for use in a glTF file as 'buffer' property.""" - def __init__(self, buffer_index=0): + def __init__(self, buffer_index=0, initial_data=None): self.__data = bytearray(b"") + if initial_data is not None: + self.__data = bytearray(initial_data.tobytes()) self.__buffer_index = buffer_index def add_and_get_view(self, binary_data: gltf2_io_binary_data.BinaryData) -> gltf2_io.BufferView: -- cgit v1.2.3 From 64f462358522f914cad4828b3a2e7a4aba900676 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 29 Mar 2022 17:49:53 +0200 Subject: glTF exporter performance. Better cache management Better management of case of multiple object with each multiple actions --- io_scene_gltf2/__init__.py | 2 +- .../exp/gltf2_blender_gather_animation_sampler_keyframes.py | 10 ++++++++-- io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 9d8ccca2..d08c39dd 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 19), + "version": (3, 2, 20), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py index 66ce11c7..e1ed19ea 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py @@ -132,7 +132,8 @@ def get_object_matrix(blender_obj_uuid: str, bake_range_end: int, current_frame: int, step: int, - export_settings + export_settings, + only_gather_provided=False ): data = {} @@ -144,11 +145,16 @@ def get_object_matrix(blender_obj_uuid: str, start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]]) end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]]) + if only_gather_provided: + obj_uuids = [blender_obj_uuid] + else: + obj_uuids = [uid for (uid, n) in export_settings['vtree'].nodes.items() if n.blender_type not in [VExportNode.BONE]] + frame = start_frame while frame <= end_frame: bpy.context.scene.frame_set(int(frame)) - for obj_uuid in [uid for (uid, n) in export_settings['vtree'].nodes.items() if n.blender_type not in [VExportNode.BONE]]: + for obj_uuid in obj_uuids: blender_obj = export_settings['vtree'].nodes[obj_uuid].blender_object # if this object is not animated, do not skip : diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py index 4f95431c..3539b968 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py @@ -93,7 +93,7 @@ def objectcache(func): # object is in cache, but not this action # We need to keep other actions elif cache_key_args[1] not in func.__objectcache[cache_key_args[0]].keys(): - result = func(*args) + result = func(*args, only_gather_provided=True) func.__objectcache[cache_key_args[0]][cache_key_args[1]] = result[cache_key_args[0]][cache_key_args[1]] return result[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]] # all is already cached -- cgit v1.2.3 From 9818afc829a06185440c431e520016f1d4286d06 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 29 Mar 2022 17:54:13 +0200 Subject: glTF exporter: Armature extports all actions When scene contains only 1 armature, it will export all actions, not only active + NLA --- io_scene_gltf2/__init__.py | 2 +- io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py | 10 ++++++++++ io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index d08c39dd..9bb1939f 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 20), + "version": (3, 2, 21), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py index c56517fb..0a513521 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py @@ -9,6 +9,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channels from io_scene_gltf2.io.com.gltf2_io_debug import print_console from ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions +from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode def __gather_channels_baked(obj_uuid, export_settings): @@ -307,6 +308,15 @@ def __get_blender_actions(blender_object: bpy.types.Object, blender_tracks[strip.action.name] = track.name # Always set after possible active action -> None will be overwrite action_on_type[strip.action.name] = "SHAPEKEY" + # If there are only 1 armature, include all animations, even if not in NLA + if blender_object.type == "ARMATURE": + if len(export_settings['vtree'].get_all_node_of_type(VExportNode.ARMATURE)) == 1: + # Keep all actions on objects (no keyframe animation) + # Some other object animation can be added here, and will affect armature object itself :-/ + for act in [a for a in bpy.data.actions if a.id_root == "OBJECT"]: + blender_actions.append(act) + blender_tracks[act.name] = None + export_user_extensions('gather_actions_hook', export_settings, blender_object, blender_actions, blender_tracks, action_on_type) # Remove duplicate actions. diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py index 3a7b0fa5..cf4983e1 100644 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py @@ -244,6 +244,9 @@ class VExportTree: else: return [] + def get_all_node_of_type(self, node_type): + return [n.uuid for n in self.nodes.values() if n.blender_type == node_type] + def display(self, mode): if mode == "simple": for n in self.roots: -- cgit v1.2.3 From f6d72972320a891b00d3d9e4519f89f24963bae6 Mon Sep 17 00:00:00 2001 From: Leon Schittek Date: Wed, 30 Mar 2022 21:57:34 +0200 Subject: Themes: Adapt themes to the dot grid background Update the themes to the recent changes made to the node editor's dot grid background: 1. Make sure themes aren't using a "Grid Levels" value of greater than 3 2. A lot of themes weren't updated to the dot grid at all, yet, and still used only 2 "Grid Levels" leading to the old dot grid being hardly visible. Those are changed to 3 "Grid Levels", as well, which most closely matches the visual density of the old line grid. 3. Additionally the "Grid" colour of the "Deep Grey" theme is darkened a bit to account for the dot grid being colored differently than the old line grid, which lead to the dot grid being invisible. --- presets/interface_theme/Deep_Grey.xml | 4 ++-- presets/interface_theme/Maya.xml | 2 +- presets/interface_theme/Minimal_Dark.xml | 2 +- presets/interface_theme/Print_Friendly.xml | 2 +- presets/interface_theme/White.xml | 2 +- presets/interface_theme/XSI.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/presets/interface_theme/Deep_Grey.xml b/presets/interface_theme/Deep_Grey.xml index 2fc6b4ae..85be3a98 100644 --- a/presets/interface_theme/Deep_Grey.xml +++ b/presets/interface_theme/Deep_Grey.xml @@ -464,7 +464,7 @@ Date: Thu, 31 Mar 2022 15:40:43 +0300 Subject: OBJ: reintroduce export menu item for the old (pre-3.1) python exporter While it still has known issues/bugs/limitations. Also related: D14512. Differential: D14513 --- io_scene_obj/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py index c5335f3d..78c2314e 100644 --- a/io_scene_obj/__init__.py +++ b/io_scene_obj/__init__.py @@ -501,16 +501,12 @@ def register(): bpy.utils.register_class(cls) bpy.types.TOPBAR_MT_file_import.append(menu_func_import) - # Disabling the menu entry for this python exporter now that - # there is a C++ exporter. For now, leaving the actual - # export_scene.obj pointing at the python version. - # bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) - # See comment above about menu for the python exporter - # bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) for cls in classes: bpy.utils.unregister_class(cls) -- cgit v1.2.3 From 787ea78f7fa6f0373d80ba1247768402df93f8ad Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Thu, 31 Mar 2022 21:17:33 +0200 Subject: glTF exporter: fix exporting single armature actions --- io_scene_gltf2/__init__.py | 2 +- io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 9bb1939f..c844838b 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 2, 21), + "version": (3, 2, 22), 'blender': (3, 1, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py index 0a513521..6dd401e9 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py @@ -316,6 +316,7 @@ def __get_blender_actions(blender_object: bpy.types.Object, for act in [a for a in bpy.data.actions if a.id_root == "OBJECT"]: blender_actions.append(act) blender_tracks[act.name] = None + action_on_type[act.name] = "OBJECT" export_user_extensions('gather_actions_hook', export_settings, blender_object, blender_actions, blender_tracks, action_on_type) -- cgit v1.2.3