FBX IO: Export normals with IndexToDirect reference mode #105020
@ -139,7 +139,7 @@ class MAX3DS_PT_import_include(bpy.types.Panel):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
@ -5,7 +5,7 @@
|
||||
bl_info = {
|
||||
"name": "FBX format",
|
||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
||||
"version": (5, 10, 1),
|
||||
"version": (5, 10, 3),
|
||||
"blender": (4, 1, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
||||
|
@ -23,6 +23,7 @@ from . import encode_bin, data_types
|
||||
|
||||
# "Constants"
|
||||
FBX_VERSION = 7400
|
||||
# 1004 adds use of "OtherFlags"->"TCDefinition" to control the FBX_KTIME opt-in in FBX version 7700.
|
||||
FBX_HEADER_VERSION = 1003
|
||||
FBX_SCENEINFO_VERSION = 100
|
||||
FBX_TEMPLATES_VERSION = 100
|
||||
@ -54,7 +55,19 @@ FBX_ANIM_KEY_VERSION = 4008
|
||||
FBX_NAME_CLASS_SEP = b"\x00\x01"
|
||||
FBX_ANIM_PROPSGROUP_NAME = "d"
|
||||
|
||||
FBX_KTIME = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
|
||||
FBX_KTIME_V7 = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
|
||||
# FBX 2019.5 (FBX version 7700) changed the number of "ktimes" per second, however, the new value is opt-in until FBX
|
||||
# version 8000 where it will probably become opt-out.
|
||||
FBX_KTIME_V8 = 141120000
|
||||
# To explicitly use the V7 value in FBX versions 7700-7XXX: fbx_root->"FBXHeaderExtension"->"OtherFlags"->"TCDefinition"
|
||||
# is set to 127.
|
||||
# To opt in to the V8 value in FBX version 7700-7XXX: "TCDefinition" is set to 0.
|
||||
FBX_TIMECODE_DEFINITION_TO_KTIME_PER_SECOND = {
|
||||
0: FBX_KTIME_V8,
|
||||
127: FBX_KTIME_V7,
|
||||
}
|
||||
# The "ktimes" per second for Blender exported FBX is constant because the exported `FBX_VERSION` is constant.
|
||||
FBX_KTIME = FBX_KTIME_V8 if FBX_VERSION >= 8000 else FBX_KTIME_V7
|
||||
|
||||
|
||||
MAT_CONVERT_LIGHT = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
|
||||
@ -216,7 +229,7 @@ UNITS = {
|
||||
"degree": 360.0,
|
||||
"radian": math.pi * 2.0,
|
||||
"second": 1.0, # Ref unit!
|
||||
"ktime": FBX_KTIME,
|
||||
"ktime": FBX_KTIME, # For export use only because the imported "ktimes" per second may vary.
|
||||
}
|
||||
|
||||
|
||||
|
@ -48,6 +48,9 @@ from .fbx_utils import (
|
||||
MESH_ATTRIBUTE_SHARP_FACE,
|
||||
MESH_ATTRIBUTE_SHARP_EDGE,
|
||||
expand_shape_key_range,
|
||||
FBX_KTIME_V7,
|
||||
FBX_KTIME_V8,
|
||||
FBX_TIMECODE_DEFINITION_TO_KTIME_PER_SECOND,
|
||||
)
|
||||
|
||||
LINEAR_INTERPOLATION_VALUE = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value
|
||||
@ -803,9 +806,8 @@ def blen_read_invalid_animation_curve(key_times, key_values):
|
||||
return key_times, key_values
|
||||
|
||||
|
||||
def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_offset, fps):
|
||||
from .fbx_utils import FBX_KTIME
|
||||
timefac = fps / FBX_KTIME
|
||||
def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_offset, fps, fbx_ktime):
|
||||
timefac = fps / fbx_ktime
|
||||
|
||||
# Convert from FBX timing to Blender timing.
|
||||
# Cannot subtract in-place because key_times could be read directly from FBX and could be used by multiple Actions.
|
||||
@ -838,19 +840,21 @@ def blen_read_animation_curve(fbx_curve):
|
||||
return blen_read_invalid_animation_curve(key_times, key_values)
|
||||
|
||||
|
||||
def blen_store_keyframes(fbx_key_times, blen_fcurve, key_values, blen_start_offset, fps, fbx_start_offset=0):
|
||||
def blen_store_keyframes(fbx_key_times, blen_fcurve, key_values, blen_start_offset, fps, fbx_ktime, fbx_start_offset=0):
|
||||
"""Set all keyframe times and values for a newly created FCurve.
|
||||
Linear interpolation is currently assumed.
|
||||
|
||||
This is a convenience function for calling blen_store_keyframes_multi with only a single fcurve and values array."""
|
||||
blen_store_keyframes_multi(fbx_key_times, [(blen_fcurve, key_values)], blen_start_offset, fps, fbx_start_offset)
|
||||
blen_store_keyframes_multi(fbx_key_times, [(blen_fcurve, key_values)], blen_start_offset, fps, fbx_ktime,
|
||||
fbx_start_offset)
|
||||
|
||||
|
||||
def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_start_offset, fps, fbx_start_offset=0):
|
||||
def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_start_offset, fps, fbx_ktime,
|
||||
fbx_start_offset=0):
|
||||
"""Set all keyframe times and values for multiple pairs of newly created FCurves and keyframe values arrays, where
|
||||
each pair has the same keyframe times.
|
||||
Linear interpolation is currently assumed."""
|
||||
bl_key_times = _convert_fbx_time_to_blender_time(fbx_key_times, blen_start_offset, fbx_start_offset, fps)
|
||||
bl_key_times = _convert_fbx_time_to_blender_time(fbx_key_times, blen_start_offset, fbx_start_offset, fps, fbx_ktime)
|
||||
num_keys = len(bl_key_times)
|
||||
|
||||
# Compatible with C float type
|
||||
@ -883,7 +887,8 @@ def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_
|
||||
blen_fcurve.update()
|
||||
|
||||
|
||||
def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms):
|
||||
def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms,
|
||||
fbx_ktime):
|
||||
"""
|
||||
'Bake' loc/rot/scale into the action,
|
||||
taking any pre_ and post_ matrix into account to transform from fbx into blender space.
|
||||
@ -947,10 +952,9 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
assert(channel in {0, 1, 2})
|
||||
blen_curve = blen_curves[channel]
|
||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
|
||||
|
||||
elif isinstance(item, ShapeKey):
|
||||
deform_values = shape_key_deforms.setdefault(item, [])
|
||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||
assert(fbxprop == b'DeformPercent')
|
||||
for channel, curve in channel_to_curve.items():
|
||||
@ -960,12 +964,14 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
|
||||
values = values / 100.0
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
|
||||
|
||||
# Store the minimum and maximum shape key values, so that the shape key's slider range can be expanded
|
||||
# if necessary after reading all animations.
|
||||
deform_values.append(values.min())
|
||||
deform_values.append(values.max())
|
||||
if values.size:
|
||||
deform_values = shape_key_deforms.setdefault(item, [])
|
||||
deform_values.append(values.min())
|
||||
deform_values.append(values.max())
|
||||
|
||||
elif isinstance(item, Camera):
|
||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||
@ -981,7 +987,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
# Remap the imported values from FBX to Blender.
|
||||
values = values / 1000.0
|
||||
values *= global_scale
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
|
||||
|
||||
else: # Object or PoseBone:
|
||||
transform_data = item.fbx_transform_data
|
||||
@ -1042,10 +1048,10 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
|
||||
# Each channel has the same keyframe times, so the combined times can be passed once along with all the curves
|
||||
# and values arrays.
|
||||
blen_store_keyframes_multi(combined_fbx_times, zip(blen_curves, channel_values), anim_offset, fps)
|
||||
blen_store_keyframes_multi(combined_fbx_times, zip(blen_curves, channel_values), anim_offset, fps, fbx_ktime)
|
||||
|
||||
|
||||
def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_offset, global_scale):
|
||||
def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_offset, global_scale, fbx_ktime):
|
||||
"""
|
||||
Recreate an action per stack/layer/object combinations.
|
||||
Only the first found action is linked to objects, more complex setups are not handled,
|
||||
@ -1092,7 +1098,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
|
||||
id_data.animation_data.action = action
|
||||
# And actually populate the action!
|
||||
blen_read_animations_action_item(action, item, cnodes, scene.render.fps, anim_offset, global_scale,
|
||||
shape_key_values)
|
||||
shape_key_values, fbx_ktime)
|
||||
|
||||
# If the minimum/maximum animated value is outside the slider range of the shape key, attempt to expand the slider
|
||||
# range until the animated range fits and has extra room to be decreased or increased further.
|
||||
@ -3629,6 +3635,21 @@ def load(operator, context, filepath="",
|
||||
|
||||
# Animation!
|
||||
def _():
|
||||
# Find the number of "ktimes" per second for this file.
|
||||
# Start with the default for this FBX version.
|
||||
fbx_ktime = FBX_KTIME_V8 if version >= 8000 else FBX_KTIME_V7
|
||||
# Try to find the value of the nested elem_root->'FBXHeaderExtension'->'OtherFlags'->'TCDefinition' element
|
||||
# and look up the "ktimes" per second for its value.
|
||||
if header := elem_find_first(elem_root, b'FBXHeaderExtension'):
|
||||
# The header version that added TCDefinition support is 1004.
|
||||
if elem_prop_first(elem_find_first(header, b'FBXHeaderVersion'), default=0) >= 1004:
|
||||
if other_flags := elem_find_first(header, b'OtherFlags'):
|
||||
if timecode_definition := elem_find_first(other_flags, b'TCDefinition'):
|
||||
timecode_definition_value = elem_prop_first(timecode_definition)
|
||||
# If its value is unknown or missing, default to FBX_KTIME_V8.
|
||||
fbx_ktime = FBX_TIMECODE_DEFINITION_TO_KTIME_PER_SECOND.get(timecode_definition_value,
|
||||
FBX_KTIME_V8)
|
||||
|
||||
fbx_tmpl_astack = fbx_template_get((b'AnimationStack', b'FbxAnimStack'))
|
||||
fbx_tmpl_alayer = fbx_template_get((b'AnimationLayer', b'FbxAnimLayer'))
|
||||
stacks = {}
|
||||
@ -3742,7 +3763,8 @@ def load(operator, context, filepath="",
|
||||
curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel)
|
||||
|
||||
# And now that we have sorted all this, apply animations!
|
||||
blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, settings.anim_offset, global_scale)
|
||||
blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, settings.anim_offset, global_scale,
|
||||
fbx_ktime)
|
||||
|
||||
_(); del _
|
||||
|
||||
|
@ -5,7 +5,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": (4, 1, 34),
|
||||
"version": (4, 1, 36),
|
||||
'blender': (4, 1, 0),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
@ -48,6 +48,7 @@ from bpy.props import (StringProperty,
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
CollectionProperty)
|
||||
from bpy.types import Operator
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
@ -155,6 +156,102 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
|
||||
default=''
|
||||
)
|
||||
|
||||
# gltfpack properties
|
||||
export_use_gltfpack: BoolProperty(
|
||||
name='Use Gltfpack',
|
||||
description='Use gltfpack to simplify the mesh and/or compress its textures',
|
||||
default=False,
|
||||
)
|
||||
|
||||
export_gltfpack_tc: BoolProperty(
|
||||
name='KTX2 Compression',
|
||||
description='Convert all textures to KTX2 with BasisU supercompression',
|
||||
default=True,
|
||||
)
|
||||
|
||||
export_gltfpack_tq: IntProperty(
|
||||
name='Texture Encoding Quality',
|
||||
description='Texture encoding quality',
|
||||
default=8,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
|
||||
export_gltfpack_si: FloatProperty(
|
||||
name='Mesh Simplification Ratio',
|
||||
description='Simplify meshes targeting triangle count ratio',
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
)
|
||||
|
||||
export_gltfpack_sa: BoolProperty(
|
||||
name='Aggressive Mesh Simplification',
|
||||
description='Aggressively simplify to the target ratio disregarding quality',
|
||||
default=False,
|
||||
)
|
||||
|
||||
export_gltfpack_slb: BoolProperty(
|
||||
name='Lock Mesh Border Vertices',
|
||||
description='Lock border vertices during simplification to avoid gaps on connected meshes',
|
||||
default=False,
|
||||
)
|
||||
|
||||
export_gltfpack_vp: IntProperty(
|
||||
name='Position Quantization',
|
||||
description='Use N-bit quantization for positions',
|
||||
default=14,
|
||||
min=1,
|
||||
max=16,
|
||||
)
|
||||
|
||||
export_gltfpack_vt: IntProperty(
|
||||
name='Texture Coordinate Quantization',
|
||||
description='Use N-bit quantization for texture coordinates',
|
||||
default=12,
|
||||
min=1,
|
||||
max=16,
|
||||
)
|
||||
|
||||
export_gltfpack_vn: IntProperty(
|
||||
name='Normal/Tangent Quantization',
|
||||
description='Use N-bit quantization for normals and tangents',
|
||||
default=8,
|
||||
min=1,
|
||||
max=16,
|
||||
)
|
||||
|
||||
export_gltfpack_vc: IntProperty(
|
||||
name='Vertex Color Quantization',
|
||||
description='Use N-bit quantization for colors',
|
||||
default=8,
|
||||
min=1,
|
||||
max=16,
|
||||
)
|
||||
|
||||
export_gltfpack_vpi: EnumProperty(
|
||||
name='Vertex Position Attributes',
|
||||
description='Type to use for vertex position attributes',
|
||||
items=(('Integer', 'Integer', 'Use integer attributes for positions'),
|
||||
('Normalized', 'Normalized', 'Use normalized attributes for positions'),
|
||||
('Floating-point', 'Floating-point', 'Use floating-point attributes for positions')),
|
||||
default='Integer',
|
||||
)
|
||||
|
||||
export_gltfpack_noq: BoolProperty(
|
||||
name='Disable Quantization',
|
||||
description='Disable quantization; produces much larger glTF files with no extensions',
|
||||
default=True,
|
||||
)
|
||||
|
||||
# TODO: some stuff in Textures
|
||||
|
||||
# TODO: Animations
|
||||
|
||||
# TODO: Scene
|
||||
|
||||
# TODO: some stuff in Miscellaneous
|
||||
|
||||
export_format: EnumProperty(
|
||||
name='Format',
|
||||
items=(('GLB', 'glTF Binary (.glb)',
|
||||
@ -965,6 +1062,25 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
|
||||
|
||||
export_settings['gltf_hierarchy_full_collections'] = self.export_hierarchy_full_collections
|
||||
|
||||
# gltfpack stuff
|
||||
export_settings['gltf_use_gltfpack'] = self.export_use_gltfpack
|
||||
if self.export_use_gltfpack:
|
||||
export_settings['gltf_gltfpack_tc'] = self.export_gltfpack_tc
|
||||
export_settings['gltf_gltfpack_tq'] = self.export_gltfpack_tq
|
||||
|
||||
export_settings['gltf_gltfpack_si'] = self.export_gltfpack_si
|
||||
export_settings['gltf_gltfpack_sa'] = self.export_gltfpack_sa
|
||||
export_settings['gltf_gltfpack_slb'] = self.export_gltfpack_slb
|
||||
|
||||
export_settings['gltf_gltfpack_vp'] = self.export_gltfpack_vp
|
||||
export_settings['gltf_gltfpack_vt'] = self.export_gltfpack_vt
|
||||
export_settings['gltf_gltfpack_vn'] = self.export_gltfpack_vn
|
||||
export_settings['gltf_gltfpack_vc'] = self.export_gltfpack_vc
|
||||
|
||||
export_settings['gltf_gltfpack_vpi'] = self.export_gltfpack_vpi
|
||||
|
||||
export_settings['gltf_gltfpack_noq'] = self.export_gltfpack_noq
|
||||
|
||||
export_settings['gltf_binary'] = bytearray()
|
||||
export_settings['gltf_binaryfilename'] = (
|
||||
path_to_uri(os.path.splitext(os.path.basename(self.filepath))[0] + '.bin')
|
||||
@ -1034,6 +1150,56 @@ class GLTF_PT_export_main(bpy.types.Panel):
|
||||
layout.prop(operator, 'will_save_settings')
|
||||
|
||||
|
||||
class GLTF_PT_export_gltfpack(bpy.types.Panel):
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
bl_region_type = 'TOOL_PROPS'
|
||||
bl_label = "gltfpack"
|
||||
bl_parent_id = "FILE_PT_operator"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
gltfpack_path = context.preferences.addons['io_scene_gltf2'].preferences.gltfpack_path_ui.strip()
|
||||
if (gltfpack_path == ''): # gltfpack not setup in plugin preferences -> dont show any gltfpack relevant options in export dialog
|
||||
return False;
|
||||
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
||||
return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False # No animation.
|
||||
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
||||
col = layout.column(heading = "gltfpack", align = True)
|
||||
col.prop(operator, 'export_use_gltfpack')
|
||||
|
||||
col = layout.column(heading = "Textures", align = True)
|
||||
col.prop(operator, 'export_gltfpack_tc')
|
||||
col.prop(operator, 'export_gltfpack_tq')
|
||||
col = layout.column(heading = "Simplification", align = True)
|
||||
col.prop(operator, 'export_gltfpack_si')
|
||||
col.prop(operator, 'export_gltfpack_sa')
|
||||
col.prop(operator, 'export_gltfpack_slb')
|
||||
col = layout.column(heading = "Vertices", align = True)
|
||||
col.prop(operator, 'export_gltfpack_vp')
|
||||
col.prop(operator, 'export_gltfpack_vt')
|
||||
col.prop(operator, 'export_gltfpack_vn')
|
||||
col.prop(operator, 'export_gltfpack_vc')
|
||||
col = layout.column(heading = "Vertex positions", align = True)
|
||||
col.prop(operator, 'export_gltfpack_vpi')
|
||||
#col = layout.column(heading = "Animations", align = True)
|
||||
#col = layout.column(heading = "Scene", align = True)
|
||||
col = layout.column(heading = "Miscellaneous", align = True)
|
||||
col.prop(operator, 'export_gltfpack_noq')
|
||||
|
||||
|
||||
class GLTF_PT_export_include(bpy.types.Panel):
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
bl_region_type = 'TOOL_PROPS'
|
||||
@ -1923,15 +2089,15 @@ class GLTF_AddonPreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
|
||||
settings_node_ui : bpy.props.BoolProperty(
|
||||
default= False,
|
||||
description="Displays glTF Material Output node in Shader Editor (Menu Add > Output)"
|
||||
)
|
||||
default= False,
|
||||
description="Displays glTF Material Output node in Shader Editor (Menu Add > Output)"
|
||||
)
|
||||
|
||||
KHR_materials_variants_ui : bpy.props.BoolProperty(
|
||||
default= False,
|
||||
description="Displays glTF UI to manage material variants",
|
||||
update=gltf_variant_ui_update
|
||||
)
|
||||
)
|
||||
|
||||
animation_ui: bpy.props.BoolProperty(
|
||||
default=False,
|
||||
@ -1939,12 +2105,21 @@ class GLTF_AddonPreferences(bpy.types.AddonPreferences):
|
||||
update=gltf_animation_ui_update
|
||||
)
|
||||
|
||||
gltfpack_path_ui: bpy.props.StringProperty(
|
||||
default="",
|
||||
name="glTFpack file path",
|
||||
description="Path to gltfpack binary",
|
||||
subtype='FILE_PATH'
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")
|
||||
row.prop(self, "KHR_materials_variants_ui", text="Material Variants")
|
||||
row.prop(self, "animation_ui", text="Animation UI")
|
||||
row = layout.row()
|
||||
row.prop(self, "gltfpack_path_ui", text="Path to gltfpack")
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
|
||||
@ -1973,6 +2148,7 @@ classes = (
|
||||
GLTF_PT_export_animation_shapekeys,
|
||||
GLTF_PT_export_animation_sampling,
|
||||
GLTF_PT_export_animation_optimize,
|
||||
GLTF_PT_export_gltfpack,
|
||||
GLTF_PT_export_user_extensions,
|
||||
ImportGLTF2,
|
||||
GLTF_PT_import_user_extensions,
|
||||
@ -1983,6 +2159,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
from .blender.com import gltf2_blender_ui as blender_ui
|
||||
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
# bpy.utils.register_module(__name__)
|
||||
|
@ -2,6 +2,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import bpy
|
||||
@ -109,6 +111,70 @@ def __create_buffer(exporter, export_settings):
|
||||
|
||||
return buffer
|
||||
|
||||
def __postprocess_with_gltfpack(export_settings):
|
||||
|
||||
gltfpack_binary_file_path = bpy.context.preferences.addons['io_scene_gltf2'].preferences.gltfpack_path_ui
|
||||
|
||||
gltf_file_path = export_settings['gltf_filepath']
|
||||
gltf_file_base = os.path.splitext(os.path.basename(gltf_file_path))[0]
|
||||
gltf_file_extension = os.path.splitext(os.path.basename(gltf_file_path))[1]
|
||||
gltf_file_directory = os.path.dirname(gltf_file_path)
|
||||
gltf_output_file_directory = os.path.join(gltf_file_directory, "gltfpacked")
|
||||
if (os.path.exists(gltf_output_file_directory) is False):
|
||||
os.makedirs(gltf_output_file_directory)
|
||||
|
||||
gltf_input_file_path = gltf_file_path
|
||||
gltf_output_file_path = os.path.join(gltf_output_file_directory, gltf_file_base + gltf_file_extension)
|
||||
|
||||
options = []
|
||||
|
||||
if (export_settings['gltf_gltfpack_tc']):
|
||||
options.append("-tc")
|
||||
|
||||
if (export_settings['gltf_gltfpack_tq']):
|
||||
options.append("-tq")
|
||||
options.append(f"{export_settings['gltf_gltfpack_tq']}")
|
||||
|
||||
if (export_settings['gltf_gltfpack_si'] != 1.0):
|
||||
options.append("-si")
|
||||
options.append(f"{export_settings['gltf_gltfpack_si']}")
|
||||
|
||||
if (export_settings['gltf_gltfpack_sa']):
|
||||
options.append("-sa")
|
||||
|
||||
if (export_settings['gltf_gltfpack_slb']):
|
||||
options.append("-slb")
|
||||
|
||||
if (export_settings['gltf_gltfpack_noq']):
|
||||
options.append("-noq")
|
||||
else:
|
||||
options.append("-vp")
|
||||
options.append(f"{export_settings['gltf_gltfpack_vp']}")
|
||||
options.append("-vt")
|
||||
options.append(f"{export_settings['gltf_gltfpack_vt']}")
|
||||
options.append("-vn")
|
||||
options.append(f"{export_settings['gltf_gltfpack_vn']}")
|
||||
options.append("-vc")
|
||||
options.append(f"{export_settings['gltf_gltfpack_vc']}")
|
||||
|
||||
match export_settings['gltf_gltfpack_vpi']:
|
||||
case "Integer":
|
||||
options.append("-vpi")
|
||||
case "Normalized":
|
||||
options.append("-vpn")
|
||||
case "Floating-point":
|
||||
options.append("-vpf")
|
||||
|
||||
parameters = []
|
||||
parameters.append("-i")
|
||||
parameters.append(gltf_input_file_path)
|
||||
parameters.append("-o")
|
||||
parameters.append(gltf_output_file_path)
|
||||
|
||||
try:
|
||||
subprocess.run([gltfpack_binary_file_path] + options + parameters, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print_console('ERROR', "Calling gltfpack was not successful")
|
||||
|
||||
def __write_file(json, buffer, export_settings):
|
||||
try:
|
||||
@ -117,6 +183,9 @@ def __write_file(json, buffer, export_settings):
|
||||
export_settings,
|
||||
gltf2_blender_json.BlenderJSONEncoder,
|
||||
buffer)
|
||||
if (export_settings['gltf_use_gltfpack'] == True):
|
||||
__postprocess_with_gltfpack(export_settings)
|
||||
|
||||
except AssertionError as e:
|
||||
_, _, tb = sys.exc_info()
|
||||
traceback.print_tb(tb) # Fixed format
|
||||
|
@ -446,13 +446,15 @@ class PrimitiveCreator:
|
||||
vc_color_name = material_info['vc_info']['color']
|
||||
elif material_info['vc_info']['color_type'] == "active":
|
||||
# Get active (render) Vertex Color
|
||||
vc_color_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
|
||||
if self.blender_mesh.color_attributes.render_color_index != -1:
|
||||
vc_color_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
|
||||
|
||||
if material_info['vc_info']['alpha_type'] == "name":
|
||||
vc_alpha_name = material_info['vc_info']['alpha']
|
||||
elif material_info['vc_info']['alpha_type'] == "active":
|
||||
# Get active (render) Vertex Color
|
||||
vc_alpha_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
|
||||
if self.blender_mesh.color_attributes.render_color_index != -1:
|
||||
vc_alpha_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
|
||||
|
||||
if vc_color_name is not None:
|
||||
|
||||
@ -472,7 +474,7 @@ class PrimitiveCreator:
|
||||
# We need to check if we need to add alpha
|
||||
add_alpha = vc_alpha_name is not None
|
||||
mat = get_material_from_idx(material_idx, self.materials, self.export_settings)
|
||||
add_alpha = add_alpha and not (mat.blend_method is None or mat.blend_method == 'OPAQUE')
|
||||
add_alpha = mat is not None and add_alpha and not (mat.blend_method is None or mat.blend_method == 'OPAQUE')
|
||||
# Manage Vertex Color (RGB and Alpha if needed)
|
||||
self.__manage_color_attribute(vc_color_name, vc_alpha_name if add_alpha else None)
|
||||
else:
|
||||
@ -950,6 +952,33 @@ class PrimitiveCreator:
|
||||
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
|
||||
additional_fields.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))
|
||||
|
||||
|
||||
if self.export_settings['gltf_loose_edges']:
|
||||
additional_fields_edges = []
|
||||
for i in range(max_index):
|
||||
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
|
||||
additional_fields_edges.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))
|
||||
|
||||
new_dt = np.dtype(self.dots_edges.dtype.descr + additional_fields_edges)
|
||||
dots_edges = np.zeros(self.dots_edges.shape, dtype=new_dt)
|
||||
for f in self.dots_edges.dtype.names:
|
||||
dots_edges[f] = self.dots_edges[f]
|
||||
|
||||
self.dots_edges = dots_edges
|
||||
|
||||
if self.export_settings['gltf_loose_points']:
|
||||
additional_fields_points = []
|
||||
for i in range(max_index):
|
||||
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
|
||||
additional_fields_points.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))
|
||||
|
||||
new_dt = np.dtype(self.dots_points.dtype.descr + additional_fields_points)
|
||||
dots_points = np.zeros(self.dots_points.shape, dtype=new_dt)
|
||||
for f in self.dots_points.dtype.names:
|
||||
dots_points[f] = self.dots_points[f]
|
||||
|
||||
self.dots_points = dots_points
|
||||
|
||||
# Keep the existing custom attribute
|
||||
# Data will be exported twice, one for COLOR_O, one for the custom attribute
|
||||
new_dt = np.dtype(self.dots.dtype.descr + additional_fields)
|
||||
@ -964,7 +993,7 @@ class PrimitiveCreator:
|
||||
self.dots['COLOR_0' +str(i)] = data_dots[:, i]
|
||||
if self.export_settings['gltf_loose_edges'] and attr.domain == "POINT":
|
||||
self.dots_edges['COLOR_0' + str(i)] = data_dots_edges[:, i]
|
||||
if self.export_settings['gltf_loose_points'] and attr['blender_domain'] == "POINT":
|
||||
if self.export_settings['gltf_loose_points'] and attr.domain == "POINT":
|
||||
self.dots_points['COLOR_0' + str(i)] = data_dots_points[:, i]
|
||||
|
||||
# Add COLOR_0 in attribute list
|
||||
|
@ -545,6 +545,12 @@ def get_base_material(material_idx, materials, export_settings):
|
||||
mat,
|
||||
export_settings
|
||||
)
|
||||
|
||||
if material is None:
|
||||
# If no material, the mesh can still have vertex color
|
||||
# So, retrieving it
|
||||
material_info["vc_info"] = {"color_type": "active", "alpha_type": "active"}
|
||||
|
||||
return material, material_info
|
||||
|
||||
def get_all_textures(idx=0):
|
||||
|
@ -6,8 +6,8 @@
|
||||
bl_info = {
|
||||
"name": "Bsurfaces GPL Edition",
|
||||
"author": "Eclectiel, Vladimir Spivak (cwolf3d)",
|
||||
"version": (1, 8, 1),
|
||||
"blender": (2, 80, 0),
|
||||
"version": (1, 8, 2),
|
||||
"blender": (4, 0, 0),
|
||||
"location": "View3D EditMode > Sidebar > Edit Tab",
|
||||
"description": "Modeling and retopology tool",
|
||||
"doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/bsurfaces.html",
|
||||
@ -3539,11 +3539,10 @@ class MESH_OT_SURFSK_init(Operator):
|
||||
global_mesh_object = mesh_object.name
|
||||
bpy.context.scene.bsurfaces.SURFSK_mesh = bpy.data.objects[global_mesh_object]
|
||||
|
||||
bpy.context.scene.tool_settings.snap_elements = {'FACE'}
|
||||
bpy.context.scene.tool_settings.snap_elements = {'FACE_PROJECT'}
|
||||
bpy.context.scene.tool_settings.use_snap = True
|
||||
bpy.context.scene.tool_settings.use_snap_self = False
|
||||
bpy.context.scene.tool_settings.use_snap_align_rotation = True
|
||||
bpy.context.scene.tool_settings.use_snap_project = True
|
||||
bpy.context.scene.tool_settings.use_snap_rotate = True
|
||||
bpy.context.scene.tool_settings.use_snap_scale = True
|
||||
|
||||
|
@ -16,7 +16,7 @@ bl_info = {
|
||||
'author': 'Robin Hohnsbeen',
|
||||
'description': 'Bake vector displacement brushes easily from a plane',
|
||||
'blender': (3, 5, 0),
|
||||
'version': (1, 0, 2),
|
||||
'version': (1, 0, 3),
|
||||
'location': 'Sculpt Mode: View3D > Sidebar > Tool Tab',
|
||||
'warning': '',
|
||||
'category': 'Baking',
|
||||
@ -88,7 +88,6 @@ class PT_VDMBaker(bpy.types.Panel):
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
layout.operator(create_sculpt_plane.bl_idname, icon='ADD')
|
||||
|
||||
layout.separator()
|
||||
@ -187,7 +186,6 @@ class create_vdm_brush(bpy.types.Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
vdm_plane = context.active_object
|
||||
|
||||
addon_data = get_addon_data()
|
||||
new_brush_name = addon_data.draft_brush_name
|
||||
reference_brush_name = addon_data.draft_brush_name
|
||||
@ -214,6 +212,7 @@ class create_vdm_brush(bpy.types.Operator):
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
vdm_bake_material = bakematerial.get_vdm_bake_material()
|
||||
vdm_texture_image = None
|
||||
try:
|
||||
# Prepare baking
|
||||
scene.render.engine = 'CYCLES'
|
||||
|
@ -12,56 +12,62 @@ def get_vdm_bake_material():
|
||||
material: Baking material
|
||||
"""
|
||||
material_name = 'VDM_baking_material'
|
||||
if material_name not in bpy.data.materials:
|
||||
new_material = bpy.data.materials.new(name=material_name)
|
||||
if material_name in bpy.data.materials:
|
||||
# Recreate material every time to ensure it is unchanged by the user which could lead to issues.
|
||||
# I would like to keep it directly after bake though so people could look
|
||||
# at how it is made, if they are interested.
|
||||
bpy.data.materials.remove(bpy.data.materials[material_name])
|
||||
|
||||
new_material.use_nodes = True
|
||||
nodes = new_material.node_tree.nodes
|
||||
nodes.remove(nodes['Principled BSDF'])
|
||||
material_output = nodes['Material Output']
|
||||
new_material = bpy.data.materials.new(name=material_name)
|
||||
|
||||
# Create relevant nodes
|
||||
combine_node = nodes.new('ShaderNodeCombineXYZ')
|
||||
new_material.use_nodes = True
|
||||
nodes = new_material.node_tree.nodes
|
||||
principled_node = next(n for n in new_material.node_tree.nodes if n.type == "BSDF_PRINCIPLED")
|
||||
nodes.remove(principled_node)
|
||||
material_output = next(n for n in new_material.node_tree.nodes if n.type == "OUTPUT_MATERIAL")
|
||||
|
||||
separate_node1 = nodes.new('ShaderNodeSeparateXYZ')
|
||||
separate_node2 = nodes.new('ShaderNodeSeparateXYZ')
|
||||
# Create relevant nodes
|
||||
combine_node = nodes.new('ShaderNodeCombineXYZ')
|
||||
|
||||
vector_subtract_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_subtract_node.operation = 'SUBTRACT'
|
||||
separate_node1 = nodes.new('ShaderNodeSeparateXYZ')
|
||||
separate_node2 = nodes.new('ShaderNodeSeparateXYZ')
|
||||
|
||||
vector_multiply_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_multiply_node.operation = 'MULTIPLY'
|
||||
vector_multiply_node.inputs[1].default_value = [2.0, 2.0, 2.0]
|
||||
vector_subtract_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_subtract_node.operation = 'SUBTRACT'
|
||||
|
||||
vector_add_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_add_node.operation = 'ADD'
|
||||
vector_add_node.inputs[1].default_value = [-0.5, -0.5, -0.5]
|
||||
vector_multiply_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_multiply_node.operation = 'MULTIPLY'
|
||||
vector_multiply_node.inputs[1].default_value = [2.0, 2.0, 2.0]
|
||||
|
||||
tex_coord_node = nodes.new('ShaderNodeTexCoord')
|
||||
vector_add_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_add_node.operation = 'ADD'
|
||||
vector_add_node.inputs[1].default_value = [-0.5, -0.5, -0.5]
|
||||
|
||||
image_node = nodes.new('ShaderNodeTexImage')
|
||||
image_node.name = "VDMTexture"
|
||||
tex_coord_node = nodes.new('ShaderNodeTexCoord')
|
||||
|
||||
# Connect nodes
|
||||
tree = new_material.node_tree
|
||||
tree.links.new(combine_node.outputs[0], material_output.inputs[0])
|
||||
image_node = nodes.new('ShaderNodeTexImage')
|
||||
image_node.name = "VDMTexture"
|
||||
|
||||
tree.links.new(separate_node1.outputs[0], combine_node.inputs[0])
|
||||
tree.links.new(separate_node1.outputs[1], combine_node.inputs[1])
|
||||
# Connect nodes
|
||||
tree = new_material.node_tree
|
||||
tree.links.new(combine_node.outputs[0], material_output.inputs[0])
|
||||
|
||||
tree.links.new(
|
||||
vector_subtract_node.outputs[0], separate_node1.inputs[0])
|
||||
tree.links.new(separate_node1.outputs[0], combine_node.inputs[0])
|
||||
tree.links.new(separate_node1.outputs[1], combine_node.inputs[1])
|
||||
|
||||
tree.links.new(
|
||||
vector_multiply_node.outputs[0], vector_subtract_node.inputs[1])
|
||||
tree.links.new(
|
||||
vector_subtract_node.outputs[0], separate_node1.inputs[0])
|
||||
|
||||
tree.links.new(
|
||||
vector_add_node.outputs[0], vector_multiply_node.inputs[0])
|
||||
tree.links.new(
|
||||
vector_multiply_node.outputs[0], vector_subtract_node.inputs[1])
|
||||
|
||||
tree.links.new(tex_coord_node.outputs[2], vector_add_node.inputs[0])
|
||||
tree.links.new(
|
||||
tex_coord_node.outputs[3], vector_subtract_node.inputs[0])
|
||||
tree.links.new(tex_coord_node.outputs[3], separate_node2.inputs[0])
|
||||
tree.links.new(separate_node2.outputs[2], combine_node.inputs[2])
|
||||
tree.links.new(
|
||||
vector_add_node.outputs[0], vector_multiply_node.inputs[0])
|
||||
|
||||
tree.links.new(tex_coord_node.outputs[2], vector_add_node.inputs[0])
|
||||
tree.links.new(
|
||||
tex_coord_node.outputs[3], vector_subtract_node.inputs[0])
|
||||
tree.links.new(tex_coord_node.outputs[3], separate_node2.inputs[0])
|
||||
tree.links.new(separate_node2.outputs[2], combine_node.inputs[2])
|
||||
|
||||
return bpy.data.materials[material_name]
|
||||
|
Loading…
Reference in New Issue
Block a user