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
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):
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)
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()
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))
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
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
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
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)
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")
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)