3D Print Toolbox: Add hollow out #105194

Merged
Mikhail Rachinskiy merged 9 commits from usfreitas/blender-addons:hollow into main 2024-03-18 12:24:30 +01:00
38 changed files with 310 additions and 205 deletions
Showing only changes of commit 913618d0a2 - Show all commits

View File

@ -102,12 +102,18 @@ class Import3DS(bpy.types.Operator, ImportHelper):
description="Transform to matrix world", description="Transform to matrix world",
default=False, default=False,
) )
use_collection: BoolProperty(
name="Collection",
description="Create a new collection",
default=False,
)
use_cursor: BoolProperty( use_cursor: BoolProperty(
name="Cursor Origin", name="Cursor Origin",
description="Read the 3D cursor location", description="Read the 3D cursor location",
default=False, default=False,
) )
def execute(self, context): def execute(self, context):
from . import import_3ds from . import import_3ds
keywords = self.as_keywords(ignore=("axis_forward", keywords = self.as_keywords(ignore=("axis_forward",
@ -166,6 +172,9 @@ class MAX3DS_PT_import_include(bpy.types.Panel):
layrow.prop(operator, "use_keyframes") layrow.prop(operator, "use_keyframes")
layrow.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER') layrow.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER')
layrow = layout.row(align=True) layrow = layout.row(align=True)
layrow.prop(operator, "use_collection")
layrow.label(text="", icon='OUTLINER_COLLECTION' if operator.use_collection else 'GROUP')
layrow = layout.row(align=True)
layrow.prop(operator, "use_cursor") layrow.prop(operator, "use_cursor")
layrow.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR') layrow.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR')
@ -249,14 +258,14 @@ class Export3DS(bpy.types.Operator, ExportHelper):
description="Object types to export", description="Object types to export",
default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'}, default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'},
) )
use_hierarchy: BoolProperty(
name="Hierarchy",
description="Export hierarchy chunks",
default=False,
)
use_keyframes: BoolProperty( use_keyframes: BoolProperty(
name="Animation", name="Animation",
description="Write the keyframe data", description="Write the keyframe data",
default=True,
)
use_hierarchy: BoolProperty(
name="Hierarchy",
description="Export hierarchy chunks",
default=False, default=False,
) )
use_cursor: BoolProperty( use_cursor: BoolProperty(
@ -310,12 +319,12 @@ class MAX3DS_PT_export_include(bpy.types.Panel):
layrow.label(text="", icon='RESTRICT_SELECT_OFF' if operator.use_selection else 'RESTRICT_SELECT_ON') layrow.label(text="", icon='RESTRICT_SELECT_OFF' if operator.use_selection else 'RESTRICT_SELECT_ON')
layout.column().prop(operator, "object_filter") layout.column().prop(operator, "object_filter")
layrow = layout.row(align=True) layrow = layout.row(align=True)
layrow.prop(operator, "use_hierarchy")
layrow.label(text="", icon='OUTLINER' if operator.use_hierarchy else 'CON_CHILDOF')
layrow = layout.row(align=True)
layrow.prop(operator, "use_keyframes") layrow.prop(operator, "use_keyframes")
layrow.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER') layrow.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER')
layrow = layout.row(align=True) layrow = layout.row(align=True)
layrow.prop(operator, "use_hierarchy")
layrow.label(text="", icon='OUTLINER' if operator.use_hierarchy else 'CON_CHILDOF')
layrow = layout.row(align=True)
layrow.prop(operator, "use_cursor") layrow.prop(operator, "use_cursor")
layrow.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR') layrow.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR')

View File

@ -1563,7 +1563,7 @@ def make_ambient_node(world):
########## ##########
def save(operator, context, filepath="", scale_factor=1.0, use_scene_unit=False, use_selection=False, def save(operator, context, filepath="", scale_factor=1.0, use_scene_unit=False, use_selection=False,
object_filter=None, use_hierarchy=False, use_keyframes=False, global_matrix=None, use_cursor=False): object_filter=None, use_keyframes=True, use_hierarchy=False, global_matrix=None, use_cursor=False):
"""Save the Blender scene to a 3ds file.""" """Save the Blender scene to a 3ds file."""
# Time the export # Time the export

View File

