Export ImageTexture node for an image identified in material node tree #39
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user