Export ImageTexture node for an image identified in material node tree #39

Open
Vincent Marchetti wants to merge 12 commits from vmarchetti/io_scene_x3d:image-export into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
Showing only changes of commit b17bc0b40a - Show all commits

View File

@ -18,6 +18,7 @@ import mathutils
from bpy_extras.io_utils import create_derived_objects
from .material_node_search import imageTexture_in_material
# h3d defines
H3D_TOP_LEVEL = 'TOP_LEVEL_TI'
@ -188,153 +189,7 @@ def h3d_is_object_view(scene, obj):
return False
# node based material identifiers
BSDF_PRINCIPLED = 'BSDF_PRINCIPLED'
BASE_COLOR = 'Base Color'
IMAGE = 'image'
# values of bl_idname for shader nodes to be located
# reference Python API list of subclasses of ShaderNode
# at https://docs.blender.org/api/current/bpy.types.ShaderNode.html#bpy.types.ShaderNode
MATERIAL_OUTPUT= "ShaderNodeOutputMaterial"
BSDF_PRINCIPLED = "ShaderNodeBsdfPrincipled"
IMAGE_TEXTURE = "ShaderNodeTexImage"
# supported values of Image Texture Node extension property
REPEAT="REPEAT"
CLIP ="CLIP"
class ImageInMaterial:
"""
An instance of this class is a callable object that takes as input arguemnt
a Material object, and returns an Image instance or None.
It is defined through a class so as to have a built-in cache mechanism
"""
class ImageExportRecord:
"""
a collection of properties of an image used as a texture, relevant
for export of an X3D ImageTextureNode
reference:
Blender ImageTexture: https://docs.blender.org/manual/en/latest/render/shader_nodes/textures/image.html
instance properties:
image -- the Blender Image instance
extension -- a string enum value which, in the X3D context, governs
whether the image should be repeated for UV coordinates
outside the (0,0) to (1,1) square. Blender TextureImages
have "extension" options not implemented by X3D
is_packed -- a boolean value, if true then the image data is to be
saved to a disk file associated with the output X3D file.
The name packed comes from the initial method of determining
whether this file needs to be exported by the presence of
a Blender PackedFile instance connected to the Blender image
url -- if packed, then this string value is to be the URL
included in the ImageTexture node and also used to
define where the image should be saved (relative to the
location of the X3D file.
"""
def __init__(self):
self.image = None
self.extension = None
self.url = None
@property
def is_packed(self):
return self.url is not None
def __init__(self):
"""
cachedResult will be a dictionary whose keys are Material instances
and values are instance of
"""
self.cachedResult = dict()
def __call__(self, material):
if material not in self.cachedResult:
self.cachedResult[material] = self.evaluate(material)
return self.cachedResult[material]
@staticmethod
def _find_node_by_idname_(nodes, idname):
"""
nodes a sequence of Nodes, idname a string
if 0 targets are found, returns None
if 1 target is found returns that Node
if more than 1 targets are found prints warning string and returns first found
"""
logger.debug("enter _find_node_by_idname_ search for %s in %r" % (idname, nodes))
nodelist = [nd for nd in nodes if nd.bl_idname == idname]
logger.debug("result _find_node_by_idname_ found %i" % len(nodelist))
if len(nodelist) == 0:
return None
if len(nodelist) > 1:
logger.warn("_find_node_by_idname_ : multiple (%i) nodes of type %s found" % (len(nodelist), idname))
return nodelist[0]
def evaluate(self, material):
logger.debug("evaluating image in material %s" % material.name)
material_output = self._find_node_by_idname_( material.node_tree.nodes, MATERIAL_OUTPUT)
if material_output is None:
logger.warn("%s not found in material %s" % (MATERIAL_OUTPUT, material.name))
return None
bsdf_principled = self._find_node_by_idname_(
[ndlink.from_node for ndlink in material_output.inputs.get("Surface").links],
BSDF_PRINCIPLED)
if bsdf_principled is None : return None
image_texture = self._find_node_by_idname_(
[ndlink.from_node for ndlink in bsdf_principled.inputs.get(BASE_COLOR).links],
IMAGE_TEXTURE )
if image_texture is None: return None
logger.debug("located %s ShaderNode" % IMAGE_TEXTURE)
x3d_supported_file_extension = {"PNG" : ".png","JPEG" : ".jpg"}
if image_texture.image.file_format not in x3d_supported_file_extension:
logger.warn("images of format %s not supported" % image_texture.image.file_format)
return None
retVal = self.ImageExportRecord()
retVal.image = image_texture.image
# ref https://docs.blender.org/api/current/bpy.types.ShaderNodeTexImage.html#bpy.types.ShaderNodeTexImage.extension
x3d_supported_extension = [CLIP, REPEAT]
if image_texture.extension in x3d_supported_extension:
retVal.extension = image_texture.extension
else:
logger.warn("image_texture.extension value %s unsupported in X3D" % image_texture.extension)
retVal.extension=REPEAT
packed_file = image_texture.image.packed_file
if packed_file is not None and packed_file.size > 0:
from os.path import splitext, basename
name_base = splitext( basename(image_texture.image.name))[0]
url_ext = x3d_supported_file_extension[image_texture.image.file_format]
retVal.url = name_base +url_ext
logger.info("ImageExportRecord : %s packed: %r url: %s : extension: %r" % \
(retVal.image.name, retVal.is_packed,
retVal.url , retVal.extension))
return retVal
def imagesToSave(self):
"""
a generator that yields the ImageExportRecord instances
for images that have packed attribute True
"""
for image_export_record in self.cachedResult.values():
if image_export_record and ( image_export_record.is_packed or True ):
yield image_export_record
# -----------------------------------------------------------------------------
# Functions for writing output file
@ -427,7 +282,6 @@ def export(file,
gpu_shader_cache[None] = gpu.export_shader(scene, gpu_shader_dummy_mat)
h3d_material_route = []
imageInMaterial = ImageInMaterial()
# -------------------------------------------------------------------------
# File Writing Functions
# -------------------------------------------------------------------------
@ -757,10 +611,9 @@ def export(file,
# writeImageTexture(ident, image)
# and also potentially write
# the TextureTransform
imageRecord = imageInMaterial(material)
if imageRecord:
logger.info("output ImageTexture for %s" % imageRecord.image.name )
writeImageTexture(ident, imageRecord)
imageTextureNode = imageTexture_in_material(material)
if imageTextureNode:
writeImageTexture(ident, imageTextureNode)
if use_h3d:
mat_tmp = material if material else gpu_shader_dummy_mat
@ -1344,11 +1197,11 @@ def export(file,
fw('%s</ComposedShader>\n' % ident)
def writeImageTexture(ident, image_record):
image=image_record.image
def writeImageTexture(ident, imageTextureNode):
image=imageTextureNode.image
image_id = quoteattr(unique_name(image, IM_ + image.name, uuid_cache_image, clean_func=clean_def, sep="_"))
logger.info("write ImageTexture X3D node for %s format %s" % (image.name, image.file_format ))
logger.info("write ImageTexture X3D node for %r format %r filepath %r" % (image.name, image.file_format, image.filepath ))
return
if image.tag:
@ -1652,15 +1505,16 @@ def export(file,
if copy_set:
for c in copy_set:
logger.info("copy_set item %r" % copy_set)
bpy_extras.io_utils.path_reference_copy(copy_set)
else:
logger.info("no items in copy_set")
bpy_extras.io_utils.path_reference_copy(copy_set)
for svRecord in imageInMaterial.imagesToSave():
image_filepath = os.path.join(base_dst, svRecord.url)
logger.info("writing image for texture to %s" % image_filepath)
svRecord.image.save( filepath = image_filepath )
# for svRecord in imageInMaterial.imagesToSave():
# image_filepath = os.path.join(base_dst, svRecord.url)
# logger.info("writing image for texture to %s" % image_filepath)
# svRecord.image.save( filepath = image_filepath )
print('Info: finished X3D export to %r' % file.name)