@ -10,6 +10,7 @@ import struct
import mathutils import mathutils
from bpy_extras.image_utils import load_image from bpy_extras.image_utils import load_image
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
from pathlib import Path
BOUNDS_3DS = [] BOUNDS_3DS = []
@ -1772,11 +1773,25 @@ def load_3ds(filepath, context, CONSTRAIN=10.0, UNITS=False, IMAGE_SEARCH=True,
def load(operator, context, files=None, directory="", filepath="", constrain_size=0.0, use_scene_unit=False, def load(operator, context, files=None, directory="", filepath="", constrain_size=0.0, use_scene_unit=False,
use_image_search=True, object_filter=None, use_world_matrix=False, use_keyframes=True, use_image_search=True, object_filter=None, use_world_matrix=False, use_keyframes=True,
use_apply_transform=True, global_matrix=None, use_cursor=False, use_center_pivot=False): use_apply_transform=True, global_matrix=None, use_cursor=False, use_center_pivot=False, use_collection=False):
for f in files: # Get the active collection
load_3ds(os.path.join(directory, f.name), context, CONSTRAIN=constrain_size, UNITS=use_scene_unit, collection_init = context.view_layer.active_layer_collection.collection
# Load each selected file
for file in files:
# Create new collections if activated (collection name = 3ds file name)
if use_collection:
collection = bpy.data.collections.new(Path(file.name).stem)
context.scene.collection.children.link(collection)
context.view_layer.active_layer_collection = context.view_layer.layer_collection.children[collection.name]
load_3ds(Path(directory, file.name), context, CONSTRAIN=constrain_size, UNITS=use_scene_unit,
IMAGE_SEARCH=use_image_search, FILTER=object_filter, WORLD_MATRIX=use_world_matrix, KEYFRAME=use_keyframes, IMAGE_SEARCH=use_image_search, FILTER=object_filter, WORLD_MATRIX=use_world_matrix, KEYFRAME=use_keyframes,
APPLY_MATRIX=use_apply_transform, CONVERSE=global_matrix, CURSOR=use_cursor, PIVOT=use_center_pivot,) APPLY_MATRIX=use_apply_transform, CONVERSE=global_matrix, CURSOR=use_cursor, PIVOT=use_center_pivot,)
# Retrive the initial collection as active
active = context.view_layer.layer_collection.children.get(collection_init.name)
if active is not None:
context.view_layer.active_layer_collection = active
return {'FINISHED'} return {'FINISHED'}

View File

@ -5,7 +5,7 @@
bl_info = { bl_info = {
'name': 'glTF 2.0 format', 'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (4, 2, 5), "version": (4, 2, 8),
'blender': (4, 1, 0), 'blender': (4, 1, 0),
'location': 'File > Import-Export', 'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0', 'description': 'Import-Export as glTF 2.0',
@ -648,6 +648,15 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
default=False default=False
) )
export_leaf_bone: BoolProperty(
name='Add Leaf Bones',
description=(
'Append a final bone to the end of each chain to specify last bone length '
'(use this when you intend to edit the armature from exported data)'
),
default=False
)
export_optimize_animation_size: BoolProperty( export_optimize_animation_size: BoolProperty(
name='Optimize Animation Size', name='Optimize Animation Size',
description=( description=(
@ -955,6 +964,8 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
def execute(self, context): def execute(self, context):
import os import os
import datetime import datetime
import logging
from .io.com.gltf2_io_debug import Log
from .blender.exp import gltf2_blender_export from .blender.exp import gltf2_blender_export
from .io.com.gltf2_io_path import path_to_uri from .io.com.gltf2_io_path import path_to_uri
@ -966,6 +977,8 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
# All custom export settings are stored in this container. # All custom export settings are stored in this container.
export_settings = {} export_settings = {}
export_settings['loglevel'] = logging.INFO
export_settings['exported_images'] = {} export_settings['exported_images'] = {}
export_settings['exported_texture_nodes'] = [] export_settings['exported_texture_nodes'] = []
export_settings['additional_texture_export'] = [] export_settings['additional_texture_export'] = []
@ -1035,6 +1048,7 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
export_settings['gltf_flatten_bones_hierarchy'] = self.export_hierarchy_flatten_bones export_settings['gltf_flatten_bones_hierarchy'] = self.export_hierarchy_flatten_bones
export_settings['gltf_flatten_obj_hierarchy'] = self.export_hierarchy_flatten_objs export_settings['gltf_flatten_obj_hierarchy'] = self.export_hierarchy_flatten_objs
export_settings['gltf_armature_object_remove'] = self.export_armature_object_remove export_settings['gltf_armature_object_remove'] = self.export_armature_object_remove
export_settings['gltf_leaf_bone'] = self.export_leaf_bone
if self.export_animations: if self.export_animations:
export_settings['gltf_frame_range'] = self.export_frame_range export_settings['gltf_frame_range'] = self.export_frame_range
export_settings['gltf_force_sampling'] = self.export_force_sampling export_settings['gltf_force_sampling'] = self.export_force_sampling
@ -1154,7 +1168,19 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
export_settings['pre_export_callbacks'] = pre_export_callbacks export_settings['pre_export_callbacks'] = pre_export_callbacks
export_settings['post_export_callbacks'] = post_export_callbacks export_settings['post_export_callbacks'] = post_export_callbacks
return gltf2_blender_export.save(context, export_settings)
# Initialize logging for export
export_settings['log'] = Log(export_settings['loglevel'])
res = gltf2_blender_export.save(context, export_settings)
# Display popup log, if any
for message_type, message in export_settings['log'].messages():
self.report({message_type}, message)
export_settings['log'].flush()
return res
def draw(self, context): def draw(self, context):
pass # Is needed to get panels available pass # Is needed to get panels available
@ -2110,14 +2136,18 @@ class ImportGLTF2(Operator, ConvertGLTF2_Base, ImportHelper):
gltf_importer.read() gltf_importer.read()
gltf_importer.checks() gltf_importer.checks()
print("Data are loaded, start creating Blender stuff") gltf_importer.log.info("Data are loaded, start creating Blender stuff")
start_time = time.time() start_time = time.time()
BlenderGlTF.create(gltf_importer) BlenderGlTF.create(gltf_importer)
elapsed_s = "{:.2f}s".format(time.time() - start_time) elapsed_s = "{:.2f}s".format(time.time() - start_time)
print("glTF import finished in " + elapsed_s) gltf_importer.log.info("glTF import finished in " + elapsed_s)
gltf_importer.log.removeHandler(gltf_importer.log_handler) # Display popup log, if any
for message_type, message in gltf_importer.log.messages():
self.report({message_type}, message)
gltf_importer.log.flush()
return {'FINISHED'} return {'FINISHED'}
@ -2127,16 +2157,16 @@ class ImportGLTF2(Operator, ConvertGLTF2_Base, ImportHelper):
def set_debug_log(self): def set_debug_log(self):
import logging import logging
if bpy.app.debug_value == 0: if bpy.app.debug_value == 0: # Default values => Display all messages except debug ones
self.loglevel = logging.CRITICAL
elif bpy.app.debug_value == 1:
self.loglevel = logging.ERROR
elif bpy.app.debug_value == 2:
self.loglevel = logging.WARNING
elif bpy.app.debug_value == 3:
self.loglevel = logging.INFO self.loglevel = logging.INFO
else: elif bpy.app.debug_value == 1:
self.loglevel = logging.NOTSET self.loglevel = logging.WARNING
elif bpy.app.debug_value == 2:
self.loglevel = logging.ERROR
elif bpy.app.debug_value == 3:
self.loglevel = logging.CRITICAL
elif bpy.app.debug_value == 4:
self.loglevel = logging.DEBUG
class GLTF2_filter_action(bpy.types.PropertyGroup): class GLTF2_filter_action(bpy.types.PropertyGroup):

View File

@ -6,7 +6,6 @@ import bpy
import typing import typing
from .....io.exp.gltf2_io_user_extensions import export_user_extensions from .....io.exp.gltf2_io_user_extensions import export_user_extensions
from .....blender.com.gltf2_blender_data_path import skip_sk from .....blender.com.gltf2_blender_data_path import skip_sk
from .....io.com import gltf2_io_debug
from .....io.com import gltf2_io from .....io.com import gltf2_io
from ....exp.gltf2_blender_gather_cache import cached from ....exp.gltf2_blender_gather_cache import cached
from ....com.gltf2_blender_data_path import get_target_object_path, get_target_property_name, get_rotation_modes from ....com.gltf2_blender_data_path import get_target_object_path, get_target_property_name, get_rotation_modes
@ -76,7 +75,7 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
# example of target_property : location, rotation_quaternion, value # example of target_property : location, rotation_quaternion, value
target_property = get_target_property_name(fcurve.data_path) target_property = get_target_property_name(fcurve.data_path)
except: except:
gltf2_io_debug.print_console("WARNING", "Invalid animation fcurve name on action {}".format(blender_action.name)) export_settings['log'].warning("Invalid animation fcurve data path on action {}".format(blender_action.name))
continue continue
object_path = get_target_object_path(fcurve.data_path) object_path = get_target_object_path(fcurve.data_path)
@ -123,10 +122,10 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
type_ = "SK" type_ = "SK"
except: except:
# Something is wrong, for example a bone animation is linked to an object mesh... # Something is wrong, for example a bone animation is linked to an object mesh...
gltf2_io_debug.print_console("WARNING", "Animation target {} not found".format(object_path)) export_settings['log'].warning("Invalid animation fcurve data path on action {}".format(blender_action.name))
continue continue
else: else:
gltf2_io_debug.print_console("WARNING", "Animation target {} not found".format(object_path)) export_settings['log'].warning("Animation target {} not found".format(object_path))
continue continue
# Detect that object or bone are not multiple keyed for euler and quaternion # Detect that object or bone are not multiple keyed for euler and quaternion
@ -169,7 +168,7 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
targets[target] = target_data targets[target] = target_data
for targ in multiple_rotation_mode_detected.keys(): for targ in multiple_rotation_mode_detected.keys():
gltf2_io_debug.print_console("WARNING", "Multiple rotation mode detected for {}".format(targ.name)) export_settings['log'].warning("Multiple rotation mode detected for {}".format(targ.name))
# Now that all curves are extracted, # Now that all curves are extracted,
# - check that there is no normal + delta transforms # - check that there is no normal + delta transforms
@ -329,24 +328,24 @@ def needs_baking(obj_uuid: str,
# Sampling due to unsupported interpolation # Sampling due to unsupported interpolation
interpolation = [c for c in channels if c is not None][0].keyframe_points[0].interpolation interpolation = [c for c in channels if c is not None][0].keyframe_points[0].interpolation
if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]: if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]:
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"Baking animation because of an unsupported interpolation method: {}".format( "Baking animation because of an unsupported interpolation method: {}".format(interpolation)
interpolation)
) )
return True return True
if any(any(k.interpolation != interpolation for k in c.keyframe_points) for c in channels if c is not None): if any(any(k.interpolation != interpolation for k in c.keyframe_points) for c in channels if c is not None):
# There are different interpolation methods in one action group # There are different interpolation methods in one action group
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"Baking animation because there are keyframes with different " "Baking animation because there are keyframes with different "
"interpolation methods in one channel" "interpolation methods in one channel"
) )
return True return True
if not all_equal([len(c.keyframe_points) for c in channels if c is not None]): if not all_equal([len(c.keyframe_points) for c in channels if c is not None]):
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"Baking animation because the number of keyframes is not " "Baking animation because the number of keyframes is not "
"equal for all channel tracks") "equal for all channel tracks"
)
return True return True
if len([c for c in channels if c is not None][0].keyframe_points) <= 1: if len([c for c in channels if c is not None][0].keyframe_points) <= 1:
@ -355,8 +354,7 @@ def needs_baking(obj_uuid: str,
if not all_equal(list(zip([[k.co[0] for k in c.keyframe_points] for c in channels if c is not None]))): if not all_equal(list(zip([[k.co[0] for k in c.keyframe_points] for c in channels if c is not None]))):
# The channels have differently located keyframes # The channels have differently located keyframes
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning("Baking animation because of differently located keyframes in one channel")
"Baking animation because of differently located keyframes in one channel")
return True return True
if export_settings['vtree'].nodes[obj_uuid].blender_object.type == "ARMATURE": if export_settings['vtree'].nodes[obj_uuid].blender_object.type == "ARMATURE":
@ -364,8 +362,7 @@ def needs_baking(obj_uuid: str,
if isinstance(animation_target, bpy.types.PoseBone): if isinstance(animation_target, bpy.types.PoseBone):
if len(animation_target.constraints) != 0: if len(animation_target.constraints) != 0:
# Constraints such as IK act on the bone -> can not be represented in glTF atm # Constraints such as IK act on the bone -> can not be represented in glTF atm
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning("Baking animation because of unsupported constraints acting on the bone")
"Baking animation because of unsupported constraints acting on the bone")
return True return True
return False return False

View File

