Fix per vertex normals and colors import #6
31
README.md
31
README.md
@ -1,12 +1,37 @@
|
||||
# Web3D X3D/VRML2 format Add-on
|
||||
|
||||
## Features
|
||||
see [Web3D X3D/VRML2 Documentation](https://docs.blender.org/manual/en/4.1//addons/import_export/scene_x3d.html)
|
||||
|
||||
### Import file formats
|
||||
- .x3d
|
||||
- .wrl
|
||||
|
||||
### Export file formats
|
||||
- .x3d
|
||||
|
||||
## Guides
|
||||
|
||||
This add-on was part of [Blender 4.1 bundled add-ons](https://docs.blender.org/manual/en/4.1/addons/). This is now available as an extension on the [Extensions platform](https://extensions.blender.org/add-ons/web3d-x3d-vrml2-format).
|
||||
|
||||
To build a new version of the extension:
|
||||
* `<path_to_blender> -c extension build --source-dir=./source`
|
||||
|
||||
For more information about building extensions refer to the [documentation](https://docs.blender.org/manual/en/dev/extensions/getting_started.html).
|
||||
For more information about building extensions refer to the [documentation](https://docs.blender.org/manual/en/4.2/advanced/extensions/index.html).
|
||||
|
||||
---
|
||||
## Contribute
|
||||
|
||||
This add-on is offered as it is and maintaned by the community, no support expected.
|
||||
This add-on is offered as it is and maintaned by @Bujus_Krachus and the community, no support expected.
|
||||
|
||||
Contributions in form of [bug reports](https://projects.blender.org/extensions/io_scene_x3d/issues), bug fixes, improvements, new features, etc. are always welcome as I don't have the time to do it all.
|
||||
|
||||
For code contributions fork the repository, do your changes and create a Pull-Request to https://projects.blender.org/extensions/io_scene_x3d/src/branch/main. Describe as detailed as possible what this PR does and why it's good to have. Ideally include sample files for testing and demonstrating. Also make sure, that a merge of the PR does not break other features.
|
||||
|
||||
Fellow active maintainers are also always very welcome. If you're interested, reach out.
|
||||
|
||||
Original authors: Campbell Barton, Bart, Bastien Montagne, Seva Alekseyev
|
||||
|
||||
## Specifications
|
||||
For deeper understanding of both file formats supported by this extension, refer to:
|
||||
- [VRML (.wrl)](https://graphcomp.com/info/specs/sgi/vrml/)
|
||||
- [X3D (.x3d)](https://www.web3d.org/specifications/)
|
@ -8,6 +8,7 @@ DEBUG = False
|
||||
import os
|
||||
import shlex
|
||||
import math
|
||||
import re
|
||||
import mathutils
|
||||
from math import sin, cos, pi
|
||||
from itertools import chain
|
||||
@ -715,7 +716,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)
|
||||
@ -1219,6 +1220,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)
|
||||
|
||||
@ -1879,7 +1885,11 @@ def importMesh_IndexedFaceSet(geom, ancestry):
|
||||
|
||||
ccw = geom.getFieldAsBool('ccw', True, ancestry)
|
||||
coord = geom.getChildBySpec('Coordinate')
|
||||
if coord.reference:
|
||||
|
||||
if coord is None:
|
||||
return None
|
||||
|
||||
if coord.reference and coord.getRealNode().parsed:
|
||||
points = coord.getRealNode().parsed
|
||||
# We need unflattened coord array here, while
|
||||
# importMesh_ReadVertices uses flattened. Can't cache both :(
|
||||
@ -1983,32 +1993,49 @@ 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)
|
||||
|
||||
if color_per_vertex:
|
||||
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
|
||||
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]]
|
||||
|
||||
# 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)
|
||||
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]]]
|
||||
bpymesh.vertex_colors.new().data.foreach_set('color', cco) # deprecated
|
||||
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]]
|
||||
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]]
|
||||
bpymesh.vertex_colors.new().data.foreach_set('color', cco) # deprecated
|
||||
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)
|
||||
|
||||
|
||||
# Texture coordinates (UVs)
|
||||
tex_coord = geom.getChildBySpec('TextureCoordinate')
|
||||
@ -3003,32 +3030,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
|
||||
|
||||
@ -3177,6 +3221,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(
|
||||
|
Loading…
Reference in New Issue
Block a user