Fix per vertex normals and colors import #6

Merged
Cedric Steiert merged 10 commits from Hombre57/io_scene_x3d:per-vertex_normals_and_colors into main 2024-08-07 12:04:13 +02:00

View File

@ -9,6 +9,7 @@ import os
import shlex import shlex
import math import math
import re import re
import mathutils
from math import sin, cos, pi from math import sin, cos, pi
from itertools import chain from itertools import chain
@ -1964,14 +1965,21 @@ def importMesh_IndexedFaceSet(geom, ancestry):
vectors = normals.getFieldAsArray('vector', 3, ancestry) vectors = normals.getFieldAsArray('vector', 3, ancestry)
normal_index = geom.getFieldAsArray('normalIndex', 0, ancestry) normal_index = geom.getFieldAsArray('normalIndex', 0, ancestry)
if per_vertex: if per_vertex:
if len(normal_index) == 0:
normal_index = index
co = [co for f in processPerVertexIndex(normal_index) co = [co for f in processPerVertexIndex(normal_index)
for v in f for v in f
for co in vectors[v]] for co in mathutils.Vector(vectors[v]).normalized().to_tuple()]
bpymesh.vertices.foreach_set("normal", co) bpymesh.vertices.foreach_set("normal", co)
# Mesh must be validated before assigning normals, but validation might
# reorder corners. We must store normals in a temporary attribute
bpymesh.attributes.new("temp_custom_normals", 'FLOAT_VECTOR', 'CORNER')
bpymesh.attributes["temp_custom_normals"].data.foreach_set("vector", co)
else: else:
co = [co for (i, f) in enumerate(faces) co = [co for (i, f) in enumerate(faces)
for j in f for j in f
for co in vectors[normal_index[i] if normal_index else i]] for co in mathutils.Vector(vectors[normal_index[i] if normal_index else i]).normalized().to_tuple()]
bpymesh.polygons.foreach_set("normal", co) bpymesh.polygons.foreach_set("normal", co)
# Apply vertex/face colors # Apply vertex/face colors
@ -1988,8 +1996,6 @@ def importMesh_IndexedFaceSet(geom, ancestry):
has_color_index = len(color_index) != 0 has_color_index = len(color_index) != 0
has_valid_color_index = index.count(-1) == color_index.count(-1) has_valid_color_index = index.count(-1) == color_index.count(-1)
d = bpymesh.vertex_colors.new().data
# rebuild a corrupted colorIndex field (assuming the end of face markers -1 are missing) # rebuild a corrupted colorIndex field (assuming the end of face markers -1 are missing)
if has_color_index and not has_valid_color_index: if has_color_index and not has_valid_color_index:
# remove all -1 beforehand to ensure clean working copy # remove all -1 beforehand to ensure clean working copy
@ -2020,7 +2026,15 @@ def importMesh_IndexedFaceSet(geom, ancestry):
cco = [cco for (i, f) in enumerate(faces) cco = [cco for (i, f) in enumerate(faces)
for j in f for j in f
for cco in rgb[i]] for cco in rgb[i]]
d.foreach_set('color', cco)
if color_per_vertex:
# Mesh must be validated before assigning colors, but validation might
# reorder corners. We must store colors in a temporary attribute
bpymesh.attributes.new("temp_custom_colors", 'FLOAT_COLOR', 'CORNER')
bpymesh.attributes["temp_custom_colors"].data.foreach_set("color", cco)
else:
d = bpymesh.vertex_colors.new().data
d.foreach_set('color', cco)
# Texture coordinates (UVs) # Texture coordinates (UVs)
tex_coord = geom.getChildBySpec('TextureCoordinate') tex_coord = geom.getChildBySpec('TextureCoordinate')
@ -2070,7 +2084,37 @@ def importMesh_IndexedFaceSet(geom, ancestry):
importMesh_ApplyTextureToLoops(bpymesh, loops) importMesh_ApplyTextureToLoops(bpymesh, loops)
bpymesh.validate() bpymesh.validate(clean_customdata=False)
# Apply normals per vertex
if normals and per_vertex:
co2 = [0.0 for x in range(int(len(bpymesh.attributes["temp_custom_normals"].data)*3))]
bpymesh.attributes["temp_custom_normals"].data.foreach_get("vector", co2)
bpymesh.normals_split_custom_set(tuple(zip(*(iter(co2),) * 3)))
bpymesh.attributes.remove(bpymesh.attributes["temp_custom_normals"])
def linear_to_srgb(linear):
if linear <= 0.0031308:
return linear * 12.92
else:
return 1.055 * (linear ** (1.0 / 2.4)) - 0.055
def srgb_to_linear(srgb_value):
if srgb_value <= 0.04045:
return srgb_value / 12.92
else:
return ((srgb_value + 0.055) / 1.055) ** 2.4
# Apply colors per vertex
if colors and color_per_vertex:
cco2 = [0.0 for x in range(int(len(bpymesh.attributes["temp_custom_colors"].data)*4))]
bpymesh.attributes["temp_custom_colors"].data.foreach_get("color", cco2)
# convert color spaces to account for api changes
cco2 = [srgb_to_linear(col_val) for col_val in cco2]
bpymesh.color_attributes.new('ColorPerCorner', 'FLOAT_COLOR', 'CORNER')
bpymesh.color_attributes["ColorPerCorner"].data.foreach_set("color", cco2)
bpymesh.attributes.remove(bpymesh.attributes["temp_custom_colors"])
bpymesh.update() bpymesh.update()
return bpymesh return bpymesh
@ -2741,10 +2785,10 @@ def appearance_CreateMaterial(vrmlname, mat, ancestry, is_vcol):
bpymat.blend_method = "BLEND" bpymat.blend_method = "BLEND"
bpymat.shadow_method = "HASHED" bpymat.shadow_method = "HASHED"
# NOTE - leaving this disabled for now if is_vcol:
if False and is_vcol:
node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor") node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor")
node_vertex_color.location = (-200, 300) node_vertex_color.location = (-200, 300)
node_vertex_color.layer_name = "ColorPerCorner"
bpymat.node_tree.links.new( bpymat.node_tree.links.new(
bpymat_wrap.node_principled_bsdf.inputs["Base Color"], bpymat_wrap.node_principled_bsdf.inputs["Base Color"],
@ -3060,7 +3104,7 @@ def importShape_ProcessObject(
# solid, as understood by the spec, is always true in Blender # solid, as understood by the spec, is always true in Blender
# solid=false, we don't support it yet. # solid=false, we don't support it yet.
creaseAngle = geom.getFieldAsFloat('creaseAngle', None, ancestry) creaseAngle = geom.getFieldAsFloat('creaseAngle', None, ancestry)
if creaseAngle is not None: if creaseAngle is not None and not bpydata.has_custom_normals:
bpydata.set_sharp_from_angle(angle=creaseAngle) bpydata.set_sharp_from_angle(angle=creaseAngle)
else: else:
bpydata.polygons.foreach_set("use_smooth", [False] * len(bpydata.polygons)) bpydata.polygons.foreach_set("use_smooth", [False] * len(bpydata.polygons))