@ -5,7 +5,6 @@
import bpy import bpy
import typing import typing
from ....io.com import gltf2_io from ....io.com import gltf2_io
from ....io.com.gltf2_io_debug import print_console
from ....io.exp.gltf2_io_user_extensions import export_user_extensions from ....io.exp.gltf2_io_user_extensions import export_user_extensions
from ....blender.com.gltf2_blender_conversion import get_gltf_interpolation from ....blender.com.gltf2_blender_conversion import get_gltf_interpolation
from ...com.gltf2_blender_data_path import is_bone_anim_channel from ...com.gltf2_blender_data_path import is_bone_anim_channel
@ -276,7 +275,7 @@ def gather_action_animations( obj_uuid: int,
obj.hide_viewport = True obj.hide_viewport = True
export_settings['vtree'].nodes[obj_uuid].blender_object.hide_viewport = False export_settings['vtree'].nodes[obj_uuid].blender_object.hide_viewport = False
else: else:
print_console("WARNING", "Can't disable viewport because of drivers") export_settings['log'].warning("Can't disable viewport because of drivers")
export_settings['gltf_optimize_armature_disable_viewport'] = False # We changed the option here, so we don't need to re-check it later, during export_settings['gltf_optimize_armature_disable_viewport'] = False # We changed the option here, so we don't need to re-check it later, during
@ -300,7 +299,7 @@ def gather_action_animations( obj_uuid: int,
export_user_extensions('post_animation_switch_hook', export_settings, blender_object, blender_action, track_name, on_type) export_user_extensions('post_animation_switch_hook', export_settings, blender_object, blender_action, track_name, on_type)
except: except:
error = "Action is readonly. Please check NLA editor" error = "Action is readonly. Please check NLA editor"
print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(blender_action.name, error)) export_settings['log'].warning("Animation '{}' could not be exported. Cause: {}".format(blender_action.name, error))
continue continue
if on_type == "SHAPEKEY": if on_type == "SHAPEKEY":
@ -337,7 +336,7 @@ def gather_action_animations( obj_uuid: int,
elif type_ == "EXTRA": elif type_ == "EXTRA":
channel = None channel = None
else: else:
print("Type unknown. Should not happen") export_settings['log'].error("Type unknown. Should not happen")
if animation is None and channel is not None: if animation is None and channel is not None:
# If all channels need to be sampled, no animation was created # If all channels need to be sampled, no animation was created
@ -503,6 +502,7 @@ def __get_blender_actions(obj_uuid: str,
# so skip them for now and only write single-strip tracks. # so skip them for now and only write single-strip tracks.
non_muted_strips = [strip for strip in track.strips if strip.action is not None and strip.mute is False] non_muted_strips = [strip for strip in track.strips if strip.action is not None and strip.mute is False]
if track.strips is None or len(non_muted_strips) != 1: if track.strips is None or len(non_muted_strips) != 1:
export_settings['log'].warning("NLA track '{}' has {} strips, but only single-strip tracks are supported in 'actions' mode.".format(track.name, len(track.strips)), popup=True)
continue continue
for strip in non_muted_strips: for strip in non_muted_strips:

View File

@ -8,7 +8,6 @@ from mathutils import Matrix
from ....blender.com.gltf2_blender_data_path import get_sk_exported from ....blender.com.gltf2_blender_data_path import get_sk_exported
from ....io.com import gltf2_io from ....io.com import gltf2_io
from ....io.exp.gltf2_io_user_extensions import export_user_extensions from ....io.exp.gltf2_io_user_extensions import export_user_extensions
from ....io.com.gltf2_io_debug import print_console
from ..gltf2_blender_gather_tree import VExportNode from ..gltf2_blender_gather_tree import VExportNode
from .sampled.armature.armature_action_sampled import gather_action_armature_sampled from .sampled.armature.armature_action_sampled import gather_action_armature_sampled
from .sampled.object.gltf2_blender_gather_object_action_sampled import gather_action_object_sampled from .sampled.object.gltf2_blender_gather_object_action_sampled import gather_action_object_sampled
@ -130,7 +129,7 @@ def merge_tracks_perform(merged_tracks, animations, export_settings):
for channel in animations[anim_idx].channels: for channel in animations[anim_idx].channels:
if (channel.target.node, channel.target.path) in already_animated: if (channel.target.node, channel.target.path) in already_animated:
print_console("WARNING", "Some strips have same channel animation ({}), on node {} !".format(channel.target.path, channel.target.node.name)) export_settings['log'].warning("Some strips have same channel animation ({}), on node {} !".format(channel.target.path, channel.target.node.name))
continue continue
animations[base_animation_idx].channels.append(channel) animations[base_animation_idx].channels.append(channel)
animations[base_animation_idx].channels[-1].sampler = animations[base_animation_idx].channels[-1].sampler + offset_sampler animations[base_animation_idx].channels[-1].sampler = animations[base_animation_idx].channels[-1].sampler + offset_sampler

View File

@ -5,7 +5,6 @@
import bpy import bpy
import typing import typing
from ......io.exp.gltf2_io_user_extensions import export_user_extensions from ......io.exp.gltf2_io_user_extensions import export_user_extensions
from ......io.com.gltf2_io_debug import print_console
from ......io.com import gltf2_io from ......io.com import gltf2_io
from .....com.gltf2_blender_extras import generate_extras from .....com.gltf2_blender_extras import generate_extras
from ...fcurves.gltf2_blender_gather_fcurves_sampler import gather_animation_fcurves_sampler from ...fcurves.gltf2_blender_gather_fcurves_sampler import gather_animation_fcurves_sampler
@ -32,7 +31,7 @@ def gather_action_armature_sampled(armature_uuid: str, blender_action: typing.Op
samplers=[] # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers samplers=[] # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers
) )
except RuntimeError as error: except RuntimeError as error:
print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(name, error)) export_settings['log'].warning("Animation '{}' could not be exported. Cause: {}".format(name, error))
return None return None
export_user_extensions('pre_gather_animation_hook', export_settings, animation, blender_action, blender_object) export_user_extensions('pre_gather_animation_hook', export_settings, animation, blender_action, blender_object)

View File

@ -23,7 +23,7 @@ def gather_armature_sampled_channels(armature_uuid, blender_action_name, export_
# Then bake all bones # Then bake all bones
bones_to_be_animated = [] bones_to_be_animated = []
bones_uuid = export_settings["vtree"].get_all_bones(armature_uuid) bones_uuid = export_settings["vtree"].get_all_bones(armature_uuid)
bones_to_be_animated = [export_settings["vtree"].nodes[b].blender_bone.name for b in bones_uuid] bones_to_be_animated = [export_settings["vtree"].nodes[b].blender_bone.name for b in bones_uuid if export_settings["vtree"].nodes[b].leaf_reference is None]
# List of really animated bones is needed for optimization decision # List of really animated bones is needed for optimization decision
list_of_animated_bone_channels = {} list_of_animated_bone_channels = {}

View File

@ -157,7 +157,7 @@ def get_cache_data(path: str,
if 'bone' not in data[obj_uuid][obj_uuid].keys(): if 'bone' not in data[obj_uuid][obj_uuid].keys():
data[obj_uuid][obj_uuid]['bone'] = {} data[obj_uuid][obj_uuid]['bone'] = {}
for bone_uuid in bones: for bone_uuid in [bone for bone in bones if export_settings['vtree'].nodes[bone].leaf_reference is None]:
blender_bone = export_settings['vtree'].nodes[bone_uuid].blender_bone blender_bone = export_settings['vtree'].nodes[bone_uuid].blender_bone
if export_settings['vtree'].nodes[bone_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_type == VExportNode.BONE: if export_settings['vtree'].nodes[bone_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_type == VExportNode.BONE:

View File

@ -10,7 +10,6 @@ import bpy
import sys import sys
import traceback import traceback
from ...io.com.gltf2_io_debug import print_console, print_newline
from ...io.exp import gltf2_io_export from ...io.exp import gltf2_io_export
from ...io.exp import gltf2_io_draco_compression_extension from ...io.exp import gltf2_io_draco_compression_extension
from ...io.exp.gltf2_io_user_extensions import export_user_extensions from ...io.exp.gltf2_io_user_extensions import export_user_extensions
@ -30,7 +29,7 @@ def save(context, export_settings):
if not export_settings['gltf_current_frame']: if not export_settings['gltf_current_frame']:
bpy.context.scene.frame_set(0) bpy.context.scene.frame_set(0)
__notify_start(context) __notify_start(context, export_settings)
start_time = time.time() start_time = time.time()
pre_export_callbacks = export_settings["pre_export_callbacks"] pre_export_callbacks = export_settings["pre_export_callbacks"]
for callback in pre_export_callbacks: for callback in pre_export_callbacks:
@ -44,10 +43,11 @@ def save(context, export_settings):
__write_file(json, buffer, export_settings) __write_file(json, buffer, export_settings)
end_time = time.time() end_time = time.time()
__notify_end(context, end_time - start_time) __notify_end(context, end_time - start_time, export_settings)
if not export_settings['gltf_current_frame']: if not export_settings['gltf_current_frame']:
bpy.context.scene.frame_set(int(original_frame)) bpy.context.scene.frame_set(int(original_frame))
return {'FINISHED'} return {'FINISHED'}
@ -178,7 +178,7 @@ def __postprocess_with_gltfpack(export_settings):
try: try:
subprocess.run([gltfpack_binary_file_path] + options + parameters, check=True) subprocess.run([gltfpack_binary_file_path] + options + parameters, check=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print_console('ERROR', "Calling gltfpack was not successful") export_settings['log'].error("Calling gltfpack was not successful")
def __write_file(json, buffer, export_settings): def __write_file(json, buffer, export_settings):
try: try:
@ -196,18 +196,18 @@ def __write_file(json, buffer, export_settings):
tb_info = traceback.extract_tb(tb) tb_info = traceback.extract_tb(tb)
for tbi in tb_info: for tbi in tb_info:
filename, line, func, text = tbi filename, line, func, text = tbi
print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text)) export_settings['log'].error('An error occurred on line {} in statement {}'.format(line, text))
print_console('ERROR', str(e)) export_settings['log'].error(str(e))
raise e raise e
def __notify_start(context): def __notify_start(context, export_settings):
print_console('INFO', 'Starting glTF 2.0 export') export_settings['log'].info('Starting glTF 2.0 export')
context.window_manager.progress_begin(0, 100) context.window_manager.progress_begin(0, 100)
context.window_manager.progress_update(0) context.window_manager.progress_update(0)
def __notify_end(context, elapsed): def __notify_end(context, elapsed, export_settings):
print_console('INFO', 'Finished glTF 2.0 export in {} s'.format(elapsed)) export_settings['log'].info('Finished glTF 2.0 export in {} s'.format(elapsed))
context.window_manager.progress_end() context.window_manager.progress_end()
print_newline() print()

View File

@ -86,7 +86,7 @@ def gather_joint_vnode(vnode, export_settings):
extras=__gather_extras(blender_bone, export_settings), extras=__gather_extras(blender_bone, export_settings),
matrix=None, matrix=None,
mesh=None, mesh=None,
name=blender_bone.name, name=blender_bone.name if vtree.nodes[vnode].leaf_reference is None else vtree.nodes[vtree.nodes[vnode].leaf_reference].blender_bone.name + '_leaf',
rotation=rotation, rotation=rotation,
scale=scale, scale=scale,
skin=None, skin=None,

View File

@ -36,7 +36,7 @@ def gather_lights_punctual(blender_lamp, export_settings) -> Optional[Dict[str,
def __filter_lights_punctual(blender_lamp, export_settings) -> bool: def __filter_lights_punctual(blender_lamp, export_settings) -> bool:
if blender_lamp.type in ["HEMI", "AREA"]: if blender_lamp.type in ["HEMI", "AREA"]:
gltf2_io_debug.print_console("WARNING", "Unsupported light source {}".format(blender_lamp.type)) export_settings['log'].warning("Unsupported light source {}".format(blender_lamp.type))
return False return False
return True return True
@ -63,8 +63,7 @@ def __gather_intensity(blender_lamp, export_settings) -> Optional[float]:
quadratic_falloff_node = result[0].shader_node quadratic_falloff_node = result[0].shader_node
emission_strength = quadratic_falloff_node.inputs["Strength"].default_value / (math.pi * 4.0) emission_strength = quadratic_falloff_node.inputs["Strength"].default_value / (math.pi * 4.0)
else: else:
gltf2_io_debug.print_console('WARNING', export_settings['log'].warning('No quadratic light falloff node attached to emission strength property')
'No quadratic light falloff node attached to emission strength property')
emission_strength = blender_lamp.energy emission_strength = blender_lamp.energy
else: else:
emission_strength = emission_node.inputs["Strength"].default_value emission_strength = emission_node.inputs["Strength"].default_value

View File

@ -6,7 +6,6 @@ import bpy
from typing import Optional, Dict, List, Any, Tuple from typing import Optional, Dict, List, Any, Tuple
from ...io.com import gltf2_io from ...io.com import gltf2_io
from ...blender.com.gltf2_blender_data_path import get_sk_exported from ...blender.com.gltf2_blender_data_path import get_sk_exported
from ...io.com.gltf2_io_debug import print_console
from ...io.exp.gltf2_io_user_extensions import export_user_extensions from ...io.exp.gltf2_io_user_extensions import export_user_extensions
from ..com.gltf2_blender_extras import generate_extras from ..com.gltf2_blender_extras import generate_extras
from . import gltf2_blender_gather_primitives from . import gltf2_blender_gather_primitives
@ -59,7 +58,7 @@ def gather_mesh(blender_mesh: bpy.types.Mesh,
) )
if len(mesh.primitives) == 0: if len(mesh.primitives) == 0:
print_console("WARNING", "Mesh '{}' has no primitives and will be omitted.".format(mesh.name)) export_settings['log'].warning("Mesh '{}' has no primitives and will be omitted.".format(mesh.name))
return None return None
blender_object = None blender_object = None

View File

@ -6,7 +6,6 @@ import math
import bpy import bpy
from mathutils import Matrix, Quaternion, Vector from mathutils import Matrix, Quaternion, Vector
from ...io.com.gltf2_io_debug import print_console
from ...io.com import gltf2_io from ...io.com import gltf2_io
from ...io.com import gltf2_io_extensions from ...io.com import gltf2_io_extensions
from ...io.exp.gltf2_io_user_extensions import export_user_extensions from ...io.exp.gltf2_io_user_extensions import export_user_extensions
@ -251,7 +250,7 @@ def __gather_mesh(vnode, blender_object, export_settings):
# Be sure that object is valid (no NaN for example) # Be sure that object is valid (no NaN for example)
res = blender_object.data.validate() res = blender_object.data.validate()
if res is True: if res is True:
print_console("WARNING", "Mesh " + blender_object.data.name + " is not valid, and may be exported wrongly") export_settings['log'].warning("Mesh " + blender_object.data.name + " is not valid, and may be exported wrongly")
modifiers = blender_object.modifiers modifiers = blender_object.modifiers
if len(modifiers) == 0: if len(modifiers) == 0:

View File

@ -73,8 +73,10 @@ def __gather_skins(blender_primitive, export_settings):
# Set warning, for the case where there are more group of 4 weights needed # Set warning, for the case where there are more group of 4 weights needed
# Warning for the case where we are in the same group, will be done later (for example, 3 weights needed, but 2 wanted by user) # Warning for the case where we are in the same group, will be done later (for example, 3 weights needed, but 2 wanted by user)
if max_bone_set_index > wanted_max_bone_set_index: if max_bone_set_index > wanted_max_bone_set_index:
gltf2_io_debug.print_console("WARNING", "There are more than {} joint vertex influences." export_settings['log'].warning(
"The {} with highest weight will be used (and normalized).".format(export_settings['gltf_vertex_influences_nb'], export_settings['gltf_vertex_influences_nb'])) "There are more than {} joint vertex influences."
"The {} with highest weight will be used (and normalized).".format(export_settings['gltf_vertex_influences_nb'], export_settings['gltf_vertex_influences_nb'])
)
# Take into account only the first set of 4 weights # Take into account only the first set of 4 weights
max_bone_set_index = wanted_max_bone_set_index max_bone_set_index = wanted_max_bone_set_index
@ -99,9 +101,10 @@ def __gather_skins(blender_primitive, export_settings):
idx = 4-1-i idx = 4-1-i
if not all(weight[:, idx]): if not all(weight[:, idx]):
if warning_done is False: if warning_done is False:
gltf2_io_debug.print_console("WARNING", "There are more than {} joint vertex influences." export_settings['log'].warning(
"The {} with highest weight will be used (and normalized).".format(export_settings['gltf_vertex_influences_nb'], export_settings['gltf_vertex_influences_nb'])) "There are more than {} joint vertex influences."
"The {} with highest weight will be used (and normalized).".format(export_settings['gltf_vertex_influences_nb'], export_settings['gltf_vertex_influences_nb'])
)
warning_done = True warning_done = True
weight[:, idx] = 0.0 weight[:, idx] = 0.0

View File

@ -6,7 +6,6 @@ import bpy
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
import numpy as np import numpy as np
from ...io.com import gltf2_io, gltf2_io_constants, gltf2_io_extensions from ...io.com import gltf2_io, gltf2_io_constants, gltf2_io_extensions
from ...io.com.gltf2_io_debug import print_console
from ...blender.com.gltf2_blender_data_path import get_sk_exported from ...blender.com.gltf2_blender_data_path import get_sk_exported
from ...io.exp import gltf2_io_binary_data from ...io.exp import gltf2_io_binary_data
from .gltf2_blender_gather_cache import cached, cached_by_key from .gltf2_blender_gather_cache import cached, cached_by_key
@ -192,7 +191,7 @@ def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings
component_type = gltf2_io_constants.ComponentType.UnsignedInt component_type = gltf2_io_constants.ComponentType.UnsignedInt
indices = indices.astype(np.uint32, copy=False) indices = indices.astype(np.uint32, copy=False)
else: else:
print_console('ERROR', 'A mesh contains too many vertices (' + str(max_index) + ') and needs to be split before export.') export_settings['log'].error('A mesh contains too many vertices (' + str(max_index) + ') and needs to be split before export.')
return None return None
element_type = gltf2_io_constants.DataType.Scalar element_type = gltf2_io_constants.DataType.Scalar

View File

@ -6,7 +6,6 @@ import numpy as np
from copy import deepcopy from copy import deepcopy
from mathutils import Vector from mathutils import Vector
from ...blender.com.gltf2_blender_data_path import get_sk_exported from ...blender.com.gltf2_blender_data_path import get_sk_exported
from ...io.com.gltf2_io_debug import print_console
from ...io.com.gltf2_io_constants import ROUNDING_DIGIT from ...io.com.gltf2_io_constants import ROUNDING_DIGIT
from ...io.exp.gltf2_io_user_extensions import export_user_extensions from ...io.exp.gltf2_io_user_extensions import export_user_extensions
from ...io.com import gltf2_io_constants from ...io.com import gltf2_io_constants
@ -19,7 +18,7 @@ from . import gltf2_blender_gather_skins
def extract_primitives(materials, blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings): def extract_primitives(materials, blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings):
"""Extract primitives from a mesh.""" """Extract primitives from a mesh."""
print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) export_settings['log'].info("Extracting primitive: " + blender_mesh.name)
primitive_creator = PrimitiveCreator(materials, blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings) primitive_creator = PrimitiveCreator(materials, blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings)
primitive_creator.prepare_data() primitive_creator.prepare_data()
@ -78,7 +77,7 @@ class PrimitiveCreator:
self.blender_mesh.calc_tangents() self.blender_mesh.calc_tangents()
self.use_tangents = True self.use_tangents = True
except Exception: except Exception:
print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.') self.export_settings['log'].warning("{}: Could not calculate tangents. Please try to triangulate the mesh first.".format(self.blender_mesh.name), popup=True)
self.tex_coord_max = 0 self.tex_coord_max = 0
if self.export_settings['gltf_texcoords']: if self.export_settings['gltf_texcoords']:
@ -187,7 +186,7 @@ class PrimitiveCreator:
# Seems we sometime can have name collision about attributes # Seems we sometime can have name collision about attributes
# Avoid crash and ignoring one of duplicated attribute name # Avoid crash and ignoring one of duplicated attribute name
if attr['gltf_attribute_name'] in [a['gltf_attribute_name'] for a in self.blender_attributes]: if attr['gltf_attribute_name'] in [a['gltf_attribute_name'] for a in self.blender_attributes]:
print_console('WARNING', 'Attribute collision name: ' + blender_attribute.name + ", ignoring one of them") self.export_settings['log'].warning('Attribute collision name: ' + blender_attribute.name + ", ignoring one of them")
continue continue
self.blender_attributes.append(attr) self.blender_attributes.append(attr)
@ -426,7 +425,7 @@ class PrimitiveCreator:
self.blender_mesh.attributes[attr].data.foreach_get('vector', data) self.blender_mesh.attributes[attr].data.foreach_get('vector', data)
data = data.reshape(-1, 2) data = data.reshape(-1, 2)
else: else:
print_console('WARNING', 'We are not managing this case yet (UVMap as custom attribute for unknown type)') self.export_settings['log'].warning('We are not managing this case (UVMap as custom attribute for unknown type)')
continue continue
# Blender UV space -> glTF UV space # Blender UV space -> glTF UV space
# u,v -> u,1-v # u,v -> u,1-v
@ -448,7 +447,7 @@ class PrimitiveCreator:
pass pass
elif material_info['vc_info']['color_type'] is None and material_info['vc_info']['alpha_type'] is not None: elif material_info['vc_info']['color_type'] is None and material_info['vc_info']['alpha_type'] is not None:
print_console('WARNING', 'We are not managing this case (Vertex Color alpha without color)') self.export_settings['log'].warning('We are not managing this case (Vertex Color alpha without color)')
else: else:
vc_color_name = None vc_color_name = None
@ -475,7 +474,7 @@ class PrimitiveCreator:
if materials_use_vc is not None and materials_use_vc != vc_key: if materials_use_vc is not None and materials_use_vc != vc_key:
if warning_already_displayed is False: if warning_already_displayed is False:
print_console('WARNING', 'glTF specification does not allow this case (multiple materials with different Vertex Color)') self.export_settings['log'].warning('glTF specification does not allow this case (multiple materials with different Vertex Color)')
warning_already_displayed = True warning_already_displayed = True
materials_use_vc = vc_key materials_use_vc = vc_key
@ -520,12 +519,12 @@ class PrimitiveCreator:
all_uvmaps[tex] = uvmap_name all_uvmaps[tex] = uvmap_name
if len(set(all_uvmaps.values())) > 1: if len(set(all_uvmaps.values())) > 1:
print_console('WARNING', 'We are not managing this case (multiple UVMap for UDIM)') self.export_settings['log'].warning('We are not managing this case (multiple UVMap for UDIM)')
new_prim_indices[material_idx] = self.prim_indices[material_idx] new_prim_indices[material_idx] = self.prim_indices[material_idx]
self.additional_materials.append(None) self.additional_materials.append(None)
continue continue
print_console('INFO', 'Splitting UDIM tiles into different primitives/materials') self.export_settings['log'].info('Splitting UDIM tiles into different primitives/materials')
# Retrieve UDIM images # Retrieve UDIM images
tex = list(material_info['udim_info'].keys())[0] tex = list(material_info['udim_info'].keys())[0]
image = material_info['udim_info'][tex]['image'] image = material_info['udim_info'][tex]['image']
@ -623,7 +622,7 @@ class PrimitiveCreator:
elif tex == "anisotropyTexture": elif tex == "anisotropyTexture":
new_material.extensions["KHR_materials_anisotropy"].extension['anisotropyTexture'] = new_tex new_material.extensions["KHR_materials_anisotropy"].extension['anisotropyTexture'] = new_tex
else: else:
print_console('WARNING', 'We are not managing this case yet (UDIM for {})'.format(tex)) self.export_settings['log'].warning('We are not managing this case (UDIM for {})'.format(tex))
self.additional_materials.append((new_material, material_info, int(str(id(base_material)) + str(u) + str(v)))) self.additional_materials.append((new_material, material_info, int(str(id(base_material)) + str(u) + str(v))))
@ -696,7 +695,7 @@ class PrimitiveCreator:
has_triangle_primitive = len(primitives) != 0 has_triangle_primitive = len(primitives) != 0
primitives.extend(self.primitive_creation_edges_and_points()) primitives.extend(self.primitive_creation_edges_and_points())
print_console('INFO', 'Primitives created: %d' % len(primitives)) self.export_settings['log'].info('Primitives created: %d' % len(primitives))
return primitives, [None]*len(primitives), self.attributes if has_triangle_primitive else None return primitives, [None]*len(primitives), self.attributes if has_triangle_primitive else None
@ -769,7 +768,7 @@ class PrimitiveCreator:
# No material for them, so only one primitive for each # No material for them, so only one primitive for each
primitives.extend(self.primitive_creation_edges_and_points()) primitives.extend(self.primitive_creation_edges_and_points())
print_console('INFO', 'Primitives created: %d' % len(primitives)) self.export_settings['log'].info('Primitives created: %d' % len(primitives))
return primitives return primitives
@ -1061,7 +1060,7 @@ class PrimitiveCreator:
elif attr['blender_domain'] in ['FACE']: elif attr['blender_domain'] in ['FACE']:
data = np.empty(len(self.blender_mesh.polygons) * attr['len'], dtype=attr['type']) data = np.empty(len(self.blender_mesh.polygons) * attr['len'], dtype=attr['type'])
else: else:
print_console("ERROR", "domain not known") self.export_settings['log'].error("domain not known")
if attr['blender_data_type'] == "BYTE_COLOR": if attr['blender_data_type'] == "BYTE_COLOR":
self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('color', data) self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('color', data)
@ -1093,7 +1092,7 @@ class PrimitiveCreator:
self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data) self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data)
data = data.reshape(-1, attr['len']) data = data.reshape(-1, attr['len'])
else: else:
print_console('ERROR',"blender type not found " + attr['blender_data_type']) self.export_settings['log'].error("blender type not found " + attr['blender_data_type'])
if attr['blender_domain'] in ['CORNER']: if attr['blender_domain'] in ['CORNER']:
for i in range(attr['len']): for i in range(attr['len']):
@ -1129,7 +1128,7 @@ class PrimitiveCreator:
self.dots[attr['gltf_attribute_name'] + str(i)] = data_attr[:, i] self.dots[attr['gltf_attribute_name'] + str(i)] = data_attr[:, i]
else: else:
print_console("ERROR", "domain not known") self.export_settings['log'].error("domain not known")
def __get_uvs_attribute(self, blender_uv_idx, attr): def __get_uvs_attribute(self, blender_uv_idx, attr):
layer = self.blender_mesh.uv_layers[blender_uv_idx] layer = self.blender_mesh.uv_layers[blender_uv_idx]

View File

@ -90,7 +90,17 @@ def __gather_inverse_bind_matrices(armature_uuid, export_settings):
matrices = [] matrices = []
for b in bones_uuid: for b in bones_uuid:
if export_settings['vtree'].nodes[b].leaf_reference is None:
__collect_matrices(blender_armature_object.pose.bones[export_settings['vtree'].nodes[b].blender_bone.name]) __collect_matrices(blender_armature_object.pose.bones[export_settings['vtree'].nodes[b].blender_bone.name])
else:
inverse_bind_matrix = (
axis_basis_change @
(
blender_armature_object.matrix_world @
export_settings['vtree'].nodes[export_settings['vtree'].nodes[b].leaf_reference].matrix_world_tail
)
).inverted_safe()
matrices.append(inverse_bind_matrix) # Leaf bone
# flatten the matrices # flatten the matrices
inverse_matrices = [] inverse_matrices = []

View File

@ -54,6 +54,7 @@ class VExportNode:
self.blender_object = None self.blender_object = None
self.blender_bone = None self.blender_bone = None
self.leaf_reference = None # For leaf bones only
self.default_hide_viewport = False # Need to store the default value for meshes in case of animation baking on armature self.default_hide_viewport = False # Need to store the default value for meshes in case of animation baking on armature
@ -181,8 +182,10 @@ class VExportTree:
if parent_uuid is None or not self.nodes[parent_uuid].blender_type == VExportNode.ARMATURE: if parent_uuid is None or not self.nodes[parent_uuid].blender_type == VExportNode.ARMATURE:
# correct workflow is to parent skinned mesh to armature, but ... # correct workflow is to parent skinned mesh to armature, but ...
# all users don't use correct workflow # all users don't use correct workflow
print("WARNING: Armature must be the parent of skinned mesh") self.export_settings['log'].warning(
print("Armature is selected by its name, but may be false in case of instances") "Armature must be the parent of skinned mesh"
"Armature is selected by its name, but may be false in case of instances"
)
# Search an armature by name, and use the first found # Search an armature by name, and use the first found
# This will be done after all objects are setup # This will be done after all objects are setup
node.armature_needed = modifiers["ARMATURE"].object.name node.armature_needed = modifiers["ARMATURE"].object.name
@ -246,9 +249,13 @@ class VExportTree:
if self.export_settings['gltf_rest_position_armature'] is False: if self.export_settings['gltf_rest_position_armature'] is False:
# Use pose bone for TRS # Use pose bone for TRS
node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.matrix node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.matrix
if self.export_settings['gltf_leaf_bone'] is True:
node.matrix_world_tail = self.nodes[node.armature].matrix_world @ Matrix.Translation(blender_bone.tail)
node.matrix_world_tail = node.matrix_world_tail @ self.axis_basis_change
else: else:
# Use edit bone for TRS --> REST pose will be used # Use edit bone for TRS --> REST pose will be used
node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.bone.matrix_local node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.bone.matrix_local
# Tail will be set after, as we need to be in edit mode
node.matrix_world = node.matrix_world @ self.axis_basis_change node.matrix_world = node.matrix_world @ self.axis_basis_change
if delta is True: if delta is True:
@ -423,7 +430,7 @@ class VExportTree:
elif parent_keep_tag is False: elif parent_keep_tag is False:
self.nodes[uuid].keep_tag = False self.nodes[uuid].keep_tag = False
else: else:
print("This should not happen!") self.export_settings['log'].error("This should not happen")
for child in self.nodes[uuid].children: for child in self.nodes[uuid].children:
if self.nodes[uuid].blender_type == VExportNode.INST_COLLECTION or self.nodes[uuid].is_instancier == VExportNode.INSTANCIER: if self.nodes[uuid].blender_type == VExportNode.INST_COLLECTION or self.nodes[uuid].is_instancier == VExportNode.INSTANCIER:
@ -556,13 +563,56 @@ class VExportTree:
del n.armature_needed del n.armature_needed
def bake_armature_bone_list(self): def bake_armature_bone_list(self):
if self.export_settings['gltf_leaf_bone'] is True:
self.add_leaf_bones()
# Used to store data in armature vnode # Used to store data in armature vnode
# If armature is removed from export # If armature is removed from export
# Data are still available, even if armature is not exported (so bones are re-parented) # Data are still available, even if armature is not exported (so bones are re-parented)
for n in [n for n in self.nodes.values() if n.blender_type == VExportNode.ARMATURE]: for n in [n for n in self.nodes.values() if n.blender_type == VExportNode.ARMATURE]:
self.get_all_bones(n.uuid) self.get_all_bones(n.uuid)
self.get_root_bones_uuid(n.uuid) self.get_root_bones_uuid(n.uuid)
def add_leaf_bones(self):
# If we are using rest pose, we need to get tail of editbone, going to edit mode for each armature
if self.export_settings['gltf_rest_position_armature'] is True:
for obj_uuid in [n for n in self.nodes if self.nodes[n].blender_type == VExportNode.ARMATURE]:
armature = self.nodes[obj_uuid].blender_object
bpy.context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode="EDIT")
for bone in armature.data.edit_bones:
if len(bone.children) == 0:
self.nodes[self.nodes[obj_uuid].bones[bone.name]].matrix_world_tail = armature.matrix_world @ Matrix.Translation(bone.tail) @ self.axis_basis_change
bpy.ops.object.mode_set(mode="OBJECT")
for bone_uuid in [n for n in self.nodes if self.nodes[n].blender_type == VExportNode.BONE \
and len(self.nodes[n].children) == 0]:
bone_node = self.nodes[bone_uuid]
# Add a new node
node = VExportNode()
node.uuid = str(uuid.uuid4())
node.parent_uuid = bone_uuid
node.parent_bone_uuid = bone_uuid
node.blender_object = bone_node.blender_object
node.armature = bone_node.armature
node.blender_type = VExportNode.BONE
node.leaf_reference = bone_uuid
node.keep_tag = True
node.matrix_world = bone_node.matrix_world_tail.copy()
self.add_children(bone_uuid, node.uuid)
self.add_node(node)
def add_neutral_bones(self): def add_neutral_bones(self):
added_armatures = [] added_armatures = []
for n in [n for n in self.nodes.values() if \ for n in [n for n in self.nodes.values() if \
@ -575,7 +625,7 @@ class VExportTree:
# Be sure to add it to really exported meshes # Be sure to add it to really exported meshes
if n.node.skin is None: if n.node.skin is None:
print("WARNING: {} has no skin, skipping adding neutral bone data on it.".format(n.blender_object.name)) self.export_settings['log'].warning("{} has no skin, skipping adding neutral bone data on it.".format(n.blender_object.name))
continue continue
if n.armature not in added_armatures: if n.armature not in added_armatures:
@ -691,5 +741,5 @@ class VExportTree:
if len(self.get_root_bones_uuid(arma_uuid)) > 1: if len(self.get_root_bones_uuid(arma_uuid)) > 1:
# We can't remove armature # We can't remove armature
self.export_settings['gltf_armature_object_remove'] = False self.export_settings['gltf_armature_object_remove'] = False
print("WARNING: We can't remove armature object because some armatures have multiple root bones.") self.export_settings['log'].warning("We can't remove armature object because some armatures have multiple root bones.")
break break

View File

@ -47,7 +47,7 @@ def gather_image(
# In case we can't retrieve image (for example packed images, with original moved) # In case we can't retrieve image (for example packed images, with original moved)
# We don't create invalid image without uri # We don't create invalid image without uri
factor_uri = None factor_uri = None
if uri is None: return None, None, None, False if uri is None: return None, None, None, None
buffer_view, factor_buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings) buffer_view, factor_buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
@ -340,8 +340,7 @@ def __get_image_data_mapping(sockets, results, use_tile, export_settings) -> Exp
keys = list(composed_image.fills.keys()) # do not loop on dict, we may have to delete an element keys = list(composed_image.fills.keys()) # do not loop on dict, we may have to delete an element
for k in [k for k in keys if isinstance(composed_image.fills[k], FillImage)]: for k in [k for k in keys if isinstance(composed_image.fills[k], FillImage)]:
if composed_image.fills[k].image.size[0] == 0 or composed_image.fills[k].image.size[1] == 0: if composed_image.fills[k].image.size[0] == 0 or composed_image.fills[k].image.size[1] == 0:
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning("Image '{}' has no size and cannot be exported.".format(
"Image '{}' has no size and cannot be exported.".format(
composed_image.fills[k].image)) composed_image.fills[k].image))
del composed_image.fills[k] del composed_image.fills[k]

View File

@ -8,7 +8,6 @@ import bpy
from ....io.com import gltf2_io from ....io.com import gltf2_io
from ....io.com.gltf2_io_extensions import Extension from ....io.com.gltf2_io_extensions import Extension
from ....io.exp.gltf2_io_user_extensions import export_user_extensions from ....io.exp.gltf2_io_user_extensions import export_user_extensions
from ....io.com.gltf2_io_debug import print_console
from ...com.gltf2_blender_extras import generate_extras from ...com.gltf2_blender_extras import generate_extras
from ..gltf2_blender_gather_cache import cached, cached_by_key from ..gltf2_blender_gather_cache import cached, cached_by_key
from . import gltf2_blender_gather_materials_unlit from . import gltf2_blender_gather_materials_unlit
@ -328,9 +327,10 @@ def __gather_orm_texture(blender_material, export_settings):
result = (occlusion, roughness_socket, metallic_socket) result = (occlusion, roughness_socket, metallic_socket)
if not gltf2_blender_gather_texture_info.check_same_size_images(result, export_settings): if not gltf2_blender_gather_texture_info.check_same_size_images(result, export_settings):
print_console("INFO", export_settings['log'].info(
"Occlusion and metal-roughness texture will be exported separately " "Occlusion and metal-roughness texture will be exported separately "
"(use same-sized images if you want them combined)") "(use same-sized images if you want them combined)"
)
return None return None
# Double-check this will past the filter in texture_info # Double-check this will past the filter in texture_info
@ -508,7 +508,7 @@ def __get_final_material_with_indices(blender_material, base_material, caching_i
elif tex.startswith("additional"): elif tex.startswith("additional"):
export_settings['additional_texture_export'][export_settings['additional_texture_export_current_idx'] + int(tex[10:])].tex_coord = ind export_settings['additional_texture_export'][export_settings['additional_texture_export_current_idx'] + int(tex[10:])].tex_coord = ind
else: else:
print_console("ERROR", "some Textures tex coord are not managed") export_settings['log'].error("some Textures tex coord are not managed")
export_settings['additional_texture_export_current_idx'] = len(export_settings['additional_texture_export']) export_settings['additional_texture_export_current_idx'] = len(export_settings['additional_texture_export'])

View File

@ -31,7 +31,7 @@ def gather_texture(
""" """
if not __filter_texture(blender_shader_sockets, export_settings): if not __filter_texture(blender_shader_sockets, export_settings):
return None, None, False return None, None, None
source, webp_image, image_data, factor, udim_image = __gather_source(blender_shader_sockets, use_tile, export_settings) source, webp_image, image_data, factor, udim_image = __gather_source(blender_shader_sockets, use_tile, export_settings)
@ -168,9 +168,10 @@ def __gather_name(blender_shader_sockets, export_settings):
def __gather_sampler(blender_shader_sockets, export_settings): def __gather_sampler(blender_shader_sockets, export_settings):
shader_nodes = [get_texture_node_from_socket(socket, export_settings) for socket in blender_shader_sockets] shader_nodes = [get_texture_node_from_socket(socket, export_settings) for socket in blender_shader_sockets]
if len(shader_nodes) > 1: if len(shader_nodes) > 1:
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"More than one shader node tex image used for a texture. " "More than one shader node tex image used for a texture. "
"The resulting glTF sampler will behave like the first shader node tex image.") "The resulting glTF sampler will behave like the first shader node tex image."
)
first_valid_shader_node = next(filter(lambda x: x is not None, shader_nodes)) first_valid_shader_node = next(filter(lambda x: x is not None, shader_nodes))
# group_path can't be a list, so transform it to str # group_path can't be a list, so transform it to str

View File

@ -204,7 +204,7 @@ def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
texture_transform = None texture_transform = None
if node.node and node.node.type == 'MAPPING': if node.node and node.node.type == 'MAPPING':
texture_transform = get_texture_transform_from_mapping_node(node) texture_transform = get_texture_transform_from_mapping_node(node, export_settings)
node = previous_node(NodeSocket(node.node.inputs['Vector'], node.group_path)) node = previous_node(NodeSocket(node.node.inputs['Vector'], node.group_path))
uvmap_info = {} uvmap_info = {}

View File

@ -11,7 +11,6 @@ from mathutils import Vector, Matrix
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
from ...com.gltf2_blender_material_helpers import get_gltf_node_name, get_gltf_node_old_name, get_gltf_old_group_node_name from ...com.gltf2_blender_material_helpers import get_gltf_node_name, get_gltf_node_old_name, get_gltf_old_group_node_name
from ....blender.com.gltf2_blender_conversion import texture_transform_blender_to_gltf from ....blender.com.gltf2_blender_conversion import texture_transform_blender_to_gltf
from io_scene_gltf2.io.com import gltf2_io_debug
import typing import typing
@ -494,9 +493,9 @@ def previous_node(socket: NodeSocket):
return ShNode(prev_socket.socket.node, prev_socket.group_path) return ShNode(prev_socket.socket.node, prev_socket.group_path)
return ShNode(None, None) return ShNode(None, None)
def get_texture_transform_from_mapping_node(mapping_node): def get_texture_transform_from_mapping_node(mapping_node, export_settings):
if mapping_node.node.vector_type not in ["TEXTURE", "POINT", "VECTOR"]: if mapping_node.node.vector_type not in ["TEXTURE", "POINT", "VECTOR"]:
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"Skipping exporting texture transform because it had type " + "Skipping exporting texture transform because it had type " +
mapping_node.node.vector_type + "; recommend using POINT instead" mapping_node.node.vector_type + "; recommend using POINT instead"
) )
@ -506,7 +505,7 @@ def get_texture_transform_from_mapping_node(mapping_node):
rotation_0, rotation_1 = mapping_node.node.inputs['Rotation'].default_value[0], mapping_node.node.inputs['Rotation'].default_value[1] rotation_0, rotation_1 = mapping_node.node.inputs['Rotation'].default_value[0], mapping_node.node.inputs['Rotation'].default_value[1]
if rotation_0 or rotation_1: if rotation_0 or rotation_1:
# TODO: can we handle this? # TODO: can we handle this?
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"Skipping exporting texture transform because it had non-zero " "Skipping exporting texture transform because it had non-zero "
"rotations in the X/Y direction; only a Z rotation can be exported!" "rotations in the X/Y direction; only a Z rotation can be exported!"
) )
@ -542,7 +541,7 @@ def get_texture_transform_from_mapping_node(mapping_node):
mapping_transform = inverted(mapping_transform) mapping_transform = inverted(mapping_transform)
if mapping_transform is None: if mapping_transform is None:
gltf2_io_debug.print_console("WARNING", export_settings['log'].warning(
"Skipping exporting texture transform with type TEXTURE because " "Skipping exporting texture transform with type TEXTURE because "
"we couldn't convert it to TRS; recommend using POINT instead" "we couldn't convert it to TRS; recommend using POINT instead"
) )

