X3D/VRML import: Fix blender/blender-addons#98442, check if USE-based cache is set in IndexedFaceSet #7

Merged
Cedric Steiert merged 2 commits from Bujus_Krachus/io_scene_x3d:add-check-for-USE-based-cache into main 2024-08-06 17:31:43 +02:00
2 changed files with 75 additions and 23 deletions
Showing only changes of commit 2d9866a2d8 - Show all commits

View File

@ -1,7 +1,7 @@
schema_version = "1.0.0"
id = "web3d_x3d_vrml2_format"
name = "Web3D X3D/VRML2 format"
version = "2.3.3"
version = "2.3.4"
tagline = "Import-Export X3D, Import VRML2"
maintainer = "Community"
type = "add-on"

View File

@ -8,6 +8,7 @@ DEBUG = False
import os
import shlex
import math
import re
from math import sin, cos, pi
from itertools import chain
@ -714,7 +715,7 @@ class vrmlNode(object):
for v in f:
if v != ',':
try:
ret.append(float(v))
ret.append(float(v.strip('"')))
except:
break # quit of first non float, perhaps its a new field name on the same line? - if so we are going to ignore it :/ TODO
# print(ret)
@ -1218,6 +1219,11 @@ class vrmlNode(object):
else:
value += '\n' + l
# append a final quote if it is not there, like it's e.g. the case with multiline javascripts (#101717)
quote_count = l.count('"')
if quote_count % 2: # odd number?
value += '"'
# use shlex so we get '"a b" "b v"' --> '"a b"', '"b v"'
value_all = shlex.split(value, posix=False)
@ -1878,6 +1884,10 @@ def importMesh_IndexedFaceSet(geom, ancestry):
ccw = geom.getFieldAsBool('ccw', True, ancestry)
coord = geom.getChildBySpec('Coordinate')
if coord is None:
return None
if coord.reference and coord.getRealNode().parsed:
points = coord.getRealNode().parsed
# We need unflattened coord array here, while
@ -1975,16 +1985,37 @@ def importMesh_IndexedFaceSet(geom, ancestry):
color_per_vertex = geom.getFieldAsBool('colorPerVertex', True, ancestry)
color_index = geom.getFieldAsArray('colorIndex', 0, 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
if color_per_vertex:
# 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
color_index = [x for x in color_index if x != -1]
# copy all -1 from coordIndex to colorIndex
for i, v in enumerate(index):
if v == -1:
color_index.insert(i, -1)
if color_per_vertex and has_color_index: # Color per vertex with index
cco = [cco for f in processPerVertexIndex(color_index)
for v in f
for cco in rgb[v]]
for v in f
for cco in rgb[v]]
elif color_per_vertex: # Color per vertex without index
cco = [cco for f in faces
for (i, v) in enumerate(f)
for cco in rgb[i]]
elif color_index: # Color per face with index
cco = [cco for (i, f) in enumerate(faces)
for j in f
for cco in rgb[color_index[i]]]
elif len(faces) > len(rgb): # Static color per face without index, when all faces have the same color.
# Exported from SOLIDWORKS, see: `blender/blender-addons#105398`.
cco = [cco for (i, f) in enumerate(faces)
for j in f
for cco in rgb[0]]
else: # Color per face without index
cco = [cco for (i, f) in enumerate(faces)
for j in f
@ -2968,32 +2999,49 @@ def importShape_LoadAppearance(vrmlname, appr, ancestry, node, is_vcol):
def appearance_LoadPixelTexture(pixelTexture, ancestry):
def extract_pixel_colors(data_string):
"""
Read all hexadecimal pixel color values, distributed across multiple fields (mutliline)
"""
# Use a regular expression to find all hexadecimal color values
hex_pattern = re.compile(r'0x[0-9a-fA-F]{6}')
pixel_colors = hex_pattern.findall(data_string)
# Convert hexadecimal color values to integers
pixel_colors = [int(color, 0) for color in pixel_colors]
return pixel_colors
image = pixelTexture.getFieldAsArray('image', 0, ancestry)
# read width, height and plane_count value, assuming all are in one field called 'image' (singleline)
(w, h, plane_count) = image[0:3]
has_alpha = plane_count in {2, 4}
pixels = image[3:]
# get either hex color values (multiline) or regular color values (singleline)
pixels = extract_pixel_colors(str(pixelTexture)) # converting to string may not be ideal, but works
if len(pixels) == 0:
pixels = image[3:]
if len(pixels) != w * h:
print("ImportX3D warning: pixel count in PixelTexture is off")
print(f"ImportX3D warning: pixel count in PixelTexture is off. Pixels: {len(pixels)}, Width: {w}, Height: {h}")
bpyima = bpy.data.images.new("PixelTexture", w, h, has_alpha, True)
bpyima = bpy.data.images.new("PixelTexture", w, h, alpha=has_alpha, float_buffer=True)
if not has_alpha:
bpyima.alpha_mode = 'NONE'
# Conditional above the loop, for performance
if plane_count == 3: # RGB
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco in (pixel >> 16, pixel >> 8, pixel, 255)]
elif plane_count == 4: # RGBA
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco
in (pixel >> 24, pixel >> 16, pixel >> 8, pixel)]
elif plane_count == 1: # Intensity - does Blender even support that?
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco in (pixel, pixel, pixel, 255)]
elif plane_count == 2: # Intensity/alpha
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco
in (pixel >> 8, pixel >> 8, pixel >> 8, pixel)]
# as some image textures may have no pixel data, ignore those
if len(pixels) != 0:
# Conditional above the loop, for performance
if plane_count == 3: # RGB
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco in (pixel >> 16, pixel >> 8, pixel, 255)]
elif plane_count == 4: # RGBA
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco
in (pixel >> 24, pixel >> 16, pixel >> 8, pixel)]
elif plane_count == 1: # Intensity - does Blender even support that?
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco in (pixel, pixel, pixel, 255)]
elif plane_count == 2: # Intensity/alpha
bpyima.pixels = [(cco & 0xff) / 255 for pixel in pixels
for cco
in (pixel >> 8, pixel >> 8, pixel >> 8, pixel)]
bpyima.update()
return bpyima
@ -3142,6 +3190,10 @@ def importShape(bpycollection, node, ancestry, global_matrix):
if geom_fn is not None:
bpydata = geom_fn(geom, ancestry)
if bpydata is None:
print('ImportX3D warning: empty shape, skipping node "%s"' % vrmlname)
return
# There are no geometry importers that can legally return
# no object. It's either a bpy object, or an exception
importShape_ProcessObject(