diff --git a/rust_material/rust_material.zip b/rust_material/rust_material.zip new file mode 100644 index 0000000..b57ddcd Binary files /dev/null and b/rust_material/rust_material.zip differ diff --git a/rust_material/rust_material/__init__.py b/rust_material/rust_material/__init__.py new file mode 100644 index 0000000..b0403d7 --- /dev/null +++ b/rust_material/rust_material/__init__.py @@ -0,0 +1,110 @@ +bl_info = { + "name": "Rust Material Generator", + "author": "Saikiran Shet", + "version": (1, 2), + "blender": (4, 2, 0), + "location": "View3D > Sidebar > Rust Material", + "description": "Generates a rust material with textures and interactive controls", + "category": "Material", +} + +import bpy +import importlib + +# safe import so reloading in Blender text editor doesn't break +from . import rust_generator +importlib.reload(rust_generator) +from .rust_generator import create_rust_material + +# ------------------ Property Group ------------------ # +class RustProperties(bpy.types.PropertyGroup): + roughness: bpy.props.FloatProperty( + name="Roughness", + description="Adjust material roughness", + default=0.5, + min=0.0, + max=1.0, + step=0.01 + ) + color_tint: bpy.props.FloatVectorProperty( + name="Color Tint", + description="Tint the rust material color", + subtype='COLOR', + size=3, + min=0.0, + max=1.0, + default=(1.0, 1.0, 1.0) + ) + normal_strength: bpy.props.FloatProperty( + name="Normal Strength", + description="Strength of the normal map", + default=1.0, + min=0.0, + max=5.0, + step=0.1 + ) + displacement_height: bpy.props.FloatProperty( + name="Displacement Height", + description="Height multiplier for displacement", + default=0.05, + min=0.0, + max=1.0, + step=0.01 + ) + +# ------------------ Operator ------------------ # +class RUST_OT_generate(bpy.types.Operator): + bl_idname = "rust.generate_material" + bl_label = "Generate Rust Material" + bl_description = "Create or update a rust material with textures" + + def execute(self, context): + props = context.scene.rust_props + try: + create_rust_material( + roughness=props.roughness, + color_tint=props.color_tint, + normal_strength=props.normal_strength, + disp_height=props.displacement_height + ) + self.report({'INFO'}, "Rust Material Created / Updated Successfully!") + except Exception as e: + # also print to console for full traceback + import traceback + traceback.print_exc() + self.report({'ERROR'}, f"Rust addon error: {str(e)}") + return {'FINISHED'} + +# ------------------ Panel ------------------ # +class RUST_PT_panel(bpy.types.Panel): + bl_label = "Rust Material Generator" + bl_idname = "RUST_PT_panel" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Rust Material' + + def draw(self, context): + layout = self.layout + props = context.scene.rust_props + + layout.prop(props, "roughness") + layout.prop(props, "normal_strength") + layout.prop(props, "displacement_height") + layout.prop(props, "color_tint") + layout.operator("rust.generate_material", text="Create / Update Rust Material") + +# ------------------ Registration ------------------ # +classes = [RustProperties, RUST_OT_generate, RUST_PT_panel] + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.Scene.rust_props = bpy.props.PointerProperty(type=RustProperties) + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) + del bpy.types.Scene.rust_props + +if __name__ == "__main__": + register() diff --git a/rust_material/rust_material/assets/Metal056C.png b/rust_material/rust_material/assets/Metal056C.png new file mode 100644 index 0000000..e0f5a4d Binary files /dev/null and b/rust_material/rust_material/assets/Metal056C.png differ diff --git a/rust_material/rust_material/assets/Metal056C_4K-JPG_Color.jpg b/rust_material/rust_material/assets/Metal056C_4K-JPG_Color.jpg new file mode 100644 index 0000000..f934711 Binary files /dev/null and b/rust_material/rust_material/assets/Metal056C_4K-JPG_Color.jpg differ diff --git a/rust_material/rust_material/assets/Metal056C_4K-JPG_Displacement.jpg b/rust_material/rust_material/assets/Metal056C_4K-JPG_Displacement.jpg new file mode 100644 index 0000000..a56982e Binary files /dev/null and b/rust_material/rust_material/assets/Metal056C_4K-JPG_Displacement.jpg differ diff --git a/rust_material/rust_material/assets/Metal056C_4K-JPG_Metalness.jpg b/rust_material/rust_material/assets/Metal056C_4K-JPG_Metalness.jpg new file mode 100644 index 0000000..909fce2 Binary files /dev/null and b/rust_material/rust_material/assets/Metal056C_4K-JPG_Metalness.jpg differ diff --git a/rust_material/rust_material/assets/Metal056C_4K-JPG_NormalGL.jpg b/rust_material/rust_material/assets/Metal056C_4K-JPG_NormalGL.jpg new file mode 100644 index 0000000..85059db Binary files /dev/null and b/rust_material/rust_material/assets/Metal056C_4K-JPG_NormalGL.jpg differ diff --git a/rust_material/rust_material/assets/Metal056C_4K-JPG_Roughness.jpg b/rust_material/rust_material/assets/Metal056C_4K-JPG_Roughness.jpg new file mode 100644 index 0000000..982f112 Binary files /dev/null and b/rust_material/rust_material/assets/Metal056C_4K-JPG_Roughness.jpg differ diff --git a/rust_material/rust_material/assets/Rust_Material.blend.blend b/rust_material/rust_material/assets/Rust_Material.blend.blend new file mode 100644 index 0000000..5940350 Binary files /dev/null and b/rust_material/rust_material/assets/Rust_Material.blend.blend differ diff --git a/rust_material/rust_material/rust_generator.py b/rust_material/rust_material/rust_generator.py new file mode 100644 index 0000000..dbf0b41 --- /dev/null +++ b/rust_material/rust_material/rust_generator.py @@ -0,0 +1,126 @@ +import bpy +import os + +# filenames expected in assets folder (edit if your names differ) +TEXTURES = { + "Base Color": "Metal056C_4K-JPG_Color.jpg", + "Normal": "Metal056C_4K-JPG_NormalDX.jpg", + "Displacement": "Metal056C_4K-JPG_Displacement.jpg", + "Metalness": "Metal056C_4K-JPG_Metalness.jpg", +} + +def asset_path(filename): + addon_dir = os.path.dirname(__file__) + return os.path.join(addon_dir, "assets", filename) + +def load_image_safe(filename): + """Load an image if present. Returns image or None and prints helpful message.""" + path = asset_path(filename) + if os.path.exists(path): + try: + # reuse loaded image if already in bpy.data.images + for img in bpy.data.images: + if os.path.abspath(bpy.path.abspath(img.filepath)) == os.path.abspath(path): + return img + return bpy.data.images.load(path) + except Exception as e: + raise RuntimeError(f"Failed to load image '{path}': {e}") + else: + # do not raise here — return None so we can fall back + print(f"[Rust Addon] Texture not found: {path}") + return None + +def ensure_non_color(image): + """Set colorspace appropriately for normal and non-color maps.""" + if image is None: + return + try: + # Normal maps and data maps should use Non-Color + image.colorspace_settings.name = 'Non-Color' + except Exception: + # older Blender versions or other issues may throw — ignore + pass + +def create_rust_material(roughness=0.5, color_tint=(1.0,1.0,1.0), normal_strength=1.0, disp_height=0.05): + mat_name = "RustMaterial" + # create or reuse material + mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name) + mat.use_nodes = True + nodes = mat.node_tree.nodes + links = mat.node_tree.links + + # clear nodes cleanly + nodes.clear() + + # Output and Principled + output = nodes.new("ShaderNodeOutputMaterial"); output.location = (600, 0) + bsdf = nodes.new("ShaderNodeBsdfPrincipled"); bsdf.location = (300, 0) + bsdf.inputs['Roughness'].default_value = roughness + links.new(bsdf.outputs['BSDF'], output.inputs['Surface']) + + # Tint node (mix multiply) + tint = nodes.new("ShaderNodeMixRGB"); tint.blend_type = 'MULTIPLY' + tint.inputs['Fac'].default_value = 1.0 + tint.inputs['Color2'].default_value = (*color_tint, 1.0) + tint.location = (0, 200) + links.new(tint.outputs['Color'], bsdf.inputs['Base Color']) + + # load textures (safe) + imgs = {} + for key, fname in TEXTURES.items(): + imgs[key] = load_image_safe(fname) + + # Base Color + if imgs["Base Color"]: + tex_base = nodes.new("ShaderNodeTexImage"); tex_base.image = imgs["Base Color"] + tex_base.location = (-400, 200) + links.new(tex_base.outputs['Color'], tint.inputs['Color1']) + else: + # no texture: set tint directly on base color + bsdf.inputs['Base Color'].default_value = (*color_tint, 1.0) + + # Normal + if imgs["Normal"]: + tex_norm = nodes.new("ShaderNodeTexImage"); tex_norm.image = imgs["Normal"] + tex_norm.location = (-400, 0) + ensure_non_color(tex_norm.image) + tex_norm.image.colorspace_settings.name = 'Non-Color' + normal_map = nodes.new("ShaderNodeNormalMap"); normal_map.location = (-150, 0) + normal_map.inputs['Strength'].default_value = normal_strength + links.new(tex_norm.outputs['Color'], normal_map.inputs['Color']) + links.new(normal_map.outputs['Normal'], bsdf.inputs['Normal']) + + # Metalness + if imgs["Metalness"]: + tex_metal = nodes.new("ShaderNodeTexImage"); tex_metal.image = imgs["Metalness"] + tex_metal.location = (-400, -200) + ensure_non_color(tex_metal.image) + links.new(tex_metal.outputs['Color'], bsdf.inputs['Metallic']) + else: + bsdf.inputs['Metallic'].default_value = 0.0 + + # Displacement (connect to Material Output displacement) + if imgs["Displacement"]: + tex_disp = nodes.new("ShaderNodeTexImage"); tex_disp.image = imgs["Displacement"] + tex_disp.location = (-400, -400) + ensure_non_color(tex_disp.image) + # Convert color to height (in case it's colored): use RGB to BW or separate + rgb2bw = nodes.new("ShaderNodeRGBToBW"); rgb2bw.location = (-200, -400) + links.new(tex_disp.outputs['Color'], rgb2bw.inputs['Color']) + disp_node = nodes.new("ShaderNodeDisplacement"); disp_node.location = (100, -400) + disp_node.inputs['Scale'].default_value = disp_height + links.new(rgb2bw.outputs['Val'], disp_node.inputs['Height']) + links.new(disp_node.outputs['Displacement'], output.inputs['Displacement']) + # Also set material settings for displacement (if using cycles) + try: + # enable displacement in material settings if available + mat.cycles.displacement_method = 'BOTH' + except Exception: + pass + + # tidy node locations a bit (optional) + for n in nodes: + if n.location.x < -500: + n.location.x = -500 + + print("[Rust Addon] Material created/updated:", mat_name) diff --git a/rust_material/test_rust_gen.blend b/rust_material/test_rust_gen.blend new file mode 100644 index 0000000..4c8d5e1 Binary files /dev/null and b/rust_material/test_rust_gen.blend differ