View File

@ -6,7 +6,6 @@ import bpy
from mathutils import Matrix from mathutils import Matrix
import numpy as np import numpy as np
from ...io.imp.gltf2_io_user_extensions import import_user_extensions from ...io.imp.gltf2_io_user_extensions import import_user_extensions
from ...io.com.gltf2_io_debug import print_console
from ...io.imp.gltf2_io_binary import BinaryData from ...io.imp.gltf2_io_binary import BinaryData
from ...io.com.gltf2_io_constants import DataType, ComponentType from ...io.com.gltf2_io_constants import DataType, ComponentType
from ...blender.com.gltf2_blender_conversion import get_attribute_type from ...blender.com.gltf2_blender_conversion import get_attribute_type
@ -157,7 +156,8 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
vert_index_base = len(vert_locs) vert_index_base = len(vert_locs)
if prim.extensions is not None and 'KHR_draco_mesh_compression' in prim.extensions: if prim.extensions is not None and 'KHR_draco_mesh_compression' in prim.extensions:
print_console('INFO', 'Draco Decoder: Decode primitive {}'.format(pymesh.name or '[unnamed]'))
gltf.log.info('Draco Decoder: Decode primitive {}'.format(pymesh.name or '[unnamed]'))
decode_primitive(gltf, prim) decode_primitive(gltf, prim)
import_user_extensions('gather_import_decode_primitive', gltf, pymesh, prim, skin_idx) import_user_extensions('gather_import_decode_primitive', gltf, pymesh, prim, skin_idx)
@ -319,7 +319,7 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
layer = mesh.uv_layers.new(name=name) layer = mesh.uv_layers.new(name=name)
if layer is None: if layer is None:
print("WARNING: UV map is ignored because the maximum number of UV layers has been reached.") gltf.log.warning("WARNING: UV map is ignored because the maximum number of UV layers has been reached.")
break break
layer.uv.foreach_set('vector', squish(loop_uvs[uv_i], np.float32)) layer.uv.foreach_set('vector', squish(loop_uvs[uv_i], np.float32))
@ -639,7 +639,7 @@ def skin_into_bind_pose(gltf, skin_idx, vert_joints, vert_weights, locs, vert_no
# We set all weight ( aka 1.0 ) to the first bone # We set all weight ( aka 1.0 ) to the first bone
zeros_indices = np.where(weight_sums == 0)[0] zeros_indices = np.where(weight_sums == 0)[0]
if zeros_indices.shape[0] > 0: if zeros_indices.shape[0] > 0:
print_console('ERROR', 'File is invalid: Some vertices are not assigned to bone(s) ') gltf.log.error('File is invalid: Some vertices are not assigned to bone(s) ')
vert_weights[0][:, 0][zeros_indices] = 1.0 # Assign to first bone with all weight vert_weights[0][:, 0][zeros_indices] = 1.0 # Assign to first bone with all weight
# Reprocess IBM for these vertices # Reprocess IBM for these vertices

View File

@ -269,7 +269,7 @@ class BlenderNode():
if cache_key is not None and cache_key in pymesh.blender_name: if cache_key is not None and cache_key in pymesh.blender_name:
mesh = bpy.data.meshes[pymesh.blender_name[cache_key]] mesh = bpy.data.meshes[pymesh.blender_name[cache_key]]
else: else:
gltf.log.info("Blender create Mesh node %s", pymesh.name or pynode.mesh) gltf.log.info("Blender create Mesh node {}".format(pymesh.name or pynode.mesh))
mesh = BlenderMesh.create(gltf, pynode.mesh, pynode.skin) mesh = BlenderMesh.create(gltf, pynode.mesh, pynode.skin)
if cache_key is not None: if cache_key is not None:
pymesh.blender_name[cache_key] = mesh.name pymesh.blender_name[cache_key] = mesh.name

View File

@ -6,7 +6,6 @@ from ctypes import *
from ...io.com.gltf2_io import BufferView from ...io.com.gltf2_io import BufferView
from ...io.imp.gltf2_io_binary import BinaryData from ...io.imp.gltf2_io_binary import BinaryData
from ...io.com.gltf2_io_debug import print_console
from ...io.com.gltf2_io_draco_compression_extension import dll_path from ...io.com.gltf2_io_draco_compression_extension import dll_path
@ -63,7 +62,7 @@ def decode_primitive(gltf, prim):
# Create Draco decoder. # Create Draco decoder.
draco_buffer = bytes(BinaryData.get_buffer_view(gltf, extension['bufferView'])) draco_buffer = bytes(BinaryData.get_buffer_view(gltf, extension['bufferView']))
if not dll.decoderDecode(decoder, draco_buffer, len(draco_buffer)): if not dll.decoderDecode(decoder, draco_buffer, len(draco_buffer)):
print_console('ERROR', 'Draco Decoder: Unable to decode. Skipping primitive {}.'.format(name)) gltf.log.error('Draco Decoder: Unable to decode. Skipping primitive {}.'.format(name))
return return
# Choose a buffer index which does not yet exist, skipping over existing glTF buffers yet to be loaded # Choose a buffer index which does not yet exist, skipping over existing glTF buffers yet to be loaded
@ -76,10 +75,10 @@ def decode_primitive(gltf, prim):
# Read indices. # Read indices.
index_accessor = gltf.data.accessors[prim.indices] index_accessor = gltf.data.accessors[prim.indices]
if dll.decoderGetIndexCount(decoder) != index_accessor.count: if dll.decoderGetIndexCount(decoder) != index_accessor.count:
print_console('WARNING', 'Draco Decoder: Index count of accessor and decoded index count does not match. Updating accessor.') gltf.log.warning('Draco Decoder: Index count of accessor and decoded index count does not match. Updating accessor.')
index_accessor.count = dll.decoderGetIndexCount(decoder) index_accessor.count = dll.decoderGetIndexCount(decoder)
if not dll.decoderReadIndices(decoder, index_accessor.component_type): if not dll.decoderReadIndices(decoder, index_accessor.component_type):
print_console('ERROR', 'Draco Decoder: Unable to decode indices. Skipping primitive {}.'.format(name)) gltf.log.error('Draco Decoder: Unable to decode indices. Skipping primitive {}.'.format(name))
return return
indices_byte_length = dll.decoderGetIndicesByteLength(decoder) indices_byte_length = dll.decoderGetIndicesByteLength(decoder)
@ -102,15 +101,15 @@ def decode_primitive(gltf, prim):
for attr_idx, attr in enumerate(extension['attributes']): for attr_idx, attr in enumerate(extension['attributes']):
dracoId = extension['attributes'][attr] dracoId = extension['attributes'][attr]
if attr not in prim.attributes: if attr not in prim.attributes:
print_console('ERROR', 'Draco Decoder: Draco attribute {} not in primitive attributes. Skipping primitive {}.'.format(attr, name)) gltf.log.error('Draco Decoder: Draco attribute {} not in primitive attributes. Skipping primitive {}.'.format(attr, name))
return return
accessor = gltf.data.accessors[prim.attributes[attr]] accessor = gltf.data.accessors[prim.attributes[attr]]
if dll.decoderGetVertexCount(decoder) != accessor.count: if dll.decoderGetVertexCount(decoder) != accessor.count:
print_console('WARNING', 'Draco Decoder: Vertex count of accessor and decoded vertex count does not match for attribute {}. Updating accessor.'.format(attr, name)) gltf.log.warning('Draco Decoder: Vertex count of accessor and decoded vertex count does not match for attribute {}. Updating accessor.'.format(attr, name))
accessor.count = dll.decoderGetVertexCount(decoder) accessor.count = dll.decoderGetVertexCount(decoder)
if not dll.decoderReadAttribute(decoder, dracoId, accessor.component_type, accessor.type.encode()): if not dll.decoderReadAttribute(decoder, dracoId, accessor.component_type, accessor.type.encode()):
print_console('ERROR', 'Draco Decoder: Could not decode attribute {}. Skipping primitive {}.'.format(attr, name)) gltf.log.error('Draco Decoder: Could not decode attribute {}. Skipping primitive {}.'.format(attr, name))
return return
byte_length = dll.decoderGetAttributeByteLength(decoder, dracoId) byte_length = dll.decoderGetAttributeByteLength(decoder, dracoId)

View File

@ -42,7 +42,7 @@ def from_union(fs, x):
tb_info = traceback.extract_tb(tb) tb_info = traceback.extract_tb(tb)
for tbi in tb_info: for tbi in tb_info:
filename, line, func, text = tbi filename, line, func, text = tbi
gltf2_io_debug.print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text)) print('ERROR', 'An error occurred on line {} in statement {}'.format(line, text))
assert False assert False

