Fix T71232: .X3D/.WRL imports without materials and textures

When porting the x3d importer to 2.80, material and texture
importing didn't get ported. The old importer was using
Blender Render like materials, the new implementation uses
the standard node system via `node_shader_utils`.

Additionally this also adds a crude, text based method for
material/texture caching for WRL file (previously only
supported for X3D), fixes a possible `StructRNA has been removed`
error caused by the cache and assigns proper names to the
materials, if available.

Reviewed By: mont29

Maniphest Tasks: T76656, T71232, T83428

Differential Revision: https://developer.blender.org/D12399
This commit is contained in:
Max Schlecht 2021-10-27 12:23:39 +02:00 committed by Bastien Montagne
parent f2a08d80cc
commit 0573bc7dae
Notes: blender-bot 2023-02-14 19:06:35 +01:00
Referenced by issue #83428, WRL import
Referenced by issue #76656, .WRL imports without material fix
Referenced by issue #71232, X3D Import does not handle Materials
2 changed files with 79 additions and 59 deletions

View File

@ -21,8 +21,8 @@
bl_info = {
"name": "Web3D X3D/VRML2 format",
"author": "Campbell Barton, Bart, Bastien Montagne, Seva Alekseyev",
"version": (2, 2, 5),
"blender": (2, 81, 6),
"version": (2, 3, 0),
"blender": (2, 93, 0),
"location": "File > Import-Export",
"description": "Import-Export X3D, Import VRML2",
"warning": "",

View File

@ -1251,9 +1251,16 @@ class vrmlNode(object):
def canHaveReferences(self):
return self.node_type == NODE_NORMAL and self.getDefName()
# This is a prerequisite for raw XML-based material caching. For now, only for X3D
# This is a prerequisite for raw XML-based material caching.
# NOTE - crude, but working implementation for
# material and texture caching, based on __repr__.
# Doesn't do any XML, but is better than nothing.
def desc(self):
return None
if "material" in self.id or "texture" in self.id:
node = self.reference if self.node_type == NODE_REFERENCE else self
return frozenset(line.strip() for line in repr(node).strip().split("\n"))
else:
return None
def gzipOpen(path):
@ -1480,7 +1487,7 @@ for i, f in enumerate(files):
# NO BLENDER CODE ABOVE THIS LINE.
# -----------------------------------------------------------------------------------
import bpy
from bpy_extras import image_utils
from bpy_extras import image_utils, node_shader_utils
from mathutils import Vector, Matrix, Quaternion
GLOBALS = {'CIRCLE_DETAIL': 16}
@ -2703,31 +2710,46 @@ def appearance_CreateMaterial(vrmlname, mat, ancestry, is_vcol):
# Given an X3D material, creates a Blender material.
# texture is applied later, in appearance_Create().
# All values between 0.0 and 1.0, defaults from VRML docs.
bpymat = bpy.data.materials.new(vrmlname)
return # XXX For now...
bpymat.ambient = mat.getFieldAsFloat('ambientIntensity', 0.2, ancestry)
diff_color = mat.getFieldAsFloatTuple('diffuseColor',
[0.8, 0.8, 0.8],
ancestry)
bpymat.diffuse_color = diff_color
mat_name = mat.getDefName()
bpymat = bpy.data.materials.new(mat_name if mat_name else vrmlname)
bpymat_wrap = node_shader_utils.PrincipledBSDFWrapper(bpymat, is_readonly=False)
# NOTE - blender doesn't support emmisive color
# Store in mirror color and approximate with emit.
emit = mat.getFieldAsFloatTuple('emissiveColor', [0.0, 0.0, 0.0], ancestry)
bpymat.mirror_color = emit
bpymat.emit = (emit[0] + emit[1] + emit[2]) / 3.0
# TODO: handle 'ambientIntensity'.
#ambient = mat.getFieldAsFloat('ambientIntensity', 0.2, ancestry)
diff_color = mat.getFieldAsFloatTuple('diffuseColor', [0.8, 0.8, 0.8], ancestry)
bpymat_wrap.base_color = diff_color
emit_color = mat.getFieldAsFloatTuple('emissiveColor', [0.0, 0.0, 0.0], ancestry)
bpymat_wrap.emission_color = emit_color
# NOTE - 'shininess' is being handled as 1 - roughness for now.
shininess = mat.getFieldAsFloat('shininess', 0.2, ancestry)
bpymat.specular_hardness = int(1 + (510 * shininess))
bpymat_wrap.roughness = 1.0 - shininess
#bpymat.specular_hardness = int(1 + (510 * shininess))
# 0-1 -> 1-511
bpymat.specular_color = mat.getFieldAsFloatTuple('specularColor',
[0.0, 0.0, 0.0], ancestry)
bpymat.alpha = 1.0 - mat.getFieldAsFloat('transparency', 0.0, ancestry)
if bpymat.alpha < 0.999:
bpymat.use_transparency = True
# TODO: handle 'specularColor'.
#specular_color = mat.getFieldAsFloatTuple('specularColor',
# [0.0, 0.0, 0.0], ancestry)
alpha = 1.0 - mat.getFieldAsFloat('transparency', 0.0, ancestry)
bpymat_wrap.alpha = alpha
if alpha < 1.0:
bpymat.blend_method = "BLEND"
bpymat.shadow_method = "HASHED"
# NOTE - leaving this disabled for now
if False and is_vcol:
bpymat.use_vertex_color_paint = True
return bpymat
node_vertex_color = bpymat.node_tree.nodes.new("ShaderNodeVertexColor")
node_vertex_color.location = (-200, 300)
bpymat.node_tree.links.new(
bpymat_wrap.node_principled_bsdf.inputs["Base Color"],
node_vertex_color.outputs["Color"]
)
return bpymat_wrap
def appearance_CreateDefaultMaterial():
@ -2736,17 +2758,20 @@ def appearance_CreateDefaultMaterial():
# (but possibly with a texture).
bpymat = bpy.data.materials.new("Material")
return # XXX For now...
bpymat.ambient = 0.2
bpymat.diffuse_color = [0.8, 0.8, 0.8]
bpymat.mirror_color = (0, 0, 0)
bpymat.emit = 0
bpymat_wrap = node_shader_utils.PrincipledBSDFWrapper(bpymat, is_readonly=False)
bpymat.specular_hardness = 103
bpymat_wrap.roughness = 0.8
bpymat_wrap.base_color = (0.8, 0.8, 0.8, 1.0)
#bpymat.mirror_color = (0, 0, 0)
#bpymat.emit = 0
# TODO: handle 'shininess' and 'specularColor'.
#bpymat.specular_hardness = 103
# 0-1 -> 1-511
bpymat.specular_color = (0, 0, 0)
bpymat.alpha = 1
return bpymat
#bpymat.specular_color = (0, 0, 0)
bpymat_wrap.alpha = 1.0
return bpymat_wrap
def appearance_LoadImageTextureFile(ima_urls, node):
@ -2823,11 +2848,6 @@ def appearance_LoadTexture(tex_node, ancestry, node):
bpyima = appearance_LoadPixelTexture(tex_node, ancestry)
if bpyima: # Loading can still fail
repeat_s = tex_node.getFieldAsBool('repeatS', True, ancestry)
bpyima.use_clamp_x = not repeat_s
repeat_t = tex_node.getFieldAsBool('repeatT', True, ancestry)
bpyima.use_clamp_y = not repeat_t
# Update the desc-based cache
if desc:
texture_cache[desc] = bpyima
@ -2870,35 +2890,29 @@ def appearance_Create(vrmlname, material, tex_node, ancestry, node, is_vcol):
tex_has_alpha = False
if material:
bpymat = appearance_CreateMaterial(vrmlname, material, ancestry, is_vcol)
bpymat_wrap = appearance_CreateMaterial(vrmlname, material, ancestry, is_vcol)
else:
bpymat = appearance_CreateDefaultMaterial()
bpymat_wrap = appearance_CreateDefaultMaterial()
if tex_node: # Texture caching inside there
bpyima = appearance_LoadTexture(tex_node, ancestry, node)
if False and is_vcol:
bpymat.use_vertex_color_paint = True
if bpyima:
repeatS = tex_node.getFieldAsBool('repeatS', True, ancestry)
repeatT = tex_node.getFieldAsBool('repeatT', True, ancestry)
bpymat_wrap.base_color_texture.image = bpyima
# NOTE - not possible to handle x and y tiling individually.
extension = "REPEAT" if repeatS or repeatT else "CLIP"
bpymat_wrap.base_color_texture.extension = extension
if False and bpyima:
tex_has_alpha = bpyima.alpha_mode not in {'NONE', 'CHANNEL_PACKED'}
texture = bpy.data.textures.new(bpyima.name, 'IMAGE')
texture.image = bpyima
mtex = bpymat.texture_slots.add()
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_diffuse = True
mtex.use = True
if tex_has_alpha:
bpymat.use_transparency = True
mtex.use_map_alpha = True
mtex.alpha_factor = 0.0
bpymat_wrap.alpha_texture.image = bpyima
bpymat_wrap.alpha_texture.extension = extension
return (bpymat, bpyima, tex_has_alpha)
return (bpymat_wrap.material, bpyima, tex_has_alpha)
def importShape_LoadAppearance(vrmlname, appr, ancestry, node, is_vcol):
@ -3038,6 +3052,7 @@ def importShape_ProcessObject(
if bpydata.uv_layers:
if has_alpha and bpymat: # set the faces alpha flag?
bpymat.blend_method = 'BLEND'
bpymat.shadow_method = 'HASHED'
if texmtx:
# Apply texture transform?
@ -3500,6 +3515,11 @@ def load_web3d(
# Used when adding blender primitives
GLOBALS['CIRCLE_DETAIL'] = PREF_CIRCLE_DIV
# NOTE - reset material cache
# (otherwise we might get "StructRNA of type Material has been removed" errors)
global material_cache
material_cache = {}
bpyscene = bpycontext.scene
bpycollection = bpycontext.collection
#root_node = vrml_parse('/_Cylinder.wrl')