diff --git a/assets/blender/buildings/parts/CMakeLists.txt b/assets/blender/buildings/parts/CMakeLists.txt index 2e151b6..2a87b94 100644 --- a/assets/blender/buildings/parts/CMakeLists.txt +++ b/assets/blender/buildings/parts/CMakeLists.txt @@ -18,14 +18,20 @@ endforeach() foreach(FURNITURE_FILE ${FURNITURE_FILES}) get_filename_component(FILE_NAME ${FURNITURE_FILE} NAME_WE) set(PARTS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/resources/buildings/parts/${FILE_NAME}) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${FURNITURE_FILE} + COMMAND ${BLENDER} -b ${CMAKE_CURRENT_BINARY_DIR}/${FURNITURE_FILE} -Y -P + ${CMAKE_CURRENT_SOURCE_DIR}/bake_furniture.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/bake_furniture.py) add_custom_command( OUTPUT ${PARTS_OUTPUT_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${PARTS_OUTPUT_DIR} - COMMAND ${BLENDER} ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} - -b -Y -P + COMMAND ${BLENDER} -b ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend + -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py -- ${PARTS_OUTPUT_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PARTS_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py) + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py) list(APPEND PARTS_OUTPUT_DIRS ${PARTS_OUTPUT_DIR}) endforeach() add_custom_target(import_building_parts ALL DEPENDS ${PARTS_OUTPUT_DIRS}) diff --git a/assets/blender/buildings/parts/bake_furniture.py b/assets/blender/buildings/parts/bake_furniture.py new file mode 100644 index 0000000..3a248b3 --- /dev/null +++ b/assets/blender/buildings/parts/bake_furniture.py @@ -0,0 +1,125 @@ +import bpy + +def get_all_children_recursive(obj, mesh_list): + """Recursively finds all mesh objects in the hierarchy.""" + for child in obj.children: + if child.type == 'MESH' and child.data.uv_layers: + mesh_list.append(child) + get_all_children_recursive(child, mesh_list) + +def setup_furniture_atlas(): + # 1. Gather valid objects: Children of empties starting with "furniture-" + + furniture_meshes = [] + roots = [o for o in bpy.data.objects if o.name.startswith("furniture-")] + + for root in roots: + # If the root itself is a mesh with UVs, add it + if root.type == 'MESH' and root.data.uv_layers: + furniture_meshes.append(root) + # Find all nested children + get_all_children_recursive(root, furniture_meshes) + + # Remove duplicates if any (in case of complex parenting) + furniture_meshes = list(set(furniture_meshes)) + + if not furniture_meshes: + print("No recursive mesh objects found with UVs.") + return + + furniture_objs = furniture_meshes + + if not furniture_objs: + print("No valid furniture mesh objects found with UVs.") + return + + # 2. Manage the Atlas Image (FurnitureColor) + image_name = "FurnitureColor" + if image_name in bpy.data.images: + atlas_img = bpy.data.images[image_name] + else: + atlas_img = bpy.data.images.new(image_name, width=2048, height=2048) + + # 3. Setup Materials & Nodes + processed_mats = set() + for obj in furniture_objs: + for slot in obj.material_slots: + mat = slot.material + if mat and mat not in processed_mats: + mat.use_nodes = True + nodes = mat.node_tree.nodes + + # Find or create the target node + bake_node = nodes.get("ATLAS_TARGET") + if not bake_node: + bake_node = nodes.new('ShaderNodeTexImage') + bake_node.name = "ATLAS_TARGET" + + bake_node.image = atlas_img + nodes.active = bake_node # Essential for the Bake operator + processed_mats.add(mat) + + # 4. Selection & Context Setup + bpy.ops.object.select_all(action='DESELECT') + for obj in furniture_objs: + obj.select_set(True) + bpy.context.view_layer.objects.active = furniture_objs[0] + + # 5. Bake Configuration (Cycles, Diffuse, Color only) + scene = bpy.context.scene + scene.render.engine = 'CYCLES' + scene.cycles.device = 'CPU' + scene.cycles.samples = 10 + scene.render.bake.use_pass_direct = False + scene.render.bake.use_pass_indirect = False + scene.render.bake.use_pass_color = True + scene.render.bake.target = 'IMAGE_TEXTURES' + + print("Baking Furniture Atlas...") + override = { + 'active_object': bpy.context.view_layer.objects.active, + 'selected_objects': bpy.context.selected_objects, + } + + with bpy.context.temp_override(**override): + bpy.ops.object.bake(type='DIFFUSE', margin=2) + atlas_img.pack() + + # 6. Create and Assign Final Atlas Material + atlas_mat_name = "M_Furniture_Atlas" + if atlas_mat_name in bpy.data.materials: + atlas_mat = bpy.data.materials[atlas_mat_name] + else: + atlas_mat = bpy.data.materials.new(name=atlas_mat_name) + atlas_mat.use_nodes = True + bsdf = atlas_mat.node_tree.nodes.get("Principled BSDF") + tex_node = atlas_mat.node_tree.nodes.new('ShaderNodeTexImage') + tex_node.image = atlas_img + atlas_mat.node_tree.links.new(tex_node.outputs['Color'], bsdf.inputs['Base Color']) + nodes = atlas_mat.node_tree.nodes + bsdf = nodes.get("Principled BSDF") + if bsdf: + bsdf.inputs['Roughness'].default_value = 1.0 + + # Reassign all valid objects to the new material + for obj in furniture_objs: + obj.data.materials.clear() + obj.data.materials.append(atlas_mat) + + print("Process Complete. Atlas material applied.") + for mat in bpy.data.materials: + if mat.users == 0: + bpy.data.materials.remove(mat) + bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) + + scene.render.engine = 'BLENDER_EEVEE' + current_path = bpy.data.filepath + if current_path: + new_path = current_path.replace(".blend", "_baked.blend") + bpy.ops.wm.save_as_mainfile(filepath=new_path) + print(f"File saved to: {new_path}") + else: + print("Warning: File not saved (unsaved blend file).") + +setup_furniture_atlas() + diff --git a/assets/blender/buildings/parts/edit-firniture-props.py b/assets/blender/buildings/parts/edit-firniture-props.py index 669ee90..8960b6a 100644 --- a/assets/blender/buildings/parts/edit-firniture-props.py +++ b/assets/blender/buildings/parts/edit-firniture-props.py @@ -81,7 +81,7 @@ class TEST_PT_ObjectPanel(bpy.types.Panel): # Link button with preset arguments row = layout.row(align=True) op = row.operator("test.link_and_play", text="Link & Sit", icon='PLAY') - op.filepath = "//../../edited-normal-male.blend" # SET YOUR FILEPATH + op.filepath = "//../../characters/edited-normal-male.blend" # SET YOUR FILEPATH op.rig_name = "male" # SET YOUR RIG NAME op.action_name = "sitting-chair" # SET YOUR ACTION NAME diff --git a/assets/blender/buildings/parts/furniture-sofa.blend b/assets/blender/buildings/parts/furniture-sofa.blend index 804e2a3..69769a1 100644 --- a/assets/blender/buildings/parts/furniture-sofa.blend +++ b/assets/blender/buildings/parts/furniture-sofa.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c1a981a35e4cb343aad00e4877cdd0cc5cc16755a139f1595be7a8a9ef1a4c9 -size 762742 +oid sha256:310e328ae1fde9c38196d0b2d51e14a29d04e4b8bf6b8ae9c3eafe40670632fd +size 770682 diff --git a/assets/blender/buildings/parts/furniture.blend b/assets/blender/buildings/parts/furniture.blend index 81c8b6c..b987679 100644 --- a/assets/blender/buildings/parts/furniture.blend +++ b/assets/blender/buildings/parts/furniture.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7dcaec00f89d0129233e64f24b89090cdb5ab95e3a43a2ea3abcd1c978609bb3 -size 2091917 +oid sha256:70afac1da59fab9b7c1d831b967e6619c974b52ba8da3caeb62fb139bb83bd51 +size 2247749