From fd2106afa589c0f2016d2eaab5737ce08a386261 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Date: Sat, 20 Jul 2024 21:07:08 +0200 Subject: [PATCH 1/8] Fix per vertex normals and colors import --- source/import_x3d.py | 49 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index 4c21551..7fcf385 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -1957,7 +1957,11 @@ def importMesh_IndexedFaceSet(geom, ancestry): co = [co for f in processPerVertexIndex(normal_index) for v in f for co in vectors[v]] - 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: co = [co for (i, f) in enumerate(faces) for j in f @@ -1981,6 +1985,11 @@ def importMesh_IndexedFaceSet(geom, ancestry): cco = [cco for f in processPerVertexIndex(color_index) for v in f for cco in rgb[v]] + + # 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) elif color_index: # Color per face with index cco = [cco for (i, f) in enumerate(faces) for j in f @@ -2039,7 +2048,38 @@ def importMesh_IndexedFaceSet(geom, ancestry): importMesh_ApplyTextureToLoops(bpymesh, loops) - bpymesh.validate() + # Validating the mesh + if (normals and per_vertex) or (colors and color_per_vertex): + bpymesh.validate(clean_customdata=False) + + # Apply normals per vertex + if normals and per_vertex: + # Get normals back to a list + 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) + + # Use that list to set normals + bpymesh.normals_split_custom_set(tuple(zip(*(iter(co2),) * 3))) + + # And delete the temporary attribute + bpymesh.attributes.remove(bpymesh.attributes["temp_custom_normals"]) + # bpymesh.shade_smooth() + + # Apply colors per vertex + if colors and color_per_vertex: + # Get normals back to a list + 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) + + # Use that list to set per vertex colors + # bpymesh.color_attributes.new("Col", 'FLOAT_COLOR', 'CORNER'); + bpymesh.color_attributes["Col"].data.foreach_set("color", cco2) + + # And delete the temporary attribute + bpymesh.attributes.remove(bpymesh.attributes["temp_custom_colors"]) + else: + bpymesh.validate() + bpymesh.update() return bpymesh @@ -2710,10 +2750,9 @@ def appearance_CreateMaterial(vrmlname, mat, ancestry, is_vcol): bpymat.blend_method = "BLEND" bpymat.shadow_method = "HASHED" - # NOTE - leaving this disabled for now - if False and is_vcol: + if is_vcol: node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor") - node_vertex_color.location = (-200, 300) + node_vertex_color.layer_name = "Col" bpymat.node_tree.links.new( bpymat_wrap.node_principled_bsdf.inputs["Base Color"], -- 2.30.2 From 37e6f48a33fb959782d31dbfa9b2c6ae59889990 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Tue, 23 Jul 2024 00:49:05 +0200 Subject: [PATCH 2/8] Better node placement + code cleanup and simplification --- source/import_x3d.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index 7fcf385..a1b51d6 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -2048,34 +2048,20 @@ def importMesh_IndexedFaceSet(geom, ancestry): importMesh_ApplyTextureToLoops(bpymesh, loops) - # Validating the mesh - if (normals and per_vertex) or (colors and color_per_vertex): - bpymesh.validate(clean_customdata=False) + bpymesh.validate(clean_customdata=False) # Apply normals per vertex if normals and per_vertex: - # Get normals back to a list 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) - - # Use that list to set normals bpymesh.normals_split_custom_set(tuple(zip(*(iter(co2),) * 3))) - - # And delete the temporary attribute bpymesh.attributes.remove(bpymesh.attributes["temp_custom_normals"]) - # bpymesh.shade_smooth() # Apply colors per vertex if colors and color_per_vertex: - # Get normals back to a list 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) - - # Use that list to set per vertex colors - # bpymesh.color_attributes.new("Col", 'FLOAT_COLOR', 'CORNER'); bpymesh.color_attributes["Col"].data.foreach_set("color", cco2) - - # And delete the temporary attribute bpymesh.attributes.remove(bpymesh.attributes["temp_custom_colors"]) else: bpymesh.validate() @@ -2752,6 +2738,7 @@ def appearance_CreateMaterial(vrmlname, mat, ancestry, is_vcol): if is_vcol: node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor") + node_vertex_color.location = (-200, 300) node_vertex_color.layer_name = "Col" bpymat.node_tree.links.new( -- 2.30.2 From 790902323cf0835fe15d0e89e674be98cf056a4b Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Sat, 3 Aug 2024 23:04:00 +0200 Subject: [PATCH 3/8] Add support for per vertex normals w/o normalIndex Also suppresse a useless 'validate' (forgotten from the previous commit) and better named color attribute --- source/import_x3d.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index a1b51d6..d787553 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -1954,6 +1954,8 @@ def importMesh_IndexedFaceSet(geom, ancestry): vectors = normals.getFieldAsArray('vector', 3, ancestry) normal_index = geom.getFieldAsArray('normalIndex', 0, ancestry) if per_vertex: + if len(normal_index) == 0: + normal_index = index co = [co for f in processPerVertexIndex(normal_index) for v in f for co in vectors[v]] @@ -1980,7 +1982,6 @@ def importMesh_IndexedFaceSet(geom, ancestry): color_per_vertex = geom.getFieldAsBool('colorPerVertex', True, ancestry) color_index = geom.getFieldAsArray('colorIndex', 0, ancestry) - d = bpymesh.vertex_colors.new().data if color_per_vertex: cco = [cco for f in processPerVertexIndex(color_index) for v in f @@ -1994,11 +1995,12 @@ def importMesh_IndexedFaceSet(geom, ancestry): cco = [cco for (i, f) in enumerate(faces) for j in f for cco in rgb[color_index[i]]] + bpymesh.vertex_colors.new().data.foreach_set('color', cco) # deprecated else: # Color per face without index cco = [cco for (i, f) in enumerate(faces) for j in f for cco in rgb[i]] - d.foreach_set('color', cco) + bpymesh.vertex_colors.new().data.foreach_set('color', cco) # deprecated # Texture coordinates (UVs) tex_coord = geom.getChildBySpec('TextureCoordinate') @@ -2063,8 +2065,6 @@ def importMesh_IndexedFaceSet(geom, ancestry): bpymesh.attributes["temp_custom_colors"].data.foreach_get("color", cco2) bpymesh.color_attributes["Col"].data.foreach_set("color", cco2) bpymesh.attributes.remove(bpymesh.attributes["temp_custom_colors"]) - else: - bpymesh.validate() bpymesh.update() return bpymesh @@ -2739,7 +2739,7 @@ def appearance_CreateMaterial(vrmlname, mat, ancestry, is_vcol): if is_vcol: node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor") node_vertex_color.location = (-200, 300) - node_vertex_color.layer_name = "Col" + node_vertex_color.layer_name = "ColorPerCoin" bpymat.node_tree.links.new( bpymat_wrap.node_principled_bsdf.inputs["Base Color"], @@ -3038,7 +3038,7 @@ def importShape_ProcessObject( # solid, as understood by the spec, is always true in Blender # solid=false, we don't support it yet. 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) else: bpydata.polygons.foreach_set("use_smooth", [False] * len(bpydata.polygons)) -- 2.30.2 From 30c46be9fffadce63ab3987c74b5d74f2199f606 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Date: Sun, 4 Aug 2024 11:38:22 +0200 Subject: [PATCH 4/8] Fix the color attribute's name to avoid script crash Also reintroduce `bpymesh.vertices.foreach_set` for Per Vertx normals as requested --- source/import_x3d.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index d787553..1afa459 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -1959,6 +1959,7 @@ def importMesh_IndexedFaceSet(geom, ancestry): co = [co for f in processPerVertexIndex(normal_index) for v in f for co in vectors[v]] + 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 @@ -2063,7 +2064,8 @@ def importMesh_IndexedFaceSet(geom, ancestry): 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) - bpymesh.color_attributes["Col"].data.foreach_set("color", cco2) + bpymesh.color_attributes.new('ColorPerCoin', 'FLOAT_COLOR', 'CORNER') + bpymesh.color_attributes["ColorPerCoin"].data.foreach_set("color", cco2) bpymesh.attributes.remove(bpymesh.attributes["temp_custom_colors"]) bpymesh.update() -- 2.30.2 From 0b42c3795971fa858a0dd97288b007f6518474e2 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Sun, 4 Aug 2024 17:14:21 +0200 Subject: [PATCH 5/8] Add vector normalization step to handle vectors with too few decimals --- source/import_x3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index a4ad52f..8694f97 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -1958,7 +1958,7 @@ def importMesh_IndexedFaceSet(geom, ancestry): normal_index = index co = [co for f in processPerVertexIndex(normal_index) 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) # Mesh must be validated before assigning normals, but validation might @@ -1968,7 +1968,7 @@ def importMesh_IndexedFaceSet(geom, ancestry): else: co = [co for (i, f) in enumerate(faces) 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) # Apply vertex/face colors -- 2.30.2 From d17730d608ab16e751cf25e48200ae98933903a5 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Sun, 4 Aug 2024 19:12:59 +0200 Subject: [PATCH 6/8] Add forgotten `mathutils` import --- source/import_x3d.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/import_x3d.py b/source/import_x3d.py index 8694f97..63bcfde 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -8,6 +8,7 @@ DEBUG = False import os import shlex import math +import mathutils from math import sin, cos, pi from itertools import chain -- 2.30.2 From e8940cbc8ce032c38b57aa67559f9d8b6f5335a5 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Mon, 5 Aug 2024 13:58:56 +0200 Subject: [PATCH 7/8] Use plain english for Color attribute's name --- source/import_x3d.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index 63bcfde..41f8bde 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -2071,8 +2071,8 @@ def importMesh_IndexedFaceSet(geom, ancestry): 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) - bpymesh.color_attributes.new('ColorPerCoin', 'FLOAT_COLOR', 'CORNER') - bpymesh.color_attributes["ColorPerCoin"].data.foreach_set("color", 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() @@ -2748,7 +2748,7 @@ def appearance_CreateMaterial(vrmlname, mat, ancestry, is_vcol): if is_vcol: node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor") node_vertex_color.location = (-200, 300) - node_vertex_color.layer_name = "ColorPerCoin" + node_vertex_color.layer_name = "ColorPerCorner" bpymat.node_tree.links.new( bpymat_wrap.node_principled_bsdf.inputs["Base Color"], -- 2.30.2 From c957f6bc576e04fa0f7def353d0b198d917671a8 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Wed, 7 Aug 2024 11:33:26 +0200 Subject: [PATCH 8/8] Handle color space for vertex colors using the `color_attributes` API Thanks to @Bujus_Krachus for the patch. --- source/import_x3d.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/source/import_x3d.py b/source/import_x3d.py index 3f0ffb9..c92d55b 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -1996,8 +1996,6 @@ def importMesh_IndexedFaceSet(geom, ancestry): has_color_index = len(color_index) != 0 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) if has_color_index and not has_valid_color_index: # remove all -1 beforehand to ensure clean working copy @@ -2028,14 +2026,15 @@ def importMesh_IndexedFaceSet(geom, ancestry): cco = [cco for (i, f) in enumerate(faces) for j in f 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) tex_coord = geom.getChildBySpec('TextureCoordinate') @@ -2094,10 +2093,24 @@ def importMesh_IndexedFaceSet(geom, ancestry): 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"]) -- 2.30.2