I’m releasing my first Blender plugin to the public! It’s a pretty basic one compared to the paid options on the market, but I think it works fine, and it could serve as a foundation for future projects for someone else.
Story time:
I recently started learning and working with Unreal Engine 5.6, and I chose my old favorite game to “recreate” in the engine as my first project.
But I quickly realized I’d need the original assets from the game to speed up my workflow. So the first step was getting a Blender plugin that could import Silkroad assets into Blender, then export them as .fbx files to use in other engines like UE 5.6.
I decided to release this to the community — maybe someone else is looking for something similar.
But I quickly realized I’d need the original assets from the game to speed up my workflow. So the first step was getting a Blender plugin that could import Silkroad assets into Blender, then export them as .fbx files to use in other engines like UE 5.6.
I decided to release this to the community — maybe someone else is looking for something similar.
Plugin Installation:
• First, download Blender (v4.5.0). I just grabbed it quickly from the Steam Store.
(From what I’ve seen, the Steam version might include a few extra pre-installed addons — like instant .dds support — which could be handy later.)
• Once installed, open up Blender.
• Click on Edit > Preferences...
• In the new window, go to the Add-ons section.
• In the top-right corner, click the small arrow and select Install from Disk...
• Browse to the SROBJ_import_export_final_HU.py file and select it.
• Finally, make sure the plugin is enabled by checking the box next to its name.
Installation guide in pictures:
How to import your first assets:
How to get and convert weapon files to .srobj and .dds OR .png:
Download any Silkroad Online client — iSRO or vSRO based, it doesn’t really matter now.
Open the .pk2 files (like Media.pk2, Data.pk2, etc.) using a PK2 extractor.
(I’ve attached PK2 tools in this thread’s downloads section as well.)
Search for the asset you want. In this tutorial, I’m using the D12 CH Bow, which includes:
• bow_12.bms (model)
• bow_12.ddj (texture)
Use the Windows search bar and type bow_12.* to find them quickly.
Example file paths from the extracted Data.pk2:
• \Data\prim\mesh\item\china\weapon\bow_12.bms
• \Data\prim\mtrl\item\china\weapon\bow_12.ddj
Copy both files to your desktop or any folder you prefer.
Open ModelConverter.exe, browse to the .bms file, and convert it to .srobj.
✅ Make sure the "Convert to .SrObj (multibone!)" box is checked!
Do the same with the .ddj file: open DDS_DDJ.exe, browse for the file, and convert it to .dds.
Now you're ready to import everything into Blender.
Time to import your first asset:
Start a new General project in Blender and press the N key to open the side toolbar.
In the plugin panel, import:
• the .srobj file in the first field
• and the .dds or .png file in the second field (for textures)
Make sure the Flip UV (V-axis) box is unchecked.
(If the texture doesn’t match the UV map — aka your texture looks like sh!t — try again with ✅ Flip UV checked.)
If you want to use the asset in another game engine, just export it from Blender as an .fbx file.
Final words:
The “final” in the plugin name doesn’t mean it’s perfect! It just means I’m done working on it, and I won’t release updates for it.
Also, sorry for the Hungarian comments in the code! 😅 I tried writing them in English, but since it’s not my native language, I just kept messing up and losing focus — so I stuck with what felt natural while working.
Also, sorry for the Hungarian comments in the code! 😅 I tried writing them in English, but since it’s not my native language, I just kept messing up and losing focus — so I stuck with what felt natural while working.
That’s all for now — but I’m already working on the next plugin, which will be able to import directly from Joymax file formats, without any annoying or unnecessary conversions.
Be patient — I’m doing my best!
ONLY IMPORT PLUGIN: SROBJ_import_only_final.py (Blender v4.5.0)
import bpy
import os
import bmesh
import mathutils # mathutils importálva
from bpy.props import StringProperty, PointerProperty, BoolProperty
from bpy.types import Operator, Panel, PropertyGroup
from bpy_extras.io_utils import ImportHelper, ExportHelper
bl_info = {
"name": "Silkroad OBJ Importer (Advanced)", # Importer only
"author": "szabo176",
"blender": (4, 1, 0), # Blender 4.x compatibility: generally recommend 4.1.0, or exact desired version
"version": (2, 5, 2), # Plugin version updated due to fixes
"location": "View3D Sidebar > Silkroad Tab",
"description": "Import .srobj files with vertex groups, UVs and automatic texture assignment (DDS/PNG/JPG support).", # Description updated
"category": "Import-Export"
}
# --- Helper Functions ---
def name_exists(name):
"""Checks if an object with this name already exists in Blender."""
return name in bpy.data.objects
# --- Import Function (bmesh based, with texture assignment) ---
def import_srobj_advanced(filepath, texturepath, flip_uv_v=False):
"""
Import Silkroad OBJ (.srobj) file using bmesh,
with texture assignment and optional UV V-flip.
"""
verts = []
uv_coords_from_file = []
faces_data = []
try:
with open(filepath, 'r', encoding='utf-8') as f: # Added encoding='utf-8' for more robust file reading
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if not parts: # Check for empty lines after split
continue
if parts[0] == "o":
# After 'o' line, we read the object name, but in srobj format it's not always used
# The code later generates the object name from the filename, which is fine.
# If the 'o' name in the file needs to be used:
# obj_name_from_file = parts[1] if len(parts) > 1 else "unnamed_object"
pass # Currently not using it, but good to know for future reference
elif parts[0] == "v":
# Silkroad (X, Z, Y) -> Blender (X, -Y, Z)
# This conversion is specific to Silkroad and is correct if this is the desired behavior.
if len(parts) < 4: raise ValueError(f"Invalid 'v' line format: {line}")
x, z, y = map(float, parts[1:4])
verts.append((x, -y, z))
elif parts[0] == "vt":
if len(parts) < 3: raise ValueError(f"Invalid 'vt' line format: {line}")
u, v = map(float, parts[1:3])
uv_coords_from_file.append((u, v))
elif parts[0] == "f":
if len(parts) < 4: raise ValueError(f"Invalid 'f' line format: {line}. Faces require at least 3 vertices.")
face_verts_uvs = []
# Taking only the first 3 vertices (triangles)
# Additional check to ensure string is not empty before converting to int
for part in parts[1:4]:
indices = part.split('/')
if not indices[0]: raise ValueError(f"Empty vertex index in 'f' line: {line}")
v_idx = int(indices[0]) - 1
uv_idx = -1
if len(indices) > 1 and indices[1]: # Check if UV index exists and is not empty
uv_idx = int(indices[1]) - 1
face_verts_uvs.append((v_idx, uv_idx))
if len(face_verts_uvs) == 3:
faces_data.append(tuple(face_verts_uvs))
else:
print(f"Warning: Non-triangular face found in SROBJ file, skipping: {line}")
except Exception as e:
raise Exception(f"Error reading SROBJ file '{filepath}': {e}")
# Create Mesh and Object using bmesh
base_name = os.path.basename(filepath).split('.')[0]
final_obj_name = base_name
idx = 0
while name_exists(final_obj_name):
idx += 1
final_obj_name = f"{base_name}.{idx:03d}"
mesh = bpy.data.meshes.new(final_obj_name + "_Mesh")
obj = bpy.data.objects.new(final_obj_name, mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
bpy.context.view_layer.update() # Can be important when adding the object
bm = bmesh.new()
# Original: bm.from_mesh(mesh) - not needed if starting with an empty mesh
bm_verts = [bm.verts.new(v) for v in verts]
bm.verts.ensure_lookup_table()
# Important: Create UV layer after bm.from_mesh(mesh) or bm.verts.new() calls are finished,
# and bmesh already contains vertices.
uv_layer = bm.loops.layers.uv.new("UVMap")
# Assign Faces and UVs
for face_info in faces_data:
try:
# Ensure vertex indices are valid
bm_face_verts = []
for v_idx, _ in face_info:
if v_idx < 0 or v_idx >= len(bm_verts):
raise IndexError(f"Invalid vertex index ({v_idx}) in face: {face_info}")
bm_face_verts.append(bm_verts[v_idx])
# Check if face contains non-duplicate vertices
if len(set(bm_face_verts)) != 3:
print(f"Warning: Duplicate vertices in face {face_info}. Skipping.")
continue
bm_face = bm.faces.new(tuple(bm_face_verts))
bm_face.normal_update() # Update normal after face creation
for loop, (v_idx, uv_idx) in zip(bm_face.loops, face_info):
if uv_idx != -1 and uv_idx < len(uv_coords_from_file):
uv_coord = uv_coords_from_file[uv_idx]
# UV V-flip can also be applied here at bmesh level if needed
# Currently, it's in the material node, which is generally better as it can be modified
loop[uv_layer].uv = uv_coord
else:
print(f"Warning: Invalid UV index {uv_idx} for vertex {v_idx} in face {face_info}. UV not set.")
except ValueError as e:
print(f"Warning: Failed to create face with data {face_info} - {e}. Skipping.")
except IndexError as e:
print(f"Error: Invalid vertex or UV index in face {face_info}: {e}. Skipping.")
except Exception as e:
print(f"Unexpected error occurred while processing face {face_info}: {e}. Skipping.")
# Check geometry in bmesh (optional, can be slow for large files)
# try:
# bm.verts.ensure_lookup_table()
# bm.edges.ensure_lookup_table()
# bm.faces.ensure_lookup_table()
# except Exception as e:
# print(f"Warning: Error updating bmesh lookup tables: {e}")
bm.to_mesh(mesh)
mesh.update()
bm.free()
# --- Texture and Material Assignment ---
if texturepath and os.path.exists(texturepath):
try:
mat = bpy.data.materials.new(name=final_obj_name + "_Material")
mat.use_nodes = True
obj.data.materials.append(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# Delete all existing nodes for a clean start
for node in nodes:
nodes.remove(node)
principled_bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
principled_bsdf.location = (400, 0)
material_output = nodes.new(type='ShaderNodeOutputMaterial')
material_output.location = (600, 0)
image_texture = nodes.new(type='ShaderNodeTexImage')
# Check if image loading is successful
try:
image_texture.image = bpy.data.images.load(texturepath)
except RuntimeError as e: # More specific error handling for image loading
print(f"Error loading texture '{texturepath}': {e}. Please check file format and Blender DDS support.")
# Do not raise exception here, so that material can be created without texture at least
image_texture.image = None
image_texture.location = (0, 0)
# Add UV Map node to explicitly use the "UVMap" layer
uv_map_node = nodes.new(type='ShaderNodeUVMap')
uv_map_node.location = (-600, 100)
uv_map_node.uv_map = "UVMap" # Ensure it uses the correct UV Map
mapping = nodes.new(type='ShaderNodeMapping')
mapping.location = (-200, 0)
if flip_uv_v:
mapping.inputs['Scale'].default_value[1] = -1
# Modify links to include UV Map node
links.new(uv_map_node.outputs['UV'], mapping.inputs['Vector'])
links.new(mapping.outputs['Vector'], image_texture.inputs['Vector'])
# Only link texture if it loaded successfully
if image_texture.image:
links.new(image_texture.outputs['Color'], principled_bsdf.inputs['Base Color'])
else:
print("Warning: Texture loading failed, setting base color on material.")
# We can set a base color if there is no texture
principled_bsdf.inputs['Base Color'].default_value = (0.8, 0.8, 0.8, 1.0) # Grey
links.new(principled_bsdf.outputs['BSDF'], material_output.inputs['Surface'])
image_texture.interpolation = 'Closest'
except Exception as e:
print(f"Error assigning texture or material: {e}")
# print(traceback.format_exc()) # For development: print traceback
raise # Important to propagate error if critical
else:
print(f"No texture path specified or file does not exist: '{texturepath}'. Creating material without texture.")
mat = bpy.data.materials.new(name=final_obj_name + "_Material_NoTex")
obj.data.materials.append(mat)
# --- Export Function (REMOVED) ---
# The entire export_srobj function is removed.
# --- UI and Operators ---
class SROBJProperties(PropertyGroup):
"""Properties for import settings.""" # Description updated
import_srobj_path: StringProperty(
name="SROBJ File",
subtype='FILE_PATH',
default="",
description="Select the .srobj file to import."
)
import_texture_path: StringProperty(
name="Texture File",
subtype='FILE_PATH',
default="",
description="Select the texture file (.dds, .png, .jpg) to assign to the object."
)
import_flip_uv_v: BoolProperty(
name="Flip UV (V-axis)",
description="Flip imported UV coordinates vertically (useful for some game textures).",
default=False # Changed default to False (unchecked)
)
# export_srobj_path and related properties are removed
class SROBJ_OT_ImportUI(Operator):
"""Import SROBJ from UI"""
bl_idname = "srobj.import_ui"
bl_label = "Import SROBJ"
bl_description = "Import an SROBJ file with optional texture assignment."
def execute(self, context):
props = context.scene.srobj_props
if not props.import_srobj_path:
self.report({'ERROR'}, "SROBJ file path is not set.")
return {'CANCELLED'}
try:
import_srobj_advanced(props.import_srobj_path, props.import_texture_path, props.import_flip_uv_v)
self.report({'INFO'}, f"Successfully imported: {os.path.basename(props.import_srobj_path)}")
except Exception as e:
self.report({'ERROR'}, f"Import failed: {e}")
print(f"SROBJ Import Error: {e}") # Also print detailed error to console
return {'CANCELLED'}
return {'FINISHED'}
# SROBJ_OT_ExportUI operator is removed.
class VIEW3D_PT_srobj_panel(Panel):
"""SROBJ Importer UI Panel.""" # Title updated
bl_label = "Silkroad SROBJ Tools"
bl_idname = "VIEW3D_PT_srobj_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Silkroad'
def draw(self, context):
layout = self.layout
props = context.scene.srobj_props
# --- Import Section ---
box = layout.box()
box.label(text="Import SROBJ", icon='IMPORT')
box.prop(props, "import_srobj_path")
box.prop(props, "import_texture_path")
box.prop(props, "import_flip_uv_v")
box.operator(SROBJ_OT_ImportUI.bl_idname, text="Import SROBJ")
# --- Export Section (REMOVED) ---
# The entire export section from draw method is removed.
# --- Registration ---
classes = (
SROBJProperties,
SROBJ_OT_ImportUI,
# SROBJ_OT_ExportUI is removed
VIEW3D_PT_srobj_panel
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.srobj_props = PointerProperty(type=SROBJProperties)
def unregister():
# Important: reverse order for dependencies during unregister
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.srobj_props
if __name__ == "__main__":
register()
import os
import bmesh
import mathutils # mathutils importálva
from bpy.props import StringProperty, PointerProperty, BoolProperty
from bpy.types import Operator, Panel, PropertyGroup
from bpy_extras.io_utils import ImportHelper, ExportHelper
bl_info = {
"name": "Silkroad OBJ Importer (Advanced)", # Importer only
"author": "szabo176",
"blender": (4, 1, 0), # Blender 4.x compatibility: generally recommend 4.1.0, or exact desired version
"version": (2, 5, 2), # Plugin version updated due to fixes
"location": "View3D Sidebar > Silkroad Tab",
"description": "Import .srobj files with vertex groups, UVs and automatic texture assignment (DDS/PNG/JPG support).", # Description updated
"category": "Import-Export"
}
# --- Helper Functions ---
def name_exists(name):
"""Checks if an object with this name already exists in Blender."""
return name in bpy.data.objects
# --- Import Function (bmesh based, with texture assignment) ---
def import_srobj_advanced(filepath, texturepath, flip_uv_v=False):
"""
Import Silkroad OBJ (.srobj) file using bmesh,
with texture assignment and optional UV V-flip.
"""
verts = []
uv_coords_from_file = []
faces_data = []
try:
with open(filepath, 'r', encoding='utf-8') as f: # Added encoding='utf-8' for more robust file reading
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if not parts: # Check for empty lines after split
continue
if parts[0] == "o":
# After 'o' line, we read the object name, but in srobj format it's not always used
# The code later generates the object name from the filename, which is fine.
# If the 'o' name in the file needs to be used:
# obj_name_from_file = parts[1] if len(parts) > 1 else "unnamed_object"
pass # Currently not using it, but good to know for future reference
elif parts[0] == "v":
# Silkroad (X, Z, Y) -> Blender (X, -Y, Z)
# This conversion is specific to Silkroad and is correct if this is the desired behavior.
if len(parts) < 4: raise ValueError(f"Invalid 'v' line format: {line}")
x, z, y = map(float, parts[1:4])
verts.append((x, -y, z))
elif parts[0] == "vt":
if len(parts) < 3: raise ValueError(f"Invalid 'vt' line format: {line}")
u, v = map(float, parts[1:3])
uv_coords_from_file.append((u, v))
elif parts[0] == "f":
if len(parts) < 4: raise ValueError(f"Invalid 'f' line format: {line}. Faces require at least 3 vertices.")
face_verts_uvs = []
# Taking only the first 3 vertices (triangles)
# Additional check to ensure string is not empty before converting to int
for part in parts[1:4]:
indices = part.split('/')
if not indices[0]: raise ValueError(f"Empty vertex index in 'f' line: {line}")
v_idx = int(indices[0]) - 1
uv_idx = -1
if len(indices) > 1 and indices[1]: # Check if UV index exists and is not empty
uv_idx = int(indices[1]) - 1
face_verts_uvs.append((v_idx, uv_idx))
if len(face_verts_uvs) == 3:
faces_data.append(tuple(face_verts_uvs))
else:
print(f"Warning: Non-triangular face found in SROBJ file, skipping: {line}")
except Exception as e:
raise Exception(f"Error reading SROBJ file '{filepath}': {e}")
# Create Mesh and Object using bmesh
base_name = os.path.basename(filepath).split('.')[0]
final_obj_name = base_name
idx = 0
while name_exists(final_obj_name):
idx += 1
final_obj_name = f"{base_name}.{idx:03d}"
mesh = bpy.data.meshes.new(final_obj_name + "_Mesh")
obj = bpy.data.objects.new(final_obj_name, mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
bpy.context.view_layer.update() # Can be important when adding the object
bm = bmesh.new()
# Original: bm.from_mesh(mesh) - not needed if starting with an empty mesh
bm_verts = [bm.verts.new(v) for v in verts]
bm.verts.ensure_lookup_table()
# Important: Create UV layer after bm.from_mesh(mesh) or bm.verts.new() calls are finished,
# and bmesh already contains vertices.
uv_layer = bm.loops.layers.uv.new("UVMap")
# Assign Faces and UVs
for face_info in faces_data:
try:
# Ensure vertex indices are valid
bm_face_verts = []
for v_idx, _ in face_info:
if v_idx < 0 or v_idx >= len(bm_verts):
raise IndexError(f"Invalid vertex index ({v_idx}) in face: {face_info}")
bm_face_verts.append(bm_verts[v_idx])
# Check if face contains non-duplicate vertices
if len(set(bm_face_verts)) != 3:
print(f"Warning: Duplicate vertices in face {face_info}. Skipping.")
continue
bm_face = bm.faces.new(tuple(bm_face_verts))
bm_face.normal_update() # Update normal after face creation
for loop, (v_idx, uv_idx) in zip(bm_face.loops, face_info):
if uv_idx != -1 and uv_idx < len(uv_coords_from_file):
uv_coord = uv_coords_from_file[uv_idx]
# UV V-flip can also be applied here at bmesh level if needed
# Currently, it's in the material node, which is generally better as it can be modified
loop[uv_layer].uv = uv_coord
else:
print(f"Warning: Invalid UV index {uv_idx} for vertex {v_idx} in face {face_info}. UV not set.")
except ValueError as e:
print(f"Warning: Failed to create face with data {face_info} - {e}. Skipping.")
except IndexError as e:
print(f"Error: Invalid vertex or UV index in face {face_info}: {e}. Skipping.")
except Exception as e:
print(f"Unexpected error occurred while processing face {face_info}: {e}. Skipping.")
# Check geometry in bmesh (optional, can be slow for large files)
# try:
# bm.verts.ensure_lookup_table()
# bm.edges.ensure_lookup_table()
# bm.faces.ensure_lookup_table()
# except Exception as e:
# print(f"Warning: Error updating bmesh lookup tables: {e}")
bm.to_mesh(mesh)
mesh.update()
bm.free()
# --- Texture and Material Assignment ---
if texturepath and os.path.exists(texturepath):
try:
mat = bpy.data.materials.new(name=final_obj_name + "_Material")
mat.use_nodes = True
obj.data.materials.append(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# Delete all existing nodes for a clean start
for node in nodes:
nodes.remove(node)
principled_bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
principled_bsdf.location = (400, 0)
material_output = nodes.new(type='ShaderNodeOutputMaterial')
material_output.location = (600, 0)
image_texture = nodes.new(type='ShaderNodeTexImage')
# Check if image loading is successful
try:
image_texture.image = bpy.data.images.load(texturepath)
except RuntimeError as e: # More specific error handling for image loading
print(f"Error loading texture '{texturepath}': {e}. Please check file format and Blender DDS support.")
# Do not raise exception here, so that material can be created without texture at least
image_texture.image = None
image_texture.location = (0, 0)
# Add UV Map node to explicitly use the "UVMap" layer
uv_map_node = nodes.new(type='ShaderNodeUVMap')
uv_map_node.location = (-600, 100)
uv_map_node.uv_map = "UVMap" # Ensure it uses the correct UV Map
mapping = nodes.new(type='ShaderNodeMapping')
mapping.location = (-200, 0)
if flip_uv_v:
mapping.inputs['Scale'].default_value[1] = -1
# Modify links to include UV Map node
links.new(uv_map_node.outputs['UV'], mapping.inputs['Vector'])
links.new(mapping.outputs['Vector'], image_texture.inputs['Vector'])
# Only link texture if it loaded successfully
if image_texture.image:
links.new(image_texture.outputs['Color'], principled_bsdf.inputs['Base Color'])
else:
print("Warning: Texture loading failed, setting base color on material.")
# We can set a base color if there is no texture
principled_bsdf.inputs['Base Color'].default_value = (0.8, 0.8, 0.8, 1.0) # Grey
links.new(principled_bsdf.outputs['BSDF'], material_output.inputs['Surface'])
image_texture.interpolation = 'Closest'
except Exception as e:
print(f"Error assigning texture or material: {e}")
# print(traceback.format_exc()) # For development: print traceback
raise # Important to propagate error if critical
else:
print(f"No texture path specified or file does not exist: '{texturepath}'. Creating material without texture.")
mat = bpy.data.materials.new(name=final_obj_name + "_Material_NoTex")
obj.data.materials.append(mat)
# --- Export Function (REMOVED) ---
# The entire export_srobj function is removed.
# --- UI and Operators ---
class SROBJProperties(PropertyGroup):
"""Properties for import settings.""" # Description updated
import_srobj_path: StringProperty(
name="SROBJ File",
subtype='FILE_PATH',
default="",
description="Select the .srobj file to import."
)
import_texture_path: StringProperty(
name="Texture File",
subtype='FILE_PATH',
default="",
description="Select the texture file (.dds, .png, .jpg) to assign to the object."
)
import_flip_uv_v: BoolProperty(
name="Flip UV (V-axis)",
description="Flip imported UV coordinates vertically (useful for some game textures).",
default=False # Changed default to False (unchecked)
)
# export_srobj_path and related properties are removed
class SROBJ_OT_ImportUI(Operator):
"""Import SROBJ from UI"""
bl_idname = "srobj.import_ui"
bl_label = "Import SROBJ"
bl_description = "Import an SROBJ file with optional texture assignment."
def execute(self, context):
props = context.scene.srobj_props
if not props.import_srobj_path:
self.report({'ERROR'}, "SROBJ file path is not set.")
return {'CANCELLED'}
try:
import_srobj_advanced(props.import_srobj_path, props.import_texture_path, props.import_flip_uv_v)
self.report({'INFO'}, f"Successfully imported: {os.path.basename(props.import_srobj_path)}")
except Exception as e:
self.report({'ERROR'}, f"Import failed: {e}")
print(f"SROBJ Import Error: {e}") # Also print detailed error to console
return {'CANCELLED'}
return {'FINISHED'}
# SROBJ_OT_ExportUI operator is removed.
class VIEW3D_PT_srobj_panel(Panel):
"""SROBJ Importer UI Panel.""" # Title updated
bl_label = "Silkroad SROBJ Tools"
bl_idname = "VIEW3D_PT_srobj_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Silkroad'
def draw(self, context):
layout = self.layout
props = context.scene.srobj_props
# --- Import Section ---
box = layout.box()
box.label(text="Import SROBJ", icon='IMPORT')
box.prop(props, "import_srobj_path")
box.prop(props, "import_texture_path")
box.prop(props, "import_flip_uv_v")
box.operator(SROBJ_OT_ImportUI.bl_idname, text="Import SROBJ")
# --- Export Section (REMOVED) ---
# The entire export section from draw method is removed.
# --- Registration ---
classes = (
SROBJProperties,
SROBJ_OT_ImportUI,
# SROBJ_OT_ExportUI is removed
VIEW3D_PT_srobj_panel
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.srobj_props = PointerProperty(type=SROBJProperties)
def unregister():
# Important: reverse order for dependencies during unregister
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.srobj_props
if __name__ == "__main__":
register()
import bpy
import os
import bmesh
import mathutils # mathutils importálva
from bpy.props import StringProperty, PointerProperty, BoolProperty
from bpy.types import Operator, Panel, PropertyGroup
from bpy_extras.io_utils import ImportHelper, ExportHelper
bl_info = {
"name": "Silkroad OBJ Importer/Exporter (Advanced)",
"author": "szabo176",
"blender": (4, 1, 0), # Blender 4.x kompatibilitás: általánosan 4.1.0-t javaslok, vagy pontosan a kívánt verziót
"version": (2, 5, 2), # Plugin verziószáma frissítve a javítások miatt
"location": "View3D Sidebar > Silkroad Tab",
"description": "Import/Export .srobj files with vertex groups, UVs and automatic texture assignment (DDS/PNG/JPG support).",
"category": "Import-Export"
}
# --- Segédfüggvények ---
def name_exists(name):
"""Ellenőrzi, hogy létezik-e már ilyen nevű objektum a Blenderben."""
return name in bpy.data.objects
# --- Import Funkció (bmesh alapú, textúra hozzárendeléssel) ---
def import_srobj_advanced(filepath, texturepath, flip_uv_v=False):
"""
Silkroad OBJ (.srobj) fájl importálása bmesh segítségével,
textúra hozzárendeléssel és opcionális UV V-flip-pel.
"""
verts = []
uv_coords_from_file = []
faces_data = []
try:
with open(filepath, 'r', encoding='utf-8') as f: # Hozzáadva az encoding='utf-8' a robusztusabb fájlolvasáshoz
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if not parts: # Üres sorok ellenőrzése split után
continue
if parts[0] == "o":
# Az 'o' sor után az objektum nevét olvassuk, de az srobj formátumban ez nem mindig használt
# A kód később a fájlnévből generálja az objektum nevet, ami rendben van.
# Ha szükség van a fájlban lévő 'o' név használatára:
# obj_name_from_file = parts[1] if len(parts) > 1 else "unnamed_object"
pass # Jelenleg nem használjuk fel, de a jövőre nézve jó tudni
elif parts[0] == "v":
# Silkroad (X, Z, Y) -> Blender (X, -Y, Z)
# Ez a konverzió specifikus a Silkroad-hoz, és rendben van, ha ez a helyes.
if len(parts) < 4: raise ValueError(f"Hibás 'v' sor formátum: {line}")
x, z, y = map(float, parts[1:4])
verts.append((x, -y, z))
elif parts[0] == "vt":
if len(parts) < 3: raise ValueError(f"Hibás 'vt' sor formátum: {line}")
u, v = map(float, parts[1:3])
uv_coords_from_file.append((u, v))
elif parts[0] == "f":
if len(parts) < 4: raise ValueError(f"Hibás 'f' sor formátum: {line}. A face-eknek legalább 3 vertex-re van szükségük.")
face_verts_uvs = []
# Csak az első 3 vertex-et vesszük (háromszögek)
# További ellenőrzés, hogy a string nem üres-e, mielőtt int-é konvertáljuk
for part in parts[1:4]:
indices = part.split('/')
if not indices[0]: raise ValueError(f"Üres vertex index az 'f' sorban: {line}")
v_idx = int(indices[0]) - 1
uv_idx = -1
if len(indices) > 1 and indices[1]: # Ellenőrizzük, hogy van-e UV index, és nem üres-e
uv_idx = int(indices[1]) - 1
face_verts_uvs.append((v_idx, uv_idx))
if len(face_verts_uvs) == 3:
faces_data.append(tuple(face_verts_uvs))
else:
print(f"Figyelem: Nem háromszög alakú face található az SROBJ fájlban, kihagyva: {line}")
except Exception as e:
raise Exception(f"Hiba az SROBJ fájl olvasása közben '{filepath}': {e}")
# Mesh és Objektum létrehozása bmesh segítségével
base_name = os.path.basename(filepath).split('.')[0]
final_obj_name = base_name
idx = 0
while name_exists(final_obj_name):
idx += 1
final_obj_name = f"{base_name}.{idx:03d}"
mesh = bpy.data.meshes.new(final_obj_name + "_Mesh")
obj = bpy.data.objects.new(final_obj_name, mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
bpy.context.view_layer.update() # Fontos lehet az objektum hozzáadásakor
bm = bmesh.new()
# A vertexek közvetlen hozzáadása a bmesh-hez, majd a mesh-hez írás.
# Eredeti: bm.from_mesh(mesh) - erre nincs szükség, ha üres mesh-ből indulunk
bm_verts = [bm.verts.new(v) for v in verts]
bm.verts.ensure_lookup_table()
# Fontos: A UV réteg létrehozása azután, hogy a bm.from_mesh(mesh) vagy bm.verts.new() hívások befejeződtek,
# és a bmesh már tartalmazza a vertexeket.
uv_layer = bm.loops.layers.uv.new("UVMap")
# Face-ek és UV-k hozzárendelése
for face_info in faces_data:
try:
# Győződjünk meg róla, hogy a vertex indexek érvényesek
bm_face_verts = []
for v_idx, _ in face_info:
if v_idx < 0 or v_idx >= len(bm_verts):
raise IndexError(f"Érvénytelen vertex index ({v_idx}) a face-ben: {face_info}")
bm_face_verts.append(bm_verts[v_idx])
# Ellenőrizzük, hogy a face nem-kétszeres vertexeket tartalmaz-e
if len(set(bm_face_verts)) != 3:
print(f"Figyelem: Duplikált vertexek a face-ben {face_info}. Kihagyva.")
continue
bm_face = bm.faces.new(tuple(bm_face_verts))
bm_face.normal_update() # Frissítjük a normalt a face létrehozása után
for loop, (v_idx, uv_idx) in zip(bm_face.loops, face_info):
if uv_idx != -1 and uv_idx < len(uv_coords_from_file):
uv_coord = uv_coords_from_file[uv_idx]
# Az UV V-flip-et itt is alkalmazhatjuk a bmesh szinten, ha szükséges
# Jelenleg a material node-ban van, ami általában jobb, mert módosítható
loop[uv_layer].uv = uv_coord
else:
print(f"Figyelem: Érvénytelen UV index {uv_idx} a {v_idx} vertexhez a {face_info} face-ben. Az UV nem került beállításra.")
except ValueError as e:
print(f"Figyelem: Nem sikerült létrehozni a face-t a {face_info} adatokkal - {e}. Kihagyva.")
except IndexError as e:
print(f"Hiba: Érvénytelen vertex vagy UV index a face-ben {face_info}: {e}. Kihagyva.")
except Exception as e:
print(f"Váratlan hiba történt a face {face_info} feldolgozása közben: {e}. Kihagyva.")
# A bmesh-ben lévő geometry ellenőrzése (opcionális, nagy fájloknál lassú lehet)
# try:
# bm.verts.ensure_lookup_table()
# bm.edges.ensure_lookup_table()
# bm.faces.ensure_lookup_table()
# except Exception as e:
# print(f"Figyelem: Hiba a bmesh lookup táblák frissítésekor: {e}")
bm.to_mesh(mesh)
mesh.update()
bm.free()
# --- Textúra és Material hozzárendelés ---
if texturepath and os.path.exists(texturepath):
try:
mat = bpy.data.materials.new(name=final_obj_name + "_Material")
mat.use_nodes = True
obj.data.materials.append(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# Töröljük az összes meglévő node-ot a tiszta kezdethez
for node in nodes:
nodes.remove(node)
principled_bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
principled_bsdf.location = (400, 0)
material_output = nodes.new(type='ShaderNodeOutputMaterial')
material_output.location = (600, 0)
image_texture = nodes.new(type='ShaderNodeTexImage')
# Ellenőrizzük, hogy az image betöltése sikeres-e
try:
image_texture.image = bpy.data.images.load(texturepath)
except RuntimeError as e: # Specifikusabb hibakezelés képbetöltéshez
print(f"Hiba a textúra betöltésekor '{texturepath}': {e}. Kérjük, ellenőrizze a fájlformátumot és a Blender DDS támogatását.")
# Nem dobunk itt kivételt, hogy a material legalább létrejöjjön textúra nélkül
image_texture.image = None
image_texture.location = (0, 0)
# UV Map node hozzáadása, hogy expliciten az "UVMap" réteget használja
uv_map_node = nodes.new(type='ShaderNodeUVMap')
uv_map_node.location = (-600, 100)
uv_map_node.uv_map = "UVMap" # Biztosítjuk, hogy a megfelelő UV Map-et használja
mapping = nodes.new(type='ShaderNodeMapping')
mapping.location = (-200, 0)
if flip_uv_v:
mapping.inputs['Scale'].default_value[1] = -1
# Linkek módosítása az UV Map node bevonásával
links.new(uv_map_node.outputs['UV'], mapping.inputs['Vector'])
links.new(mapping.outputs['Vector'], image_texture.inputs['Vector'])
# Csak akkor linkeljük a textúrát, ha sikeresen betöltődött
if image_texture.image:
links.new(image_texture.outputs['Color'], principled_bsdf.inputs['Base Color'])
else:
print("Figyelem: A textúra betöltése sikertelen volt, az alap szín beállítása az anyagon.")
# Beállíthatunk egy alap színt, ha nincs textúra
principled_bsdf.inputs['Base Color'].default_value = (0.8, 0.8, 0.8, 1.0) # Szürke
links.new(principled_bsdf.outputs['BSDF'], material_output.inputs['Surface'])
image_texture.interpolation = 'Closest'
except Exception as e:
print(f"Hiba a textúra vagy material hozzárendelésekor: {e}")
# print(traceback.format_exc()) # Fejlesztéshez: traceback kiírása
raise # Fontos, hogy a hibát továbbítsuk, ha kritikus
else:
print(f"Nincs megadva textúra útvonal, vagy a fájl nem létezik: '{texturepath}'. Material létrehozása textúra nélkül.")
mat = bpy.data.materials.new(name=final_obj_name + "_Material_NoTex")
obj.data.materials.append(mat)
# --- Export Funkció ---
def export_srobj(path, context):
"""Silkroad OBJ (.srobj) fájl exportálása."""
obj = context.object
if obj is None or obj.type != 'MESH':
raise IOError('Ki kell választania egy MESH objektumot az exportáláshoz.')
# A Blender 2.8+ verziókban az adatokhoz való hozzáféréshez aktiválni kell a függvénymezőket
# Ha módosítás történik, ensure_lookup_table() és mesh.update() szükséges lehet.
mesh = obj.data
meshname = obj.name
groupnames = []
# A vertex group-ok tárolása: [group1_idx, group2_idx] (255 ha nincs)
vertgroups_export = [[255, 255] for _ in range(len(mesh.vertices))]
verts = []
for group in obj.vertex_groups:
groupnames.append(group.name)
# A mesh.calc_utils_looptriangles() vagy bmesh konverzió jobb lehet
# A vertex.co a lokális koordináták.
# A Blender globális koordinátáihoz: obj.matrix_world @ vert.co
# Azonban az SROBJ valószínűleg lokális koordinátákat vár, így a vert.co megfelelő
for v_idx, vert in enumerate(mesh.vertices):
# A Blender (X, Y, Z) -> Silkroad (X, Z, -Y) konverzió exportáláskor
verts.append((vert.co.x, vert.co.z, -vert.co.y))
current_groups_for_vert = []
for vg_entry in vert.groups:
# Csak azokat a csoportokat vesszük figyelembe, amelyeknek 0-nál nagyobb súlyuk van
if vg_entry.weight > 0.0001: # Kis tolerancia a lebegőpontos számokhoz
current_groups_for_vert.append(vg_entry.group)
# Silkroad formátum feltételezi, hogy max 2 csoportot kezel
if len(current_groups_for_vert) > 0:
vertgroups_export[v_idx][0] = current_groups_for_vert[0]
if len(current_groups_for_vert) > 1:
vertgroups_export[v_idx][1] = current_groups_for_vert[1]
# Ha több mint 2 csoportja van egy vertexnek, a többi figyelmen kívül marad
if len(current_groups_for_vert) > 2:
print(f"Figyelem: A {v_idx} vertexnek több mint 2 vertex csoportja van. Csak az első kettő kerül exportálásra.")
uv_layer = mesh.uv_layers.active
if not uv_layer:
print("Figyelem: Nincs aktív UV map található az exportáláshoz. Az UV koordináták 0,0-ra lesznek beállítva.")
unique_uv_map = {}
exported_uv_list = []
unique_normals_map = {}
exported_normal_list = []
face_data_for_export = []
# A loop_triangles garantálja, hogy a mesh háromszögekre van bontva
# Ez Blender 2.8+ esetén a loop.normal helyett használatos a face normalhoz.
mesh.calc_loop_triangles()
for tri in mesh.loop_triangles: # loop_triangles használata polygonok helyett
# A Blender normalja (poly.normal) már objektum lokális.
bl_normal = tri.normal
# A Silkroad koordináta-rendszerhez illeszkedő normal konverziója (Y és Z felcserélése)
sr_normal = mathutils.Vector((bl_normal.x, bl_normal.z, -bl_normal.y)) # X, Z, -Y a normalhoz
sr_normal.normalize()
normal_key = tuple(round(coord, 6) for coord in sr_normal)
if normal_key not in unique_normals_map:
unique_normals_map[normal_key] = len(exported_normal_list)
exported_normal_list.append(sr_normal)
face_normal_idx = unique_normals_map[normal_key]
poly_face_data = []
for loop_idx in tri.loops: # loop_indices helyett tri.loops használata
v_idx = mesh.loops[loop_idx].vertex_index
uv_coord = uv_layer.data[loop_idx].uv if uv_layer else mathutils.Vector((0.0, 0.0))
uv_key = tuple(round(coord, 6) for coord in uv_coord)
if uv_key not in unique_uv_map:
unique_uv_map[uv_key] = len(exported_uv_list)
exported_uv_list.append(uv_coord)
uv_idx = unique_uv_map[uv_key]
poly_face_data.append((v_idx, uv_idx, face_normal_idx))
face_data_for_export.append(poly_face_data)
lines = []
lines.append('#SROBJ by Perry\'s Blender plugin (Updated by AI Assistant).\n')
lines.append(f'o {meshname}\n')
# Group nevek exportálása
for groupname in groupnames:
lines.append(f'gn {groupname}\n')
# Vertex group indexek exportálása
for groups in vertgroups_export:
lines.append(f'vg {groups[0]}/{groups[1]}\n')
# Vertex koordináták exportálása
for vert_co in verts:
# **KRITIKUS HIBA JAVÍTVA:** Korábban vert_co[0]-t kétszer használt, most helyesen X, Z, -Y
lines.append(f'v {vert_co[0]:.6f} {vert_co[1]:.6f} {vert_co[2]:.6f}\n')
# Normal vektorok exportálása
for norm_co in exported_normal_list:
lines.append(f'vn {norm_co[0]:.6f} {norm_co[1]:.6f} {norm_co[2]:.6f}\n')
# UV koordináták exportálása
for uv_co in exported_uv_list:
lines.append(f'vt {uv_co[0]:.6f} {uv_co[1]:.6f}\n')
# Face-ek exportálása (1-alapú indexeléssel)
for face_loops in face_data_for_export:
line_parts = []
for v_idx, uv_idx, vn_idx in face_loops:
line_parts.append(f"{v_idx+1}/{uv_idx+1}/{vn_idx+1}")
lines.append(f"f {' '.join(line_parts)}\n")
try:
with open(path, 'w', encoding='utf-8') as f: # encoding='utf-8' hozzáadva
f.writelines(lines)
except Exception as e:
raise Exception(f"Hiba az SROBJ fájl írása közben '{path}': {e}")
# --- UI és Operátorok ---
class SROBJProperties(PropertyGroup):
"""Tulajdonságok az import/export beállításokhoz."""
import_srobj_path: StringProperty(
name="SROBJ Fájl",
subtype='FILE_PATH',
default="",
description="Válaszd ki az importálni kívánt .srobj fájlt."
)
import_texture_path: StringProperty(
name="Textúra Fájl",
subtype='FILE_PATH',
default="",
description="Válaszd ki az objektumhoz hozzárendelni kívánt textúra fájlt (.dds, .png, .jpg)."
)
import_flip_uv_v: BoolProperty(
name="UV fordítás (V-tengely)",
description="Függőlegesen megfordítja az importált UV-koordinátákat (hasznos néhány játék textúrájához).",
default=True
)
export_srobj_path: StringProperty(
name="Export SROBJ Fájl",
subtype='FILE_PATH',
default="",
description="Add meg az exportált .srobj fájl útvonalát és nevét."
)
class SROBJ_OT_ImportUI(Operator):
"""Import SROBJ from UI"""
bl_idname = "srobj.import_ui"
bl_label = "SROBJ importálása"
bl_description = "SROBJ fájl importálása opcionális textúra hozzárendeléssel."
def execute(self, context):
props = context.scene.srobj_props
if not props.import_srobj_path:
self.report({'ERROR'}, "Az SROBJ fájl útvonala nincs beállítva.")
return {'CANCELLED'}
try:
import_srobj_advanced(props.import_srobj_path, props.import_texture_path, props.import_flip_uv_v)
self.report({'INFO'}, f"Sikeresen importálva: {os.path.basename(props.import_srobj_path)}")
except Exception as e:
self.report({'ERROR'}, f"Importálás sikertelen: {e}")
print(f"SROBJ Import Hiba: {e}") # A konzolba is kiírjuk a részletesebb hibát
return {'CANCELLED'}
return {'FINISHED'}
class SROBJ_OT_ExportUI(Operator):
"""Export SROBJ from UI"""
bl_idname = "srobj.export_ui"
bl_label = "SROBJ exportálása"
bl_description = "A kiválasztott MESH objektum exportálása .srobj fájlba."
def execute(self, context):
props = context.scene.srobj_props
if not props.export_srobj_path:
self.report({'ERROR'}, "Az exportálási fájl útvonala nincs beállítva.")
return {'CANCELLED'}
if context.object is None or context.object.type != 'MESH':
self.report({'ERROR'}, "Nincs MESH objektum kiválasztva az exportáláshoz.")
return {'CANCELLED'}
export_path = props.export_srobj_path
if not export_path.lower().endswith(".srobj"):
export_path += ".srobj"
try:
export_srobj(export_path, context)
self.report({'INFO'}, f"Sikeresen exportálva: {os.path.basename(export_path)}")
except Exception as e:
self.report({'ERROR'}, f"Exportálás sikertelen: {e}")
print(f"SROBJ Export Hiba: {e}") # A konzolba is kiírjuk a részletesebb hibát
return {'CANCELLED'}
return {'FINISHED'}
class VIEW3D_PT_srobj_panel(Panel):
"""SROBJ Importer/Exporter UI Panel."""
bl_label = "Silkroad SROBJ Eszközök" # Cím magyarosítva
bl_idname = "VIEW3D_PT_srobj_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Silkroad'
def draw(self, context):
layout = self.layout
props = context.scene.srobj_props
# --- Importálás szekció ---
box = layout.box()
box.label(text="SROBJ importálása", icon='IMPORT')
box.prop(props, "import_srobj_path")
box.prop(props, "import_texture_path")
box.prop(props, "import_flip_uv_v")
box.operator(SROBJ_OT_ImportUI.bl_idname, text="SROBJ importálása")
# --- Exportálás szekció ---
box = layout.box()
box.label(text="SROBJ exportálása", icon='EXPORT')
box.prop(props, "export_srobj_path")
box.operator(SROBJ_OT_ExportUI.bl_idname, text="SROBJ exportálása")
# --- Regisztráció ---
classes = (
SROBJProperties,
SROBJ_OT_ImportUI,
SROBJ_OT_ExportUI,
VIEW3D_PT_srobj_panel
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.srobj_props = PointerProperty(type=SROBJProperties)
def unregister():
# Fontos a fordított sorrend a függőségek miatt az unregister során
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.srobj_props
if __name__ == "__main__":
register()
import os
import bmesh
import mathutils # mathutils importálva
from bpy.props import StringProperty, PointerProperty, BoolProperty
from bpy.types import Operator, Panel, PropertyGroup
from bpy_extras.io_utils import ImportHelper, ExportHelper
bl_info = {
"name": "Silkroad OBJ Importer/Exporter (Advanced)",
"author": "szabo176",
"blender": (4, 1, 0), # Blender 4.x kompatibilitás: általánosan 4.1.0-t javaslok, vagy pontosan a kívánt verziót
"version": (2, 5, 2), # Plugin verziószáma frissítve a javítások miatt
"location": "View3D Sidebar > Silkroad Tab",
"description": "Import/Export .srobj files with vertex groups, UVs and automatic texture assignment (DDS/PNG/JPG support).",
"category": "Import-Export"
}
# --- Segédfüggvények ---
def name_exists(name):
"""Ellenőrzi, hogy létezik-e már ilyen nevű objektum a Blenderben."""
return name in bpy.data.objects
# --- Import Funkció (bmesh alapú, textúra hozzárendeléssel) ---
def import_srobj_advanced(filepath, texturepath, flip_uv_v=False):
"""
Silkroad OBJ (.srobj) fájl importálása bmesh segítségével,
textúra hozzárendeléssel és opcionális UV V-flip-pel.
"""
verts = []
uv_coords_from_file = []
faces_data = []
try:
with open(filepath, 'r', encoding='utf-8') as f: # Hozzáadva az encoding='utf-8' a robusztusabb fájlolvasáshoz
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if not parts: # Üres sorok ellenőrzése split után
continue
if parts[0] == "o":
# Az 'o' sor után az objektum nevét olvassuk, de az srobj formátumban ez nem mindig használt
# A kód később a fájlnévből generálja az objektum nevet, ami rendben van.
# Ha szükség van a fájlban lévő 'o' név használatára:
# obj_name_from_file = parts[1] if len(parts) > 1 else "unnamed_object"
pass # Jelenleg nem használjuk fel, de a jövőre nézve jó tudni
elif parts[0] == "v":
# Silkroad (X, Z, Y) -> Blender (X, -Y, Z)
# Ez a konverzió specifikus a Silkroad-hoz, és rendben van, ha ez a helyes.
if len(parts) < 4: raise ValueError(f"Hibás 'v' sor formátum: {line}")
x, z, y = map(float, parts[1:4])
verts.append((x, -y, z))
elif parts[0] == "vt":
if len(parts) < 3: raise ValueError(f"Hibás 'vt' sor formátum: {line}")
u, v = map(float, parts[1:3])
uv_coords_from_file.append((u, v))
elif parts[0] == "f":
if len(parts) < 4: raise ValueError(f"Hibás 'f' sor formátum: {line}. A face-eknek legalább 3 vertex-re van szükségük.")
face_verts_uvs = []
# Csak az első 3 vertex-et vesszük (háromszögek)
# További ellenőrzés, hogy a string nem üres-e, mielőtt int-é konvertáljuk
for part in parts[1:4]:
indices = part.split('/')
if not indices[0]: raise ValueError(f"Üres vertex index az 'f' sorban: {line}")
v_idx = int(indices[0]) - 1
uv_idx = -1
if len(indices) > 1 and indices[1]: # Ellenőrizzük, hogy van-e UV index, és nem üres-e
uv_idx = int(indices[1]) - 1
face_verts_uvs.append((v_idx, uv_idx))
if len(face_verts_uvs) == 3:
faces_data.append(tuple(face_verts_uvs))
else:
print(f"Figyelem: Nem háromszög alakú face található az SROBJ fájlban, kihagyva: {line}")
except Exception as e:
raise Exception(f"Hiba az SROBJ fájl olvasása közben '{filepath}': {e}")
# Mesh és Objektum létrehozása bmesh segítségével
base_name = os.path.basename(filepath).split('.')[0]
final_obj_name = base_name
idx = 0
while name_exists(final_obj_name):
idx += 1
final_obj_name = f"{base_name}.{idx:03d}"
mesh = bpy.data.meshes.new(final_obj_name + "_Mesh")
obj = bpy.data.objects.new(final_obj_name, mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
bpy.context.view_layer.update() # Fontos lehet az objektum hozzáadásakor
bm = bmesh.new()
# A vertexek közvetlen hozzáadása a bmesh-hez, majd a mesh-hez írás.
# Eredeti: bm.from_mesh(mesh) - erre nincs szükség, ha üres mesh-ből indulunk
bm_verts = [bm.verts.new(v) for v in verts]
bm.verts.ensure_lookup_table()
# Fontos: A UV réteg létrehozása azután, hogy a bm.from_mesh(mesh) vagy bm.verts.new() hívások befejeződtek,
# és a bmesh már tartalmazza a vertexeket.
uv_layer = bm.loops.layers.uv.new("UVMap")
# Face-ek és UV-k hozzárendelése
for face_info in faces_data:
try:
# Győződjünk meg róla, hogy a vertex indexek érvényesek
bm_face_verts = []
for v_idx, _ in face_info:
if v_idx < 0 or v_idx >= len(bm_verts):
raise IndexError(f"Érvénytelen vertex index ({v_idx}) a face-ben: {face_info}")
bm_face_verts.append(bm_verts[v_idx])
# Ellenőrizzük, hogy a face nem-kétszeres vertexeket tartalmaz-e
if len(set(bm_face_verts)) != 3:
print(f"Figyelem: Duplikált vertexek a face-ben {face_info}. Kihagyva.")
continue
bm_face = bm.faces.new(tuple(bm_face_verts))
bm_face.normal_update() # Frissítjük a normalt a face létrehozása után
for loop, (v_idx, uv_idx) in zip(bm_face.loops, face_info):
if uv_idx != -1 and uv_idx < len(uv_coords_from_file):
uv_coord = uv_coords_from_file[uv_idx]
# Az UV V-flip-et itt is alkalmazhatjuk a bmesh szinten, ha szükséges
# Jelenleg a material node-ban van, ami általában jobb, mert módosítható
loop[uv_layer].uv = uv_coord
else:
print(f"Figyelem: Érvénytelen UV index {uv_idx} a {v_idx} vertexhez a {face_info} face-ben. Az UV nem került beállításra.")
except ValueError as e:
print(f"Figyelem: Nem sikerült létrehozni a face-t a {face_info} adatokkal - {e}. Kihagyva.")
except IndexError as e:
print(f"Hiba: Érvénytelen vertex vagy UV index a face-ben {face_info}: {e}. Kihagyva.")
except Exception as e:
print(f"Váratlan hiba történt a face {face_info} feldolgozása közben: {e}. Kihagyva.")
# A bmesh-ben lévő geometry ellenőrzése (opcionális, nagy fájloknál lassú lehet)
# try:
# bm.verts.ensure_lookup_table()
# bm.edges.ensure_lookup_table()
# bm.faces.ensure_lookup_table()
# except Exception as e:
# print(f"Figyelem: Hiba a bmesh lookup táblák frissítésekor: {e}")
bm.to_mesh(mesh)
mesh.update()
bm.free()
# --- Textúra és Material hozzárendelés ---
if texturepath and os.path.exists(texturepath):
try:
mat = bpy.data.materials.new(name=final_obj_name + "_Material")
mat.use_nodes = True
obj.data.materials.append(mat)
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# Töröljük az összes meglévő node-ot a tiszta kezdethez
for node in nodes:
nodes.remove(node)
principled_bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
principled_bsdf.location = (400, 0)
material_output = nodes.new(type='ShaderNodeOutputMaterial')
material_output.location = (600, 0)
image_texture = nodes.new(type='ShaderNodeTexImage')
# Ellenőrizzük, hogy az image betöltése sikeres-e
try:
image_texture.image = bpy.data.images.load(texturepath)
except RuntimeError as e: # Specifikusabb hibakezelés képbetöltéshez
print(f"Hiba a textúra betöltésekor '{texturepath}': {e}. Kérjük, ellenőrizze a fájlformátumot és a Blender DDS támogatását.")
# Nem dobunk itt kivételt, hogy a material legalább létrejöjjön textúra nélkül
image_texture.image = None
image_texture.location = (0, 0)
# UV Map node hozzáadása, hogy expliciten az "UVMap" réteget használja
uv_map_node = nodes.new(type='ShaderNodeUVMap')
uv_map_node.location = (-600, 100)
uv_map_node.uv_map = "UVMap" # Biztosítjuk, hogy a megfelelő UV Map-et használja
mapping = nodes.new(type='ShaderNodeMapping')
mapping.location = (-200, 0)
if flip_uv_v:
mapping.inputs['Scale'].default_value[1] = -1
# Linkek módosítása az UV Map node bevonásával
links.new(uv_map_node.outputs['UV'], mapping.inputs['Vector'])
links.new(mapping.outputs['Vector'], image_texture.inputs['Vector'])
# Csak akkor linkeljük a textúrát, ha sikeresen betöltődött
if image_texture.image:
links.new(image_texture.outputs['Color'], principled_bsdf.inputs['Base Color'])
else:
print("Figyelem: A textúra betöltése sikertelen volt, az alap szín beállítása az anyagon.")
# Beállíthatunk egy alap színt, ha nincs textúra
principled_bsdf.inputs['Base Color'].default_value = (0.8, 0.8, 0.8, 1.0) # Szürke
links.new(principled_bsdf.outputs['BSDF'], material_output.inputs['Surface'])
image_texture.interpolation = 'Closest'
except Exception as e:
print(f"Hiba a textúra vagy material hozzárendelésekor: {e}")
# print(traceback.format_exc()) # Fejlesztéshez: traceback kiírása
raise # Fontos, hogy a hibát továbbítsuk, ha kritikus
else:
print(f"Nincs megadva textúra útvonal, vagy a fájl nem létezik: '{texturepath}'. Material létrehozása textúra nélkül.")
mat = bpy.data.materials.new(name=final_obj_name + "_Material_NoTex")
obj.data.materials.append(mat)
# --- Export Funkció ---
def export_srobj(path, context):
"""Silkroad OBJ (.srobj) fájl exportálása."""
obj = context.object
if obj is None or obj.type != 'MESH':
raise IOError('Ki kell választania egy MESH objektumot az exportáláshoz.')
# A Blender 2.8+ verziókban az adatokhoz való hozzáféréshez aktiválni kell a függvénymezőket
# Ha módosítás történik, ensure_lookup_table() és mesh.update() szükséges lehet.
mesh = obj.data
meshname = obj.name
groupnames = []
# A vertex group-ok tárolása: [group1_idx, group2_idx] (255 ha nincs)
vertgroups_export = [[255, 255] for _ in range(len(mesh.vertices))]
verts = []
for group in obj.vertex_groups:
groupnames.append(group.name)
# A mesh.calc_utils_looptriangles() vagy bmesh konverzió jobb lehet
# A vertex.co a lokális koordináták.
# A Blender globális koordinátáihoz: obj.matrix_world @ vert.co
# Azonban az SROBJ valószínűleg lokális koordinátákat vár, így a vert.co megfelelő
for v_idx, vert in enumerate(mesh.vertices):
# A Blender (X, Y, Z) -> Silkroad (X, Z, -Y) konverzió exportáláskor
verts.append((vert.co.x, vert.co.z, -vert.co.y))
current_groups_for_vert = []
for vg_entry in vert.groups:
# Csak azokat a csoportokat vesszük figyelembe, amelyeknek 0-nál nagyobb súlyuk van
if vg_entry.weight > 0.0001: # Kis tolerancia a lebegőpontos számokhoz
current_groups_for_vert.append(vg_entry.group)
# Silkroad formátum feltételezi, hogy max 2 csoportot kezel
if len(current_groups_for_vert) > 0:
vertgroups_export[v_idx][0] = current_groups_for_vert[0]
if len(current_groups_for_vert) > 1:
vertgroups_export[v_idx][1] = current_groups_for_vert[1]
# Ha több mint 2 csoportja van egy vertexnek, a többi figyelmen kívül marad
if len(current_groups_for_vert) > 2:
print(f"Figyelem: A {v_idx} vertexnek több mint 2 vertex csoportja van. Csak az első kettő kerül exportálásra.")
uv_layer = mesh.uv_layers.active
if not uv_layer:
print("Figyelem: Nincs aktív UV map található az exportáláshoz. Az UV koordináták 0,0-ra lesznek beállítva.")
unique_uv_map = {}
exported_uv_list = []
unique_normals_map = {}
exported_normal_list = []
face_data_for_export = []
# A loop_triangles garantálja, hogy a mesh háromszögekre van bontva
# Ez Blender 2.8+ esetén a loop.normal helyett használatos a face normalhoz.
mesh.calc_loop_triangles()
for tri in mesh.loop_triangles: # loop_triangles használata polygonok helyett
# A Blender normalja (poly.normal) már objektum lokális.
bl_normal = tri.normal
# A Silkroad koordináta-rendszerhez illeszkedő normal konverziója (Y és Z felcserélése)
sr_normal = mathutils.Vector((bl_normal.x, bl_normal.z, -bl_normal.y)) # X, Z, -Y a normalhoz
sr_normal.normalize()
normal_key = tuple(round(coord, 6) for coord in sr_normal)
if normal_key not in unique_normals_map:
unique_normals_map[normal_key] = len(exported_normal_list)
exported_normal_list.append(sr_normal)
face_normal_idx = unique_normals_map[normal_key]
poly_face_data = []
for loop_idx in tri.loops: # loop_indices helyett tri.loops használata
v_idx = mesh.loops[loop_idx].vertex_index
uv_coord = uv_layer.data[loop_idx].uv if uv_layer else mathutils.Vector((0.0, 0.0))
uv_key = tuple(round(coord, 6) for coord in uv_coord)
if uv_key not in unique_uv_map:
unique_uv_map[uv_key] = len(exported_uv_list)
exported_uv_list.append(uv_coord)
uv_idx = unique_uv_map[uv_key]
poly_face_data.append((v_idx, uv_idx, face_normal_idx))
face_data_for_export.append(poly_face_data)
lines = []
lines.append('#SROBJ by Perry\'s Blender plugin (Updated by AI Assistant).\n')
lines.append(f'o {meshname}\n')
# Group nevek exportálása
for groupname in groupnames:
lines.append(f'gn {groupname}\n')
# Vertex group indexek exportálása
for groups in vertgroups_export:
lines.append(f'vg {groups[0]}/{groups[1]}\n')
# Vertex koordináták exportálása
for vert_co in verts:
# **KRITIKUS HIBA JAVÍTVA:** Korábban vert_co[0]-t kétszer használt, most helyesen X, Z, -Y
lines.append(f'v {vert_co[0]:.6f} {vert_co[1]:.6f} {vert_co[2]:.6f}\n')
# Normal vektorok exportálása
for norm_co in exported_normal_list:
lines.append(f'vn {norm_co[0]:.6f} {norm_co[1]:.6f} {norm_co[2]:.6f}\n')
# UV koordináták exportálása
for uv_co in exported_uv_list:
lines.append(f'vt {uv_co[0]:.6f} {uv_co[1]:.6f}\n')
# Face-ek exportálása (1-alapú indexeléssel)
for face_loops in face_data_for_export:
line_parts = []
for v_idx, uv_idx, vn_idx in face_loops:
line_parts.append(f"{v_idx+1}/{uv_idx+1}/{vn_idx+1}")
lines.append(f"f {' '.join(line_parts)}\n")
try:
with open(path, 'w', encoding='utf-8') as f: # encoding='utf-8' hozzáadva
f.writelines(lines)
except Exception as e:
raise Exception(f"Hiba az SROBJ fájl írása közben '{path}': {e}")
# --- UI és Operátorok ---
class SROBJProperties(PropertyGroup):
"""Tulajdonságok az import/export beállításokhoz."""
import_srobj_path: StringProperty(
name="SROBJ Fájl",
subtype='FILE_PATH',
default="",
description="Válaszd ki az importálni kívánt .srobj fájlt."
)
import_texture_path: StringProperty(
name="Textúra Fájl",
subtype='FILE_PATH',
default="",
description="Válaszd ki az objektumhoz hozzárendelni kívánt textúra fájlt (.dds, .png, .jpg)."
)
import_flip_uv_v: BoolProperty(
name="UV fordítás (V-tengely)",
description="Függőlegesen megfordítja az importált UV-koordinátákat (hasznos néhány játék textúrájához).",
default=True
)
export_srobj_path: StringProperty(
name="Export SROBJ Fájl",
subtype='FILE_PATH',
default="",
description="Add meg az exportált .srobj fájl útvonalát és nevét."
)
class SROBJ_OT_ImportUI(Operator):
"""Import SROBJ from UI"""
bl_idname = "srobj.import_ui"
bl_label = "SROBJ importálása"
bl_description = "SROBJ fájl importálása opcionális textúra hozzárendeléssel."
def execute(self, context):
props = context.scene.srobj_props
if not props.import_srobj_path:
self.report({'ERROR'}, "Az SROBJ fájl útvonala nincs beállítva.")
return {'CANCELLED'}
try:
import_srobj_advanced(props.import_srobj_path, props.import_texture_path, props.import_flip_uv_v)
self.report({'INFO'}, f"Sikeresen importálva: {os.path.basename(props.import_srobj_path)}")
except Exception as e:
self.report({'ERROR'}, f"Importálás sikertelen: {e}")
print(f"SROBJ Import Hiba: {e}") # A konzolba is kiírjuk a részletesebb hibát
return {'CANCELLED'}
return {'FINISHED'}
class SROBJ_OT_ExportUI(Operator):
"""Export SROBJ from UI"""
bl_idname = "srobj.export_ui"
bl_label = "SROBJ exportálása"
bl_description = "A kiválasztott MESH objektum exportálása .srobj fájlba."
def execute(self, context):
props = context.scene.srobj_props
if not props.export_srobj_path:
self.report({'ERROR'}, "Az exportálási fájl útvonala nincs beállítva.")
return {'CANCELLED'}
if context.object is None or context.object.type != 'MESH':
self.report({'ERROR'}, "Nincs MESH objektum kiválasztva az exportáláshoz.")
return {'CANCELLED'}
export_path = props.export_srobj_path
if not export_path.lower().endswith(".srobj"):
export_path += ".srobj"
try:
export_srobj(export_path, context)
self.report({'INFO'}, f"Sikeresen exportálva: {os.path.basename(export_path)}")
except Exception as e:
self.report({'ERROR'}, f"Exportálás sikertelen: {e}")
print(f"SROBJ Export Hiba: {e}") # A konzolba is kiírjuk a részletesebb hibát
return {'CANCELLED'}
return {'FINISHED'}
class VIEW3D_PT_srobj_panel(Panel):
"""SROBJ Importer/Exporter UI Panel."""
bl_label = "Silkroad SROBJ Eszközök" # Cím magyarosítva
bl_idname = "VIEW3D_PT_srobj_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Silkroad'
def draw(self, context):
layout = self.layout
props = context.scene.srobj_props
# --- Importálás szekció ---
box = layout.box()
box.label(text="SROBJ importálása", icon='IMPORT')
box.prop(props, "import_srobj_path")
box.prop(props, "import_texture_path")
box.prop(props, "import_flip_uv_v")
box.operator(SROBJ_OT_ImportUI.bl_idname, text="SROBJ importálása")
# --- Exportálás szekció ---
box = layout.box()
box.label(text="SROBJ exportálása", icon='EXPORT')
box.prop(props, "export_srobj_path")
box.operator(SROBJ_OT_ExportUI.bl_idname, text="SROBJ exportálása")
# --- Regisztráció ---
classes = (
SROBJProperties,
SROBJ_OT_ImportUI,
SROBJ_OT_ExportUI,
VIEW3D_PT_srobj_panel
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.srobj_props = PointerProperty(type=SROBJProperties)
def unregister():
# Fontos a fordított sorrend a függőségek miatt az unregister során
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.srobj_props
if __name__ == "__main__":
register()