View File

@ -8,48 +8,17 @@
import time import time
import logging import logging
import logging.handlers
# #
# Globals # Globals
# #
OUTPUT_LEVELS = ['ERROR', 'WARNING', 'INFO', 'PROFILE', 'DEBUG', 'VERBOSE']
g_current_output_level = 'DEBUG'
g_profile_started = False g_profile_started = False
g_profile_start = 0.0 g_profile_start = 0.0
g_profile_end = 0.0 g_profile_end = 0.0
g_profile_delta = 0.0 g_profile_delta = 0.0
#
# Functions
#
def set_output_level(level):
"""Set an output debug level."""
global g_current_output_level
if OUTPUT_LEVELS.index(level) < 0:
return
g_current_output_level = level
def print_console(level, output):
"""Print to Blender console with a given header and output."""
global OUTPUT_LEVELS
global g_current_output_level
if OUTPUT_LEVELS.index(level) > OUTPUT_LEVELS.index(g_current_output_level):
return
print(get_timestamp() + " | " + level + ': ' + output)
def print_newline():
"""Print a new line to Blender console."""
print()
def get_timestamp(): def get_timestamp():
@ -57,23 +26,13 @@ def get_timestamp():
return time.strftime("%H:%M:%S", current_time) return time.strftime("%H:%M:%S", current_time)
def print_timestamp(label=None):
"""Print a timestamp to Blender console."""
output = 'Timestamp: ' + get_timestamp()
if label is not None:
output = output + ' (' + label + ')'
print_console('PROFILE', output)
def profile_start(): def profile_start():
"""Start profiling by storing the current time.""" """Start profiling by storing the current time."""
global g_profile_start global g_profile_start
global g_profile_started global g_profile_started
if g_profile_started: if g_profile_started:
print_console('ERROR', 'Profiling already started') print('ERROR', 'Profiling already started')
return return
g_profile_started = True g_profile_started = True
@ -88,7 +47,7 @@ def profile_end(label=None):
global g_profile_started global g_profile_started
if not g_profile_started: if not g_profile_started:
print_console('ERROR', 'Profiling not started') print('ERROR', 'Profiling not started')
return return
g_profile_started = False g_profile_started = False
@ -101,16 +60,60 @@ def profile_end(label=None):
if label is not None: if label is not None:
output = output + ' (' + label + ')' output = output + ' (' + label + ')'
print_console('PROFILE', output) print('PROFILE', output)
# TODO: need to have a unique system for logging importer/exporter
# TODO: this logger is used for importer, but in io and in blender part, but is written here in a _io_ file
class Log: class Log:
def __init__(self, loglevel): def __init__(self, loglevel):
self.logger = logging.getLogger('glTFImporter') self.logger = logging.getLogger('glTFImporter')
self.hdlr = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') # For console display
self.hdlr.setFormatter(formatter) self.console_handler = logging.StreamHandler()
self.logger.addHandler(self.hdlr) formatter = logging.Formatter('%(asctime)s | %(levelname)s: %(message)s', "%H:%M:%S")
self.console_handler.setFormatter(formatter)
# For popup display
self.popup_handler = logging.handlers.MemoryHandler(1024*10)
self.logger.addHandler(self.console_handler)
#self.logger.addHandler(self.popup_handler) => Make sure to not attach the popup handler to the logger
self.logger.setLevel(int(loglevel)) self.logger.setLevel(int(loglevel))
def error(self, message, popup=False):
self.logger.error(message)
if popup:
self.popup_handler.buffer.append(('ERROR', message))
def warning(self, message, popup=False):
self.logger.warning(message)
if popup:
self.popup_handler.buffer.append(('WARNING', message))
def info(self, message, popup=False):
self.logger.info(message)
if popup:
self.popup_handler.buffer.append(('INFO', message))
def debug(self, message, popup=False):
self.logger.debug(message)
if popup:
self.popup_handler.buffer.append(('DEBUG', message))
def critical(self, message, popup=False):
self.logger.critical(message)
if popup:
self.popup_handler.buffer.append(('ERROR', message)) # There is no Critical level in Blender, so we use error
def profile(self, message, popup=False): # There is no profile level in logging, so we use info
self.logger.info(message)
if popup:
self.popup_handler.buffer.append(('PROFILE', message))
def messages(self):
return self.popup_handler.buffer
def flush(self):
self.logger.removeHandler(self.console_handler)
self.popup_handler.flush()
self.logger.removeHandler(self.popup_handler)

