FBX IO: Export normals with IndexToDirect reference mode #105020

Merged
Thomas Barlow merged 3 commits from Mysteryem/blender-addons:fbx_normals_IndexToDirect into main 2023-12-02 02:08:21 +01:00
11 changed files with 395 additions and 75 deletions
Showing only changes of commit bc5132040f - Show all commits

View File

@ -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

View File

@ -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",

View File

@ -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.
}

View File

@ -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,10 +964,12 @@ 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.
if values.size:
deform_values = shape_key_deforms.setdefault(item, [])
deform_values.append(values.min())
deform_values.append(values.max())
@ -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 _

View File

@ -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'
@ -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__)

View File

@ -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

View File

@ -446,12 +446,14 @@ class PrimitiveCreator:
vc_color_name = material_info['vc_info']['color']
elif material_info['vc_info']['color_type'] == "active":
# Get active (render) Vertex Color
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
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

View File

@ -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):

View File

@ -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

View File

@ -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'

View File

@ -12,13 +12,19 @@ def get_vdm_bake_material():
material: Baking material
"""
material_name = 'VDM_baking_material'
if material_name not in bpy.data.materials:
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 = bpy.data.materials.new(name=material_name)
new_material.use_nodes = True
nodes = new_material.node_tree.nodes
nodes.remove(nodes['Principled BSDF'])
material_output = nodes['Material Output']
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")
# Create relevant nodes
combine_node = nodes.new('ShaderNodeCombineXYZ')