Export ImageTexture node for an image identified in material node tree #39
@ -162,6 +162,28 @@ class X3D_PT_export_include(bpy.types.Panel):
|
|||||||
col.enabled = not blender_version_higher_279
|
col.enabled = not blender_version_higher_279
|
||||||
col.prop(operator, "use_h3d")
|
col.prop(operator, "use_h3d")
|
||||||
|
|
||||||
|
class X3D_PT_export_external_resource(bpy.types.Panel):
|
||||||
|
bl_space_type = 'FILE_BROWSER'
|
||||||
|
bl_region_type = 'TOOL_PROPS'
|
||||||
|
bl_label = "Image Textures"
|
||||||
|
bl_parent_id = "FILE_PT_operator"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
return operator.bl_idname == "EXPORT_SCENE_OT_x3d"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
|
sfile = context.space_data
|
||||||
|
operator = sfile.active_operator
|
||||||
|
|
||||||
|
layout.prop(operator, "path_mode")
|
||||||
|
|
||||||
class X3D_PT_export_transform(bpy.types.Panel):
|
class X3D_PT_export_transform(bpy.types.Panel):
|
||||||
bl_space_type = 'FILE_BROWSER'
|
bl_space_type = 'FILE_BROWSER'
|
||||||
@ -198,6 +220,8 @@ class X3D_PT_export_transform(bpy.types.Panel):
|
|||||||
layout.prop(operator, "axis_up")
|
layout.prop(operator, "axis_up")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class X3D_PT_export_geometry(bpy.types.Panel):
|
class X3D_PT_export_geometry(bpy.types.Panel):
|
||||||
bl_space_type = 'FILE_BROWSER'
|
bl_space_type = 'FILE_BROWSER'
|
||||||
bl_region_type = 'TOOL_PROPS'
|
bl_region_type = 'TOOL_PROPS'
|
||||||
@ -419,6 +443,7 @@ classes = (
|
|||||||
X3D_PT_export_include,
|
X3D_PT_export_include,
|
||||||
X3D_PT_export_transform,
|
X3D_PT_export_transform,
|
||||||
X3D_PT_export_geometry,
|
X3D_PT_export_geometry,
|
||||||
|
X3D_PT_export_external_resource,
|
||||||
ImportX3D,
|
ImportX3D,
|
||||||
X3D_PT_import_general,
|
X3D_PT_import_general,
|
||||||
X3D_PT_import_transform,
|
X3D_PT_import_transform,
|
||||||
|
@ -7,14 +7,18 @@
|
|||||||
# - Doesn't handle multiple UV textures on a single mesh (create a mesh for each texture).
|
# - Doesn't handle multiple UV textures on a single mesh (create a mesh for each texture).
|
||||||
# - Can't get the texture array associated with material * not the UV ones.
|
# - Can't get the texture array associated with material * not the UV ones.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger("export_x3d")
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import os
|
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
|
|
||||||
from bpy_extras.io_utils import create_derived_objects
|
from bpy_extras.io_utils import create_derived_objects
|
||||||
|
|
||||||
|
from .material_node_search import imageTexture_in_material
|
||||||
|
|
||||||
# h3d defines
|
# h3d defines
|
||||||
H3D_TOP_LEVEL = 'TOP_LEVEL_TI'
|
H3D_TOP_LEVEL = 'TOP_LEVEL_TI'
|
||||||
@ -185,6 +189,8 @@ def h3d_is_object_view(scene, obj):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Functions for writing output file
|
# Functions for writing output file
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@ -206,6 +212,7 @@ def export(file,
|
|||||||
name_decorations=True,
|
name_decorations=True,
|
||||||
):
|
):
|
||||||
|
|
||||||
|
logger.info("export with path_mode: %r" % path_mode)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Global Setup
|
# Global Setup
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@ -259,6 +266,19 @@ def export(file,
|
|||||||
|
|
||||||
# store files to copy
|
# store files to copy
|
||||||
copy_set = set()
|
copy_set = set()
|
||||||
|
import os, os.path
|
||||||
|
if path_mode == 'COPY':
|
||||||
|
# create a per-export temporary folder. image data which does not
|
||||||
|
# exist on disk yet will be saved here from which it can be copied by
|
||||||
|
# the bpy_extras.io_utils.path_reference_copy utility
|
||||||
|
blender_tempfolder = bpy.app.tempdir
|
||||||
|
import uuid
|
||||||
|
temporary_image_directory = os.path.join(blender_tempfolder,uuid.uuid4().hex)
|
||||||
|
os.mkdir(temporary_image_directory)
|
||||||
|
logger.info("temporary_image_directory: %s" % temporary_image_directory )
|
||||||
|
else:
|
||||||
|
temporary_image_directory = None
|
||||||
|
|
||||||
|
|
||||||
# store names of newly created meshes, so we dont overlap
|
# store names of newly created meshes, so we dont overlap
|
||||||
mesh_name_set = set()
|
mesh_name_set = set()
|
||||||
@ -512,27 +532,6 @@ def export(file,
|
|||||||
|
|
||||||
is_coords_written = False
|
is_coords_written = False
|
||||||
|
|
||||||
mesh_materials = mesh.materials[:]
|
|
||||||
if not mesh_materials:
|
|
||||||
mesh_materials = [None]
|
|
||||||
|
|
||||||
mesh_material_tex = [None] * len(mesh_materials)
|
|
||||||
mesh_material_mtex = [None] * len(mesh_materials)
|
|
||||||
mesh_material_images = [None] * len(mesh_materials)
|
|
||||||
|
|
||||||
for i, material in enumerate(mesh_materials):
|
|
||||||
if 0 and material:
|
|
||||||
for mtex in material.texture_slots:
|
|
||||||
if mtex:
|
|
||||||
tex = mtex.texture
|
|
||||||
if tex and tex.type == 'IMAGE':
|
|
||||||
image = tex.image
|
|
||||||
if image:
|
|
||||||
mesh_material_tex[i] = tex
|
|
||||||
mesh_material_mtex[i] = mtex
|
|
||||||
mesh_material_images[i] = image
|
|
||||||
break
|
|
||||||
|
|
||||||
# fast access!
|
# fast access!
|
||||||
mesh_vertices = mesh.vertices[:]
|
mesh_vertices = mesh.vertices[:]
|
||||||
mesh_loops = mesh.loops[:]
|
mesh_loops = mesh.loops[:]
|
||||||
@ -540,21 +539,14 @@ def export(file,
|
|||||||
mesh_polygons_materials = [p.material_index for p in mesh_polygons]
|
mesh_polygons_materials = [p.material_index for p in mesh_polygons]
|
||||||
mesh_polygons_vertices = [p.vertices[:] for p in mesh_polygons]
|
mesh_polygons_vertices = [p.vertices[:] for p in mesh_polygons]
|
||||||
|
|
||||||
if len(set(mesh_material_images)) > 0: # make sure there is at least one image
|
|
||||||
mesh_polygons_image = [mesh_material_images[material_index] for material_index in mesh_polygons_materials]
|
|
||||||
else:
|
|
||||||
mesh_polygons_image = [None] * len(mesh_polygons)
|
|
||||||
mesh_polygons_image_unique = set(mesh_polygons_image)
|
|
||||||
|
|
||||||
# group faces
|
# group faces
|
||||||
polygons_groups = {}
|
polygons_groups = {}
|
||||||
for material_index in range(len(mesh_materials)):
|
for material_index in range(len(mesh.materials)):
|
||||||
for image in mesh_polygons_image_unique:
|
polygons_groups[material_index] = []
|
||||||
polygons_groups[material_index, image] = []
|
|
||||||
del mesh_polygons_image_unique
|
|
||||||
|
|
||||||
for i, (material_index, image) in enumerate(zip(mesh_polygons_materials, mesh_polygons_image)):
|
|
||||||
polygons_groups[material_index, image].append(i)
|
for i, material_index in enumerate(mesh_polygons_materials):
|
||||||
|
polygons_groups[material_index].append(i)
|
||||||
|
|
||||||
# Py dict are sorted now, so we can use directly polygons_groups.items()
|
# Py dict are sorted now, so we can use directly polygons_groups.items()
|
||||||
# and still get consistent reproducible outputs.
|
# and still get consistent reproducible outputs.
|
||||||
@ -587,9 +579,9 @@ def export(file,
|
|||||||
for ltri in mesh.loop_triangles:
|
for ltri in mesh.loop_triangles:
|
||||||
polygons_to_loop_triangles_indices[ltri.polygon_index].append(ltri)
|
polygons_to_loop_triangles_indices[ltri.polygon_index].append(ltri)
|
||||||
|
|
||||||
for (material_index, image), polygons_group in polygons_groups.items():
|
for material_index, polygons_group in polygons_groups.items():
|
||||||
if polygons_group:
|
if polygons_group:
|
||||||
material = mesh_materials[material_index]
|
material = mesh.materials[material_index]
|
||||||
|
|
||||||
fw('%s<Shape>\n' % ident)
|
fw('%s<Shape>\n' % ident)
|
||||||
ident += '\t'
|
ident += '\t'
|
||||||
@ -627,33 +619,9 @@ def export(file,
|
|||||||
fw('%s<Appearance>\n' % ident)
|
fw('%s<Appearance>\n' % ident)
|
||||||
ident += '\t'
|
ident += '\t'
|
||||||
|
|
||||||
if image and not use_h3d:
|
imageTextureNode = imageTexture_in_material(material)
|
||||||
writeImageTexture(ident, image)
|
if imageTextureNode:
|
||||||
|
writeImageTexture(ident, imageTextureNode)
|
||||||
# transform by mtex
|
|
||||||
loc = mesh_material_mtex[material_index].offset[:2]
|
|
||||||
|
|
||||||
# mtex_scale * tex_repeat
|
|
||||||
sca_x, sca_y = mesh_material_mtex[material_index].scale[:2]
|
|
||||||
|
|
||||||
sca_x *= mesh_material_tex[material_index].repeat_x
|
|
||||||
sca_y *= mesh_material_tex[material_index].repeat_y
|
|
||||||
|
|
||||||
# flip x/y is a sampling feature, convert to transform
|
|
||||||
if mesh_material_tex[material_index].use_flip_axis:
|
|
||||||
rot = math.pi / -2.0
|
|
||||||
sca_x, sca_y = sca_y, -sca_x
|
|
||||||
else:
|
|
||||||
rot = 0.0
|
|
||||||
|
|
||||||
ident_step = ident + (' ' * (-len(ident) +
|
|
||||||
fw('%s<TextureTransform ' % ident)))
|
|
||||||
fw('\n')
|
|
||||||
# fw('center="%.6f %.6f" ' % (0.0, 0.0))
|
|
||||||
fw(ident_step + 'translation="%.6f %.6f"\n' % loc)
|
|
||||||
fw(ident_step + 'scale="%.6f %.6f"\n' % (sca_x, sca_y))
|
|
||||||
fw(ident_step + 'rotation="%.6f"\n' % rot)
|
|
||||||
fw(ident_step + '/>\n')
|
|
||||||
|
|
||||||
if use_h3d:
|
if use_h3d:
|
||||||
mat_tmp = material if material else gpu_shader_dummy_mat
|
mat_tmp = material if material else gpu_shader_dummy_mat
|
||||||
@ -1237,8 +1205,12 @@ def export(file,
|
|||||||
|
|
||||||
fw('%s</ComposedShader>\n' % ident)
|
fw('%s</ComposedShader>\n' % ident)
|
||||||
|
|
||||||
def writeImageTexture(ident, 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="_"))
|
image_id = quoteattr(unique_name(image, IM_ + image.name, uuid_cache_image, clean_func=clean_def, sep="_"))
|
||||||
|
logger.info("write ImageTexture X3D node for %r format %r filepath %r" % (image.name, image.file_format, image.filepath ))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if image.tag:
|
if image.tag:
|
||||||
fw('%s<ImageTexture USE=%s />\n' % (ident, image_id))
|
fw('%s<ImageTexture USE=%s />\n' % (ident, image_id))
|
||||||
@ -1249,24 +1221,79 @@ def export(file,
|
|||||||
fw('%s<ImageTexture ' % ident)))
|
fw('%s<ImageTexture ' % ident)))
|
||||||
fw('DEF=%s\n' % image_id)
|
fw('DEF=%s\n' % image_id)
|
||||||
|
|
||||||
# collect image paths, can load multiple
|
if (path_mode != 'COPY') and not image.filepath :
|
||||||
# [relative, name-only, absolute]
|
# this is the case where the path_mode choice is intended to
|
||||||
|
# reference an existing image file, but no filepath for that
|
||||||
|
# file is specified in the image data structure
|
||||||
|
logger.warning("no filepath available for Path Mode %s" % path_mode )
|
||||||
|
|
||||||
|
# setting COPY_SUBDIR to None in calls to
|
||||||
|
# bpy_extras.io_utils.path_reference will cause copied images
|
||||||
|
# to be placed in same directory as the exported X3D file.
|
||||||
|
COPY_SUBDIR=None
|
||||||
|
has_packed_file = image.packed_file and image.packed_file.size > 0
|
||||||
|
if path_mode == 'COPY' and ( has_packed_file or not image.filepath) :
|
||||||
|
# write to temporary folder so that
|
||||||
|
# bpy_extras.io_utils.path_reference_copy can
|
||||||
|
# copy it final location at end of export
|
||||||
|
use_file_format = image.file_format or 'PNG'
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_ext = {'JPEG':'.jpg' , 'PNG':'.png'}[use_file_format]
|
||||||
|
except KeyError:
|
||||||
|
# if image data not in JPEG or PNG it is not supported in
|
||||||
|
# X3D standard. If image file_format is "FFMPEG" that is a movie
|
||||||
|
# format, and would be referenced in an X3D MovieTexture node
|
||||||
|
if use_file_format == "FFMPEG" :
|
||||||
|
logger.warning("movie textures not supported for X3D export")
|
||||||
|
else:
|
||||||
|
logger.warning("image texture file format %r not supported" % use_file_format)
|
||||||
|
else:
|
||||||
|
image_base = os.path.splitext( os.path.basename(image.name))[0]
|
||||||
|
image_filename = image_base + image_ext
|
||||||
|
|
||||||
|
filepath_full = os.path.join(temporary_image_directory, image_filename)
|
||||||
|
|
||||||
|
logger.info("writing image for texture to %s" % filepath_full)
|
||||||
|
image.save( filepath = filepath_full )
|
||||||
|
|
||||||
|
filepath_ref = bpy_extras.io_utils.path_reference(
|
||||||
|
filepath_full,
|
||||||
|
temporary_image_directory,
|
||||||
|
base_dst,
|
||||||
|
path_mode,
|
||||||
|
COPY_SUBDIR,
|
||||||
|
copy_set,
|
||||||
|
image.library)
|
||||||
|
else:
|
||||||
filepath = image.filepath
|
filepath = image.filepath
|
||||||
filepath_full = bpy.path.abspath(filepath, library=image.library)
|
filepath_full = bpy.path.abspath(filepath, library=image.library)
|
||||||
filepath_ref = bpy_extras.io_utils.path_reference(filepath_full, base_src, base_dst, path_mode, "textures", copy_set, image.library)
|
filepath_ref = bpy_extras.io_utils.path_reference(
|
||||||
filepath_base = os.path.basename(filepath_full)
|
filepath_full,
|
||||||
|
base_src,
|
||||||
|
base_dst,
|
||||||
|
path_mode,
|
||||||
|
COPY_SUBDIR,
|
||||||
|
copy_set,
|
||||||
|
image.library)
|
||||||
|
|
||||||
images = [
|
# following replaces Windows filepath separator with POSIX /Unix
|
||||||
filepath_ref,
|
# separator
|
||||||
filepath_base,
|
filepath_ref = filepath_ref.replace("\\", "/")
|
||||||
]
|
|
||||||
if path_mode != 'RELATIVE':
|
|
||||||
images.append(filepath_full)
|
|
||||||
|
|
||||||
images = [f.replace('\\', '/') for f in images]
|
image_urls = [ filepath_ref ]
|
||||||
images = [f for i, f in enumerate(images) if f not in images[:i]]
|
|
||||||
|
|
||||||
fw(ident_step + "url='%s'\n" % ' '.join(['"%s"' % escape(f) for f in images]))
|
logger.info("node urls: %s" % (image_urls,))
|
||||||
|
fw(ident_step + "url='%s'\n" % ' '.join(['"%s"' % escape(f) for f in image_urls]))
|
||||||
|
|
||||||
|
# default value of repeatS, repeatT fields is true, so only need to
|
||||||
|
# specify if extension value is CLIP
|
||||||
|
x3d_supported_extension = ["CLIP", "REPEAT"]
|
||||||
|
if imageTextureNode.extension not in x3d_supported_extension:
|
||||||
|
logger.warning("imageTextureNode.extension value %s unsupported in X3D" % imageTextureNode.extension)
|
||||||
|
|
||||||
|
if imageTextureNode.extension == "CLIP":
|
||||||
|
fw(ident_step + "repeatS='false' repeatT='false'")
|
||||||
fw(ident_step + '/>\n')
|
fw(ident_step + '/>\n')
|
||||||
|
|
||||||
def writeBackground(ident, world):
|
def writeBackground(ident, world):
|
||||||
@ -1513,9 +1540,12 @@ def export(file,
|
|||||||
if use_h3d:
|
if use_h3d:
|
||||||
bpy.data.materials.remove(gpu_shader_dummy_mat)
|
bpy.data.materials.remove(gpu_shader_dummy_mat)
|
||||||
|
|
||||||
# copy all collected files.
|
if copy_set:
|
||||||
# print(copy_set)
|
for c in copy_set:
|
||||||
|
logger.info("copy_set item %r" % copy_set)
|
||||||
bpy_extras.io_utils.path_reference_copy(copy_set)
|
bpy_extras.io_utils.path_reference_copy(copy_set)
|
||||||
|
else:
|
||||||
|
logger.info("no items in copy_set")
|
||||||
|
|
||||||
print('Info: finished X3D export to %r' % file.name)
|
print('Info: finished X3D export to %r' % file.name)
|
||||||
|
|
||||||
@ -1559,6 +1589,7 @@ def save(context,
|
|||||||
name_decorations=True
|
name_decorations=True
|
||||||
):
|
):
|
||||||
|
|
||||||
|
logger.info("save: context %r to filepath %r" % (context,filepath))
|
||||||
bpy.path.ensure_ext(filepath, '.x3dz' if use_compress else '.x3d')
|
bpy.path.ensure_ext(filepath, '.x3dz' if use_compress else '.x3d')
|
||||||
|
|
||||||
if bpy.ops.object.mode_set.poll():
|
if bpy.ops.object.mode_set.poll():
|
||||||
|
81
source/material_node_search.py
Normal file
81
source/material_node_search.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2024 Vincent Marchetti
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger("export_x3d.material_node_search")
|
||||||
|
|
||||||
|
"""
|
||||||
|
functions implementing searching the node tree for Blender materials in search
|
||||||
|
of particular nodes enabling export of material properties into other formats
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
class _ShaderNodeTypes:
|
||||||
|
MATERIAL_OUTPUT= "ShaderNodeOutputMaterial"
|
||||||
|
BSDF_PRINCIPLED = "ShaderNodeBsdfPrincipled"
|
||||||
|
IMAGE_TEXTURE = "ShaderNodeTexImage"
|
||||||
|
|
||||||
|
|
||||||
|
def _find_node_by_idname(nodes, idname):
|
||||||
|
"""
|
||||||
|
nodes a sequence of Nodes, idname a string
|
||||||
|
Each node assumed to ne an instance of Node(bpy_struct)
|
||||||
|
https://docs.blender.org/api/current/bpy.types.Node.html
|
||||||
|
|
||||||
|
The idname is searched for in the Node instance member bl_idname
|
||||||
|
https://docs.blender.org/api/current/bpy.types.Node.html#bpy.types.Node.bl_idname
|
||||||
|
and is generally some string version of the name of the subclass
|
||||||
|
|
||||||
|
See https://docs.blender.org/api/current/bpy.types.ShaderNode.html for list of
|
||||||
|
|||||||
|
ShaderNode subclasses
|
||||||
|
|
||||||
|
returns first matching node found,returns None if no matching nodes
|
||||||
|
prints warning if multiple matches 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:
|
||||||
Cedric Steiert
commented
the output as code is not really needed in the docstring, but if you want simplify: Returns None if no targets are found or returns the first found target the output as code is not really needed in the docstring, but if you want simplify: Returns None if no targets are found or returns the first found target
|
|||||||
|
_logger.warn("_find_node_by_idname : multiple (%i) nodes of type %s found" % (len(nodelist), idname))
|
||||||
|
return nodelist[0]
|
||||||
|
|
||||||
|
def imageTexture_in_material(material):
|
||||||
|
"""
|
||||||
|
Identifies ImageTexture node used as the input to Base Color attribute of BSDF node.
|
||||||
|
Does not search for images used as textures inside a NodeGrouo node.
|
||||||
|
|
||||||
|
argument material an instance of Material(ID)
|
||||||
|
https://docs.blender.org/api/current/bpy.types.Material.html
|
||||||
|
|
||||||
|
returns instance of ShaderNodeTexImage
|
||||||
|
https://docs.blender.org/api/current/bpy.types.ShaderNodeTexImage.html
|
||||||
|
"""
|
||||||
|
_logger.debug("evaluating image in material %s" % material.name)
|
||||||
|
|
||||||
|
material_output = _find_node_by_idname( material.node_tree.nodes, _ShaderNodeTypes.MATERIAL_OUTPUT)
|
||||||
|
if material_output is None:
|
||||||
|
_logger.warn("%s not found in material %s" % (_ShaderNodeTypes.MATERIAL_OUTPUT, material.name))
|
||||||
|
return None
|
||||||
|
|
||||||
|
SURFACE_ATTRIBUTE= "Surface"
|
||||||
|
bsdf_principled = _find_node_by_idname(
|
||||||
|
[ndlink.from_node for ndlink in material_output.inputs.get(SURFACE_ATTRIBUTE).links],
|
||||||
|
_ShaderNodeTypes.BSDF_PRINCIPLED)
|
||||||
|
if bsdf_principled is None :
|
||||||
|
return None
|
||||||
|
|
||||||
|
BASE_COLOR_ATTRIBUTE = 'Base Color'
|
||||||
|
image_texture = _find_node_by_idname(
|
||||||
|
[ndlink.from_node for ndlink in bsdf_principled.inputs.get(BASE_COLOR_ATTRIBUTE).links],
|
||||||
|
_ShaderNodeTypes.IMAGE_TEXTURE )
|
||||||
Cedric Steiert
commented
for better readability put the return into it's own line for better readability put the return into it's own line
|
|||||||
|
if image_texture is None:
|
||||||
|
return None
|
||||||
|
_logger.debug("located image texture node %r" % image_texture)
|
||||||
|
return image_texture
|
||||||
|
|
Loading…
Reference in New Issue
Block a user
a few typos here