View File

@ -7,9 +7,6 @@ import sys
from pathlib import Path from pathlib import Path
import bpy import bpy
from ...io.com.gltf2_io_debug import print_console
def dll_path() -> Path: def dll_path() -> Path:
""" """
Get the DLL path depending on the underlying platform. Get the DLL path depending on the underlying platform.
@ -37,7 +34,7 @@ def dll_path() -> Path:
}.get(sys.platform) }.get(sys.platform)
if path is None or library_name is None: if path is None or library_name is None:
print_console('WARNING', 'Unsupported platform {}, Draco mesh compression is unavailable'.format(sys.platform)) print('WARNING', 'Unsupported platform {}, Draco mesh compression is unavailable'.format(sys.platform))
return path / library_name return path / library_name
@ -51,7 +48,7 @@ def dll_exists(quiet=False) -> bool:
exists = path.exists() and path.is_file() exists = path.exists() and path.is_file()
if quiet is False: if quiet is False:
if exists: if exists:
print_console('INFO', 'Draco mesh compression is available, use library at %s' % dll_path().absolute()) print('INFO', 'Draco mesh compression is available, use library at %s' % dll_path().absolute())
else: else:
print_console('ERROR', 'Draco mesh compression is not available because library could not be found at %s' % dll_path().absolute()) print('ERROR', 'Draco mesh compression is not available because library could not be found at %s' % dll_path().absolute())
return exists return exists

