From: Mikko Rasa Date: Tue, 23 Jun 2020 15:14:59 +0000 (+0300) Subject: Rework Blender material and texture export X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=180b20bee11425a776c5ead05afcf6a63945d3b2 Rework Blender material and texture export --- diff --git a/blender/io_mspgl/export_material.py b/blender/io_mspgl/export_material.py index 356186df..f51a4a49 100644 --- a/blender/io_mspgl/export_material.py +++ b/blender/io_mspgl/export_material.py @@ -1,5 +1,24 @@ import os +def create_technique_resource(material, resources, single_file): + from .datafile import Resource, Statement + tech_res = Resource(material.name+".tech") + + mat_res = resources[material.name+".mat"] + + st = Statement("pass", "") + if single_file: + st.sub.append(tech_res.create_embed_statement("material", mat_res)) + else: + st.sub.append(tech_res.create_reference_statement("material", mat_res)) + + if material.render_mode=='CUSTOM': + st.sub.append(Statement("shader", material.shader)) + + tech_res.statements.append(st) + + return tech_res + class MaterialExporter: def __init__(self): self.single_file = True @@ -14,97 +33,53 @@ class MaterialExporter: def export_technique_resources(self, material, resources): texture_export = self.create_texture_exporter() - mat_name = material.name+".mat" - if mat_name not in resources: - resources[mat_name] = self.export_material(material) + from .material import Material + material = Material(material) - if False and self.use_textures: - for s in material.texture_slots: - if s and s.texture.type=='IMAGE' and s.texture.image: - tex_name = s.texture.name+".tex2d" + if self.use_textures: + for p in material.properties: + if p.texture: + tex_name = p.texture.image.name+".tex2d" if tex_name not in resources: - resources[tex_name] = texture_export.export_texture(s.texture) + resources[tex_name] = texture_export.export_texture(p.texture, p.tex_usage) + + mat_name = material.name+".mat" + if mat_name not in resources: + resources[mat_name] = self.export_material(material, resources=resources) def export_technique(self, material, *, resources): - from .datafile import Resource, Statement - tech_res = Resource(material.name+".tech") - - mat_res = resources[material.name+".mat"] - textures = {} - if False and self.use_textures: - image_texture_slots = [s for s in material.texture_slots if s and s.texture.type=='IMAGE' and s.texture.image] - for s in image_texture_slots: - if s.use_map_color_diffuse: - textures["diffuse_map"] = s.texture - elif s.use_map_normal: - if s.texture.use_normal_map: - textures["normal_map"] = s.texture - else: - textures["displace_map"] = s.texture - - if material.technique: - if not material.inherit_tech: - return tech_res + return create_technique_resource(material, resources, self.single_file) - if self.single_file: - raise Exception("Can't export inherited technique to a single file") - - st = Statement("inherit", material.technique) - for s, t in textures.items(): - fn = os.path.basename(t.image.filepath) - if t.default_filter and fn: - st.sub.append(Statement("texture", s, fn)) - else: - st.sub.append(tech_res.create_reference_statement("texture", s, resources[t.name+".tex2d"])) - if material.override_material: - st.sub.append(tech_res.create_reference_statement("material", "surface", mat_res)) - tech_res.statements.append(st) - else: - st = Statement("pass", "") - if self.single_file: - st.sub.append(tech_res.create_embed_statement("material", mat_res)) - else: - st.sub.append(tech_res.create_reference_statement("material", mat_res)) - - if "diffuse_map" in textures: - diffuse_tex = textures["diffuse_map"] - tex_res = resources[diffuse_tex.name+".tex2d"] - ss = Statement("texunit", 0) - fn = os.path.basename(diffuse_tex.image.filepath) - if self.single_file: - ss.sub.append(tech_res.create_embed_statement("texture2d", tex_res)) - elif diffuse_tex.default_filter and fn: - ss.sub.append(Statement("texture", fn)) - else: - ss.sub.append(tech_res.create_reference_statement("texture", tex_res)) - st.sub.append(ss) - - tech_res.statements.append(st) - - return tech_res - - def export_material(self, material): + def export_material(self, material, *, resources): from .datafile import Resource, Statement mat_res = Resource(material.name+".mat") - statements = mat_res.statements - from .util import get_colormap - cm = get_colormap(material.srgb_colors) - - if False and any(s.use_map_color_diffuse for s in material.texture_slots if s): - statements.append(Statement("diffuse", 1.0, 1.0, 1.0, 1.0)) - amb = cm(material.ambient) - statements.append(Statement("ambient", amb, amb, amb, 1.0)) - else: - diff = material.diffuse_color - statements.append(Statement("diffuse", cm(diff[0]), cm(diff[1]), cm(diff[2]), 1.0)) - statements.append(Statement("ambient", cm(diff[0]), cm(diff[1]), cm(diff[2]), 1.0)) - spec = material.specular_color*material.specular_intensity - statements.append(Statement("specular", cm(spec.r), cm(spec.g), cm(spec.g), 1.0)) - statements.append(Statement("shininess", min(2/material.roughness**2-2, 250))) + st = Statement("pbr") + st.sub.append(self.create_property_statement(mat_res, material.base_color, "base_color", resources)) + st.sub.append(self.create_property_statement(mat_res, material.metalness, "metalness", resources)) + st.sub.append(self.create_property_statement(mat_res, material.roughness, "roughness", resources)) + st.sub.append(self.create_property_statement(mat_res, material.normal, "normal", resources, tex_only=True)) + st.sub.append(self.create_property_statement(mat_res, material.emission, "emission", resources)) + mat_res.statements.append(st) return mat_res + def create_property_statement(self, mat_res, prop, keyword, resources, *, tex_only=False): + from .datafile import Statement + if self.use_textures and prop.texture: + tex_res = resources[prop.texture.image.name+".tex2d"] + fn = os.path.basename(prop.texture.image.filepath) + if self.single_file: + raise Exception("Can't export textures to a single file") + elif prop.texture.default_filter and fn: + return Statement(keyword+"_map", fn) + else: + return mat_res.create_reference_statement(keyword+"_map", tex_res) + elif type(prop.value)==tuple: + return Statement(keyword, *prop.value) + else: + return Statement(keyword, prop.value) + class MaterialMapExporter: def __init__(self): @@ -112,52 +87,25 @@ class MaterialMapExporter: def export_technique_resources(self, material_map, resources): from .datafile import Resource, Statement, Token - diffuse_name = material_map.name+"_diffuse.tex2d" - if diffuse_name not in resources: - diffuse_res = Resource(diffuse_name) - - fmt = 'SRGB_ALPHA' if material_map.srgb_colors else 'RGBA' + base_color_name = material_map.name+"_base_color.tex2d" + if base_color_name not in resources: + base_color_res = Resource(base_color_name) - diffuse_res.statements.append(Statement("min_filter", Token('NEAREST'))) - diffuse_res.statements.append(Statement("mag_filter", Token('NEAREST'))) - diffuse_res.statements.append(Statement("storage", Token(fmt), *material_map.size)) - diffuse_res.statements.append(Statement("raw_data", material_map.diffuse_data)) + base_color_res.statements.append(Statement("min_filter", Token('NEAREST'))) + base_color_res.statements.append(Statement("mag_filter", Token('NEAREST'))) + base_color_res.statements.append(Statement("storage", Token('SRGB_ALPHA'), *material_map.size)) + base_color_res.statements.append(Statement("raw_data", material_map.base_color_data)) - resources[diffuse_name] = diffuse_res - - if "basic_white.mat" not in resources: - mat_res = Resource("basic_white.mat") - mat_res.statements.append(Statement("diffuse", 1.0, 1.0, 1.0, 1.0)) - - resources["basic_white.mat"] = mat_res - - def export_technique(self, material_map, *, resources=None): - from .datafile import Resource, Statement - tech_res = Resource(material_map.name+".tech") + resources[base_color_name] = base_color_res - mat_res = resources["basic_white.mat"] - diffuse_res = resources[material_map.name+"_diffuse.tex2d"] - - if material_map.technique: - if self.single_file: - raise Exception("Can't export inherited technique to a single file") + mat_name = material_map.name+".mat" + if mat_name not in resources: + mat_res = Resource(mat_name) + st = Statement("pbr") + st.sub.append(mat_res.create_reference_statement("base_color_map", base_color_res)) + mat_res.statements.append(st) - st = Statement("inherit", material_map.technique) - st.sub.append(tech_res.create_reference_statement("texture", "diffuse_map", diffuse_res)) - st.sub.append(tech_res.create_reference_statement("material", "surface", mat_res)) - tech_res.statements.append(st) - else: - st = Statement("pass", "") - if self.single_file: - st.sub.append(tech_res.create_embed_statement("material", mat_res)) - else: - st.sub.append(tech_res.create_reference_statement("material", mat_res)) - ss = Statement("texunit", 0) - if self.single_file: - ss.sub.append(tech_res.create_embed_statement("texture2d", diffuse_res)) - else: - ss.sub.append(tech_res.create_reference_statement("texture", diffuse_res)) - st.sub.append(ss) - tech_res.statements.append(st) + resources[mat_name] = mat_res - return tech_res + def export_technique(self, material_map, *, resources): + return create_technique_resource(material_map, resources, self.single_file) diff --git a/blender/io_mspgl/export_object.py b/blender/io_mspgl/export_object.py index 6e399b01..5b81ace4 100644 --- a/blender/io_mspgl/export_object.py +++ b/blender/io_mspgl/export_object.py @@ -105,29 +105,31 @@ class ObjectExporter: progress.push_task_slice("LOD {}".format(lod_index), i, len(lods)) material_map = None - mapped_count = sum(m.material_map for m in l.data.materials if m) + mapped_count = sum(m.render_mode!='EXTERNAL' and m.material_map for m in l.data.materials if m) if mapped_count: - material_map_tech = l.data.materials[0].technique - tech_mismatch = any(m.technique!=material_map_tech for m in l.data.materials) - if mapped_count!=len(l.data.materials) or tech_mismatch: + mmk = lambda m: m.shader if m.render_mode=='CUSTOM' else "" + material_map_key = mmk(l.data.materials[0]) + key_mismatch = any(mmk(m)!=material_map_key for m in l.data.materials) + if mapped_count!=len(l.data.materials) or key_mismatch: raise Exception("Conflicting settings in object materials") - if material_map_tech in material_maps: - material_map = material_maps[material_map_tech] + if material_map_key in material_maps: + material_map = material_maps[material_map_key] else: material_map = create_material_map(context, l.data.materials[0]) - material_maps[material_map_tech] = material_map + material_maps[material_map_key] = material_map - tech_name = "material_map_{}.tech".format(os.path.splitext(material_map_tech)[0]) + tech_name = "{}.tech".format(material_map.name) if tech_name not in resources: material_map_export.export_technique_resources(material_map, resources) resources[tech_name] = material_map_export.export_technique(material_map, resources=resources) elif l.material_slots and l.material_slots[0].material: material = l.material_slots[0].material - tech_name = material.name+".tech" - if tech_name not in resources: - material_export.export_technique_resources(material, resources) - resources[tech_name] = material_export.export_technique(material, resources=resources) + if material.render_mode!='EXTERNAL': + tech_name = material.name+".tech" + if tech_name not in resources: + material_export.export_technique_resources(material, resources) + resources[tech_name] = material_export.export_technique(material, resources=resources) elif "stub.tech" not in resources: resources["stub.tech"] = self.export_stub_technique() @@ -179,7 +181,7 @@ class ObjectExporter: tech_res = resources["stub.tech"] if tech_res.name!=prev_tech: - if material and not material.material_map and material.technique and not material.inherit_tech: + if material and material.render_mode=='EXTERNAL': lod_st.append(Statement("technique", material.technique)) elif not self.single_file: lod_st.append(obj_res.create_reference_statement("technique", tech_res)) @@ -201,5 +203,9 @@ class ObjectExporter: def export_stub_technique(self): from .datafile import Resource, Statement tech_res = Resource("stub.tech") - tech_res.statements.append(Statement("pass", "")) + pass_st = Statement("pass", "") + tech_res.statements.append(pass_st) + mat_st = Statement("material") + pass_st.sub.append(mat_st) + mat_st.sub.append(Statement("basic")) return tech_res diff --git a/blender/io_mspgl/export_texture.py b/blender/io_mspgl/export_texture.py index dd8191ce..3b220130 100644 --- a/blender/io_mspgl/export_texture.py +++ b/blender/io_mspgl/export_texture.py @@ -4,40 +4,53 @@ class TextureExporter: def __init__(self): self.inline_data = True - def export_texture(self, texture): + def export_texture(self, tex_node, usage='RGB'): + image = tex_node.image from .datafile import Resource, Statement, Token - tex_res = Resource(texture.name+".tex2d") + tex_res = Resource(image.name+".tex2d") - if texture.use_interpolation: - if texture.use_mipmap: + use_interpolation = tex_node.interpolation!='Closest' + if use_interpolation: + if tex_node.use_mipmap: tex_res.statements.append(Statement("filter", Token('LINEAR_MIPMAP_LINEAR'))) tex_res.statements.append(Statement("generate_mipmap", True)) else: tex_res.statements.append(Statement("filter", Token('LINEAR'))) - tex_res.statements.append(Statement("max_anisotropy", texture.filter_eccentricity)) + tex_res.statements.append(Statement("max_anisotropy", tex_node.max_anisotropy)) else: - if texture.use_mipmap: + if tex_node.use_mipmap: tex_res.statements.append(Statement("filter", Token('NEAREST_MIPMAP_NEAREST'))) tex_res.statements.append(Statement("generate_mipmap", True)) else: tex_res.statements.append(Statement("filter", Token('NEAREST'))) - fn = os.path.basename(texture.image.filepath) + colorspace = image.colorspace_settings.name + if usage=='RGBA': + fmt = 'SRGB_ALPHA' if colorspace=='sRGB' else 'RGBA' + elif usage=='GRAY': + if colorspace=='sRGB': + raise Exception("Grayscale textures with sRGB colorspace are not supported") + fmt = 'LUMINANCE' + else: + fmt = 'SRGB' if colorspace=='sRGB' else 'RGB' + + tex_res.statements.append(Statement("storage", Token(fmt), image.size[0], image.size[1])) + + fn = os.path.basename(image.filepath) if not self.inline_data and fn: tex_res.statements.append(Statement("external_image", fn)) else: texdata = "" - colorspace = texture.image.colorspace_settings.name - if texture.use_alpha: - fmt = 'SRGB_ALPHA' if colorspace=='sRGB' else 'RGBA' - for p in texture.image.pixels: + if usage=='RGBA': + for p in image.pixels: texdata += "\\x{:02X}".format(int(p*255)) + elif usage=='GRAY': + for i in range(0, len(image.pixels), 4): + texdata += "\\x{:02X}".format(image.pixels[i]) else: - fmt = 'SRGB' if colorspace=='sRGB' else 'RGB' - for i in range(0, len(texture.image.pixels), 4): + for i in range(0, len(image.pixels), 4): for j in range(3): - texdata += "\\x{:02X}".format(int(texture.image.pixels[i+j]*255)) - tex_res.statements.append(Statement("storage", Token(fmt), texture.image.size[0], texture.image.size[1])) + texdata += "\\x{:02X}".format(int(image.pixels[i+j]*255)) tex_res.statements.append(Statement("raw_data", texdata)) return tex_res diff --git a/blender/io_mspgl/material.py b/blender/io_mspgl/material.py index 470ffc57..b275caa3 100644 --- a/blender/io_mspgl/material.py +++ b/blender/io_mspgl/material.py @@ -1,20 +1,111 @@ import os +def get_linked_node_and_socket(node_tree, socket): + for l in node_tree.links: + if socket==l.to_socket: + return (l.from_node, l.from_socket) + elif socket==l.from_socket: + return (l.to_node, l.to_socket) + return (None, None) + +class MaterialProperty: + def __init__(self, value): + self.value = value + self.texture = None + self.tex_usage = None + + def set_from_input(self, node_tree, input_socket, alpha_socket=None): + if type(self.value)==tuple: + if alpha_socket: + self.value = input_socket.default_value[:len(self.value)-1]+(alpha_socket.default_value,) + else: + self.value = input_socket.default_value[:len(self.value)] + else: + self.value = input_socket.default_value + + from_node, _ = get_linked_node_and_socket(node_tree, input_socket) + alpha_from = None + if from_node: + if from_node.type=='NORMAL_MAP': + from_node, _ = get_linked_node_and_socket(node_tree, from_node.inputs["Color"]) + + if alpha_socket: + alpha_from, _ = get_linked_node_and_socket(node_tree, alpha_socket) + if alpha_from and alpha_from!=from_node: + raise Exception("Separate textures for color and alpha are not supported") + + if from_node.type=='TEX_IMAGE': + self.texture = from_node + if alpha_from: + self.tex_usage = 'RGBA' + elif type(self.value)==tuple: + self.tex_usage = 'RGB' + else: + self.tex_usage = 'GRAY' + else: + raise Exception("Unsupported property input node type "+from_node.type) + +class Material: + def __init__(self, material): + self.name = material.name + + self.base_color = MaterialProperty((0.8, 0.8, 0.8, 1.0)) + self.metalness = MaterialProperty(0.0) + self.roughness = MaterialProperty(0.5) + self.normal = MaterialProperty((0.0, 0.0, 0.1)) + self.emission = MaterialProperty((0.0, 0.0, 0.0)) + + self.render_mode = material.render_mode + self.technique = material.technique + self.shader = material.shader + + if self.render_mode=='EXTERNAL' and not self.technique: + raise Exception("Missing technique with external rendering mode") + elif self.render_mode=='CUSTOM' and not self.shader: + raise Exception("Missing shader with custom rendering mode") + + out_node = None + for n in material.node_tree.nodes: + if n.type=='OUTPUT_MATERIAL': + out_node = n + break + + if not out_node: + raise Exception("No material output node found") + + surface_node, _ = get_linked_node_and_socket(material.node_tree, out_node.inputs["Surface"]) + if not surface_node: + raise Exception("Material has no surface node") + elif surface_node.type!='BSDF_PRINCIPLED': + raise Exception("Unsupported surface node type "+surface_node.type) + + self.base_color.set_from_input(material.node_tree, surface_node.inputs["Base Color"], surface_node.inputs["Alpha"]) + self.metalness.set_from_input(material.node_tree, surface_node.inputs["Metallic"]) + self.roughness.set_from_input(material.node_tree, surface_node.inputs["Roughness"]) + self.normal.set_from_input(material.node_tree, surface_node.inputs["Normal"]) + self.emission.set_from_input(material.node_tree, surface_node.inputs["Emission"]) + + self.properties = (self.base_color, self.metalness, self.roughness, self.normal, self.emission) + + class MaterialMap: def __init__(self, materials): - self.technique = materials[0].technique - if self.technique: - self.name = "material_map_"+os.path.splitext(self.technique)[0] + self.render_mode = materials[0].render_mode + if self.render_mode=='EXTERNAL': + raise Exception("Material map with external render mode does not make sense") + + self.shader = materials[0].shader + if self.shader: + self.name = "material_map_"+os.path.splitext(self.shader)[0] else: self.name = "material_map" self.materials = materials self.material_names = [m.name for m in self.materials] - self.srgb_colors = materials[0].srgb_colors for m in self.materials: - if m.technique!=self.technique: - raise Exception("Conflicting techniques in MaterialMap constructor") - if m.srgb_colors!=self.srgb_colors: - raise Exception("Conflicting colorspace settings in MaterialMap constructor") + if m.render_mode!=self.render_mode: + raise Exception("Conflicting render modes in MaterialMap constructor") + if self.render_mode=='CUSTOM' and m.shader!=self.shader: + raise Exception("Conflicting shaders in MaterialMap constructor") count = len(self.materials) size = 1 @@ -27,12 +118,14 @@ class MaterialMap: from .util import get_colormap - cm = get_colormap(self.srgb_colors) - self.diffuse_data = "" - for m in self.materials: - diff = [int(cm(c)*255) for c in m.diffuse_color] - self.diffuse_data += "\\x{:02X}\\x{:02X}\\x{:02X}\\xFF".format(*diff) - self.diffuse_data += "\\x00\\x00\\x00\\x00"*(self.size[0]*self.size[1]-count) + cm = get_colormap(True) + self.base_color_data = "" + for m in map(Material, self.materials): + if any(p.texture for p in m.properties): + raise Exception("Texturing is incompatible with material map") + base_color = [int(cm(c)*255) for c in m.base_color.value] + self.base_color_data += "\\x{:02X}\\x{:02X}\\x{:02X}\\xFF".format(*base_color) + self.base_color_data += "\\x00\\x00\\x00\\x00"*(self.size[0]*self.size[1]-count) def get_material_uv(self, material): index = self.material_names.index(material.name) diff --git a/blender/io_mspgl/properties.py b/blender/io_mspgl/properties.py index ce4e692f..0605ba0c 100644 --- a/blender/io_mspgl/properties.py +++ b/blender/io_mspgl/properties.py @@ -64,36 +64,40 @@ class MspGLMaterialProperties(bpy.types.Panel): if not mat: return - self.layout.prop(mat, "technique") - self.layout.prop(mat, "inherit_tech") - if mat.inherit_tech: - self.layout.prop(mat, "override_material") - self.layout.prop(mat, "srgb_colors") + self.layout_prop(mat, "render_mode") + if mat.render_mode=='CUSTOM': + self.layout_prop(mat, "shader") + elif mat.render_mode=='EXTERNAL': + self.layout_prop(mat, "technique") self.layout.prop(mat, "array_atlas") if mat.array_atlas: self.layout.prop(mat, "array_layer") - self.layout.prop(mat, "material_map") + if mat.render_mode!='EXTERNAL': + self.layout.prop(mat, "material_map") -class MspGLTextureProperties(bpy.types.Panel): - bl_idname = "TEXTURE_PT_mspgl_properties" +class MspGLTextureNodeProperties(bpy.types.Panel): + bl_idname = "NODE_PT_mspgl_properties" bl_label = "MspGL properties" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "texture" + bl_space_type = "NODE_EDITOR" + bl_region_type = "UI" + bl_category = "Item" @classmethod def poll(cls, context): - mat = context.active_object.active_material - return mat is not None and mat.active_texture is not None + node = context.active_node + return node and node.type=='TEX_IMAGE' def draw(self, context): - tex = context.active_object.active_material.active_texture - if not tex: + node = context.active_node + if not node: return - self.layout.prop(tex, "default_filter") + self.layout.prop(node, "default_filter") + if not node.default_filter: + self.layout.prop(node, "use_mipmap") + self.layout.prop(node, "max_anisotropy") -classes = [MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureProperties] +classes = [MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureNodeProperties] def register_properties(): bpy.types.Mesh.winding_test = bpy.props.BoolProperty(name="Winding test", description="Perform winding test to skip back faces") @@ -115,15 +119,19 @@ def register_properties(): bpy.types.Object.lod_for_parent = bpy.props.BoolProperty(name="LoD for parent", description="This object is a level of detail for its parent") bpy.types.Object.lod_index = bpy.props.IntProperty(name="LoD index", description="Index of the level of detail", min=1, max=16, default=1) - bpy.types.Material.technique = bpy.props.StringProperty(name="Technique", description="Name of an external technique to use for rendering") - bpy.types.Material.inherit_tech = bpy.props.BoolProperty(name="Inherit technique", description="Inherit from the technique to customize textures") - bpy.types.Material.override_material = bpy.props.BoolProperty(name="Override material", description="Override material in the inherited technique as well", default=True) - bpy.types.Material.srgb_colors = bpy.props.BoolProperty(name="sRGB colors", description="Export material colors as sRGB instead of linear", default=True) + bpy.types.Material.render_mode = bpy.props.EnumProperty(name="Render mode", description="How this material should be rendered", default="BUILTIN", + items=(("BUILTIN", "Built-in", "Use built-in shaders"), + ("CUSTOM", "Custom shader", "Use a custom shader"), + ("EXTERNAL", "External technique", "Use an externally defined technique"))) + bpy.types.Material.technique = bpy.props.StringProperty(name="Custom technique", description="Name of an external technique to use for rendering") + bpy.types.Material.shader = bpy.props.StringProperty(name="Custom shader", description="Name of an external technique to use for rendering") bpy.types.Material.array_atlas = bpy.props.BoolProperty(name="Texture array atlas", description="The material is stored in a texture array") bpy.types.Material.array_layer = bpy.props.IntProperty("Texture array layer", description="Layer of the texture array atlas to use") bpy.types.Material.material_map = bpy.props.BoolProperty(name="Material map", description="Make this material part of a material map") - bpy.types.Texture.default_filter = bpy.props.BoolProperty(name="Default filter", description="Let the loading program determine filtering options") + bpy.types.ShaderNodeTexImage.default_filter = bpy.props.BoolProperty(name="Default filter", description="Let the loading program determine filtering options") + bpy.types.ShaderNodeTexImage.use_mipmap = bpy.props.BoolProperty(name="Use mipmaps", description="Use mipmaps (automatically generated) for the texture", default=True) + bpy.types.ShaderNodeTexImage.max_anisotropy = bpy.props.FloatProperty(name="Maximum anisotropy", description="Maximum anisotropy to use in texture filtering", min=1, max=16, default=1) for c in classes: bpy.utils.register_class(c)