Release Blender L1960_Tools V1_8_5
This commit is contained in:
BIN
Blender/L1960_Tools/_Release/L1960_Tools_1_8_5.zip
Normal file
BIN
Blender/L1960_Tools/_Release/L1960_Tools_1_8_5.zip
Normal file
Binary file not shown.
@@ -0,0 +1,333 @@
|
|||||||
|
import bpy
|
||||||
|
import math
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
### GENRATE MLOD ###
|
||||||
|
|
||||||
|
plugin_dir = bpy.utils.user_resource('SCRIPTS')
|
||||||
|
plugin_path = "addons\L1960Tools"
|
||||||
|
L1960_path = os.path.join(plugin_dir, plugin_path)
|
||||||
|
|
||||||
|
colorpalettes = [
|
||||||
|
json.dumps(["ColorPalette_01.png", "BF1BC94A0DE4398F"]),
|
||||||
|
json.dumps(["ColorPalette_02.png", "A95B2794A0EE8340"])
|
||||||
|
]
|
||||||
|
|
||||||
|
enum_palettes = []
|
||||||
|
for palettes in colorpalettes:
|
||||||
|
file = json.loads(palettes)[0]
|
||||||
|
enum_palettes.append((palettes, file[:-4], "Select " + file[:-4] + " for MLOD"))
|
||||||
|
|
||||||
|
class EnumColorPalettes(bpy.types.PropertyGroup):
|
||||||
|
mlod_enum_selection: bpy.props.EnumProperty(name="Color Palettes for MLOD", items = enum_palettes, description = "Choose a palette", default = 0)
|
||||||
|
|
||||||
|
class MESH_OT_set_up_mlod(bpy.types.Operator):
|
||||||
|
"""Set´s up a material to be used for MLOD´s"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.set_up_mlod"
|
||||||
|
bl_label = "Set´s up a material to be used for MLOD´s"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
#Load Color Palettes
|
||||||
|
self.import_palettes_textures()
|
||||||
|
|
||||||
|
#Selected Mesh
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
|
||||||
|
if obj not in bpy.context.selected_objects or obj.type != "MESH":
|
||||||
|
self.report({'WARNING'}, 'Select a Mesh to continue')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
arr_layers = obj.data.uv_layers
|
||||||
|
if not arr_layers.get("MLOD") or len(arr_layers) > 1:
|
||||||
|
for uv_layer in reversed(arr_layers):
|
||||||
|
arr_layers.remove(uv_layer)
|
||||||
|
arr_layers.new(name = 'MLOD')
|
||||||
|
|
||||||
|
if not len(obj.data.materials) == 0:
|
||||||
|
obj.data.materials.clear()
|
||||||
|
|
||||||
|
palette_enum_selection = json.loads(context.scene.color_palettes.mlod_enum_selection)
|
||||||
|
mlod_material_name = "MLOD_" + palette_enum_selection[0][:-4] + "_" + palette_enum_selection[1]
|
||||||
|
|
||||||
|
if not bpy.data.materials.get(mlod_material_name):
|
||||||
|
material = bpy.data.materials.new(name=mlod_material_name)
|
||||||
|
material.use_nodes = True
|
||||||
|
bsdf_node = material.node_tree.nodes["Principled BSDF"]
|
||||||
|
texImage = material.node_tree.nodes.new('ShaderNodeTexImage')
|
||||||
|
texImage.image = bpy.data.images.get(palette_enum_selection[0])
|
||||||
|
|
||||||
|
material.node_tree.links.new(bsdf_node.inputs['Base Color'], texImage.outputs['Color'])
|
||||||
|
|
||||||
|
obj.data.materials.append(bpy.data.materials.get(mlod_material_name))
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Mesh configured like MLOD')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def import_palettes_textures(self):
|
||||||
|
for image_name in colorpalettes:
|
||||||
|
image_name = json.loads(image_name)[0]
|
||||||
|
texture_name = image_name.split(".")[0]
|
||||||
|
if texture_name not in bpy.data.textures:
|
||||||
|
texture = bpy.data.textures.new(name=texture_name, type='IMAGE')
|
||||||
|
else:
|
||||||
|
texture = bpy.data.textures.get(texture_name)
|
||||||
|
if image_name not in bpy.data.images:
|
||||||
|
image = bpy.data.images.load(os.path.join(L1960_path, image_name))
|
||||||
|
else:
|
||||||
|
image = bpy.data.images.get(image_name)
|
||||||
|
texture.image = image
|
||||||
|
|
||||||
|
# texture_filepath = os.path.join(L1960_path, colorpalettes[1])
|
||||||
|
|
||||||
|
### Palette MLOD ###
|
||||||
|
# class MESH_OT_bake_basic_mlod(bpy.types.Operator):
|
||||||
|
# """Set´s up a basic MLOD via Bake"""
|
||||||
|
|
||||||
|
# bl_idname = "mesh.bake_basic_mlod"
|
||||||
|
# bl_label = "Set´s up a basic MLOD via Bake"
|
||||||
|
# bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
# def execute(self, context):
|
||||||
|
# obj = context.active_object
|
||||||
|
|
||||||
|
# #Palette selection
|
||||||
|
# palette_enum_selection = json.loads(context.scene.color_palettes.mlod_enum_selection)
|
||||||
|
|
||||||
|
# if obj not in bpy.context.selected_objects or obj.type != "MESH":
|
||||||
|
# self.report({'WARNING'}, 'Select a Mesh to continue')
|
||||||
|
# return {"CANCELLED"}
|
||||||
|
|
||||||
|
# faces_with_color = self.get_faces_with_color(obj)
|
||||||
|
|
||||||
|
# grouped_colors = self.groupe_colors(faces_with_color, threshold=50)
|
||||||
|
|
||||||
|
# matched_colors = self.find_matching_color(grouped_colors, palette_enum_selection[0][:-4])
|
||||||
|
|
||||||
|
# self.adjust_UV(obj, matched_colors)
|
||||||
|
|
||||||
|
# #Entfernen des referenz Bake Image
|
||||||
|
# #bpy.data.images.remove(bpy.data.images["MLOD_Temp"])
|
||||||
|
|
||||||
|
# # Entfernen aller Meterials
|
||||||
|
# if not len(obj.data.materials) == 0:
|
||||||
|
# obj.data.materials.clear()
|
||||||
|
|
||||||
|
# # Create new Material with selected Palette
|
||||||
|
# mlod_material_name = "MLOD_" + palette_enum_selection[0][:-4] + palette_enum_selection[1]
|
||||||
|
|
||||||
|
# texture_filepath = os.path.join(L1960_path, palette_enum_selection[0])
|
||||||
|
# image = bpy.data.images.load(texture_filepath)
|
||||||
|
|
||||||
|
# if not bpy.data.materials.get(mlod_material_name):
|
||||||
|
# material = bpy.data.materials.new(name=mlod_material_name)
|
||||||
|
# material.use_nodes = True
|
||||||
|
# bsdf_node = material.node_tree.nodes["Principled BSDF"]
|
||||||
|
# texImage = material.node_tree.nodes.new('ShaderNodeTexImage')
|
||||||
|
# texImage.image = image
|
||||||
|
# material.node_tree.links.new(bsdf_node.inputs['Base Color'], texImage.outputs['Color'])
|
||||||
|
# obj.data.materials.append(bpy.data.materials.get(mlod_material_name))
|
||||||
|
|
||||||
|
# self.report({'INFO'}, f'MLOD sucessfully created with {palette_enum_selection[0][:-4]}.')
|
||||||
|
# return {"FINISHED"}
|
||||||
|
|
||||||
|
# def get_faces_with_color(self, obj):
|
||||||
|
|
||||||
|
# #Bake Image
|
||||||
|
# image = self.bake_diffuse(obj)
|
||||||
|
|
||||||
|
# mesh = obj.data
|
||||||
|
# uv_layer = mesh.uv_layers.active.data # direct UV access
|
||||||
|
|
||||||
|
# colors = []
|
||||||
|
|
||||||
|
# for poly in mesh.polygons: # same as "face" in BMesh
|
||||||
|
# # Durchschnittliche UV-Koordinaten berechnen
|
||||||
|
# uv = Vector((0.0, 0.0))
|
||||||
|
# for loop_index in poly.loop_indices:
|
||||||
|
# uv += uv_layer[loop_index].uv
|
||||||
|
# uv /= len(poly.loop_indices)
|
||||||
|
|
||||||
|
# # UV → Pixel-Koordinaten (Bildgröße)
|
||||||
|
# x = int(uv.x * (image.size[0] - 1))
|
||||||
|
# y = int(uv.y * (image.size[1] - 1))
|
||||||
|
|
||||||
|
# # RGBA Pixel holen
|
||||||
|
# index = (y * image.size[0] + x) * 4
|
||||||
|
# pixel = image.pixels[index:index+4]
|
||||||
|
|
||||||
|
# # In 0–255 Werte umwandeln
|
||||||
|
# rgb_255 = [round(c * 255) for c in pixel][:3]
|
||||||
|
|
||||||
|
# colors.append({
|
||||||
|
# "face": poly.index,
|
||||||
|
# "rgb": rgb_255
|
||||||
|
# })
|
||||||
|
|
||||||
|
# # print(colors[-1]) # -> [{'face': <BMFace(0x000002B417086A30), index=0, totverts=4>, 'rgb': [0, 0, 0]}]
|
||||||
|
|
||||||
|
# return colors
|
||||||
|
|
||||||
|
# def groupe_colors(self, color_list, threshold = 10):
|
||||||
|
# groups = []
|
||||||
|
|
||||||
|
# for color in color_list:
|
||||||
|
# found_group = False
|
||||||
|
# for group in groups:
|
||||||
|
# # Compute average color of the group
|
||||||
|
# avg_color = [sum(c["rgb"][i] for c in group)/len(group) for i in range(3)]
|
||||||
|
# if self.color_distance(color["rgb"], avg_color) <= threshold:
|
||||||
|
# group.append(color)
|
||||||
|
# found_group = True
|
||||||
|
# break
|
||||||
|
# if not found_group:
|
||||||
|
# groups.append([color])
|
||||||
|
|
||||||
|
# # Compute the average color for each group
|
||||||
|
# grouped_colors = [{
|
||||||
|
# "face": [c["face"] for c in group],
|
||||||
|
# "rgb": [round(sum(c["rgb"][i] for c in group) / len(group)) for i in range(3)]
|
||||||
|
# }
|
||||||
|
# for group in groups
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# return grouped_colors
|
||||||
|
|
||||||
|
# def color_distance(self, c1, c2):
|
||||||
|
# return math.sqrt(
|
||||||
|
# (c1[0]-c2[0])**2 +
|
||||||
|
# (c1[1]-c2[1])**2 +
|
||||||
|
# (c1[2]-c2[2])**2
|
||||||
|
# )
|
||||||
|
|
||||||
|
# def read_color_csv(self, palette):
|
||||||
|
# colors = []
|
||||||
|
# try:
|
||||||
|
# with open(f"{palette}.csv", newline="") as datei:
|
||||||
|
# reader = csv.DictReader(datei)
|
||||||
|
# for row in reader:
|
||||||
|
# colors.append({
|
||||||
|
# "rgb": [int(row["r"]), int(row["g"]), int(row["b"])],
|
||||||
|
# "norm_x": float(row["norm_x"]),
|
||||||
|
# "norm_y": float(row["norm_y"])
|
||||||
|
# })
|
||||||
|
# return colors
|
||||||
|
# except:
|
||||||
|
# return []
|
||||||
|
|
||||||
|
# def find_matching_color(self, colors, palette):
|
||||||
|
# match = []
|
||||||
|
# for color in colors:
|
||||||
|
# best = None
|
||||||
|
# min_dist = float("inf")
|
||||||
|
|
||||||
|
# for target in self.read_color_csv(palette):
|
||||||
|
# dist = self.color_distance(target["rgb"], color["rgb"])
|
||||||
|
# if dist < min_dist:
|
||||||
|
# min_dist = dist
|
||||||
|
# best = {
|
||||||
|
# "face": color["face"],
|
||||||
|
# "rgb": color["rgb"],
|
||||||
|
# "norm_x": target["norm_x"],
|
||||||
|
# "norm_y": target["norm_y"]
|
||||||
|
# }
|
||||||
|
# match.append(best)
|
||||||
|
# return match
|
||||||
|
|
||||||
|
# def adjust_UV(self, obj, new_face_coords):
|
||||||
|
# scale = 0.001 # Skalierung
|
||||||
|
|
||||||
|
# mesh = obj.data
|
||||||
|
|
||||||
|
# arr_layers = obj.data.uv_layers
|
||||||
|
# if not arr_layers.get("MLOD") or len(arr_layers) > 1:
|
||||||
|
# for uv_layer in reversed(arr_layers):
|
||||||
|
# arr_layers.remove(uv_layer)
|
||||||
|
# arr_layers.new(name = 'MLOD')
|
||||||
|
# uv_layer = arr_layers.get("MLOD")
|
||||||
|
# mesh.uv_layers.active = uv_layer
|
||||||
|
|
||||||
|
# for poly in mesh.polygons:
|
||||||
|
# for coords in new_face_coords:
|
||||||
|
# if poly.index in coords["face"]:
|
||||||
|
|
||||||
|
# # Mittelpunkt der UVs dieses Polygons berechnen
|
||||||
|
# uvs = [uv_layer.data[i].uv.copy() for i in poly.loop_indices]
|
||||||
|
# center = sum(uvs, Vector((0.0, 0.0))) / len(uvs)
|
||||||
|
|
||||||
|
# # Skalieren + Verschieben
|
||||||
|
# for i in poly.loop_indices:
|
||||||
|
# uv = uv_layer.data[i].uv
|
||||||
|
# uv -= center
|
||||||
|
# uv *= scale
|
||||||
|
# uv += Vector((float(coords["norm_x"]), float(coords["norm_y"])))
|
||||||
|
|
||||||
|
# # Mesh updaten
|
||||||
|
# mesh.update()
|
||||||
|
|
||||||
|
# def bake_diffuse(self, selected_obj, res = 1024):
|
||||||
|
|
||||||
|
# source_obj = None
|
||||||
|
# for obj in bpy.data.objects:
|
||||||
|
# if obj.type == 'MESH' and obj.name.endswith("_LOD0"):
|
||||||
|
# print(obj.name)
|
||||||
|
# source_obj = obj.copy()
|
||||||
|
# source_obj.data = obj.data.copy()
|
||||||
|
# bpy.context.collection.objects.link(source_obj)
|
||||||
|
# break
|
||||||
|
|
||||||
|
# if source_obj is None:
|
||||||
|
# source_obj = selected_obj.copy()
|
||||||
|
# source_obj.data = selected_obj.data.copy()
|
||||||
|
# bpy.context.collection.objects.link(source_obj)
|
||||||
|
|
||||||
|
# # Material erstellen
|
||||||
|
# if "MLOD_Temp" in bpy.data.materials:
|
||||||
|
# mat = bpy.data.materials.remove["MLOD_Temp"]
|
||||||
|
# mat = bpy.data.materials.new(name="MLOD_Temp")
|
||||||
|
# source_obj.data.materials.clear()
|
||||||
|
# source_obj.data.materials.append(mat)
|
||||||
|
|
||||||
|
# # WICHTIG: Material als aktives Material setzen
|
||||||
|
# mat.use_nodes = True
|
||||||
|
# source_obj.active_material = mat
|
||||||
|
|
||||||
|
# # Ziel Image Node konfigurieren
|
||||||
|
# nodes = mat.node_tree.nodes
|
||||||
|
# links = mat.node_tree.links
|
||||||
|
# nodes.clear()
|
||||||
|
# tex_node = nodes.new(type="ShaderNodeTexImage")
|
||||||
|
# bsdf = nodes.new(type="ShaderNodeBsdfPrincipled")
|
||||||
|
# output = nodes.new(type="ShaderNodeOutputMaterial")
|
||||||
|
# links.new(bsdf.outputs["BSDF"], output.inputs["Surface"])
|
||||||
|
# links.new(tex_node.outputs["Color"], bsdf.inputs["Base Color"])
|
||||||
|
# nodes.active = tex_node
|
||||||
|
|
||||||
|
# # Erstellen des Images
|
||||||
|
# if "MLOD_Temp" in bpy.data.images:
|
||||||
|
# bpy.data.images.remove(bpy.data.images["MLOD_Temp"])
|
||||||
|
# image = bpy.data.images.new("MLOD_Temp", res, res)
|
||||||
|
# tex_node.image = image
|
||||||
|
|
||||||
|
# # Selected Object = Quelle
|
||||||
|
# bpy.context.view_layer.objects.active = source_obj
|
||||||
|
# # Active Object = Ziel
|
||||||
|
# selected_obj.select_set(True)
|
||||||
|
|
||||||
|
# # Bake
|
||||||
|
# bpy.context.scene.render.engine = 'CYCLES'
|
||||||
|
# bpy.ops.object.bake(type='COMBINED', width=512, height=512, cage_extrusion=0.5, pass_filter={'COLOR'}, margin=16, use_selected_to_active=True)
|
||||||
|
|
||||||
|
# # Temporäre Helfer entfernen
|
||||||
|
# bpy.data.objects.remove(source_obj, do_unlink=True)
|
||||||
|
# if "MLOD_Temp" in bpy.data.materials:
|
||||||
|
# bpy.data.materials.remove(mat)
|
||||||
|
|
||||||
|
# bpy.context.view_layer.objects.active = selected_obj
|
||||||
|
|
||||||
|
# return image
|
||||||
1025
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/ColorPalette_01.csv
Normal file
1025
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/ColorPalette_01.csv
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
1025
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/ColorPalette_02.csv
Normal file
1025
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/ColorPalette_02.csv
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,94 @@
|
|||||||
|
import bpy
|
||||||
|
from math import pi
|
||||||
|
|
||||||
|
L1960_Arr_PlainData = [
|
||||||
|
["Proj_Empty_01", (0, 0, 2), (0, 0, 0)],
|
||||||
|
["Proj_Empty_02", (2, 0, 0), ((pi * 90 / 180), 0, (pi * 90 / 180))],
|
||||||
|
["Proj_Empty_03", (0, 2, 0), ((pi * 90 / 180), 0, (pi * 180 / 180))],
|
||||||
|
["Proj_Empty_04", (0, 0, -2), ((pi * 180 / 180), 0, 0)],
|
||||||
|
["Proj_Empty_05", (-2, 0, 0), ((pi * 90 / 180), 0, (pi * -90 / 180))],
|
||||||
|
["Proj_Empty_06", (0, -2, 0), ((pi * 90 / 180), 0, 0)]
|
||||||
|
]
|
||||||
|
|
||||||
|
class MESH_OT_add_auto_cube_projection(bpy.types.Operator):
|
||||||
|
"""Check Empty´s for projecting, if not existing create new one´s"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.add_auto_cube_projection"
|
||||||
|
bl_label = "Add Plain_Axes to scene"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
oldSelection = context.selected_objects
|
||||||
|
oldActive = context.active_object
|
||||||
|
|
||||||
|
for EmptyData in L1960_Arr_PlainData:
|
||||||
|
|
||||||
|
EmptyName = EmptyData[0]
|
||||||
|
EmptyLocation = EmptyData[1]
|
||||||
|
EmptyRotation = EmptyData[2]
|
||||||
|
|
||||||
|
if not scene.objects.get(EmptyName):
|
||||||
|
bpy.ops.object.empty_add(type='PLAIN_AXES', align='WORLD', location=EmptyLocation, scale=(1, 1, 1), rotation=EmptyRotation)
|
||||||
|
empty = context.active_object
|
||||||
|
empty.name = EmptyName
|
||||||
|
empty.hide_select = True
|
||||||
|
empty.hide_set(True)
|
||||||
|
|
||||||
|
#Change back to old selection and select old active
|
||||||
|
for obj in oldSelection:
|
||||||
|
obj.select_set(True)
|
||||||
|
context.view_layer.objects.active = oldActive
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Added/Fixed Emptys for Projection to Scene')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class MESH_OT_add_modifier_to_mesh(bpy.types.Operator):
|
||||||
|
"""Add Modifier to selected Mesh´s and prepare UV-Maps"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.add_modifier_to_mesh"
|
||||||
|
bl_label = "Add Modifier to selected Mesh"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
newModifierName = "CubeTexModifier"
|
||||||
|
arr_obj = context.selected_objects
|
||||||
|
if not arr_obj:
|
||||||
|
self.report({'WARNING'}, 'Select a mesh to add the UVProject-Modifier')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
empty_objs = [emp_obj for emp_obj in context.scene.objects if emp_obj.name.startswith("Proj_Empty")]
|
||||||
|
if len(empty_objs) < 6:
|
||||||
|
self.report({'WARNING'}, 'Create/Recreate projectors, they need to be set up first!')
|
||||||
|
return {"CANCELLED"};
|
||||||
|
|
||||||
|
for obj in arr_obj:
|
||||||
|
|
||||||
|
if obj.data.uv_layers.get("UVMap"):
|
||||||
|
obj.data.uv_layers['UVMap'].name = 'UVMap0'
|
||||||
|
|
||||||
|
if not obj.data.uv_layers.get("UVMap0"):
|
||||||
|
obj.data.uv_layers.new(name = 'UVMap0')
|
||||||
|
|
||||||
|
if not obj.data.uv_layers.get("UVMap1"):
|
||||||
|
obj.data.uv_layers.new(name = 'UVMap1')
|
||||||
|
|
||||||
|
|
||||||
|
if obj.type == "MESH" and newModifierName not in obj.modifiers:
|
||||||
|
obj.modifiers.new(type='UV_PROJECT', name=newModifierName)
|
||||||
|
mod = obj.modifiers[newModifierName]
|
||||||
|
mod.uv_layer = "UVMap1"
|
||||||
|
mod.projector_count = 6
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for p in mod.projectors:
|
||||||
|
p.object = bpy.data.objects[L1960_Arr_PlainData[i][0]]
|
||||||
|
i = i+1
|
||||||
|
else:
|
||||||
|
self.report({'INFO'}, 'UVProject-Modifier allready set')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Added UVProject-Modifier to mesh')
|
||||||
|
return {"FINISHED"}
|
||||||
187
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/Dekogon.py
Normal file
187
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/Dekogon.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import bpy
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
### GROUP OBJECTS TO COLLECTIONS ###
|
||||||
|
|
||||||
|
class MESH_OT_group_objects_in_collections(bpy.types.Operator):
|
||||||
|
"""Groups objects by name into seperate collections, usefull for Unreal Decogon import"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.group_objects_in_collections"
|
||||||
|
bl_label = "Groups objects by name into seperate collections, usefull for Unreal Decogon import"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
# Erstellen einer leeren Dictionary zur Speicherung der Collections nach Basisnamen
|
||||||
|
collection_dict = {}
|
||||||
|
|
||||||
|
# Durchlaufen aller Objekte in der Szene
|
||||||
|
for obj in context.scene.objects:
|
||||||
|
# Überprüfen, ob das Objekt ein leeres Elternobjekt ist
|
||||||
|
if obj.type == 'EMPTY':
|
||||||
|
# Anwenden der Regex auf den Objektnamen
|
||||||
|
match = re.match(r'^(.*?)_(.*?)_(\d+)([a-zA-Z]+)$', obj.name)
|
||||||
|
if match:
|
||||||
|
prefix = match.group(1)
|
||||||
|
name = match.group(2)
|
||||||
|
number = match.group(3)
|
||||||
|
letter = match.group(4)
|
||||||
|
|
||||||
|
# Extrahieren des Basisnamens für die Collection
|
||||||
|
base_name = f"L1960_{name}"
|
||||||
|
|
||||||
|
# Hinzufügen des Objekts zur entsprechenden Liste in der Dictionary
|
||||||
|
if base_name not in collection_dict:
|
||||||
|
collection_dict[base_name] = {}
|
||||||
|
if number not in collection_dict[base_name]:
|
||||||
|
collection_dict[base_name][number] = {}
|
||||||
|
if letter not in collection_dict[base_name][number]:
|
||||||
|
collection_dict[base_name][number][letter] = {'empty': obj, 'children': []}
|
||||||
|
|
||||||
|
# Durchlaufen der Dictionary und Erstellen der Collections
|
||||||
|
for base_name, number_dict in collection_dict.items():
|
||||||
|
# Erstellen einer neuen Collection für den Basisnamen
|
||||||
|
base_collection = bpy.data.collections.new(base_name)
|
||||||
|
bpy.context.scene.collection.children.link(base_collection)
|
||||||
|
|
||||||
|
# Durchlaufen der sortierten Liste der Objekte und Verschieben in die Collections
|
||||||
|
for number, letter_dict in number_dict.items():
|
||||||
|
# Erstellen einer Collection für die Nummer und Verschieben des leeren Elternobjekts dorthin
|
||||||
|
number_collection = bpy.data.collections.new(f"{base_name}_{number}")
|
||||||
|
base_collection.children.link(number_collection)
|
||||||
|
|
||||||
|
# Durchlaufen der Buchstaben und Erstellen der Buchstaben-Collection unter der Nummer
|
||||||
|
for letter, obj_data in letter_dict.items():
|
||||||
|
empty = obj_data['empty']
|
||||||
|
children = empty.children
|
||||||
|
|
||||||
|
letter_collection = bpy.data.collections.new(f"{base_name}_{number}{letter}")
|
||||||
|
number_collection.children.link(letter_collection)
|
||||||
|
|
||||||
|
# Verschieben des leeren Elternobjekts in die entsprechende Collection
|
||||||
|
letter_collection.objects.link(empty)
|
||||||
|
|
||||||
|
# Verschieben der Kinder des leeren Elternobjekts in die entsprechende Collection
|
||||||
|
for child in children:
|
||||||
|
letter_collection.objects.link(child)
|
||||||
|
|
||||||
|
# Entfernen des leeren Elternobjekts und seiner Kinder aus der Szene
|
||||||
|
bpy.context.collection.objects.unlink(empty)
|
||||||
|
for child in children:
|
||||||
|
bpy.context.collection.objects.unlink(child)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------------------
|
||||||
|
# Regular expression pattern to match any _LOD1, _LOD2, etc., but not _LOD0
|
||||||
|
pattern = re.compile(r"_LOD[1-9]\d*$")
|
||||||
|
|
||||||
|
# Loop through all objects in the scene
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
# If the object name matches the pattern, hide it
|
||||||
|
if pattern.search(obj.name):
|
||||||
|
obj.hide_set(True) # This hides the object in the viewport
|
||||||
|
else:
|
||||||
|
obj.hide_set(False) # Ensure _LOD0 objects remain visible
|
||||||
|
|
||||||
|
# Update the view layer to reflect changes
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'All objects sorted')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
### ASSIGN TEXTURES ###
|
||||||
|
|
||||||
|
class MESH_OT_assign_textures(bpy.types.Operator):
|
||||||
|
"""Assign Textures to Mesh´s, usefull for Unreal Decogon import"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.assign_textures"
|
||||||
|
bl_label = "Assign Textures to Mesh´s, usefull for Unreal Decogon import"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
# Define the path to the folder containing your textures
|
||||||
|
texture_folder_path = context.scene.dekogon_file_path
|
||||||
|
|
||||||
|
texture_prefix = context.scene.dekogon_settings_prefix
|
||||||
|
if texture_prefix != "":
|
||||||
|
if texture_prefix[-1] != "_":
|
||||||
|
texture_prefix = texture_prefix + "_"
|
||||||
|
|
||||||
|
texture_suffix = context.scene.dekogon_settings_suffix
|
||||||
|
if texture_suffix != "":
|
||||||
|
if texture_suffix[0] != "_":
|
||||||
|
texture_suffix = "_" + texture_suffix
|
||||||
|
|
||||||
|
texture_filetype = context.scene.dekogon_settings_filetype
|
||||||
|
|
||||||
|
if not os.path.exists(texture_folder_path):
|
||||||
|
self.report({'ERROR'}, f"The texture folder path '{texture_folder_path}' does not exist.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
else:
|
||||||
|
self.report({'INFO'}, f"Texture folder path: {texture_folder_path}")
|
||||||
|
# List all files in the texture folder for debugging purposes
|
||||||
|
#texture_files = os.listdir(texture_folder_path)
|
||||||
|
#self.report({'INFO'}, "Files in texture folder:")
|
||||||
|
#for f in texture_files:
|
||||||
|
# print(f" - {f}")
|
||||||
|
|
||||||
|
# Iterate over all materials in the Blender file
|
||||||
|
for material in bpy.data.materials:
|
||||||
|
self.report({'INFO'}, f"Processing material: {material.name}")
|
||||||
|
# Ensure the material uses nodes
|
||||||
|
if not material.use_nodes:
|
||||||
|
material.use_nodes = True
|
||||||
|
self.report({'INFO'}, f"Enabled nodes for material: {material.name}")
|
||||||
|
|
||||||
|
# Get the node tree of the material
|
||||||
|
if material.node_tree is None:
|
||||||
|
self.report({'WARNING'}, f"Material {material.name} has no node tree.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
nodes = material.node_tree.nodes
|
||||||
|
|
||||||
|
# Find or create an Image Texture node
|
||||||
|
image_texture_node = None
|
||||||
|
for node in nodes:
|
||||||
|
if node.type == 'TEX_IMAGE':
|
||||||
|
image_texture_node = node
|
||||||
|
break
|
||||||
|
if image_texture_node is None:
|
||||||
|
image_texture_node = nodes.new(type='ShaderNodeTexImage')
|
||||||
|
self.report({'INFO'}, f"Created new Image Texture node for material: {material.name}")
|
||||||
|
else:
|
||||||
|
self.report({'INFO'}, f"Found existing Image Texture node for material: {material.name}")
|
||||||
|
|
||||||
|
# Construct the expected texture filename based on the material name
|
||||||
|
texture_filename = f"{texture_prefix}{material.name}{texture_suffix}.{texture_filetype}"
|
||||||
|
texture_filepath = os.path.join(texture_folder_path, texture_filename)
|
||||||
|
self.report({'INFO'}, f"Looking for texture: {texture_filepath}")
|
||||||
|
|
||||||
|
# Check if the texture file exists
|
||||||
|
if os.path.exists(texture_filepath):
|
||||||
|
try:
|
||||||
|
# Load the image and assign it to the Image Texture node
|
||||||
|
image = bpy.data.images.load(texture_filepath)
|
||||||
|
image_texture_node.image = image
|
||||||
|
self.report({'INFO'}, f"Loaded texture: {texture_filename} for material: {material.name}")
|
||||||
|
|
||||||
|
# Link the Image Texture node to the Base Color of the Principled BSDF shader
|
||||||
|
principled_node = None
|
||||||
|
for node in nodes:
|
||||||
|
if node.type == 'BSDF_PRINCIPLED':
|
||||||
|
principled_node = node
|
||||||
|
break
|
||||||
|
if principled_node is not None:
|
||||||
|
links = material.node_tree.links
|
||||||
|
links.new(image_texture_node.outputs['Color'], principled_node.inputs['Base Color'])
|
||||||
|
self.report({'INFO'}, f"Linked texture to Principled BSDF for material: {material.name}")
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, f"No Principled BSDF node found for material: {material.name}")
|
||||||
|
except RuntimeError as e:
|
||||||
|
self.report({'ERROR'}, f"Failed to load image {texture_filename}: {e}")
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, "Texture file not found for material {material.name}: {texture_filename}")
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Texture assignment complete.')
|
||||||
|
return {"FINISHED"}
|
||||||
139
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/Helper.py
Normal file
139
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/Helper.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import bpy
|
||||||
|
import bmesh
|
||||||
|
|
||||||
|
### FIX MATERIALS ###
|
||||||
|
|
||||||
|
class MESH_OT_fix_material_names(bpy.types.Operator):
|
||||||
|
"""Fixes the material naming, if duplicated are present e.g. Material.001, Material.002 ..."""
|
||||||
|
|
||||||
|
bl_idname = "mesh.fix_material_names"
|
||||||
|
bl_label = "Fixes the material naming, if duplicated are present"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
#Remove all duplicated materials
|
||||||
|
self.merge_duplicated_materials()
|
||||||
|
|
||||||
|
#Merge material slots for every mesh in the scene
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
self.merge_material_slots(obj)
|
||||||
|
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'All duplicated Materials fixed')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def merge_duplicated_materials(self):
|
||||||
|
for material in bpy.data.materials:
|
||||||
|
#Remove MI for Dekogon material names
|
||||||
|
if material.name.split('_')[0] == 'MI':
|
||||||
|
material.name = material.name[3:]
|
||||||
|
#Check for .001 at end and remove
|
||||||
|
if material.name[-3:].isnumeric():
|
||||||
|
opti_matName = material.name[:-4]
|
||||||
|
if bpy.data.materials.get(opti_matName): #check if og_mat exists
|
||||||
|
material.user_remap(bpy.data.materials.get(opti_matName))
|
||||||
|
print("Removed Material: " + material.name)
|
||||||
|
bpy.data.materials.remove(material)
|
||||||
|
else:
|
||||||
|
material.name = opti_matName
|
||||||
|
|
||||||
|
def merge_material_slots(self, obj):
|
||||||
|
duplicated_material_list = []
|
||||||
|
|
||||||
|
#create list with indexes of material slots with the same name and merge them
|
||||||
|
for og_slot in obj.material_slots:
|
||||||
|
for slot in obj.material_slots:
|
||||||
|
if slot.name == og_slot.name:
|
||||||
|
if slot.slot_index == og_slot.slot_index:
|
||||||
|
continue
|
||||||
|
if og_slot.slot_index in duplicated_material_list:
|
||||||
|
continue
|
||||||
|
duplicated_material_list.append(int(slot.slot_index))
|
||||||
|
|
||||||
|
#delete all material slots within list
|
||||||
|
for slot_index in sorted(duplicated_material_list, reverse=True):
|
||||||
|
obj.data.materials.pop(index = slot_index)
|
||||||
|
|
||||||
|
|
||||||
|
### FIX NAMING CONVENTIONS ###
|
||||||
|
|
||||||
|
class MESH_OT_fix_naming_conventions(bpy.types.Operator):
|
||||||
|
"""Changes . to _ to solve naming issues with workbench"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.fix_naming_convention"
|
||||||
|
bl_label = "Changes . to _ to solve naming issues with workbench"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for obj in bpy.data.objects:
|
||||||
|
obj.name = obj.name.replace(".","_")
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Fixed Naming')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class MESH_OT_generate_empty_for_mesh(bpy.types.Operator):
|
||||||
|
"""Generates a Empty with the objects name at it´s position"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.generate_empty_for_mesh"
|
||||||
|
bl_label = "Generates a Empty with the objects name at it´s position"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
scene = bpy.context.scene
|
||||||
|
Selection = bpy.context.selected_objects
|
||||||
|
FilteredSelection = [obj for obj in Selection if obj.type != 'EMPTY']
|
||||||
|
ActiveObj = bpy.context.active_object
|
||||||
|
|
||||||
|
for obj in FilteredSelection:
|
||||||
|
oldEmpty = scene.objects.get("Socket_" + obj.name)
|
||||||
|
if oldEmpty:
|
||||||
|
oldEmpty.location = obj.location
|
||||||
|
oldEmpty.rotation_euler = obj.rotation_euler
|
||||||
|
else:
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.ops.object.empty_add(type='PLAIN_AXES', align='WORLD', location=obj.location, scale=(1, 1, 1), rotation=obj.rotation_euler)
|
||||||
|
empty = bpy.context.active_object
|
||||||
|
empty.name = "Socket_" + obj.name
|
||||||
|
|
||||||
|
#Change back to selection and select old active
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Generated Emptys for selectes Meshes in Scene')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class MESH_OT_select_face_id(bpy.types.Operator):
|
||||||
|
"""Highlights Face with given ID"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.select_face_id"
|
||||||
|
bl_label = "Highlight/Select Face with given Index"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
EditObj = bpy.context.active_object
|
||||||
|
|
||||||
|
face_Index = int(context.scene.face_id_field)
|
||||||
|
|
||||||
|
if EditObj != None:
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||||||
|
bm = bmesh.from_edit_mesh(EditObj.data)
|
||||||
|
bm.faces.ensure_lookup_table()
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == 'VIEW_3D':
|
||||||
|
for space in area.spaces:
|
||||||
|
if space.type == 'VIEW_3D':
|
||||||
|
space.shading.type = 'WIREFRAME'
|
||||||
|
try:
|
||||||
|
bm.faces[face_Index].select = True
|
||||||
|
bmesh.update_edit_mesh(EditObj.data)
|
||||||
|
|
||||||
|
self.report({'INFO'}, f'Highlighted Face with Index: {face_Index}')
|
||||||
|
return {"FINISHED"}
|
||||||
|
except IndexError:
|
||||||
|
self.report({'INFO'}, f'Index: {face_Index} out of range!')
|
||||||
|
return {"FINISHED"}
|
||||||
|
else:
|
||||||
|
self.report({'INFO'}, 'Please select a Mesh first.')
|
||||||
|
return {"FINISHED"}
|
||||||
244
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/MaterialToMask.py
Normal file
244
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/MaterialToMask.py
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import bpy
|
||||||
|
import bmesh
|
||||||
|
|
||||||
|
# Callback for Enum Items
|
||||||
|
def get_uv_layers(self, context):
|
||||||
|
items = []
|
||||||
|
obj = context.object
|
||||||
|
|
||||||
|
if obj and obj.type == 'MESH' and obj.data.uv_layers:
|
||||||
|
for i, uv in enumerate(obj.data.uv_layers):
|
||||||
|
items.append((str(i), uv.name, f"UV Layer {i}"))
|
||||||
|
else:
|
||||||
|
items.append(("NONE", "No UVs", "No UV available within Mesh"))
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
class UVEnumProperties(bpy.types.PropertyGroup):
|
||||||
|
uv_enum: bpy.props.EnumProperty(name="MASK UV Map", description="Select Mask UV-Layer", items=get_uv_layers, default=0)
|
||||||
|
|
||||||
|
class MESH_OT_merge_materials_to_mask(bpy.types.Operator):
|
||||||
|
"""Merges 4 Materials into one Material (Mask)"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.merge_materials_to_mask"
|
||||||
|
bl_label = "Merges 4 Materials into one Material (Mask)"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
scene = context.scene
|
||||||
|
ts = scene.tool_settings
|
||||||
|
arr_obj = context.selected_objects
|
||||||
|
activeObj = context.active_object
|
||||||
|
|
||||||
|
# Get all inputs from user
|
||||||
|
selected_uv = context.scene.settings_uv_enum_props.uv_enum
|
||||||
|
ignore_custom = context.scene.settings_multi_ignor
|
||||||
|
mat_nameing = context.scene.settings_mat_nameing
|
||||||
|
island_margin_custom = context.scene.settings_uv_island_margin_slider
|
||||||
|
|
||||||
|
# Scale and offset for UV allignment
|
||||||
|
SCALE = 0.5
|
||||||
|
OFFSETS = [
|
||||||
|
(0.0, 0.5),
|
||||||
|
(0.5, 0.5),
|
||||||
|
(0.0, 0.0),
|
||||||
|
(0.5, 0.0)
|
||||||
|
]
|
||||||
|
IGNORE = ["glass", "glas", "grass", "gras", "atlas"]
|
||||||
|
if ignore_custom.strip():
|
||||||
|
IGNORE.extend(ignore_custom.split(","))
|
||||||
|
|
||||||
|
# No Mesh selected
|
||||||
|
if not arr_obj:
|
||||||
|
self.report({'WARNING'}, 'Select one mesh')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
# Only one Mesh selected?
|
||||||
|
if len(arr_obj) > 1:
|
||||||
|
self.report({'WARNING'}, 'Only one Mesh can be selected')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
obj = arr_obj[0]
|
||||||
|
|
||||||
|
# UV Maps present?
|
||||||
|
if not obj.data.uv_layers or not selected_uv:
|
||||||
|
self.report({'WARNING'}, 'No UV-Map selected')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
obj.data.uv_layers.active_index = int(selected_uv)
|
||||||
|
|
||||||
|
# Deactivate uv Select Sync in UV Editor. Makes Problems
|
||||||
|
old_ts_state = ts.use_uv_select_sync
|
||||||
|
ts.use_uv_select_sync = False
|
||||||
|
|
||||||
|
skip = 0
|
||||||
|
idx_multi = None
|
||||||
|
idx_multi_name = 1
|
||||||
|
delete = []
|
||||||
|
|
||||||
|
# Loop through all Materials
|
||||||
|
for i, slot in enumerate(obj.material_slots):
|
||||||
|
|
||||||
|
# Skip Materials in static ignore list or user ignore list
|
||||||
|
if any(str(name).lower() in slot.material.name.lower() for name in IGNORE):
|
||||||
|
self.report({'INFO'}, f'Material "{slot.material.name}" skipped.')
|
||||||
|
skip += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Switch to edit mode
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
# Create bmesh to work with
|
||||||
|
bm = bmesh.from_edit_mesh(obj.data)
|
||||||
|
uv_layer = bm.loops.layers.uv.active
|
||||||
|
|
||||||
|
# Deselect all
|
||||||
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
if slot.material:
|
||||||
|
# Select Mat slot
|
||||||
|
obj.active_material_index = i
|
||||||
|
|
||||||
|
# Select all Faces of Mat
|
||||||
|
bpy.ops.object.material_slot_select()
|
||||||
|
|
||||||
|
# If UV has assigned faces
|
||||||
|
if any(f.select for f in bm.faces):
|
||||||
|
# Repack UV Islands
|
||||||
|
bpy.ops.uv.select_all(action='SELECT')
|
||||||
|
bpy.ops.uv.pack_islands(margin=island_margin_custom)
|
||||||
|
|
||||||
|
# Select Offset
|
||||||
|
group = (i - skip) % len(OFFSETS)
|
||||||
|
offset = OFFSETS[group]
|
||||||
|
|
||||||
|
# Scale and offset UV
|
||||||
|
for f in bm.faces:
|
||||||
|
if f.select:
|
||||||
|
for loop in f.loops:
|
||||||
|
uv = loop[uv_layer].uv
|
||||||
|
uv[0] = offset[0] + uv[0] * SCALE
|
||||||
|
uv[1] = offset[1] + uv[1] * SCALE
|
||||||
|
|
||||||
|
# Apply Changes
|
||||||
|
bmesh.update_edit_mesh(obj.data)
|
||||||
|
|
||||||
|
# Create Texture Attributes with previous material names
|
||||||
|
match group:
|
||||||
|
case 0:
|
||||||
|
mat = bpy.data.materials.get(obj.material_slots[i].name)
|
||||||
|
mat["1. Black "] = mat.name
|
||||||
|
|
||||||
|
# Change name and use as base to merge
|
||||||
|
mat.name = str(mat_nameing).replace("$MeshName", obj.name.replace("L1960_", "").replace("KB3D_AMC_", "")) + "_" + str(idx_multi_name)
|
||||||
|
idx_multi_name += 1
|
||||||
|
idx_multi = i
|
||||||
|
self.report({'INFO'}, f'Material "{mat.name}" created.')
|
||||||
|
continue
|
||||||
|
case 1:
|
||||||
|
mat = bpy.data.materials.get(obj.material_slots[idx_multi].name)
|
||||||
|
mat["2. Red "] = slot.material.name
|
||||||
|
case 2:
|
||||||
|
mat = bpy.data.materials.get(obj.material_slots[idx_multi].name)
|
||||||
|
mat["3. Green "] = slot.material.name
|
||||||
|
case 3:
|
||||||
|
mat = bpy.data.materials.get(obj.material_slots[idx_multi].name)
|
||||||
|
mat["4. Blue "] = slot.material.name
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Merge Material A -> B
|
||||||
|
self.merge_mat_A_to_B(obj, i, idx_multi)
|
||||||
|
|
||||||
|
# Add Material B to delete list
|
||||||
|
delete.insert(0, i)
|
||||||
|
|
||||||
|
# Switch to object mode
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
# Delete not used materials from list
|
||||||
|
for mat in delete:
|
||||||
|
obj.active_material_index = mat
|
||||||
|
bpy.ops.object.material_slot_remove()
|
||||||
|
|
||||||
|
# Put UV first
|
||||||
|
self.put_UV_first(obj, selected_uv)
|
||||||
|
|
||||||
|
# Change UV names like in Reforger Tools
|
||||||
|
mesh = obj.data
|
||||||
|
uv_layers = mesh.uv_layers
|
||||||
|
for i, layer in enumerate(uv_layers):
|
||||||
|
layer.name = f"UVMap{i + 1}"
|
||||||
|
|
||||||
|
# Activate Select Sync in UV Editor
|
||||||
|
ts.use_uv_select_sync = old_ts_state
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'Merged Materials to Multitextures.')
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def merge_mat_A_to_B(self, obj, mat_A, mat_B):
|
||||||
|
# check if Mat Name is index or string and convert
|
||||||
|
if type(mat_A) is str:
|
||||||
|
mat_A = bpy.data.materials.get(mat_A)
|
||||||
|
mat_A = obj.material_slots.find(mat_A)
|
||||||
|
if type(mat_B) is str:
|
||||||
|
mat_B = bpy.data.materials.get(mat_B)
|
||||||
|
mat_B = obj.material_slots.find(mat_B)
|
||||||
|
|
||||||
|
# Change to Edit Mode
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
# Select all Faces from Mat B
|
||||||
|
obj.active_material_index = mat_A
|
||||||
|
bpy.ops.object.material_slot_select()
|
||||||
|
|
||||||
|
# Select Mat A and assign Faces from Mat B
|
||||||
|
obj.active_material_index = mat_B
|
||||||
|
bpy.ops.object.material_slot_assign() # Faces from B → A
|
||||||
|
|
||||||
|
# Switch to object mode
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
def put_UV_first(self, obj, target_index):
|
||||||
|
target_index = int(target_index)
|
||||||
|
mesh = obj.data
|
||||||
|
uv_layers = mesh.uv_layers
|
||||||
|
|
||||||
|
if target_index < 0 or target_index >= len(uv_layers):
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(uv_layers) <= 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sicherstellen, dass wir im Object Mode sind
|
||||||
|
if obj.mode != 'OBJECT':
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
# Backup aller UV-Layer
|
||||||
|
uv_backup = []
|
||||||
|
for layer in uv_layers:
|
||||||
|
uv_backup.append({
|
||||||
|
"name": layer.name,
|
||||||
|
"data": [loop.uv.copy() for loop in layer.data]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Neue Reihenfolge: target_index zuerst
|
||||||
|
new_order = [target_index] + [
|
||||||
|
i for i in range(len(uv_layers)) if i != target_index
|
||||||
|
]
|
||||||
|
|
||||||
|
# Alle UV-Layer löschen (rückwärts!)
|
||||||
|
for i in range(len(uv_layers) - 1, -1, -1):
|
||||||
|
uv_layers.remove(uv_layers[i])
|
||||||
|
|
||||||
|
# UV-Layer neu erstellen
|
||||||
|
for idx in new_order:
|
||||||
|
info = uv_backup[idx]
|
||||||
|
new_layer = uv_layers.new(name=info["name"])
|
||||||
|
for i, uv in enumerate(info["data"]):
|
||||||
|
new_layer.data[i].uv = uv
|
||||||
|
|
||||||
|
# Ersten UV-Layer aktiv & render setzen
|
||||||
|
uv_layers.active_index = 0
|
||||||
|
uv_layers[0].active_render = True
|
||||||
42
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/PrepareLods.py
Normal file
42
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/PrepareLods.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
### PREPARE LODS ###
|
||||||
|
|
||||||
|
class MESH_OT_prepare_lods_decimate(bpy.types.Operator):
|
||||||
|
"""Copy current Mesh and apply decimate Modifier"""
|
||||||
|
|
||||||
|
bl_idname = "mesh.prepare_lods_decimate"
|
||||||
|
bl_label = "Copy current Mesh and apply decimate Modifier"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
#Selected Mesh
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
|
||||||
|
if obj not in bpy.context.selected_objects or obj.type != "MESH":
|
||||||
|
self.report({'WARNING'}, 'Select a Mesh to continue')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if not obj.name[:-1].endswith('LOD'):
|
||||||
|
obj.name = obj.name + '_LOD0'
|
||||||
|
|
||||||
|
LODnumber = context.scene.lod_slider #Get from Slider
|
||||||
|
startLODcount = int(obj.name[-1])
|
||||||
|
endLODcount = startLODcount + LODnumber
|
||||||
|
|
||||||
|
for i in range (startLODcount + 1, endLODcount):
|
||||||
|
new_obj = obj.copy()
|
||||||
|
new_obj.data = obj.data.copy()
|
||||||
|
new_obj.name = obj.name[:-1] + str(i)
|
||||||
|
bpy.context.collection.objects.link(new_obj)
|
||||||
|
|
||||||
|
for t in range (startLODcount, i):
|
||||||
|
newModifierName = 'LOD_Decimate_' + str(t)
|
||||||
|
new_obj.modifiers.new(type='DECIMATE', name=newModifierName)
|
||||||
|
mod = new_obj.modifiers[newModifierName]
|
||||||
|
mod.ratio = 0.49
|
||||||
|
mod.use_collapse_triangulate = True
|
||||||
|
|
||||||
|
self.report({'INFO'}, 'LOD´s created')
|
||||||
|
return {"FINISHED"}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import csv
|
||||||
|
|
||||||
|
palettes = ["ColorPalette_01", "ColorPalette_02"]
|
||||||
|
|
||||||
|
for palette in palettes:
|
||||||
|
# Bild öffnen (Pfad anpassen)
|
||||||
|
bild = Image.open(f"{palette}.png").convert("RGB")
|
||||||
|
|
||||||
|
breite, hoehe = bild.size
|
||||||
|
tiles_x, tiles_y = 32, 32
|
||||||
|
|
||||||
|
# Auf 32x32 verkleinern (nimmt repräsentative Pixel pro Kachel)
|
||||||
|
klein = bild.resize((32, 32), Image.NEAREST)
|
||||||
|
|
||||||
|
# CSV-Datei schreiben
|
||||||
|
with open(f"{palette}.csv", mode="w", newline="") as datei:
|
||||||
|
writer = csv.writer(datei)
|
||||||
|
writer.writerow(["norm_x", "norm_y", "r", "g", "b"])
|
||||||
|
|
||||||
|
for py in range(tiles_y):
|
||||||
|
for px in range(tiles_x):
|
||||||
|
# Farbe an diesem Pixel holen
|
||||||
|
r, g, b = bild.getpixel((int(px), int(py)))
|
||||||
|
|
||||||
|
# Normalisierung [0,1]
|
||||||
|
norm_x = (px / (breite)) + 1/64
|
||||||
|
norm_y = 1.0 - ((py / (hoehe))) - 1/64
|
||||||
|
|
||||||
|
writer.writerow([f"{norm_x:.6f}", f"{norm_y:.6f}", r, g, b])
|
||||||
222
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/__init__.py
Normal file
222
Blender/L1960_Tools/_Source/L1960_Tools_1_8_5/__init__.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
bl_info = {
|
||||||
|
"name": "L1960 Tools",
|
||||||
|
"author": "Prodeath21",
|
||||||
|
"version": (1, 8, 5),
|
||||||
|
"blender": (4, 2, 0),
|
||||||
|
"location": "3D Viewport > Sidebar > 1960Life category",
|
||||||
|
"description": "Set´s up the Projection-Modifier automatically and add´s in the Emptys if not allready created.",
|
||||||
|
"category": "Object",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------
|
||||||
|
# Import modules
|
||||||
|
# ----------------------------------------------
|
||||||
|
if "bpy" in locals():
|
||||||
|
import imp
|
||||||
|
imp.reload(Dekogon)
|
||||||
|
imp.reload(CubeProjection)
|
||||||
|
imp.reload(Helper)
|
||||||
|
imp.reload(PrepareLods)
|
||||||
|
imp.reload(AutoColorPalette)
|
||||||
|
imp.reload(MaterialToMask)
|
||||||
|
print("L1960 Tools: Reloaded multifiles")
|
||||||
|
else:
|
||||||
|
from . import Dekogon
|
||||||
|
from . import CubeProjection
|
||||||
|
from . import Helper
|
||||||
|
from . import PrepareLods
|
||||||
|
from . import AutoColorPalette
|
||||||
|
from . import MaterialToMask
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from . Dekogon import MESH_OT_group_objects_in_collections, MESH_OT_assign_textures
|
||||||
|
from . CubeProjection import MESH_OT_add_auto_cube_projection, MESH_OT_add_modifier_to_mesh
|
||||||
|
from . Helper import MESH_OT_fix_material_names, MESH_OT_fix_naming_conventions, MESH_OT_generate_empty_for_mesh, MESH_OT_select_face_id
|
||||||
|
from . PrepareLods import MESH_OT_prepare_lods_decimate
|
||||||
|
from . AutoColorPalette import MESH_OT_set_up_mlod, EnumColorPalettes
|
||||||
|
from . MaterialToMask import UVEnumProperties, MESH_OT_merge_materials_to_mask
|
||||||
|
|
||||||
|
class L1960_PT_dekogon(bpy.types.Panel):
|
||||||
|
#where to add the panel
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
#add labels
|
||||||
|
bl_label = "1960-Life Import"
|
||||||
|
bl_category = "1960-Life"
|
||||||
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
box = self.layout.box()
|
||||||
|
# Dekogon-Import
|
||||||
|
box.label(text="Dekogon-Import")
|
||||||
|
row = box.row()
|
||||||
|
row.enabled = context.mode == 'OBJECT'
|
||||||
|
row.operator("mesh.group_objects_in_collections", text="Group to Collections")
|
||||||
|
row = box.row()
|
||||||
|
split = row.split(factor=0.5)
|
||||||
|
split.enabled = context.mode == 'OBJECT'
|
||||||
|
split.operator("mesh.assign_textures", text="Assign Textures")
|
||||||
|
split.prop(context.scene, "dekogon_file_path", text="")
|
||||||
|
row = box.row()
|
||||||
|
row.prop(context.scene, "section1",
|
||||||
|
text="Settings",
|
||||||
|
icon="TRIA_DOWN" if context.scene.section1 else "TRIA_RIGHT",
|
||||||
|
emboss=False)
|
||||||
|
if context.scene.section1:
|
||||||
|
box.prop(context.scene, "dekogon_settings_prefix", text="Prefix")
|
||||||
|
box.prop(context.scene, "dekogon_settings_suffix", text="Suffix")
|
||||||
|
box.prop(context.scene, "dekogon_settings_filetype", text="File Type")
|
||||||
|
self.layout.separator()
|
||||||
|
|
||||||
|
box = self.layout.box()
|
||||||
|
# Americano-Import
|
||||||
|
box.label(text="Materials-to-Multi")
|
||||||
|
row = box.row()
|
||||||
|
row.enabled = context.mode == 'OBJECT'
|
||||||
|
row.operator("mesh.merge_materials_to_mask", text="Merge Materials to Mask")
|
||||||
|
row = box.row()
|
||||||
|
row.prop(context.scene, "section2",
|
||||||
|
text="Settings",
|
||||||
|
icon="TRIA_DOWN" if context.scene.section2 else "TRIA_RIGHT",
|
||||||
|
emboss=False)
|
||||||
|
if context.scene.section2:
|
||||||
|
row = box.row()
|
||||||
|
props = context.scene.settings_uv_enum_props
|
||||||
|
row.prop(props, "uv_enum", text="UV Mask")
|
||||||
|
box.prop(context.scene, "settings_multi_ignor", text="Ignore")
|
||||||
|
box.prop(context.scene, "settings_mat_nameing", text="Naming")
|
||||||
|
box.prop(context.scene, "settings_uv_island_margin_slider", text="Island Margin")
|
||||||
|
|
||||||
|
class L1960_PT_tools(bpy.types.Panel):
|
||||||
|
#where to add the panel
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
#add labels
|
||||||
|
bl_label = "1960-Life Tools"
|
||||||
|
bl_category = "1960-Life"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
"""define the layout of the panel"""
|
||||||
|
box = self.layout.box()
|
||||||
|
# UV-Project helper
|
||||||
|
box.label(text="UV-Projection")
|
||||||
|
row = box.row()
|
||||||
|
row.enabled = context.mode == 'OBJECT'
|
||||||
|
row.operator("mesh.add_auto_cube_projection", text="Set up Projectors")
|
||||||
|
row = box.row()
|
||||||
|
row.operator("mesh.add_modifier_to_mesh", text="Add Modifier to Mesh")
|
||||||
|
self.layout.separator()
|
||||||
|
# Helpers
|
||||||
|
box = self.layout.box()
|
||||||
|
box.label(text="Various Helper")
|
||||||
|
row = box.row()
|
||||||
|
row.enabled = context.mode == 'OBJECT'
|
||||||
|
row.operator("mesh.fix_material_names", text="Fix Material Name´s")
|
||||||
|
row = box.row()
|
||||||
|
row.operator("mesh.fix_naming_convention", text="Fix Naming Convention")
|
||||||
|
row = box.row()
|
||||||
|
row.enabled = context.mode == 'OBJECT'
|
||||||
|
row.operator("mesh.generate_empty_for_mesh", text="Generate Empty")
|
||||||
|
row = box.row()
|
||||||
|
split = row.split(factor=0.7)
|
||||||
|
split.operator("mesh.select_face_id", text="Show Face Index")
|
||||||
|
split.prop(context.scene, "face_id_field", text="")
|
||||||
|
self.layout.separator()
|
||||||
|
# Generate LODs
|
||||||
|
box = self.layout.box()
|
||||||
|
box.label(text="LOD´s")
|
||||||
|
row = box.row()
|
||||||
|
row.operator("mesh.prepare_lods_decimate", text="Create LOD´s")
|
||||||
|
box.prop(context.scene, "lod_slider", text="Amount")
|
||||||
|
box = self.layout.box()
|
||||||
|
box.prop(context.scene.color_palettes, "mlod_enum_selection", expand=True)
|
||||||
|
row = box.row()
|
||||||
|
row.enabled = context.mode == 'OBJECT'
|
||||||
|
# row.operator("mesh.set_up_mlod", text="Set up MLOD")
|
||||||
|
row.operator("mesh.set_up_mlod", text="Set up MLOD (Palette)")
|
||||||
|
self.layout.separator()
|
||||||
|
###############################
|
||||||
|
# Enfusion Blender Tools Linked
|
||||||
|
box = self.layout.box()
|
||||||
|
box.label(text="EBT Linked")
|
||||||
|
row = box.row()
|
||||||
|
row.operator("ebt.sort_collections", text="Sort Objects")
|
||||||
|
# colliders setup is allowed in both OBJECT and EDIT mode
|
||||||
|
row = box.row()
|
||||||
|
row.operator("ebt.collider_cache_reload", icon="FILE_REFRESH", text="")
|
||||||
|
row.operator("ebt.collider_setup", text=" Colliders Setup")
|
||||||
|
# Light Setup
|
||||||
|
row = box.row()
|
||||||
|
row.operator("ebt.setup_light", text=" Light Setup")
|
||||||
|
# Update Materials
|
||||||
|
row = box.row()
|
||||||
|
col = row.column(align=True)
|
||||||
|
col.operator(
|
||||||
|
"ebt.update_blender_materials",
|
||||||
|
)
|
||||||
|
row = box.row()
|
||||||
|
col = row.column(align=True)
|
||||||
|
col.operator("ebt.open_in_workbench")
|
||||||
|
row.prop(context.scene, "ebt_xob_path")
|
||||||
|
row = box.row()
|
||||||
|
col = row.column(align=True)
|
||||||
|
|
||||||
|
#register the panel with blender
|
||||||
|
modules = [ L1960_PT_dekogon,
|
||||||
|
L1960_PT_tools,
|
||||||
|
MESH_OT_add_auto_cube_projection,
|
||||||
|
MESH_OT_add_modifier_to_mesh,
|
||||||
|
MESH_OT_merge_materials_to_mask,
|
||||||
|
MESH_OT_fix_material_names,
|
||||||
|
MESH_OT_group_objects_in_collections,
|
||||||
|
MESH_OT_assign_textures,
|
||||||
|
MESH_OT_fix_naming_conventions,
|
||||||
|
MESH_OT_generate_empty_for_mesh,
|
||||||
|
MESH_OT_prepare_lods_decimate,
|
||||||
|
MESH_OT_set_up_mlod,
|
||||||
|
EnumColorPalettes,
|
||||||
|
MESH_OT_select_face_id,
|
||||||
|
UVEnumProperties
|
||||||
|
]
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for mod in modules:
|
||||||
|
bpy.utils.register_class(mod)
|
||||||
|
|
||||||
|
bpy.types.Scene.lod_slider = bpy.props.IntProperty(name="LOD Number", default=3, min=0, max=10)
|
||||||
|
bpy.types.Scene.color_palettes = bpy.props.PointerProperty(type=EnumColorPalettes)
|
||||||
|
bpy.types.Scene.face_id_field = bpy.props.IntProperty(name="Face ID", default=0, min=0)
|
||||||
|
bpy.types.Scene.section1 = bpy.props.BoolProperty(name="Expand Section", default=False)
|
||||||
|
bpy.types.Scene.section2 = bpy.props.BoolProperty(name="Expand Section", default=False)
|
||||||
|
bpy.types.Scene.dekogon_file_path = bpy.props.StringProperty(name="Textures", description="Path to the texture folder, textures to be applied from", subtype="DIR_PATH", default="")
|
||||||
|
bpy.types.Scene.dekogon_settings_prefix = bpy.props.StringProperty(name="Prefix", default="TX")
|
||||||
|
bpy.types.Scene.dekogon_settings_suffix = bpy.props.StringProperty(name="Suffix", default="ALB")
|
||||||
|
bpy.types.Scene.dekogon_settings_filetype = bpy.props.StringProperty(name="File Type", default="tga")
|
||||||
|
bpy.types.Scene.settings_mat_nameing = bpy.props.StringProperty(name="Naming", default="Multi_$MeshName", description="Naming for new multi materials, use $MeshName as placeholder")
|
||||||
|
bpy.types.Scene.settings_multi_ignor = bpy.props.StringProperty(name="Ignore", default="", description="Ignore material when string is in name (split by comma) | Default: grass, glass, atlas")
|
||||||
|
bpy.types.Scene.settings_uv_enum_props = bpy.props.PointerProperty(type=UVEnumProperties)
|
||||||
|
bpy.types.Scene.settings_uv_island_margin_slider = bpy.props.FloatProperty(name="Island Margin", default=0.02, min=0.01, max=0.1)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for mod in modules:
|
||||||
|
bpy.utils.unregister_class(mod)
|
||||||
|
|
||||||
|
del bpy.types.Scene.lod_slider
|
||||||
|
del bpy.types.Scene.color_palettes
|
||||||
|
del bpy.types.Scene.face_id_field
|
||||||
|
del bpy.types.Scene.section1
|
||||||
|
del bpy.types.Scene.section2
|
||||||
|
del bpy.types.Scene.dekogon_file_path
|
||||||
|
del bpy.types.Scene.dekogon_settings_prefix
|
||||||
|
del bpy.types.Scene.dekogon_settings_suffix
|
||||||
|
del bpy.types.Scene.dekogon_settings_filetype
|
||||||
|
del bpy.types.Scene.settings_mat_nameing
|
||||||
|
del bpy.types.Scene.settings_multi_ignor
|
||||||
|
del bpy.types.Scene.settings_uv_enum_props
|
||||||
|
del bpy.types.Scene.settings_uv_island_margin_slider
|
||||||
|
|
||||||
|
if __name__== "__main__":
|
||||||
|
register()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user