View File

@ -6,7 +6,6 @@ from ctypes import *
from pathlib import Path from pathlib import Path
from ...io.exp.gltf2_io_binary_data import BinaryData from ...io.exp.gltf2_io_binary_data import BinaryData
from ...io.com.gltf2_io_debug import print_console
from ...io.com.gltf2_io_draco_compression_extension import dll_path from ...io.com.gltf2_io_draco_compression_extension import dll_path
@ -90,7 +89,7 @@ def __traverse_node(node, f):
def __encode_node(node, dll, export_settings, encoded_primitives_cache): def __encode_node(node, dll, export_settings, encoded_primitives_cache):
if node.mesh is not None: if node.mesh is not None:
print_console('INFO', 'Draco encoder: Encoding mesh {}.'.format(node.name)) export_settings['log'].info('Draco encoder: Encoding mesh {}.'.format(node.name))
for primitive in node.mesh.primitives: for primitive in node.mesh.primitives:
__encode_primitive(primitive, dll, export_settings, encoded_primitives_cache) __encode_primitive(primitive, dll, export_settings, encoded_primitives_cache)
@ -112,7 +111,7 @@ def __encode_primitive(primitive, dll, export_settings, encoded_primitives_cache
return return
if 'POSITION' not in attributes: if 'POSITION' not in attributes:
print_console('WARNING', 'Draco encoder: Primitive without positions encountered. Skipping.') export_settings['log'].warning('Draco encoder: Primitive without positions encountered. Skipping.')
return return
positions = attributes['POSITION'] positions = attributes['POSITION']
@ -141,7 +140,7 @@ def __encode_primitive(primitive, dll, export_settings, encoded_primitives_cache
preserve_triangle_order = primitive.targets is not None and len(primitive.targets) > 0 preserve_triangle_order = primitive.targets is not None and len(primitive.targets) > 0
if not dll.encoderEncode(encoder, preserve_triangle_order): if not dll.encoderEncode(encoder, preserve_triangle_order):
print_console('ERROR', 'Could not encode primitive. Skipping primitive.') export_settings['log'].error('Could not encode primitive. Skipping primitive.')
byte_length = dll.encoderGetByteLength(encoder) byte_length = dll.encoderGetByteLength(encoder)
encoded_data = bytes(byte_length) encoded_data = bytes(byte_length)

View File

@ -13,5 +13,6 @@ def export_user_extensions(hook_name, export_settings, *args):
try: try:
hook(*args, export_settings) hook(*args, export_settings)
except Exception as e: except Exception as e:
print(hook_name, "fails on", extension) export_settings['log'].error("Extension hook", hook_name, "fails on", extension)
print(str(e)) export_settings['log'].error(str(e))

View File

@ -32,11 +32,9 @@ class glTFImporter():
self.variant_mapping = {} # Used to map between mgltf material idx and blender material, for Variants self.variant_mapping = {} # Used to map between mgltf material idx and blender material, for Variants
if 'loglevel' not in self.import_settings.keys(): if 'loglevel' not in self.import_settings.keys():
self.import_settings['loglevel'] = logging.ERROR self.import_settings['loglevel'] = logging.CRITICAL
log = Log(import_settings['loglevel']) self.log = Log(import_settings['loglevel'])
self.log = log.logger
self.log_handler = log.hdlr
# TODO: move to a com place? # TODO: move to a com place?
self.extensions_managed = [ self.extensions_managed = [

View File

@ -9,5 +9,5 @@ def import_user_extensions(hook_name, gltf, *args):
try: try:
hook(*args, gltf) hook(*args, gltf)
except Exception as e: except Exception as e:
print(hook_name, "fails on", extension) gltf.log.error(hook_name, "fails on", extension)
print(str(e)) gltf.log.error(str(e))

View File

@ -417,7 +417,7 @@ def export(file,
location = matrix.to_translation()[:] location = matrix.to_translation()[:]
radius = lamp.distance * math.cos(beamWidth) radius = lamp.cutoff_distance * math.cos(beamWidth)
# radius = lamp.dist*math.cos(beamWidth) # radius = lamp.dist*math.cos(beamWidth)
ident_step = ident + (' ' * (-len(ident) + \ ident_step = ident + (' ' * (-len(ident) + \
fw('%s<SpotLight ' % ident))) fw('%s<SpotLight ' % ident)))
@ -479,7 +479,7 @@ def export(file,
fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(light.color)) fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(light.color))
fw(ident_step + 'intensity="%.4f"\n' % intensity) fw(ident_step + 'intensity="%.4f"\n' % intensity)
fw(ident_step + 'radius="%.4f" \n' % light.distance) fw(ident_step + 'radius="%.4f" \n' % light.cutoff_distance)
fw(ident_step + 'location="%.4f %.4f %.4f"\n' % location) fw(ident_step + 'location="%.4f %.4f %.4f"\n' % location)
fw(ident_step + '/>\n') fw(ident_step + '/>\n')

View File

@ -3171,7 +3171,7 @@ def importLamp_PointLight(node, ancestry):
bpylamp = bpy.data.lights.new(vrmlname, 'POINT') bpylamp = bpy.data.lights.new(vrmlname, 'POINT')
bpylamp.energy = intensity bpylamp.energy = intensity
bpylamp.distance = radius bpylamp.cutoff_distance = radius
bpylamp.color = color bpylamp.color = color
mtx = Matrix.Translation(Vector(location)) mtx = Matrix.Translation(Vector(location))
@ -3220,7 +3220,7 @@ def importLamp_SpotLight(node, ancestry):
bpylamp = bpy.data.lights.new(vrmlname, 'SPOT') bpylamp = bpy.data.lights.new(vrmlname, 'SPOT')
bpylamp.energy = intensity bpylamp.energy = intensity
bpylamp.distance = radius bpylamp.cutoff_distance = radius
bpylamp.color = color bpylamp.color = color
bpylamp.spot_size = cutOffAngle bpylamp.spot_size = cutOffAngle
if beamWidth > cutOffAngle: if beamWidth > cutOffAngle:

View File

@ -1839,6 +1839,7 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
# If Bump add bump node in between # If Bump add bump node in between
bump_node_texture = nodes.new(type='ShaderNodeTexImage') bump_node_texture = nodes.new(type='ShaderNodeTexImage')
img = bpy.data.images.load(path.join(import_path, sname[2])) img = bpy.data.images.load(path.join(import_path, sname[2]))
img.colorspace_settings.is_data = True
bump_node_texture.image = img bump_node_texture.image = img
bump_node_texture.label = 'Bump' bump_node_texture.label = 'Bump'
@ -1857,6 +1858,7 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
# If Normal add normal node in between # If Normal add normal node in between
normal_node_texture = nodes.new(type='ShaderNodeTexImage') normal_node_texture = nodes.new(type='ShaderNodeTexImage')
img = bpy.data.images.load(path.join(import_path, sname[2])) img = bpy.data.images.load(path.join(import_path, sname[2]))
img.colorspace_settings.is_data = True
normal_node_texture.image = img normal_node_texture.image = img
normal_node_texture.label = 'Normal' normal_node_texture.label = 'Normal'