├── .gitignore ├── LICENSE ├── POLYGON.blend ├── POLYGON_Adventure.py ├── POLYGON_BossZombies.py ├── POLYGON_City.py ├── POLYGON_CityCharacters.py ├── POLYGON_Explorers.py ├── POLYGON_FantasyCharacters.py ├── POLYGON_FantasyKingdom.py ├── POLYGON_FantasyRivals.py ├── POLYGON_Farm.py ├── POLYGON_GangWarfare.py ├── POLYGON_Heist.py ├── POLYGON_HorrorMansion.py ├── POLYGON_Kids.py ├── POLYGON_Knights.py ├── POLYGON_ModularFantasyHeroes.py ├── POLYGON_Office.py ├── POLYGON_Pirates.py ├── POLYGON_Samurai.py ├── POLYGON_SciFiCity.py ├── POLYGON_SciFiSpace.py ├── POLYGON_Starter.py ├── POLYGON_StreetRacer.py ├── POLYGON_Vikings.py ├── POLYGON_War.py ├── POLYGON_WarMap.py ├── POLYGON_Western.py ├── README.MD └── assets └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | vendor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Flynsarmy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /POLYGON.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flynsarmy/synty-in-blender/b36302a49b92eb9215f876a593f2d72bf01aed79/POLYGON.blend -------------------------------------------------------------------------------- /POLYGON_Adventure.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | from mathutils import Matrix 4 | 5 | def import_characters(): 6 | # Deselect all 7 | bpy.ops.object.select_all(action='DESELECT') 8 | 9 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 10 | bpy.context.view_layer.active_layer_collection = \ 11 | bpy.context.view_layer.layer_collection.children['Characters'] 12 | 13 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 14 | folder = bpy.path.abspath("//Character_Files/Unity_Version_Mechanim") 15 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 16 | 17 | # Import them 18 | for fbx in fbxs: 19 | bpy.ops.import_scene.fbx( 20 | filepath=os.path.join(folder, fbx), 21 | use_anim=False, 22 | ignore_leaf_bones=True, 23 | force_connect_children=True, 24 | automatic_bone_orientation=True, 25 | ) 26 | 27 | collection = bpy.data.collections["Characters"] 28 | 29 | # Loop through Characters 30 | first_armature = None 31 | for obj in collection.objects: 32 | # Save the first armature object 33 | if first_armature == None and obj.type == 'ARMATURE': 34 | first_armature = obj 35 | continue 36 | 37 | if obj.type == 'MESH': 38 | # Always use non-duplicate version of material 39 | obj.active_material = bpy.data.materials[ 40 | obj.active_material.name.split('.')[0] 41 | ] 42 | 43 | # Rename Meshes to be the same as their parent MeshObjects 44 | obj.data.name = obj.name 45 | 46 | if first_armature != None and obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 47 | # Set all mesh Armature modifiers to the first armature object 48 | obj.modifiers[0].object = first_armature 49 | # Make our first armature the object parent 50 | obj.parent = first_armature 51 | 52 | # Deselect all 53 | bpy.ops.object.select_all(action='DESELECT') 54 | 55 | # Select unnecessary armatures 56 | first_armature = None 57 | for obj in collection.objects: 58 | if obj.type == 'ARMATURE': 59 | if first_armature == None: 60 | first_armature = obj 61 | else: 62 | obj.select_set(True) 63 | 64 | # Delete them 65 | bpy.ops.object.delete() 66 | 67 | first_armature.name = "Armature" 68 | 69 | # Apply location, rotation, scale to deltas 70 | for obj in collection.all_objects: 71 | if obj.name != 'Armature': 72 | mat = obj.matrix_local 73 | obj.data.transform(mat) 74 | obj.matrix_local = Matrix() 75 | else: 76 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 77 | obj.rotation_euler = (0, 0, 0) 78 | obj.delta_scale = obj.scale 79 | obj.scale = (1, 1, 1) 80 | 81 | 82 | def import_buildings(): 83 | # Deselect all 84 | bpy.ops.object.select_all(action='DESELECT') 85 | 86 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 87 | bpy.context.view_layer.active_layer_collection = \ 88 | bpy.context.view_layer.layer_collection.children['Buildings'] 89 | 90 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 91 | folder = bpy.path.abspath("//FBX/") 92 | fbxs = [f for f in os.listdir(folder) \ 93 | if f.endswith(".fbx") and f.startswith("SM_Bld_") 94 | ] 95 | 96 | collection = bpy.data.collections["Buildings"] 97 | 98 | # Import them 99 | for fbx in fbxs: 100 | bpy.ops.import_scene.fbx( 101 | filepath=os.path.join(folder, fbx), 102 | use_anim=False, 103 | ignore_leaf_bones=True, 104 | force_connect_children=True, 105 | automatic_bone_orientation=True, 106 | ) 107 | 108 | for obj in collection.objects: 109 | if obj.type == 'MESH': 110 | # Always use non-duplicate version of material 111 | obj.active_material = bpy.data.materials[ 112 | obj.active_material.name.split('.')[0] 113 | ] 114 | 115 | # Rename Meshes to be the same as their parent MeshObjects 116 | obj.data.name = obj.name 117 | 118 | 119 | def import_environments(): 120 | # Deselect all 121 | bpy.ops.object.select_all(action='DESELECT') 122 | 123 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 124 | bpy.context.view_layer.active_layer_collection = \ 125 | bpy.context.view_layer.layer_collection.children['Environments'] 126 | 127 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 128 | folder = bpy.path.abspath("//FBX") 129 | fbxs = [f for f in os.listdir(folder) \ 130 | if f.endswith(".fbx") and f.startswith("SM_Env_") 131 | ] 132 | 133 | collection = bpy.data.collections["Environments"] 134 | 135 | # Import them 136 | for fbx in fbxs: 137 | bpy.ops.import_scene.fbx( 138 | filepath=os.path.join(folder, fbx), 139 | use_anim=False, 140 | ignore_leaf_bones=True, 141 | force_connect_children=True, 142 | automatic_bone_orientation=True, 143 | ) 144 | 145 | for obj in collection.objects: 146 | if obj.type == 'MESH': 147 | for material_slot in obj.material_slots: 148 | # Always use non-duplicate version of material 149 | material_slot.material = bpy.data.materials[ 150 | material_slot.material.name.split('.')[0] 151 | ] 152 | 153 | # if material_slot.material.name in ['blinn283', 'blinn284', 'blinn285', 'lambert2']: 154 | # material_slot.material = bpy.data.materials["lambert14"] 155 | 156 | # Rename Meshes to be the same as their parent MeshObjects 157 | obj.data.name = obj.name 158 | 159 | 160 | def import_props(): 161 | # Deselect all 162 | bpy.ops.object.select_all(action='DESELECT') 163 | 164 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 165 | bpy.context.view_layer.active_layer_collection = \ 166 | bpy.context.view_layer.layer_collection.children['Props'] 167 | 168 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 169 | folder = bpy.path.abspath("//FBX") 170 | fbxs = [f for f in os.listdir(folder) \ 171 | if f.endswith(".fbx") and (f.startswith("SM_Prop_") or f.startswith('SM_Item_')) 172 | ] 173 | 174 | collection = bpy.data.collections["Props"] 175 | 176 | # Import them 177 | for fbx in fbxs: 178 | bpy.ops.import_scene.fbx( 179 | filepath=os.path.join(folder, fbx), 180 | use_anim=False, 181 | ignore_leaf_bones=True, 182 | force_connect_children=True, 183 | automatic_bone_orientation=True, 184 | ) 185 | 186 | for obj in collection.objects: 187 | if obj.type == 'MESH': 188 | for material_slot in obj.material_slots: 189 | # Always use non-duplicate version of material 190 | material_slot.material = bpy.data.materials[ 191 | material_slot.material.name.split('.')[0] 192 | ] 193 | 194 | # if material_slot.material.name in ['blinn283', 'lambert2']: 195 | # material_slot.material = bpy.data.materials["lambert14"] 196 | 197 | # Rename Meshes to be the same as their parent MeshObjects 198 | obj.data.name = obj.name 199 | 200 | 201 | def import_weapons(): 202 | # Deselect all 203 | bpy.ops.object.select_all(action='DESELECT') 204 | 205 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 206 | bpy.context.view_layer.active_layer_collection = \ 207 | bpy.context.view_layer.layer_collection.children['Weapons'] 208 | 209 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 210 | folder = bpy.path.abspath("//FBX") 211 | fbxs = [f for f in os.listdir(folder) \ 212 | if f.endswith(".fbx") and f.startswith("SM_Wep_") 213 | ] 214 | 215 | collection = bpy.data.collections["Weapons"] 216 | 217 | # Import them 218 | for fbx in fbxs: 219 | bpy.ops.import_scene.fbx( 220 | filepath=os.path.join(folder, fbx), 221 | use_anim=False, 222 | ignore_leaf_bones=True, 223 | force_connect_children=True, 224 | automatic_bone_orientation=True, 225 | ) 226 | 227 | for obj in collection.objects: 228 | if obj.type == 'MESH': 229 | for material_slot in obj.material_slots: 230 | # Always use non-duplicate version of material 231 | material_slot.material = bpy.data.materials[ 232 | material_slot.material.name.split('.')[0] 233 | ] 234 | 235 | # if material_slot.material.name in ['blinn283', 'lambert2']: 236 | # material_slot.material = bpy.data.materials["lambert14"] 237 | 238 | # Rename Meshes to be the same as their parent MeshObjects 239 | obj.data.name = obj.name 240 | 241 | 242 | def fix_materials(): 243 | # Textures pointing to the wrong directory. Fix that 244 | # https://blender.stackexchange.com/a/280804 245 | for image in bpy.data.images.values(): 246 | filename = os.path.basename(image.filepath) 247 | if image.source == "FILE": 248 | if filename in ["Characters_White.png", "Characters_Black.png"]: 249 | # Make absolute 250 | image.filepath = bpy.path.abspath( 251 | "//Textures/" + filename 252 | ) 253 | elif filename in ["Texture_01.psd"]: 254 | image.filepath = bpy.path.abspath( 255 | "//Textures/PolyAdventureTexture_01.png" 256 | ) 257 | 258 | for material in bpy.data.materials: 259 | if material.name in ['Black', 'White']: 260 | material.name = 'POLYGONAdventure_' + material.name 261 | 262 | # https://blender.stackexchange.com/a/129014 263 | bsdf = material.node_tree.nodes["Principled BSDF"] 264 | bsdf.inputs["Metallic"].default_value = 0.0 265 | bsdf.inputs["Specular"].default_value = 0.5 266 | bsdf.inputs["Roughness"].default_value = 0.5 267 | elif material.name in ['blinn265']: 268 | material.name = 'POLYGONAdventure_Base' 269 | 270 | # https://blender.stackexchange.com/a/129014 271 | bsdf = material.node_tree.nodes["Principled BSDF"] 272 | bsdf.inputs["Metallic"].default_value = 0.0 273 | bsdf.inputs["Specular"].default_value = 0.5 274 | bsdf.inputs["Roughness"].default_value = 0.5 275 | 276 | 277 | 278 | def cleanup(): 279 | bpy.ops.object.select_all(action='DESELECT') 280 | 281 | # Apply rotation, scale 282 | for obj in bpy.data.objects: 283 | # Skip the 'Characters' collection. Things mess up if we apply those. 284 | if obj.name != 'Armature' or (obj.parent and obj.parent.name != 'Armature'): 285 | obj.select_set(True) 286 | bpy.ops.object.transform_apply(scale=True, rotation=True) 287 | 288 | # Removed orphaned data 289 | for block in bpy.data.meshes: 290 | if block.users == 0: 291 | bpy.data.meshes.remove(block) 292 | 293 | for block in bpy.data.materials: 294 | if block.users == 0: 295 | bpy.data.materials.remove(block) 296 | 297 | for block in bpy.data.textures: 298 | if block.users == 0: 299 | bpy.data.textures.remove(block) 300 | 301 | for block in bpy.data.images: 302 | if block.users == 0: 303 | bpy.data.images.remove(block) 304 | 305 | 306 | import_characters() 307 | import_buildings() 308 | import_environments() 309 | import_props() 310 | import_weapons() 311 | fix_materials() 312 | cleanup() -------------------------------------------------------------------------------- /POLYGON_BossZombies.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//SourceFiles/Chr") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx") and f.startswith("SK_Chr_ZombieBoss_")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | # Loop through Characters 27 | first_armature = None 28 | collection = bpy.data.collections["Characters"] 29 | for obj in collection.objects: 30 | # Save the first armature object 31 | if first_armature == None and obj.type == 'ARMATURE': 32 | first_armature = obj 33 | continue 34 | 35 | if first_armature != None and obj.type == 'MESH' and obj.modifiers[0].type == 'ARMATURE': 36 | # Set all mesh Armature modifiers to the first armature object 37 | obj.modifiers[0].object = first_armature 38 | # Make our first armature the object parent 39 | obj.parent = first_armature 40 | 41 | # Add missing texture node to the material 42 | texture_path = bpy.path.abspath("//SourceFiles\\Textures\\PolygonZombieBoss_Texture_01_A.png") 43 | for material_slot in obj.material_slots: 44 | material = material_slot.material 45 | 46 | # https://blender.stackexchange.com/a/129014 47 | bsdf = material.node_tree.nodes["Principled BSDF"] 48 | tex_image = material.node_tree.nodes.new('ShaderNodeTexImage') 49 | tex_image.image = bpy.data.images.load(texture_path) 50 | material.node_tree.links.new(bsdf.inputs['Base Color'], tex_image.outputs['Color']) 51 | 52 | # Deselect all 53 | bpy.ops.object.select_all(action='DESELECT') 54 | 55 | # Select unnecessary armatures 56 | first_armature = None 57 | for obj in collection.objects: 58 | if obj.type == 'ARMATURE': 59 | if first_armature == None: 60 | first_armature = obj 61 | else: 62 | obj.select_set(True) 63 | 64 | # Delete them 65 | bpy.ops.object.delete() 66 | 67 | bpy.context.view_layer.objects.active = first_armature 68 | 69 | # Remove IK bones 70 | bpy.ops.object.mode_set(mode='EDIT') 71 | # Remove the IK bones 72 | for bone in first_armature.data.edit_bones: 73 | if bone.name.startswith("ik_"): 74 | first_armature.data.edit_bones.remove(bone) 75 | bpy.ops.object.mode_set(mode='OBJECT') 76 | 77 | # Apply rotation, scale 78 | for obj in collection.objects: 79 | obj.select_set(True) 80 | bpy.ops.object.transform_apply(scale=True, rotation=True) 81 | 82 | first_armature.name = "Characters" 83 | 84 | 85 | import_characters() 86 | -------------------------------------------------------------------------------- /POLYGON_City.py: -------------------------------------------------------------------------------- 1 | ## IMPORTANT! Some files in Polygon_City_SourceFiles/SourceFiles/FBX/*.fbx are in ASCII 2 | ## format and need conversion. Use Autodesk FBX Converter 2013 to convert them. 3 | ## Put the converted files in a 'FBX 2013' subfolder like so: 4 | ## Polygon_City_SourceFiles/SourceFiles/FBX/FBX 2013/*.fbx 5 | ## Here are the files that need conversion: 6 | ## - SM_Bld_Station_01.fbx 7 | ## - SM_Env_Flower_01.fbx 8 | 9 | import bpy 10 | import os 11 | import mathutils 12 | 13 | def import_characters(): 14 | # Deselect all 15 | bpy.ops.object.select_all(action='DESELECT') 16 | 17 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 18 | bpy.context.view_layer.active_layer_collection = \ 19 | bpy.context.view_layer.layer_collection.children['Characters'] 20 | 21 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 22 | folder = bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/Characters") 23 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 24 | 25 | # Import them 26 | for fbx in fbxs: 27 | bpy.ops.import_scene.fbx( 28 | filepath=os.path.join(folder, fbx), 29 | use_anim=False, 30 | ignore_leaf_bones=True, 31 | force_connect_children=True, 32 | automatic_bone_orientation=True, 33 | ) 34 | 35 | collection = bpy.data.collections["Characters"] 36 | 37 | # Loop through Characters 38 | first_armature = None 39 | for obj in collection.objects: 40 | # Save the first armature object 41 | if first_armature == None and obj.type == 'ARMATURE': 42 | first_armature = obj 43 | continue 44 | 45 | if obj.type == 'MESH': 46 | # Always use non-duplicate version of material 47 | obj.active_material = bpy.data.materials[ 48 | obj 49 | .active_material.name 50 | .split('.')[0] 51 | ] 52 | 53 | # Rename Meshes to be the same as their parent MeshObjects 54 | obj.data.name = obj.name 55 | 56 | if first_armature != None and obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 57 | # Set all mesh Armature modifiers to the first armature object 58 | obj.modifiers[0].object = first_armature 59 | # Make our first armature the object parent 60 | obj.parent = first_armature 61 | 62 | # Deselect all 63 | bpy.ops.object.select_all(action='DESELECT') 64 | 65 | # Select unnecessary armatures 66 | first_armature = None 67 | for obj in collection.objects: 68 | if obj.type == 'ARMATURE': 69 | if first_armature == None: 70 | first_armature = obj 71 | else: 72 | obj.select_set(True) 73 | 74 | # Delete them 75 | bpy.ops.object.delete() 76 | 77 | first_armature.name = "Armature" 78 | 79 | 80 | def import_buildings(): 81 | # Deselect all 82 | bpy.ops.object.select_all(action='DESELECT') 83 | 84 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 85 | bpy.context.view_layer.active_layer_collection = \ 86 | bpy.context.view_layer.layer_collection.children['Buildings'] 87 | 88 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 89 | folder = bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/FBX/") 90 | fbxs = [f for f in os.listdir(folder) \ 91 | if f.endswith(".fbx") and f.startswith("SM_Bld_") and f not in ["SM_Bld_Station_01.fbx"] 92 | ] 93 | 94 | collection = bpy.data.collections["Buildings"] 95 | 96 | # Import them 97 | for fbx in fbxs: 98 | bpy.ops.import_scene.fbx( 99 | filepath=os.path.join(folder, fbx), 100 | use_anim=False, 101 | ignore_leaf_bones=True, 102 | force_connect_children=True, 103 | automatic_bone_orientation=True, 104 | ) 105 | # Import the converted file 106 | bpy.ops.import_scene.fbx( 107 | filepath=bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/FBX/FBX 2013/SM_Bld_Station_01.fbx"), 108 | use_anim=False, 109 | ignore_leaf_bones=True, 110 | force_connect_children=True, 111 | automatic_bone_orientation=True, 112 | ) 113 | 114 | for obj in collection.objects: 115 | if obj.type == 'MESH': 116 | # Always use non-duplicate version of material 117 | obj.active_material = bpy.data.materials[ 118 | obj 119 | .active_material.name 120 | .split('.')[0] 121 | ] 122 | 123 | # Fix scaling 124 | if obj.scale.x < 1.0: 125 | obj.scale *= 100 126 | 127 | # Use the same material as the characters 128 | for material_slot in obj.material_slots: 129 | if material_slot.material.name in ['blinn283', 'blinn284', 'blinn285']: 130 | material_slot.material = bpy.data.materials["lambert14"] 131 | 132 | 133 | # Rename Meshes to be the same as their parent MeshObjects 134 | obj.data.name = obj.name 135 | 136 | 137 | def import_environments(): 138 | # Deselect all 139 | bpy.ops.object.select_all(action='DESELECT') 140 | 141 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 142 | bpy.context.view_layer.active_layer_collection = \ 143 | bpy.context.view_layer.layer_collection.children['Environments'] 144 | 145 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 146 | folder = bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/FBX") 147 | fbxs = [f for f in os.listdir(folder) \ 148 | if f.endswith(".fbx") and f.startswith("SM_Env_") and f not in ["SM_Env_Flower_01.fbx"] 149 | ] 150 | 151 | collection = bpy.data.collections["Environments"] 152 | 153 | # Import them 154 | for fbx in fbxs: 155 | bpy.ops.import_scene.fbx( 156 | filepath=os.path.join(folder, fbx), 157 | use_anim=False, 158 | ignore_leaf_bones=True, 159 | force_connect_children=True, 160 | automatic_bone_orientation=True, 161 | ) 162 | # Import the converted file 163 | bpy.ops.import_scene.fbx( 164 | filepath=bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/FBX/FBX 2013/SM_Env_Flower_01.fbx"), 165 | use_anim=False, 166 | ignore_leaf_bones=True, 167 | force_connect_children=True, 168 | automatic_bone_orientation=True, 169 | ) 170 | 171 | for obj in collection.objects: 172 | if obj.type == 'MESH': 173 | for material_slot in obj.material_slots: 174 | # Always use non-duplicate version of material 175 | material_slot.material = bpy.data.materials[ 176 | material_slot.material.name.split('.')[0] 177 | ] 178 | 179 | if material_slot.material.name in ['blinn283', 'blinn284', 'blinn285', 'lambert2']: 180 | material_slot.material = bpy.data.materials["lambert14"] 181 | 182 | # Rename Meshes to be the same as their parent MeshObjects 183 | obj.data.name = obj.name 184 | 185 | 186 | def import_props(): 187 | # Deselect all 188 | bpy.ops.object.select_all(action='DESELECT') 189 | 190 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 191 | bpy.context.view_layer.active_layer_collection = \ 192 | bpy.context.view_layer.layer_collection.children['Props'] 193 | 194 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 195 | folder = bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/FBX") 196 | fbxs = [f for f in os.listdir(folder) \ 197 | if f.endswith(".fbx") and f.startswith("SM_Prop_") 198 | ] 199 | 200 | collection = bpy.data.collections["Props"] 201 | 202 | # Import them 203 | for fbx in fbxs: 204 | bpy.ops.import_scene.fbx( 205 | filepath=os.path.join(folder, fbx), 206 | use_anim=False, 207 | ignore_leaf_bones=True, 208 | force_connect_children=True, 209 | automatic_bone_orientation=True, 210 | ) 211 | 212 | for obj in collection.objects: 213 | if obj.type == 'MESH': 214 | for material_slot in obj.material_slots: 215 | # Always use non-duplicate version of material 216 | material_slot.material = bpy.data.materials[ 217 | material_slot.material.name.split('.')[0] 218 | ] 219 | 220 | if material_slot.material.name in ['blinn283', 'lambert2']: 221 | material_slot.material = bpy.data.materials["lambert14"] 222 | 223 | # Rename Meshes to be the same as their parent MeshObjects 224 | obj.data.name = obj.name 225 | 226 | 227 | def import_vehicles(): 228 | # Deselect all 229 | bpy.ops.object.select_all(action='DESELECT') 230 | 231 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 232 | bpy.context.view_layer.active_layer_collection = \ 233 | bpy.context.view_layer.layer_collection.children['Vehicles'] 234 | 235 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 236 | folder = bpy.path.abspath("//Polygon_City_SourceFiles/SourceFiles/FBX//Veh") 237 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx") and f.startswith("SM_Veh_")] 238 | 239 | collection = bpy.data.collections["Vehicles"] 240 | 241 | # Import them 242 | for fbx in fbxs: 243 | bpy.ops.import_scene.fbx( 244 | filepath=os.path.join(folder, fbx), 245 | use_anim=False, 246 | ignore_leaf_bones=True, 247 | force_connect_children=True, 248 | automatic_bone_orientation=True, 249 | ) 250 | 251 | for obj in collection.objects: 252 | if obj.type == 'MESH': 253 | for material_slot in obj.material_slots: 254 | # Always use non-duplicate version of material 255 | material_slot.material = bpy.data.materials[ 256 | material_slot.material.name.split('.')[0] 257 | ] 258 | 259 | if material_slot.material.name in ['Body']: 260 | material_slot.material = bpy.data.materials["lambert14"] 261 | 262 | # Rename Meshes to be the same as their parent MeshObjects 263 | obj.data.name = obj.name 264 | 265 | 266 | def fix_materials(): 267 | # https://blender.stackexchange.com/a/280804 268 | for image in bpy.data.images.values(): 269 | filename = os.path.basename(image.filepath) 270 | if image.source == "FILE": 271 | if filename in ["PolygonCity_Texture_01_C.png"]: 272 | # Make absolute 273 | image.filepath = bpy.path.abspath( 274 | "//Polygon_City_SourceFiles\\SourceFiles\\Textures\\" + filename 275 | ) 276 | # elif filename in ["PolygonCity_Texture_Normal.png", "PolygonCity_Texture_Metallic.tga"]: 277 | # image.colorspace_settings.name = 'Non-Color' 278 | 279 | for material in bpy.data.materials: 280 | if material.name == 'lambert14': 281 | material.name = 'POLYGONCity_Base' 282 | 283 | # https://blender.stackexchange.com/a/129014 284 | bsdf = material.node_tree.nodes["Principled BSDF"] 285 | bsdf.inputs["Specular"].default_value = 0.5 286 | 287 | # Set up metallic map 288 | uv_map = material.node_tree.nodes.new('ShaderNodeUVMap') 289 | mapping = material.node_tree.nodes.new('ShaderNodeMapping') 290 | tex_metallic = material.node_tree.nodes.new('ShaderNodeTexImage') 291 | tex_metallic.image = bpy.data.images.load(bpy.path.abspath( 292 | "//Polygon_City_SourceFiles/SourceFiles/Textures/PolygonCity_Texture_Metallic.tga" 293 | )) 294 | tex_metallic.image.colorspace_settings.name = 'Non-Color' 295 | tex_metallic_mp = material.node_tree.nodes.new('ShaderNodeMath') 296 | tex_metallic_mp.operation = 'MULTIPLY' 297 | tex_metallic_mp.inputs[1].default_value = 1.5 298 | material.node_tree.links.new(mapping.inputs['Vector'], uv_map.outputs['UV']) 299 | material.node_tree.links.new(tex_metallic.inputs['Vector'], mapping.outputs['Vector']) 300 | material.node_tree.links.new(tex_metallic_mp.inputs[0], tex_metallic.outputs['Color']) 301 | material.node_tree.links.new(bsdf.inputs['Metallic'], tex_metallic_mp.outputs[0]) 302 | 303 | # Set up normal map 304 | uv_map = material.node_tree.nodes.new('ShaderNodeUVMap') 305 | mapping = material.node_tree.nodes.new('ShaderNodeMapping') 306 | tex_normal = material.node_tree.nodes.new('ShaderNodeTexImage') 307 | tex_normal.image = bpy.data.images.load(bpy.path.abspath( 308 | "//Polygon_City_SourceFiles/SourceFiles/Textures/PolygonCity_Texture_Normal.png" 309 | )) 310 | tex_normal.image.colorspace_settings.name = 'Non-Color' 311 | normal = material.node_tree.nodes["Normal Map"] 312 | material.node_tree.links.new(mapping.inputs['Vector'], uv_map.outputs['UV']) 313 | material.node_tree.links.new(tex_normal.inputs['Vector'], mapping.outputs['Vector']) 314 | material.node_tree.links.new(normal.inputs['Color'], tex_normal.outputs['Color']) 315 | material.node_tree.links.new(bsdf.inputs['Normal'], normal.outputs['Normal']) 316 | 317 | elif material.name == 'Glass': 318 | material.name = 'POLYGONCity_Glass' 319 | 320 | # https://blender.stackexchange.com/a/129014 321 | bsdf = material.node_tree.nodes["Principled BSDF"] 322 | 323 | bsdf.inputs["Base Color"].default_value = (0.054, 0.8, 0.627, 0) 324 | bsdf.inputs["Metallic"].default_value = 0.5 325 | bsdf.inputs["Specular"].default_value = 0.9 326 | bsdf.inputs["Roughness"].default_value = 0.2 327 | bsdf.inputs["Alpha"].default_value = 0.5 328 | 329 | 330 | def cleanup(): 331 | # Apply rotation, scale 332 | for obj in bpy.data.objects: 333 | obj.select_set(True) 334 | bpy.ops.object.transform_apply(scale=True, rotation=True) 335 | 336 | # Removed orphaned data 337 | for block in bpy.data.meshes: 338 | if block.users == 0: 339 | bpy.data.meshes.remove(block) 340 | 341 | for block in bpy.data.materials: 342 | if block.users == 0: 343 | bpy.data.materials.remove(block) 344 | 345 | for block in bpy.data.textures: 346 | if block.users == 0: 347 | bpy.data.textures.remove(block) 348 | 349 | for block in bpy.data.images: 350 | if block.users == 0: 351 | bpy.data.images.remove(block) 352 | 353 | 354 | import_characters() 355 | import_buildings() 356 | import_environments() 357 | import_props() 358 | import_vehicles() 359 | fix_materials() 360 | cleanup() -------------------------------------------------------------------------------- /POLYGON_CityCharacters.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Import characters 13 | bpy.ops.import_scene.fbx( 14 | filepath=bpy.path.abspath("//Source_Files/FBX/Character.fbx"), 15 | use_anim=False, 16 | ignore_leaf_bones=True, 17 | force_connect_children=True, 18 | automatic_bone_orientation=True, 19 | ) 20 | 21 | def fix_materials(): 22 | # Textures pointing to the wrong directory. Fix that 23 | # https://blender.stackexchange.com/a/280804 24 | for image in bpy.data.images.values(): 25 | if image.source == "FILE": 26 | # Make absolute 27 | image.filepath = bpy.path.abspath( 28 | "//Source_Files/Textures/" + os.path.basename(image.filepath) 29 | ) 30 | 31 | for material in bpy.data.materials: 32 | if material.name == 'lambert2': 33 | material.name = 'POLYGONCityCharacters_Base' 34 | 35 | # https://blender.stackexchange.com/a/129014 36 | bsdf = material.node_tree.nodes["Principled BSDF"] 37 | 38 | bsdf.inputs["Specular"].default_value = 0.5 39 | bsdf.inputs["Roughness"].default_value = 0.6 40 | 41 | 42 | def cleanup(): 43 | # Apply rotation, scale 44 | for obj in bpy.data.objects: 45 | obj.select_set(True) 46 | bpy.ops.object.transform_apply(scale=True, rotation=True) 47 | 48 | # Removed orphaned data 49 | for block in bpy.data.meshes: 50 | if block.users == 0: 51 | bpy.data.meshes.remove(block) 52 | 53 | for block in bpy.data.materials: 54 | if block.users == 0: 55 | bpy.data.materials.remove(block) 56 | 57 | for block in bpy.data.textures: 58 | if block.users == 0: 59 | bpy.data.textures.remove(block) 60 | 61 | for block in bpy.data.images: 62 | if block.users == 0: 63 | bpy.data.images.remove(block) 64 | 65 | 66 | import_characters() 67 | fix_materials() 68 | cleanup() -------------------------------------------------------------------------------- /POLYGON_Explorers.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//SourceFiles\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | # Select unnecessary armatures 47 | first_armature = None 48 | for obj in collection.objects: 49 | if obj.type == 'ARMATURE': 50 | if first_armature == None: 51 | first_armature = obj 52 | else: 53 | obj.select_set(True) 54 | 55 | # Delete them 56 | bpy.ops.object.delete() 57 | 58 | # Remove IK bones 59 | bpy.context.view_layer.objects.active = first_armature 60 | bpy.ops.object.mode_set(mode='EDIT') 61 | # Remove the IK bones 62 | for bone in first_armature.data.edit_bones: 63 | if bone.name.startswith("_ik_"): 64 | first_armature.data.edit_bones.remove(bone) 65 | bpy.ops.object.mode_set(mode='OBJECT') 66 | 67 | first_armature.name = "Characters" 68 | 69 | # Textures pointing to the wrong directory. Fix that 70 | # https://blender.stackexchange.com/a/280804 71 | for image in bpy.data.images.values(): 72 | if image.source == "FILE": 73 | filename = os.path.basename(image.filepath) 74 | if filename == "PolygonExplorers_Texture_01.psd": 75 | image.filepath = bpy.path.abspath( 76 | "//SourceFiles\\Textures\\PolygonExplorers_Texture_01_A.png" 77 | ) 78 | else: 79 | # Make absolute 80 | image.filepath = bpy.path.abspath( 81 | "//SourceFiles\\Textures\\" + filename 82 | ) 83 | 84 | import_characters() -------------------------------------------------------------------------------- /POLYGON_FantasyCharacters.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Source_Files\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | first_armature.name = "Characters" 69 | 70 | # Textures pointing to the wrong directory. Fix that 71 | # https://blender.stackexchange.com/a/280804 72 | for image in bpy.data.images.values(): 73 | # Textures are pointing towards PolygonCity. Fix that 74 | filename = os.path.basename(image.filepath).replace("PolygonCity", "Polygon_Fantasy_Characters") 75 | if image.source == "FILE": 76 | # Make absolute 77 | image.filepath = bpy.path.abspath( 78 | "//Source_Files\\Textures\\" + filename 79 | ) 80 | 81 | import_characters() -------------------------------------------------------------------------------- /POLYGON_FantasyKingdom.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Source_Files\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx") and "_Cape_" not in f and "_Fairy_Wing_" not in f] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | # Apply location, rotation, scale to deltas 69 | for obj in collection.all_objects: 70 | obj.delta_location += obj.location 71 | obj.location = (0, 0, 0) 72 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 73 | obj.rotation_euler = (0, 0, 0) 74 | obj.delta_scale = obj.scale 75 | obj.scale = (1, 1, 1) 76 | 77 | first_armature.name = "Characters" 78 | 79 | # Textures pointing to the wrong directory. Fix that 80 | # https://blender.stackexchange.com/a/280804 81 | for image in bpy.data.images.values(): 82 | # Textures are pointing towards PolygonCity. Fix that 83 | filename = os.path.basename(image.filepath) 84 | if image.source == "FILE": 85 | # Make absolute 86 | image.filepath = bpy.path.abspath( 87 | "//Source_Files\\Textures\\" + filename 88 | ) 89 | 90 | import_characters() -------------------------------------------------------------------------------- /POLYGON_FantasyRivals.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Source_Files\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx") and (f.startswith("SK_BR_Char") or f.startswith("SK_Char"))] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | # Apply location, rotation, scale to deltas 69 | for obj in collection.all_objects: 70 | obj.delta_location += obj.location 71 | obj.location = (0, 0, 0) 72 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 73 | obj.rotation_euler = (0, 0, 0) 74 | obj.delta_scale = obj.scale 75 | obj.scale = (1, 1, 1) 76 | 77 | first_armature.name = "Characters" 78 | 79 | # Textures pointing to the wrong directory. Fix that 80 | # https://blender.stackexchange.com/a/280804 81 | for image in bpy.data.images.values(): 82 | # Textures are pointing towards PolygonCity. Fix that 83 | filename = os.path.basename(image.filepath) 84 | if image.source == "FILE": 85 | if filename == "Texture_A1.png" or filename == "FantasyRivals_Justin_Main_01.psd": 86 | # Make absolute 87 | image.filepath = bpy.path.abspath( 88 | "//Source_Files\\Textures\\FantasyRivals_Texture_01_A.png" 89 | ) 90 | else: 91 | # Make absolute 92 | image.filepath = bpy.path.abspath( 93 | "//Source_Files\\Textures\\" + filename 94 | ) 95 | 96 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Farm.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | ## IMPORTANT! Convert the SourceFiles/Characters/*.fbx from ASCII to FBX 2013. 5 | ## Put the converted files in a 'FBX 2013' subfolder like so: 6 | ## SourceFiles/Characters/FBX 2013/*.fbx 7 | ## Do the same for the following files in SourceFiles/FBX/ : 8 | ## - SM_Chr_Attach_ScarecrowHat_01.fbx 9 | ## - SM_Veh_Pickup_01.fbx 10 | 11 | 12 | def import_characters(): 13 | # Deselect all 14 | bpy.ops.object.select_all(action='DESELECT') 15 | 16 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 17 | bpy.context.view_layer.active_layer_collection = \ 18 | bpy.context.view_layer.layer_collection.children['Characters'] 19 | 20 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 21 | folder = bpy.path.abspath("//SourceFiles/Characters/FBX 2013") 22 | fbxs = [f for f in os.listdir(folder) \ 23 | if f.endswith(".fbx") \ 24 | and f.startswith("SK_Chr_") 25 | ] 26 | 27 | # Import them 28 | for fbx in fbxs: 29 | bpy.ops.import_scene.fbx( 30 | filepath=os.path.join(folder, fbx), 31 | use_anim=False, 32 | ignore_leaf_bones=True, 33 | force_connect_children=True, 34 | automatic_bone_orientation=True, 35 | ) 36 | 37 | # Loop through Characters 38 | first_armature = None 39 | collection = bpy.data.collections["Characters"] 40 | for obj in collection.objects: 41 | # Save the first armature object 42 | if first_armature == None and obj.type == 'ARMATURE': 43 | first_armature = obj 44 | continue 45 | 46 | if obj.type == 'MESH': 47 | # Always use non-duplicate version of material 48 | obj.active_material = bpy.data.materials[ 49 | obj 50 | .active_material.name 51 | .split('.')[0] 52 | ] 53 | 54 | # Rename Meshes to be the same as their parent MeshObjects 55 | obj.data.name = obj.name 56 | 57 | 58 | if first_armature != None and obj.modifiers[0].type == 'ARMATURE': 59 | # Set all mesh Armature modifiers to the first armature object 60 | obj.modifiers[0].object = first_armature 61 | 62 | # Make our first armature the object parent 63 | obj.parent = first_armature 64 | 65 | # Deselect all 66 | bpy.ops.object.select_all(action='DESELECT') 67 | 68 | # Select unnecessary armatures 69 | first_armature = None 70 | for obj in collection.objects: 71 | if obj.type == 'ARMATURE': 72 | if first_armature == None: 73 | first_armature = obj 74 | else: 75 | obj.select_set(True) 76 | 77 | # Delete them 78 | bpy.ops.object.delete() 79 | 80 | first_armature.name = "Armature" 81 | 82 | 83 | def import_attachments(): 84 | # Deselect all 85 | bpy.ops.object.select_all(action='DESELECT') 86 | 87 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 88 | bpy.context.view_layer.active_layer_collection = \ 89 | bpy.context.view_layer.layer_collection.children['Character_Attachments'] 90 | 91 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 92 | folder = bpy.path.abspath("//SourceFiles/FBX") 93 | fbxs = [f for f in os.listdir(folder) \ 94 | if f.endswith(".fbx") \ 95 | and f.startswith("SM_Chr_Attach_") \ 96 | and f not in ["SM_Chr_Attach_ScarecrowHat_01.fbx"] 97 | ] 98 | 99 | collection = bpy.data.collections["Character_Attachments"] 100 | 101 | # Import them 102 | for fbx in fbxs: 103 | bpy.ops.import_scene.fbx( 104 | filepath=os.path.join(folder, fbx), 105 | use_anim=False, 106 | ignore_leaf_bones=True, 107 | force_connect_children=True, 108 | automatic_bone_orientation=True, 109 | ) 110 | # And the converted one 111 | bpy.ops.import_scene.fbx( 112 | filepath=bpy.path.abspath("//SourceFiles/FBX/FBX 2013/SM_Chr_Attach_ScarecrowHat_01.fbx"), 113 | use_anim=False, 114 | ignore_leaf_bones=True, 115 | force_connect_children=True, 116 | automatic_bone_orientation=True, 117 | ) 118 | 119 | for obj in collection.objects: 120 | if obj.type == 'MESH': 121 | # Use the same material as the characters 122 | obj.active_material = bpy.data.materials["lambert2"] 123 | 124 | # Rename Meshes to be the same as their parent MeshObjects 125 | obj.data.name = obj.name 126 | 127 | def import_weapons(): 128 | # Deselect all 129 | bpy.ops.object.select_all(action='DESELECT') 130 | 131 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 132 | bpy.context.view_layer.active_layer_collection = \ 133 | bpy.context.view_layer.layer_collection.children['Weapons'] 134 | 135 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 136 | folder = bpy.path.abspath("//SourceFiles/FBX") 137 | fbxs = [f for f in os.listdir(folder) \ 138 | if f.endswith(".fbx") and f.startswith("SM_Wep_") 139 | ] 140 | 141 | collection = bpy.data.collections["Weapons"] 142 | 143 | # Import them 144 | for fbx in fbxs: 145 | bpy.ops.import_scene.fbx( 146 | filepath=os.path.join(folder, fbx), 147 | use_anim=False, 148 | ignore_leaf_bones=True, 149 | force_connect_children=True, 150 | automatic_bone_orientation=True, 151 | ) 152 | 153 | for obj in collection.objects: 154 | if obj.type == 'MESH': 155 | # Use the same material as the characters 156 | obj.active_material = bpy.data.materials["lambert2"] 157 | 158 | # Rename Meshes to be the same as their parent MeshObjects 159 | obj.data.name = obj.name 160 | 161 | def import_buildings(): 162 | # Deselect all 163 | bpy.ops.object.select_all(action='DESELECT') 164 | 165 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 166 | bpy.context.view_layer.active_layer_collection = \ 167 | bpy.context.view_layer.layer_collection.children['Buildings'] 168 | 169 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 170 | folder = bpy.path.abspath("//SourceFiles/FBX") 171 | fbxs = [f for f in os.listdir(folder) \ 172 | if f.endswith(".fbx") and f.startswith("SM_Bld_") 173 | ] 174 | 175 | collection = bpy.data.collections["Buildings"] 176 | 177 | # Import them 178 | for fbx in fbxs: 179 | bpy.ops.import_scene.fbx( 180 | filepath=os.path.join(folder, fbx), 181 | use_anim=False, 182 | ignore_leaf_bones=True, 183 | force_connect_children=True, 184 | automatic_bone_orientation=True, 185 | ) 186 | 187 | for obj in collection.objects: 188 | if obj.type == 'MESH': 189 | # Use the same material as the characters 190 | obj.active_material = bpy.data.materials["lambert2"] 191 | 192 | # Rename Meshes to be the same as their parent MeshObjects 193 | obj.data.name = obj.name 194 | 195 | def import_environments(): 196 | # Deselect all 197 | bpy.ops.object.select_all(action='DESELECT') 198 | 199 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 200 | bpy.context.view_layer.active_layer_collection = \ 201 | bpy.context.view_layer.layer_collection.children['Environments'] 202 | 203 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 204 | folder = bpy.path.abspath("//SourceFiles/FBX") 205 | fbxs = [f for f in os.listdir(folder) \ 206 | if f.endswith(".fbx") and (f.startswith("SM_Env_") or f.startswith("SM_Generic_")) 207 | ] 208 | 209 | collection = bpy.data.collections["Environments"] 210 | 211 | # Import them 212 | for fbx in fbxs: 213 | bpy.ops.import_scene.fbx( 214 | filepath=os.path.join(folder, fbx), 215 | use_anim=False, 216 | ignore_leaf_bones=True, 217 | force_connect_children=True, 218 | automatic_bone_orientation=True, 219 | ) 220 | 221 | for obj in collection.objects: 222 | if obj.type == 'MESH': 223 | # Use the same material as the characters 224 | obj.active_material = bpy.data.materials["lambert2"] 225 | 226 | # Rename Meshes to be the same as their parent MeshObjects 227 | obj.data.name = obj.name 228 | 229 | def import_props(): 230 | # Deselect all 231 | bpy.ops.object.select_all(action='DESELECT') 232 | 233 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 234 | bpy.context.view_layer.active_layer_collection = \ 235 | bpy.context.view_layer.layer_collection.children['Props'] 236 | 237 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 238 | folder = bpy.path.abspath("//SourceFiles/FBX") 239 | fbxs = [f for f in os.listdir(folder) \ 240 | if f.endswith(".fbx") and f.startswith("SM_Prop_") 241 | ] 242 | 243 | collection = bpy.data.collections["Props"] 244 | 245 | # Import them 246 | for fbx in fbxs: 247 | bpy.ops.import_scene.fbx( 248 | filepath=os.path.join(folder, fbx), 249 | use_anim=False, 250 | ignore_leaf_bones=True, 251 | force_connect_children=True, 252 | automatic_bone_orientation=True, 253 | ) 254 | 255 | for obj in collection.objects: 256 | if obj.type == 'MESH': 257 | for material_slot in obj.material_slots: 258 | # Always use non-duplicate version of material 259 | material_slot.material = bpy.data.materials[ 260 | material_slot.material.name.split('.')[0] 261 | ] 262 | # Use the same material as the characters 263 | if (material_slot.material.name in [ 264 | "Farm", "lambert1", "lambert23", "Vehicles", "Blade", 265 | "lambert861" 266 | ]) \ 267 | or material_slot.material.name.startswith("Farm"): 268 | material_slot.material = bpy.data.materials["lambert2"] 269 | elif material_slot.material.name in ["Leaves"]: 270 | material_slot.material = bpy.data.materials["lambert863"] 271 | 272 | # Rename Meshes to be the same as their parent MeshObjects 273 | obj.data.name = obj.name 274 | 275 | def import_vehicles(): 276 | # Deselect all 277 | bpy.ops.object.select_all(action='DESELECT') 278 | 279 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 280 | bpy.context.view_layer.active_layer_collection = \ 281 | bpy.context.view_layer.layer_collection.children['Vehicles'] 282 | 283 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 284 | folder = bpy.path.abspath("//SourceFiles/FBX") 285 | fbxs = [f for f in os.listdir(folder) \ 286 | if f.endswith(".fbx") \ 287 | and f.startswith("SM_Veh_") \ 288 | and f not in ["SM_Veh_Pickup_01.fbx"] 289 | ] 290 | 291 | collection = bpy.data.collections["Vehicles"] 292 | 293 | # Import them 294 | for fbx in fbxs: 295 | bpy.ops.import_scene.fbx( 296 | filepath=os.path.join(folder, fbx), 297 | use_anim=False, 298 | ignore_leaf_bones=True, 299 | force_connect_children=True, 300 | automatic_bone_orientation=True, 301 | ) 302 | # And the converted one 303 | bpy.ops.import_scene.fbx( 304 | filepath=bpy.path.abspath("//SourceFiles/FBX/FBX 2013/SM_Veh_Pickup_01.fbx"), 305 | use_anim=False, 306 | ignore_leaf_bones=True, 307 | force_connect_children=True, 308 | automatic_bone_orientation=True, 309 | ) 310 | 311 | 312 | for obj in collection.objects: 313 | if obj.type == 'MESH': 314 | # Always use non-duplicate version of material 315 | obj.active_material = bpy.data.materials[ 316 | obj 317 | .active_material.name 318 | .split('.')[0] 319 | ] 320 | # Use the same material as the characters 321 | if obj.active_material.name in ["lambert1", "lambert2"]: 322 | obj.active_material = bpy.data.materials["lambert2"] 323 | 324 | # Rename Meshes to be the same as their parent MeshObjects 325 | obj.data.name = obj.name 326 | 327 | 328 | def fix_materials(): 329 | # https://blender.stackexchange.com/a/280804 330 | for image in bpy.data.images.values(): 331 | filename = os.path.basename(image.filepath) 332 | if image.source == "FILE": 333 | if filename in ['Fence_Alpha.tga', 'PolygonFarm_Texture_01_A.png', 'PolygonFarm_Signs.png']: 334 | image.filepath = bpy.path.abspath( 335 | "//SourceFiles\\Textures\\" + filename 336 | ) 337 | elif filename == 'leavessheet_cavity.png': 338 | image.filepath = bpy.path.abspath( 339 | "//SourceFiles\\Textures\\Leaves_Diff.tga" 340 | ) 341 | 342 | for material in bpy.data.materials: 343 | # Leaves material 344 | if material.name == 'lambert863': 345 | material.name = "POLYGONFarm_Leaves" 346 | 347 | # https://blender.stackexchange.com/a/129014 348 | bsdf = material.node_tree.nodes["Principled BSDF"] 349 | 350 | tex_image = material.node_tree.nodes.new('ShaderNodeTexImage') 351 | tex_image.image = bpy.data.images.load(bpy.path.abspath( 352 | "//SourceFiles\\Textures\\Leaves_Diff.tga" 353 | )) 354 | 355 | norm_image = material.node_tree.nodes.new('ShaderNodeTexImage') 356 | norm_image.image = bpy.data.images.load(bpy.path.abspath( 357 | "//SourceFiles\\Textures\\Leaves_Normals.png" 358 | )) 359 | 360 | normal_map = material.node_tree.nodes["Normal Map"] 361 | material.node_tree.links.new(bsdf.inputs['Alpha'], tex_image.outputs['Alpha']) 362 | material.node_tree.links.new(normal_map.inputs['Color'], norm_image.outputs['Color']) 363 | 364 | bsdf.inputs["Metallic"].default_value = 0.0 365 | bsdf.inputs["Specular"].default_value = 0.2 366 | bsdf.inputs["Roughness"].default_value = 0.8 367 | 368 | # Glass material 369 | elif material.name == 'lambert3': 370 | material.name = "POLYGONFarm_Glass" 371 | 372 | bsdf = material.node_tree.nodes["Principled BSDF"] 373 | 374 | tex_image = material.node_tree.nodes.new('ShaderNodeTexImage') 375 | tex_image.image = bpy.data.images.load(bpy.path.abspath( 376 | "//SourceFiles\\Textures\\PolygonFarm_Texture_01_A.png" 377 | )) 378 | material.node_tree.links.new(bsdf.inputs['Base Color'], tex_image.outputs['Color']) 379 | 380 | bsdf.inputs["Metallic"].default_value = 0.0 381 | bsdf.inputs["Specular"].default_value = 0.8 382 | bsdf.inputs["Roughness"].default_value = 0.2 383 | bsdf.inputs["Alpha"].default_value = 0.5 384 | 385 | elif material.name == 'fence': 386 | material.name = "POLYGONFarm_Fence" 387 | elif material.name == 'lambert864': 388 | material.name = "POLYGONFarm_Signs" 389 | 390 | bsdf = material.node_tree.nodes["Principled BSDF"] 391 | bsdf.inputs["Metallic"].default_value = 0.0 392 | bsdf.inputs["Specular"].default_value = 0.2 393 | bsdf.inputs["Roughness"].default_value = 0.8 394 | elif material.name == 'lambert9': 395 | material.name = "POLYGONFarm_Wire" 396 | 397 | # https://blender.stackexchange.com/a/129014 398 | bsdf = material.node_tree.nodes["Principled BSDF"] 399 | 400 | alpha_tex = material.node_tree.nodes['Image Texture.001'] 401 | material.node_tree.links.new(bsdf.inputs['Alpha'], alpha_tex.outputs['Alpha']) 402 | 403 | bsdf.inputs["Metallic"].default_value = 0.0 404 | bsdf.inputs["Specular"].default_value = 0.2 405 | bsdf.inputs["Roughness"].default_value = 0.8 406 | elif material.name == 'lambert2': 407 | material.name = "POLYGONFarm_Base" 408 | 409 | bsdf = material.node_tree.nodes["Principled BSDF"] 410 | bsdf.inputs["Metallic"].default_value = 0.0 411 | bsdf.inputs["Specular"].default_value = 0.2 412 | bsdf.inputs["Roughness"].default_value = 0.8 413 | 414 | 415 | def cleanup(): 416 | # Apply rotation, scale 417 | for obj in bpy.data.objects: 418 | obj.select_set(True) 419 | bpy.ops.object.transform_apply(scale=True, rotation=True) 420 | 421 | # Removed orphaned data 422 | for block in bpy.data.meshes: 423 | if block.users == 0: 424 | bpy.data.meshes.remove(block) 425 | 426 | for block in bpy.data.materials: 427 | if block.users == 0: 428 | bpy.data.materials.remove(block) 429 | 430 | for block in bpy.data.textures: 431 | if block.users == 0: 432 | bpy.data.textures.remove(block) 433 | 434 | for block in bpy.data.images: 435 | if block.users == 0: 436 | bpy.data.images.remove(block) 437 | 438 | import_characters() 439 | import_attachments() 440 | import_weapons() 441 | import_buildings() 442 | import_environments() 443 | import_props() 444 | import_vehicles() 445 | fix_materials() 446 | cleanup() -------------------------------------------------------------------------------- /POLYGON_GangWarfare.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//CHR") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | # Apply location, rotation, scale to deltas 69 | for obj in collection.all_objects: 70 | obj.delta_location += obj.location 71 | obj.location = (0, 0, 0) 72 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 73 | obj.rotation_euler = (0, 0, 0) 74 | obj.delta_scale = obj.scale 75 | obj.scale = (1, 1, 1) 76 | 77 | first_armature.name = "Characters" 78 | 79 | # Textures pointing to the wrong directory. Fix that 80 | # https://blender.stackexchange.com/a/280804 81 | for image in bpy.data.images.values(): 82 | # Textures are pointing towards PolygonCity. Fix that 83 | filename = os.path.basename(image.filepath) 84 | if image.source == "FILE": 85 | if filename == "PolygonGangWars_Texture_01_A.psd": 86 | # Make absolute 87 | image.filepath = bpy.path.abspath( 88 | "//UnrealFBXs\\tex\\PolygonGangWarfare_Texture_01_A.png" 89 | ) 90 | else: 91 | # Make absolute 92 | image.filepath = bpy.path.abspath( 93 | "//UnrealFBXs\\tex\\" + filename 94 | ) 95 | 96 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Heist.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//_SourceFiles\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | # Apply location, rotation, scale to deltas 69 | for obj in collection.all_objects: 70 | obj.delta_location += obj.location 71 | obj.location = (0, 0, 0) 72 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 73 | obj.rotation_euler = (0, 0, 0) 74 | obj.delta_scale = obj.scale 75 | obj.scale = (1, 1, 1) 76 | 77 | first_armature.name = "Characters" 78 | 79 | # Textures pointing to the wrong directory. Fix that 80 | # https://blender.stackexchange.com/a/280804 81 | for image in bpy.data.images.values(): 82 | # Textures are pointing towards PolygonCity. Fix that 83 | filename = os.path.basename(image.filepath) 84 | if image.source == "FILE": 85 | if filename == "PolygonHeist_Texture.psd": 86 | # Make absolute 87 | image.filepath = bpy.path.abspath( 88 | "//_SourceFiles\\Textures\\PolygonHeist_Texture_01_A.png" 89 | ) 90 | else: 91 | # Make absolute 92 | image.filepath = bpy.path.abspath( 93 | "//_SourceFiles\\Textures\\" + filename 94 | ) 95 | 96 | import_characters() -------------------------------------------------------------------------------- /POLYGON_HorrorMansion.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Import the characters 13 | bpy.ops.import_scene.fbx( 14 | filepath=bpy.path.abspath("//SourceFiles\\FBX\\Characters.fbx"), 15 | use_anim=False, 16 | ignore_leaf_bones=True, 17 | force_connect_children=True, 18 | automatic_bone_orientation=True, 19 | ) 20 | 21 | collection = bpy.data.collections["Characters"] 22 | 23 | # Apply location, rotation, scale to deltas 24 | for obj in collection.all_objects: 25 | obj.delta_location += obj.location 26 | obj.location = (0, 0, 0) 27 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 28 | obj.rotation_euler = (0, 0, 0) 29 | obj.delta_scale = obj.scale 30 | obj.scale = (1, 1, 1) 31 | 32 | # Textures pointing to the wrong directory. Fix that 33 | # https://blender.stackexchange.com/a/280804 34 | for image in bpy.data.images.values(): 35 | # Textures are pointing towards PolygonCity. Fix that 36 | filename = os.path.basename(image.filepath) 37 | if image.source == "FILE": 38 | if filename == "PolygonHorror_Texture_01.psd": 39 | # Make absolute 40 | image.filepath = bpy.path.abspath( 41 | "//SourceFiles\\Textures\\Alts\\PolygonHorror_Texture_01_A.png" 42 | ) 43 | else: 44 | # Make absolute 45 | image.filepath = bpy.path.abspath( 46 | "//SourceFiles\\Textures\\Alts\\" + filename 47 | ) 48 | 49 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Kids.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//SourceFiles/Chr") 14 | fbxs = [f for f in os.listdir(folder) \ 15 | if f.endswith(".fbx") \ 16 | and f.startswith("SK_Chr_") \ 17 | and not f.startswith("SK_Chr_Baby") 18 | # These ones have 's' versions whatever that means 19 | and f not in ["SK_Chr_Eyebrows_01.fbx", "SK_Chr_Eyes_Female_01.fbx", "SK_Chr_Eyes_Male_01.fbx"] 20 | ] 21 | 22 | # Import them 23 | for fbx in fbxs: 24 | bpy.ops.import_scene.fbx( 25 | filepath=os.path.join(folder, fbx), 26 | use_anim=False, 27 | ignore_leaf_bones=True, 28 | force_connect_children=True, 29 | automatic_bone_orientation=True, 30 | ) 31 | 32 | # Loop through Characters 33 | first_armature = None 34 | collection = bpy.data.collections["Characters"] 35 | for obj in collection.objects: 36 | # Save the first armature object 37 | if first_armature == None and obj.type == 'ARMATURE': 38 | first_armature = obj 39 | continue 40 | 41 | if obj.type == 'MESH': 42 | # Always use non-duplicate version of material 43 | obj.active_material = bpy.data.materials[ 44 | obj 45 | .active_material.name 46 | # SK_Chr_Kid_Schoolboy_01 has a broken 'lambert3' material. Set it to 47 | # the same as all the others 48 | .replace("lambert3", "lambert2") 49 | # The eyes/eyebrows should also be on lambert2 50 | .replace("KIds", "lambert2") 51 | .split('.')[0] 52 | ] 53 | 54 | # Rename Meshes to be the same as their parent MeshObjects 55 | obj.data.name = obj.name 56 | 57 | 58 | if first_armature != None and obj.modifiers[0].type == 'ARMATURE': 59 | # Set all mesh Armature modifiers to the first armature object 60 | obj.modifiers[0].object = first_armature 61 | 62 | # Make our first armature the object parent 63 | obj.parent = first_armature 64 | 65 | # Deselect all 66 | bpy.ops.object.select_all(action='DESELECT') 67 | 68 | # Select unnecessary armatures 69 | first_armature = None 70 | for obj in collection.objects: 71 | if obj.type == 'ARMATURE': 72 | if first_armature == None: 73 | first_armature = obj 74 | else: 75 | obj.select_set(True) 76 | 77 | # Delete them 78 | bpy.ops.object.delete() 79 | 80 | first_armature.name = "Armature" 81 | 82 | 83 | def import_attachments(): 84 | # Deselect all 85 | bpy.ops.object.select_all(action='DESELECT') 86 | 87 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 88 | bpy.context.view_layer.active_layer_collection = \ 89 | bpy.context.view_layer.layer_collection.children['Character_Attachments'] 90 | 91 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 92 | folder = bpy.path.abspath("//SourceFiles/FBX") 93 | fbxs = [f for f in os.listdir(folder) \ 94 | if f.endswith(".fbx") \ 95 | and (f.startswith("SM_Chr_Attach_") or f.startswith("SM_Chr_Hair")) 96 | ] 97 | 98 | collection = bpy.data.collections["Character_Attachments"] 99 | 100 | # Import them 101 | for fbx in fbxs: 102 | bpy.ops.import_scene.fbx( 103 | filepath=os.path.join(folder, fbx), 104 | use_anim=False, 105 | ignore_leaf_bones=True, 106 | force_connect_children=True, 107 | automatic_bone_orientation=True, 108 | ) 109 | 110 | for obj in collection.objects: 111 | for material_slot in obj.material_slots: 112 | # Always use non-duplicate version of material 113 | material_slot.material = bpy.data.materials[ 114 | material_slot.material.name.split('.')[0] 115 | ] 116 | 117 | # Remove duplicate glass materials 118 | if material_slot.material.name in [ 119 | "lambert1431", "lambert1440", "paintballMask_GlassSHD", "_Glass_kids" 120 | ]: 121 | material_slot.material = bpy.data.materials["lambert1431"] 122 | else: 123 | # Use the base material for everything else 124 | material_slot.material = bpy.data.materials["lambert2"] 125 | 126 | # Rename Meshes to be the same as their parent MeshObjects 127 | obj.data.name = obj.name 128 | 129 | 130 | def import_environments(): 131 | # Deselect all 132 | bpy.ops.object.select_all(action='DESELECT') 133 | 134 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 135 | bpy.context.view_layer.active_layer_collection = \ 136 | bpy.context.view_layer.layer_collection.children['Environments'] 137 | 138 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 139 | folder = bpy.path.abspath("//SourceFiles/FBX") 140 | fbxs = [f for f in os.listdir(folder) \ 141 | if f.endswith(".fbx") and (f.startswith("SM_Env_") or f.startswith("SM_Generic_")) 142 | ] 143 | 144 | collection = bpy.data.collections["Environments"] 145 | 146 | # Import them 147 | for fbx in fbxs: 148 | bpy.ops.import_scene.fbx( 149 | filepath=os.path.join(folder, fbx), 150 | use_anim=False, 151 | ignore_leaf_bones=True, 152 | force_connect_children=True, 153 | automatic_bone_orientation=True, 154 | ) 155 | 156 | for obj in collection.objects: 157 | if obj.type == 'MESH': 158 | for material_slot in obj.material_slots: 159 | # Always use non-duplicate version of material 160 | material_slot.material = bpy.data.materials[ 161 | material_slot.material.name.split('.')[0] 162 | ] 163 | 164 | if material_slot.material.name == "GlassSHD": 165 | material_slot.material = bpy.data.materials["lambert1431"] 166 | else: 167 | material_slot.material = bpy.data.materials["lambert2"] 168 | 169 | # Rename Meshes to be the same as their parent MeshObjects 170 | obj.data.name = obj.name 171 | 172 | 173 | def import_props(): 174 | # Deselect all 175 | bpy.ops.object.select_all(action='DESELECT') 176 | 177 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 178 | bpy.context.view_layer.active_layer_collection = \ 179 | bpy.context.view_layer.layer_collection.children['Props'] 180 | 181 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 182 | folder = bpy.path.abspath("//SourceFiles/FBX") 183 | fbxs = [f for f in os.listdir(folder) \ 184 | if f.endswith(".fbx") \ 185 | and (f.startswith("SM_Prop_") or f.startswith("SK_Prop_")) 186 | ] 187 | 188 | collection = bpy.data.collections["Props"] 189 | 190 | # Import them 191 | for fbx in fbxs: 192 | bpy.ops.import_scene.fbx( 193 | filepath=os.path.join(folder, fbx), 194 | use_anim=False, 195 | ignore_leaf_bones=True, 196 | force_connect_children=True, 197 | automatic_bone_orientation=True, 198 | ) 199 | 200 | for obj in collection.objects: 201 | if obj.type == 'MESH': 202 | for material_slot in obj.material_slots: 203 | # Always use non-duplicate version of material 204 | material_slot.material = bpy.data.materials[ 205 | material_slot.material.name.split('.')[0] 206 | ] 207 | 208 | if material_slot.material.name.startswith("Glass"): 209 | material_slot.material = bpy.data.materials["lambert1431"] 210 | else: 211 | material_slot.material = bpy.data.materials["lambert2"] 212 | 213 | # Rename Meshes to be the same as their parent MeshObjects 214 | obj.data.name = obj.name 215 | 216 | 217 | def import_vehicles(): 218 | # Deselect all 219 | bpy.ops.object.select_all(action='DESELECT') 220 | 221 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 222 | bpy.context.view_layer.active_layer_collection = \ 223 | bpy.context.view_layer.layer_collection.children['Vehicles'] 224 | 225 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 226 | folder = bpy.path.abspath("//SourceFiles/FBX") 227 | fbxs = [f for f in os.listdir(folder) \ 228 | if f.endswith(".fbx") \ 229 | and (f.startswith("SM_Veh_") or f.startswith("SK_Veh_")) 230 | ] 231 | 232 | collection = bpy.data.collections["Vehicles"] 233 | 234 | # Import them 235 | for fbx in fbxs: 236 | bpy.ops.import_scene.fbx( 237 | filepath=os.path.join(folder, fbx), 238 | use_anim=False, 239 | ignore_leaf_bones=True, 240 | force_connect_children=True, 241 | automatic_bone_orientation=True, 242 | ) 243 | 244 | for obj in collection.objects: 245 | if obj.type == 'MESH': 246 | for material_slot in obj.material_slots: 247 | # Always use non-duplicate version of material 248 | material_slot.material = bpy.data.materials[ 249 | material_slot.material.name.split('.')[0] 250 | ] 251 | 252 | if material_slot.material.name.startswith("Glass"): 253 | material_slot.material = bpy.data.materials["lambert1431"] 254 | else: 255 | material_slot.material = bpy.data.materials["lambert2"] 256 | 257 | # Rename Meshes to be the same as their parent MeshObjects 258 | obj.data.name = obj.name 259 | 260 | 261 | def import_weapons(): 262 | # Deselect all 263 | bpy.ops.object.select_all(action='DESELECT') 264 | 265 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 266 | bpy.context.view_layer.active_layer_collection = \ 267 | bpy.context.view_layer.layer_collection.children['Weapons'] 268 | 269 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 270 | folder = bpy.path.abspath("//SourceFiles/FBX") 271 | fbxs = [f for f in os.listdir(folder) \ 272 | if f.endswith(".fbx") \ 273 | and (f.startswith("SK_Wep_") or f.startswith("SM_Wep_")) 274 | ] 275 | 276 | collection = bpy.data.collections["Weapons"] 277 | 278 | # Import them 279 | for fbx in fbxs: 280 | bpy.ops.import_scene.fbx( 281 | filepath=os.path.join(folder, fbx), 282 | use_anim=False, 283 | ignore_leaf_bones=True, 284 | force_connect_children=True, 285 | automatic_bone_orientation=True, 286 | ) 287 | 288 | for obj in collection.objects: 289 | # Rename any armatures to their first mesh object name 290 | # So they're not just 'Armature'. 291 | if obj.type == 'ARMATURE': 292 | for obj2 in collection.objects: 293 | if obj2.type == 'MESH' and obj2.parent == obj: 294 | obj.name = obj2.name 295 | break 296 | 297 | 298 | if obj.type == 'MESH': 299 | # The static mesh assets are sitting inside EMPTYs. Pull them out. 300 | if obj.parent and obj.parent.type == 'EMPTY': 301 | obj.parent = None 302 | 303 | # Some objects are scaled 100x too large. 304 | if obj.scale.x == 1.0: 305 | obj.scale /= 100.0 306 | 307 | for material_slot in obj.material_slots: 308 | # Always use non-duplicate version of material 309 | material_slot.material = bpy.data.materials[ 310 | material_slot.material.name.split('.')[0] 311 | ] 312 | # Use the same material as the characters 313 | material_slot.material = bpy.data.materials["lambert2"] 314 | 315 | # Rename Meshes to be the same as their parent MeshObjects 316 | obj.data.name = obj.name 317 | 318 | # Delete all EMPTYs 319 | bpy.ops.object.select_all(action='DESELECT') 320 | for obj in collection.objects: 321 | if obj.type == 'EMPTY': 322 | obj.select_set(True) 323 | bpy.ops.object.delete() 324 | 325 | 326 | def fix_materials(): 327 | # https://blender.stackexchange.com/a/280804 328 | for image in bpy.data.images.values(): 329 | filename = os.path.basename(image.filepath) 330 | if image.source == "FILE": 331 | # Eye/Eyebrow pointing to wrong file in wrong directory. Fix that 332 | if filename == "Polygon_Kids_Texture_Facial_Expression_Frown_Freckles_01.png": 333 | # Make absolute 334 | image.filepath = bpy.path.abspath( 335 | "//SourceFiles/Textures/Faces_Human/Normal/Expression/Polygon_Kids_Texture_Facial_Expression_Frown_01.png" 336 | ) 337 | elif filename == "Polygon_Kids_Texture_A _Justin.psd": 338 | image.filepath = bpy.path.abspath( 339 | "//SourceFiles/Textures/PolygonKids_Texture_01_A.png" 340 | ) 341 | 342 | for material in bpy.data.materials: 343 | if material.name == 'lambert2': 344 | material.name = 'POLYGONKids_Base' 345 | 346 | # https://blender.stackexchange.com/a/129014 347 | bsdf = material.node_tree.nodes["Principled BSDF"] 348 | bsdf.inputs["Metallic"].default_value = 0.0 349 | bsdf.inputs["Specular"].default_value = 0.2 350 | bsdf.inputs["Roughness"].default_value = 0.8 351 | elif material.name == 'lambert1431': 352 | material.name = 'POLYGONKids_Glass' 353 | 354 | # https://blender.stackexchange.com/a/129014 355 | bsdf = material.node_tree.nodes["Principled BSDF"] 356 | bsdf.inputs["Metallic"].default_value = 0.0 357 | bsdf.inputs["Specular"].default_value = 0.2 358 | bsdf.inputs["Roughness"].default_value = 0.8 359 | bsdf.inputs["Alpha"].default_value = 0.5 360 | 361 | def cleanup(): 362 | # Apply rotation, scale 363 | for obj in bpy.data.objects: 364 | obj.select_set(True) 365 | bpy.ops.object.transform_apply(scale=True, rotation=True) 366 | 367 | # Removed orphaned data 368 | for block in bpy.data.meshes: 369 | if block.users == 0: 370 | bpy.data.meshes.remove(block) 371 | 372 | for block in bpy.data.materials: 373 | if block.users == 0: 374 | bpy.data.materials.remove(block) 375 | 376 | for block in bpy.data.textures: 377 | if block.users == 0: 378 | bpy.data.textures.remove(block) 379 | 380 | for block in bpy.data.images: 381 | if block.users == 0: 382 | bpy.data.images.remove(block) 383 | 384 | import_characters() 385 | import_attachments() 386 | import_environments() 387 | import_props() 388 | import_vehicles() 389 | import_weapons() 390 | fix_materials() 391 | cleanup() -------------------------------------------------------------------------------- /POLYGON_Knights.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Source_Files\\Character_Files\\FBX_Characters\\Updated_Unreal_Rig") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | # Apply location, rotation, scale to deltas 69 | for obj in collection.all_objects: 70 | obj.delta_location += obj.location 71 | obj.location = (0, 0, 0) 72 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 73 | obj.rotation_euler = (0, 0, 0) 74 | obj.delta_scale = obj.scale 75 | obj.scale = (1, 1, 1) 76 | 77 | first_armature.name = "Characters" 78 | 79 | # Textures pointing to the wrong directory. Fix that 80 | # https://blender.stackexchange.com/a/280804 81 | for image in bpy.data.images.values(): 82 | filename = os.path.basename(image.filepath) 83 | if image.source == "FILE": 84 | if filename == "Characters_Texture_03.psd": 85 | # Make absolute 86 | image.filepath = bpy.path.abspath( 87 | "//Source_Files\\Textures\\Characters_Texture_Black.png" 88 | ) 89 | else: 90 | # Make absolute 91 | image.filepath = bpy.path.abspath( 92 | "//Source_Files\\Textures\\" + filename 93 | ) 94 | 95 | import_characters() -------------------------------------------------------------------------------- /POLYGON_ModularFantasyHeroes.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | # https://blender.stackexchange.com/a/272730 5 | def delete_hierarchy(parent_obj_name): 6 | bpy.ops.object.select_all(action='DESELECT') 7 | obj = bpy.data.objects[parent_obj_name] 8 | obj.animation_data_clear() 9 | names = set() 10 | # Go over all the objects in the hierarchy like @zeffi suggested: 11 | def get_child_names(obj): 12 | for child in obj.children: 13 | names.add(child.name) 14 | if child.children: 15 | get_child_names(child) 16 | 17 | get_child_names(obj) 18 | names.add(parent_obj_name) 19 | objects = bpy.data.objects 20 | 21 | # Remove the animation from the all the child objects 22 | if names: 23 | for child_name in names: 24 | bpy.data.objects[child_name].animation_data_clear() 25 | objects[child_name].select_set(state=True) 26 | bpy.data.objects.remove(objects[child_name]) 27 | 28 | def import_characters(): 29 | # Deselect all 30 | bpy.ops.object.select_all(action='DESELECT') 31 | 32 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 33 | bpy.context.view_layer.active_layer_collection = \ 34 | bpy.context.view_layer.layer_collection.children['Characters'] 35 | 36 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 37 | folder = bpy.path.abspath("//Source_Files/FBX") 38 | 39 | # Import them 40 | bpy.ops.import_scene.fbx( 41 | filepath=os.path.join(folder, "ModularCharactersFixedScale.fbx"), 42 | use_anim=False, 43 | ignore_leaf_bones=True, 44 | force_connect_children=True, 45 | automatic_bone_orientation=True, 46 | ) 47 | 48 | collection = bpy.data.collections["Characters"] 49 | 50 | # https://discord.com/channels/502587764299006004/1103191808667824129 51 | # LegLeft_Male_* and LegRight_Male_* are reversed. It's a known issue 52 | # that won't get fixed so let's fix them here. 53 | for obj in collection.objects: 54 | if obj.type == 'MESH': 55 | if obj.name.startswith('Chr_LegLeft_Male_'): 56 | obj.name = 'Fixed' + obj.name.replace('LegLeft', 'LegRight') 57 | elif obj.name.startswith('Chr_LegRight_Male_'): 58 | obj.name = 'Fixed' + obj.name.replace('LegRight', 'LegLeft') 59 | 60 | # Now go through and remove the 'Fixed' prefixes we added above 61 | for obj in collection.objects: 62 | if obj.type == 'MESH': 63 | if obj.name.startswith('Fixed'): 64 | obj.name = obj.name[5:] 65 | 66 | # Rename Meshes to be the same as their parent MeshObjects 67 | for obj in collection.objects: 68 | if obj.type == 'MESH': 69 | obj.data.name = obj.name[4:] 70 | 71 | # Delete unneeded nodes 72 | delete_hierarchy('Modular_Characters') 73 | 74 | 75 | 76 | 77 | def import_weapons(): 78 | # Deselect all 79 | bpy.ops.object.select_all(action='DESELECT') 80 | 81 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 82 | bpy.context.view_layer.active_layer_collection = \ 83 | bpy.context.view_layer.layer_collection.children['Weapons'] 84 | 85 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 86 | folder = bpy.path.abspath("//Source_Files/FBX/Weapons") 87 | fbxs = [f for f in os.listdir(folder) \ 88 | if f.endswith(".fbx") \ 89 | and (f.startswith("SK_Wep_") or f.startswith("SM_Wep_")) 90 | ] 91 | 92 | collection = bpy.data.collections["Weapons"] 93 | 94 | # Import them 95 | for fbx in fbxs: 96 | bpy.ops.import_scene.fbx( 97 | filepath=os.path.join(folder, fbx), 98 | use_anim=False, 99 | ignore_leaf_bones=True, 100 | force_connect_children=True, 101 | automatic_bone_orientation=True, 102 | ) 103 | 104 | for obj in collection.objects: 105 | if obj.type == 'MESH': 106 | # The static mesh assets are sitting inside EMPTYs. Pull them out. 107 | if obj.parent and obj.parent.type == 'EMPTY': 108 | obj.parent = None 109 | 110 | for material_slot in obj.material_slots: 111 | # Always use non-duplicate version of material 112 | material_slot.material = bpy.data.materials[ 113 | material_slot.material.name.split('.')[0] 114 | ] 115 | # Use the same material as the characters 116 | material_slot.material = bpy.data.materials["lambert2"] 117 | 118 | # Rename Meshes to be the same as their parent MeshObjects 119 | obj.data.name = obj.name 120 | 121 | 122 | def fix_materials(): 123 | # https://blender.stackexchange.com/a/280804 124 | for image in bpy.data.images.values(): 125 | filename = os.path.basename(image.filepath) 126 | if image.source == "FILE": 127 | # Eye/Eyebrow pointing to wrong file in wrong directory. Fix that 128 | if filename == "PolygonFantasyHero_Texture_01.png": 129 | # Make absolute 130 | image.filepath = bpy.path.abspath( 131 | "//Source_Files/Textures/PolygonFantasyHero_Texture_01.png" 132 | ) 133 | 134 | for material in bpy.data.materials: 135 | if material.name == 'lambert2': 136 | material.name = 'POLYGONModularFantasyHeroes_Base' 137 | 138 | def cleanup(): 139 | # Apply rotation, scale 140 | for obj in bpy.data.objects: 141 | obj.select_set(True) 142 | bpy.ops.object.transform_apply(scale=True, rotation=True) 143 | 144 | # Removed orphaned data 145 | for block in bpy.data.meshes: 146 | if block.users == 0: 147 | bpy.data.meshes.remove(block) 148 | 149 | for block in bpy.data.materials: 150 | if block.users == 0: 151 | bpy.data.materials.remove(block) 152 | 153 | for block in bpy.data.textures: 154 | if block.users == 0: 155 | bpy.data.textures.remove(block) 156 | 157 | for block in bpy.data.images: 158 | if block.users == 0: 159 | bpy.data.images.remove(block) 160 | 161 | import_characters() 162 | import_weapons() 163 | fix_materials() 164 | cleanup() -------------------------------------------------------------------------------- /POLYGON_Office.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Import the characters 13 | bpy.ops.import_scene.fbx( 14 | filepath=bpy.path.abspath("//SourceFiles/FBX/Character.fbx"), 15 | use_anim=False, 16 | ignore_leaf_bones=True, 17 | force_connect_children=True, 18 | automatic_bone_orientation=True, 19 | ) 20 | 21 | collection = bpy.data.collections["Characters"] 22 | 23 | for obj in collection.objects: 24 | if obj.type == 'MESH': 25 | # All these characters share the same material 26 | obj.active_material = bpy.data.materials['lambert295'] 27 | 28 | 29 | def import_character_attachments(): 30 | # Deselect all 31 | bpy.ops.object.select_all(action='DESELECT') 32 | 33 | # Highlight the 'Buildings' collection - https://blender.stackexchange.com/a/248563 34 | bpy.context.view_layer.active_layer_collection = \ 35 | bpy.context.view_layer.layer_collection.children['Character_Attachments'] 36 | 37 | # Find our FBX's - https://blender.stackexchange.com/a/253543 38 | folder = bpy.path.abspath("//SourceFiles/FBX/") 39 | fbxs = [f for f in os.listdir(folder) \ 40 | if f.endswith(".fbx") and f.startswith("SM_Chr_Attachment_") 41 | ] 42 | 43 | collection = bpy.data.collections["Character_Attachments"] 44 | 45 | # Import them 46 | for fbx in fbxs: 47 | bpy.ops.import_scene.fbx( 48 | filepath=os.path.join(folder, fbx), 49 | use_anim=False, 50 | ignore_leaf_bones=True, 51 | force_connect_children=True, 52 | automatic_bone_orientation=True, 53 | ) 54 | 55 | for obj in collection.objects: 56 | if obj.type == 'MESH': 57 | # All char attachments in this pack use a single texture and it's always 58 | # the base one. 59 | obj.active_material = bpy.data.materials['lambert295'] 60 | 61 | 62 | # Rename Meshes to be the same as their parent MeshObjects 63 | obj.data.name = obj.name 64 | 65 | 66 | def import_buildings(): 67 | # Deselect all 68 | bpy.ops.object.select_all(action='DESELECT') 69 | 70 | # Highlight the 'Buildings' collection - https://blender.stackexchange.com/a/248563 71 | bpy.context.view_layer.active_layer_collection = \ 72 | bpy.context.view_layer.layer_collection.children['Buildings'] 73 | 74 | # Find our FBX's - https://blender.stackexchange.com/a/253543 75 | folder = bpy.path.abspath("//SourceFiles/FBX/") 76 | fbxs = [f for f in os.listdir(folder) \ 77 | if f.endswith(".fbx") and f.startswith("SM_Bld_") 78 | ] 79 | 80 | collection = bpy.data.collections["Buildings"] 81 | 82 | # Import them 83 | for fbx in fbxs: 84 | bpy.ops.import_scene.fbx( 85 | filepath=os.path.join(folder, fbx), 86 | use_anim=False, 87 | ignore_leaf_bones=True, 88 | force_connect_children=True, 89 | automatic_bone_orientation=True, 90 | ) 91 | 92 | for obj in collection.objects: 93 | if obj.type == 'MESH': 94 | for material_slot in obj.material_slots: 95 | # Always use non-duplicate version of material 96 | material_slot.material = bpy.data.materials[ 97 | material_slot.material.name.split('.')[0] 98 | ] 99 | 100 | for material_slot in obj.material_slots: 101 | if material_slot.material.name.startswith("A_Glass") \ 102 | or material_slot.material.name.startswith("Glass"): 103 | material_slot.material = bpy.data.materials["A_Glass"] 104 | elif material_slot.material.name.startswith('lambert') \ 105 | or material_slot.material.name.startswith('OfficeSHD'): 106 | material_slot.material = bpy.data.materials['lambert295'] 107 | 108 | 109 | # Rename Meshes to be the same as their parent MeshObjects 110 | obj.data.name = obj.name 111 | 112 | 113 | def import_props(): 114 | # Deselect all 115 | bpy.ops.object.select_all(action='DESELECT') 116 | 117 | # Highlight the 'Buildings' collection - https://blender.stackexchange.com/a/248563 118 | bpy.context.view_layer.active_layer_collection = \ 119 | bpy.context.view_layer.layer_collection.children['Props'] 120 | 121 | # Find our FBX's - https://blender.stackexchange.com/a/253543 122 | folder = bpy.path.abspath("//SourceFiles/FBX/") 123 | fbxs = [f for f in os.listdir(folder) \ 124 | if f.endswith(".fbx") and f.startswith("SM_Prop_") 125 | ] 126 | 127 | collection = bpy.data.collections["Props"] 128 | 129 | # Import them 130 | for fbx in fbxs: 131 | bpy.ops.import_scene.fbx( 132 | filepath=os.path.join(folder, fbx), 133 | use_anim=False, 134 | ignore_leaf_bones=True, 135 | force_connect_children=True, 136 | automatic_bone_orientation=True, 137 | ) 138 | 139 | for obj in collection.objects: 140 | if obj.type == 'MESH': 141 | # A couple of meshes have incorrect materials 142 | if obj.name in ['SM_Prop_Sculpture_02', 'SM_Prop_Sculpture_Base_01']: 143 | obj.active_material = bpy.data.materials['lambert295'] 144 | # This one has a material named 'lambert12' and it's the only 145 | # one that needs to be set to Chrome so do that here to avoid 146 | # name clashes in other meshes. 147 | elif obj.name == 'SM_Prop_Sculpture_01': 148 | obj.active_material.name = 'POLYGONOffice_Chrome' 149 | 150 | for material_slot in obj.material_slots: 151 | # Always use non-duplicate version of material 152 | material_slot.material = bpy.data.materials[ 153 | material_slot.material.name.split('.')[0] 154 | ] 155 | 156 | if obj.name == 'SM_Prop_Clock_02' and material_slot.material.name == 'lambert2': 157 | material_slot.material = bpy.data.materials["Screen"] 158 | elif obj.name == 'SM_Prop_Laptop_02' and material_slot.material.name == 'OfficeSHD18': 159 | material_slot.material = bpy.data.materials["Screen"] 160 | elif obj.name == 'SM_Prop_Laptop_01' and material_slot.material.name == 'OfficeSHD16': 161 | material_slot.material = bpy.data.materials["Screen"] 162 | elif obj.name == 'SM_Prop_GraphicsTablet_02' and material_slot.material.name == 'OfficeSHD1': 163 | material_slot.material = bpy.data.materials["Screen"] 164 | elif obj.name == 'SM_Prop_GraphicsTablet_03' and material_slot.material.name == 'OfficeSHD1': 165 | material_slot.material = bpy.data.materials["Screen3"] 166 | elif obj.name == 'SM_Prop_GraphicsTablet_04' and material_slot.material.name == 'OfficeSHD1': 167 | material_slot.material = bpy.data.materials["Screen2"] 168 | elif obj.name == 'SM_Prop_TV_Wall_01' and material_slot.material.name == '_lambert2': 169 | material_slot.material = bpy.data.materials["Screen2"] 170 | elif obj.name in ['SM_Prop_Photocopier_01', 'SM_Prop_Photocopier_02'] and material_slot.material.name == 'Screen': 171 | material_slot.material = bpy.data.materials["Screen2"] 172 | elif obj.name == 'SM_Prop_Laptop_Folding_Screen_01' and material_slot.material.name == 'OfficeSHD16': 173 | material_slot.material = bpy.data.materials["Screen"] 174 | elif obj.name == 'SM_Prop_Laptop_Folding_Screen_02' and material_slot.material.name == 'OfficeSHD16': 175 | material_slot.material = bpy.data.materials["Screen2"] 176 | elif obj.name == 'SM_Prop_Monitor_Crt_01' and material_slot.material.name == 'OfficeSHD7': 177 | material_slot.material = bpy.data.materials["Screen"] 178 | elif obj.name == 'SM_Prop_ArcadeMachine_01' and material_slot.material.name == 'OfficeSHD1': 179 | material_slot.material = bpy.data.materials.new(name="POLYGONOffice_Arcade") 180 | elif obj.name == 'SM_Prop_Bottle_01' and material_slot.material.name == 'lambert8': 181 | material_slot.material = bpy.data.materials["A_Glass"] 182 | elif material_slot.material.name == 'glassSHD': 183 | material_slot.material = bpy.data.materials["A_Glass"] 184 | # Everything else goes to default mat 185 | elif material_slot.material.name.startswith("lambert") \ 186 | or material_slot.material.name.startswith("Office") \ 187 | or material_slot.material.name.startswith("gang"): 188 | material_slot.material = bpy.data.materials["lambert295"] 189 | 190 | # Rename Meshes to be the same as their parent MeshObjects 191 | obj.data.name = obj.name 192 | 193 | 194 | def import_weapons(): 195 | # Deselect all 196 | bpy.ops.object.select_all(action='DESELECT') 197 | 198 | # Highlight the 'Buildings' collection - https://blender.stackexchange.com/a/248563 199 | bpy.context.view_layer.active_layer_collection = \ 200 | bpy.context.view_layer.layer_collection.children['Weapons'] 201 | 202 | # Find our FBX's - https://blender.stackexchange.com/a/253543 203 | folder = bpy.path.abspath("//SourceFiles/FBX/") 204 | fbxs = [f for f in os.listdir(folder) \ 205 | if f.endswith(".fbx") and f.startswith("SM_Wep_") 206 | ] 207 | 208 | collection = bpy.data.collections["Weapons"] 209 | 210 | # Import them 211 | for fbx in fbxs: 212 | bpy.ops.import_scene.fbx( 213 | filepath=os.path.join(folder, fbx), 214 | use_anim=False, 215 | ignore_leaf_bones=True, 216 | force_connect_children=True, 217 | automatic_bone_orientation=True, 218 | ) 219 | 220 | for obj in collection.objects: 221 | if obj.type == 'MESH': 222 | for material_slot in obj.material_slots: 223 | # Always use non-duplicate version of material 224 | material_slot.material = bpy.data.materials[ 225 | material_slot.material.name.split('.')[0] 226 | ] 227 | 228 | # All weapons in this pack use a single texture and it's always 229 | # the base one. 230 | obj.active_material = bpy.data.materials['lambert295'] 231 | 232 | # Rename Meshes to be the same as their parent MeshObjects 233 | obj.data.name = obj.name 234 | 235 | 236 | def fix_materials(): 237 | # https://blender.stackexchange.com/a/280804 238 | for image in bpy.data.images.values(): 239 | filename = os.path.basename(image.filepath) 240 | if image.source == "FILE": 241 | # Eye/Eyebrow pointing to wrong file in wrong directory. Fix that 242 | if filename == "PolygonOffice_Texture_01_A_New.psd": 243 | # Make absolute 244 | image.filepath = bpy.path.abspath( 245 | "//SourceFiles/Textures/PolygonOffice_Texture_01_A.png" 246 | ) 247 | else: 248 | # Make absolute 249 | image.filepath = bpy.path.abspath( 250 | "//SourceFiles/Textures/" + filename 251 | ) 252 | 253 | 254 | for material in bpy.data.materials: 255 | if material.name == 'lambert295': 256 | material.name = 'POLYGONOffice_Base' 257 | 258 | # https://blender.stackexchange.com/a/129014 259 | bsdf = material.node_tree.nodes["Principled BSDF"] 260 | bsdf.inputs["Metallic"].default_value = 0.0 261 | bsdf.inputs["Specular IOR Level"].default_value = 0.2 262 | bsdf.inputs["Roughness"].default_value = 0.8 263 | 264 | tex_emissive = material.node_tree.nodes.new('ShaderNodeTexImage') 265 | tex_emissive.image = bpy.data.images.load(bpy.path.abspath( 266 | "//SourceFiles/Textures/Emissive_01.png" 267 | )) 268 | tex_emissive_mp = material.node_tree.nodes.new('ShaderNodeMath') 269 | tex_emissive_mp.operation = 'MULTIPLY' 270 | tex_emissive_mp.inputs[1].default_value = 2 271 | material.node_tree.links.new(tex_emissive_mp.inputs[0], tex_emissive.outputs['Color']) 272 | material.node_tree.links.new(bsdf.inputs['Emission Strength'], tex_emissive_mp.outputs[0]) 273 | elif material.name == 'A_Glass': 274 | material.name = 'POLYGONOffice_Glass' 275 | 276 | # https://blender.stackexchange.com/a/129014 277 | bsdf = material.node_tree.nodes["Principled BSDF"] 278 | bsdf.inputs["Alpha"].default_value = 0.5 279 | elif material.name == 'Net1': 280 | material.name = 'POLYGONOffice_Net' 281 | 282 | bsdf = material.node_tree.nodes["Principled BSDF"] 283 | tex_emissive = material.node_tree.nodes["Image Texture.001"] 284 | 285 | material.node_tree.links.new(bsdf.inputs['Alpha'], tex_emissive.outputs['Alpha']) 286 | elif material.name == 'POLYGONOffice_Arcade': 287 | material.use_nodes = True 288 | bsdf = material.node_tree.nodes.new("ShaderNodeBsdfPrincipled") 289 | tex_albedo = material.node_tree.nodes.new('ShaderNodeTexImage') 290 | tex_albedo.image = bpy.data.images.load(bpy.path.abspath( 291 | "//SourceFiles/Textures/PolygonOffice_Texture_Sceen_Arcade_01.png" 292 | )) 293 | material.node_tree.links.new(bsdf.inputs['Base Color'], tex_albedo.outputs['Color']) 294 | material.node_tree.links.new(bsdf.inputs['Emission Color'], tex_albedo.outputs['Color']) 295 | 296 | # Set up the 5 Screen materials 297 | for i in range(1, 4): 298 | if material.name == 'Screen' + str(i).replace('1', ''): 299 | material.name = 'POLYGONOffice_Screen' + str(i) 300 | 301 | bsdf = material.node_tree.nodes["Principled BSDF"] 302 | tex_albedo = material.node_tree.nodes.new('ShaderNodeTexImage') 303 | tex_albedo.image = bpy.data.images.load(bpy.path.abspath( 304 | "//SourceFiles/Textures/PolygonOffice_Texture_Sceen_0" + str(i) + ".png" 305 | )) 306 | material.node_tree.links.new(bsdf.inputs['Base Color'], tex_albedo.outputs['Color']) 307 | material.node_tree.links.new(bsdf.inputs['Emission Color'], tex_albedo.outputs['Color']) 308 | 309 | 310 | 311 | 312 | def cleanup(): 313 | # Apply rotation, scale 314 | for obj in bpy.data.objects: 315 | obj.select_set(True) 316 | bpy.ops.object.transform_apply(scale=True, rotation=True) 317 | 318 | # Removed orphaned data 319 | for block in bpy.data.meshes: 320 | if block.users == 0: 321 | bpy.data.meshes.remove(block) 322 | 323 | for block in bpy.data.materials: 324 | if block.users == 0: 325 | bpy.data.materials.remove(block) 326 | 327 | for block in bpy.data.textures: 328 | if block.users == 0: 329 | bpy.data.textures.remove(block) 330 | 331 | for block in bpy.data.images: 332 | if block.users == 0: 333 | bpy.data.images.remove(block) 334 | 335 | 336 | import_characters() 337 | import_character_attachments() 338 | import_buildings() 339 | import_props() 340 | import_weapons() 341 | fix_materials() 342 | cleanup() -------------------------------------------------------------------------------- /POLYGON_Pirates.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//SourceFiles\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx") and f.startswith("Character_")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | # Select unnecessary armatures 47 | first_armature = None 48 | for obj in collection.objects: 49 | if obj.type == 'ARMATURE': 50 | if first_armature == None: 51 | first_armature = obj 52 | else: 53 | obj.select_set(True) 54 | 55 | # Delete them 56 | bpy.ops.object.delete() 57 | 58 | # Apply location, rotation, scale to deltas 59 | for obj in collection.all_objects: 60 | obj.delta_location += obj.location 61 | obj.location = (0, 0, 0) 62 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 63 | obj.rotation_euler = (0, 0, 0) 64 | obj.delta_scale = obj.scale 65 | obj.scale = (1, 1, 1) 66 | 67 | first_armature.name = "Characters" 68 | 69 | # Textures pointing to the wrong directory. Fix that 70 | # https://blender.stackexchange.com/a/280804 71 | for image in bpy.data.images.values(): 72 | if image.source == "FILE": 73 | # Textures are pointing towards PolygonCity. Fix that 74 | filename = os.path.basename(image.filepath) 75 | 76 | if filename == "Texture_01.psd": 77 | image.filepath = bpy.path.abspath( 78 | "//SourceFiles\\Textures\\Texture_01_A.png" 79 | ) 80 | else: 81 | # Make absolute 82 | image.filepath = bpy.path.abspath( 83 | "//SourceFiles\\Textures\\" + filename 84 | ) 85 | 86 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Samurai.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//SourceFiles\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | # Select unnecessary armatures 47 | first_armature = None 48 | for obj in collection.objects: 49 | if obj.type == 'ARMATURE': 50 | if first_armature == None: 51 | first_armature = obj 52 | else: 53 | obj.select_set(True) 54 | 55 | # Delete them 56 | bpy.ops.object.delete() 57 | 58 | # Apply location, rotation, scale to deltas 59 | for obj in collection.all_objects: 60 | obj.delta_location += obj.location 61 | obj.location = (0, 0, 0) 62 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 63 | obj.rotation_euler = (0, 0, 0) 64 | obj.delta_scale = obj.scale 65 | obj.scale = (1, 1, 1) 66 | 67 | first_armature.name = "Characters" 68 | 69 | # Textures pointing to the wrong directory. Fix that 70 | # https://blender.stackexchange.com/a/280804 71 | for image in bpy.data.images.values(): 72 | if image.source == "FILE": 73 | # Textures are pointing towards PolygonCity. Fix that 74 | filename = os.path.basename(image.filepath) 75 | 76 | # Make absolute 77 | image.filepath = bpy.path.abspath( 78 | "//SourceFiles\\Textures\\" + filename 79 | ) 80 | 81 | import_characters() -------------------------------------------------------------------------------- /POLYGON_SciFiCity.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Import the characters 13 | bpy.ops.import_scene.fbx( 14 | filepath=bpy.path.abspath("//Source_Files\\FBX\\Characters.fbx"), 15 | use_anim=False, 16 | ignore_leaf_bones=True, 17 | force_connect_children=True, 18 | automatic_bone_orientation=True, 19 | ) 20 | 21 | collection = bpy.data.collections["Characters"] 22 | 23 | # Apply location, rotation, scale to deltas 24 | for obj in collection.all_objects: 25 | obj.delta_location += obj.location 26 | obj.location = (0, 0, 0) 27 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 28 | obj.rotation_euler = (0, 0, 0) 29 | obj.delta_scale = obj.scale 30 | obj.scale = (1, 1, 1) 31 | 32 | # Textures pointing to the wrong directory. Fix that 33 | # https://blender.stackexchange.com/a/280804 34 | for image in bpy.data.images.values(): 35 | # Textures are pointing towards wrong file. Fix that 36 | filename = os.path.basename(image.filepath).replace("PolygonSciFiCity_Texture_", "PolygonSciFi_") 37 | if image.source == "FILE": 38 | # Make absolute 39 | image.filepath = bpy.path.abspath( 40 | "//Source_Files\\Textures\\" + filename 41 | ) 42 | 43 | import_characters() -------------------------------------------------------------------------------- /POLYGON_SciFiSpace.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//_SourceFiles\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if \ 15 | f.endswith(".fbx") \ 16 | and not "_Suit_" in f \ 17 | and not "_Head_" in f \ 18 | and not f.startswith("SK_Chr_Attach_") \ 19 | # The SpaceSoldier's not ending in 01.fbx are ASCII. Skip them 20 | and not (f.startswith("SK_Chr_SpaceSoldier_") and not f.endswith("01.fbx")) 21 | ] 22 | 23 | # Import them 24 | for fbx in fbxs: 25 | bpy.ops.import_scene.fbx( 26 | filepath=os.path.join(folder, fbx), 27 | use_anim=False, 28 | ignore_leaf_bones=True, 29 | force_connect_children=True, 30 | automatic_bone_orientation=True, 31 | ) 32 | 33 | collection = bpy.data.collections["Characters"] 34 | 35 | # Loop through Characters 36 | first_armature = None 37 | for obj in collection.objects: 38 | # Save the first armature object 39 | if first_armature == None and obj.type == 'ARMATURE': 40 | first_armature = obj 41 | continue 42 | 43 | if first_armature != None and obj.type == 'MESH': 44 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 45 | # Set all mesh Armature modifiers to the first armature object 46 | obj.modifiers[0].object = first_armature 47 | 48 | # Add missing texture node to the material 49 | texture_path = bpy.path.abspath("//_SourceFiles\\Textures\\PolygonSciFiSpace_Texture_01_A.png") 50 | for material_slot in obj.material_slots: 51 | material = material_slot.material 52 | 53 | # https://blender.stackexchange.com/a/129014 54 | bsdf = material.node_tree.nodes["Principled BSDF"] 55 | tex_image = material.node_tree.nodes.new('ShaderNodeTexImage') 56 | tex_image.image = bpy.data.images.load(texture_path) 57 | material.node_tree.links.new(bsdf.inputs['Base Color'], tex_image.outputs['Color']) 58 | 59 | # Make our first armature the object parent 60 | obj.parent = first_armature 61 | 62 | # Deselect all 63 | bpy.ops.object.select_all(action='DESELECT') 64 | 65 | # Remove IK bones 66 | bpy.context.view_layer.objects.active = first_armature 67 | bpy.ops.object.mode_set(mode='EDIT') 68 | # Remove the IK bones 69 | for bone in first_armature.data.edit_bones: 70 | if bone.name.startswith("ik_"): 71 | first_armature.data.edit_bones.remove(bone) 72 | bpy.ops.object.mode_set(mode='OBJECT') 73 | 74 | # Select unnecessary armatures 75 | first_armature = None 76 | for obj in collection.objects: 77 | if obj.type == 'ARMATURE': 78 | if first_armature == None: 79 | first_armature = obj 80 | else: 81 | obj.select_set(True) 82 | 83 | # Delete them 84 | bpy.ops.object.delete() 85 | 86 | # Apply location, rotation, scale to deltas 87 | for obj in collection.all_objects: 88 | obj.delta_location += obj.location 89 | obj.location = (0, 0, 0) 90 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 91 | obj.rotation_euler = (0, 0, 0) 92 | obj.delta_scale = obj.scale 93 | obj.scale = (1, 1, 1) 94 | 95 | first_armature.name = "Characters" 96 | 97 | # Textures pointing to the wrong directory. Fix that 98 | # https://blender.stackexchange.com/a/280804 99 | for image in bpy.data.images.values(): 100 | if image.source == "FILE": 101 | # Textures are pointing towards PolygonCity. Fix that 102 | filename = os.path.basename(image.filepath) 103 | 104 | # Make absolute 105 | image.filepath = bpy.path.abspath( 106 | "//_SourceFiles\\Textures\\" + filename 107 | ) 108 | 109 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Starter.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Import the characters 13 | bpy.ops.import_scene.fbx( 14 | filepath=bpy.path.abspath("//SourceFiles\\FBX\\Characters.fbx"), 15 | use_anim=False, 16 | ignore_leaf_bones=True, 17 | force_connect_children=True, 18 | automatic_bone_orientation=True, 19 | ) 20 | 21 | collection = bpy.data.collections["Characters"] 22 | 23 | # Apply location, rotation, scale to deltas 24 | for obj in collection.all_objects: 25 | obj.delta_location += obj.location 26 | obj.location = (0, 0, 0) 27 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 28 | obj.rotation_euler = (0, 0, 0) 29 | obj.delta_scale = obj.scale 30 | obj.scale = (1, 1, 1) 31 | 32 | # Textures pointing to the wrong directory. Fix that 33 | # https://blender.stackexchange.com/a/280804 34 | for image in bpy.data.images.values(): 35 | # Textures are pointing towards PolygonCity. Fix that 36 | filename = os.path.basename(image.filepath) 37 | if image.source == "FILE": 38 | if filename == "Polygonstarter_Texture_01_A_2k.psd": 39 | # Make absolute 40 | image.filepath = bpy.path.abspath( 41 | "//SourceFiles\\Textures\\PolygonStarter_Texture_01.png" 42 | ) 43 | else: 44 | # Make absolute 45 | image.filepath = bpy.path.abspath( 46 | "//SourceFiles\\Textures\\" + filename 47 | ) 48 | 49 | import_characters() -------------------------------------------------------------------------------- /POLYGON_StreetRacer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Import the characters 13 | bpy.ops.import_scene.fbx( 14 | filepath=bpy.path.abspath("//PolygonStreetRacer_SourceFiles\\FBX\\Characters.fbx"), 15 | use_anim=False, 16 | ignore_leaf_bones=True, 17 | force_connect_children=True, 18 | automatic_bone_orientation=True, 19 | ) 20 | 21 | collection = bpy.data.collections["Characters"] 22 | 23 | # Apply location, rotation, scale to deltas 24 | for obj in collection.all_objects: 25 | obj.delta_location += obj.location 26 | obj.location = (0, 0, 0) 27 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 28 | obj.rotation_euler = (0, 0, 0) 29 | obj.delta_scale = obj.scale 30 | obj.scale = (1, 1, 1) 31 | 32 | # Textures pointing to the wrong directory. Fix that 33 | # https://blender.stackexchange.com/a/280804 34 | for image in bpy.data.images.values(): 35 | # Textures are pointing towards PolygonCity. Fix that 36 | filename = os.path.basename(image.filepath) 37 | if image.source == "FILE": 38 | if filename == "PolygonStreetRacer_Texture_Master.psd": 39 | # Make absolute 40 | image.filepath = bpy.path.abspath( 41 | "//PolygonStreetRacer_SourceFiles\\Textures\\PolygonStreetRacer_Texture_01_A.png" 42 | ) 43 | else: 44 | # Make absolute 45 | image.filepath = bpy.path.abspath( 46 | "//PolygonStreetRacer_SourceFiles\\Textures\\" + filename 47 | ) 48 | 49 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Vikings.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Source_Files\\Character_Files\\FBX_Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | # Select unnecessary armatures 47 | first_armature = None 48 | for obj in collection.objects: 49 | if obj.type == 'ARMATURE': 50 | if first_armature == None: 51 | first_armature = obj 52 | else: 53 | obj.select_set(True) 54 | 55 | # Delete them 56 | bpy.ops.object.delete() 57 | 58 | # Apply location, rotation, scale to deltas 59 | for obj in collection.all_objects: 60 | obj.delta_location += obj.location 61 | obj.location = (0, 0, 0) 62 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 63 | obj.rotation_euler = (0, 0, 0) 64 | obj.delta_scale = obj.scale 65 | obj.scale = (1, 1, 1) 66 | 67 | first_armature.name = "Characters" 68 | 69 | # Textures pointing to the wrong directory. Fix that 70 | # https://blender.stackexchange.com/a/280804 71 | for image in bpy.data.images.values(): 72 | if image.source == "FILE": 73 | # Textures are pointing towards PolygonCity. Fix that 74 | filename = os.path.basename(image.filepath) 75 | 76 | # Make absolute 77 | image.filepath = bpy.path.abspath( 78 | "//Source_Files\\Textures\\" + filename 79 | ) 80 | 81 | import_characters() -------------------------------------------------------------------------------- /POLYGON_War.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Source Files\\Characters") 14 | fbxs = [f for f in os.listdir(folder) if \ 15 | f.endswith(".fbx") \ 16 | # Unsupported FBX version 17 | and f not in [ 18 | "Character_German_Leader_01.fbx", 19 | "Character_Gernman_Officer_02.fbx", 20 | "Character_Pilot_01.fbx" 21 | ] 22 | ] 23 | 24 | # Import them 25 | for fbx in fbxs: 26 | bpy.ops.import_scene.fbx( 27 | filepath=os.path.join(folder, fbx), 28 | use_anim=False, 29 | ignore_leaf_bones=True, 30 | force_connect_children=True, 31 | automatic_bone_orientation=True, 32 | ) 33 | 34 | collection = bpy.data.collections["Characters"] 35 | 36 | # Loop through Characters 37 | first_armature = None 38 | for obj in collection.objects: 39 | # Save the first armature object 40 | if first_armature == None and obj.type == 'ARMATURE': 41 | first_armature = obj 42 | continue 43 | 44 | if first_armature != None and obj.type == 'MESH': 45 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 46 | # Set all mesh Armature modifiers to the first armature object 47 | obj.modifiers[0].object = first_armature 48 | # Make our first armature the object parent 49 | obj.parent = first_armature 50 | 51 | # Deselect all 52 | bpy.ops.object.select_all(action='DESELECT') 53 | 54 | # Select unnecessary armatures 55 | first_armature = None 56 | for obj in collection.objects: 57 | if obj.type == 'ARMATURE': 58 | if first_armature == None: 59 | first_armature = obj 60 | else: 61 | obj.select_set(True) 62 | 63 | # Delete them 64 | bpy.ops.object.delete() 65 | 66 | # Apply location, rotation, scale to deltas 67 | for obj in collection.all_objects: 68 | obj.delta_location += obj.location 69 | obj.location = (0, 0, 0) 70 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 71 | obj.rotation_euler = (0, 0, 0) 72 | 73 | # Armature is 100x too large 74 | if obj.type == "ARMATURE": 75 | obj.delta_scale = obj.scale / 100 76 | obj.scale = (1, 1, 1) 77 | elif obj.type == "MESH": 78 | obj.delta_scale = obj.scale * 100 79 | obj.scale = (1, 1, 1) 80 | 81 | 82 | first_armature.name = "Characters" 83 | 84 | # Textures pointing to the wrong directory. Fix that 85 | # https://blender.stackexchange.com/a/280804 86 | for image in bpy.data.images.values(): 87 | if image.source == "FILE": 88 | # Textures are pointing towards wrong file. Fix that 89 | filename = os.path.basename(image.filepath) 90 | 91 | if filename == "PolygonWar_Texture_01.png": 92 | filename = "PolygonWar_Texture_01_A.png" 93 | 94 | # Make absolute 95 | image.filepath = bpy.path.abspath( 96 | "//Source Files\\Textures\\" + filename 97 | ) 98 | 99 | import_characters() -------------------------------------------------------------------------------- /POLYGON_WarMap.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//SourceFiles\\FBX") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx") and f.startswith("SK_Character_")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | # Remove IK bones 47 | bpy.context.view_layer.objects.active = first_armature 48 | bpy.ops.object.mode_set(mode='EDIT') 49 | # Remove the IK bones 50 | for bone in first_armature.data.edit_bones: 51 | if bone.name.startswith("ik_"): 52 | first_armature.data.edit_bones.remove(bone) 53 | bpy.ops.object.mode_set(mode='OBJECT') 54 | 55 | # Select unnecessary armatures 56 | first_armature = None 57 | for obj in collection.objects: 58 | if obj.type == 'ARMATURE': 59 | if first_armature == None: 60 | first_armature = obj 61 | else: 62 | obj.select_set(True) 63 | 64 | # Delete them 65 | bpy.ops.object.delete() 66 | 67 | # Apply location, rotation, scale to deltas 68 | for obj in collection.all_objects: 69 | obj.delta_location += obj.location 70 | obj.location = (0, 0, 0) 71 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 72 | obj.rotation_euler = (0, 0, 0) 73 | obj.delta_scale = obj.scale 74 | obj.scale = (1, 1, 1) 75 | 76 | first_armature.name = "Characters" 77 | 78 | # Textures pointing to the wrong directory. Fix that 79 | # https://blender.stackexchange.com/a/280804 80 | for image in bpy.data.images.values(): 81 | if image.source == "FILE": 82 | # Textures are pointing towards wrong file. Fix that 83 | filename = os.path.basename(image.filepath) 84 | 85 | if filename == "PolygonWar_Map_Texture_Mike.psd": 86 | filename = "PolygonWar_Map_Texture_01_A.png" 87 | 88 | # Make absolute 89 | image.filepath = bpy.path.abspath( 90 | "//SourceFiles\\Textures\\" + filename 91 | ) 92 | 93 | import_characters() -------------------------------------------------------------------------------- /POLYGON_Western.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | def import_characters(): 5 | # Deselect all 6 | bpy.ops.object.select_all(action='DESELECT') 7 | 8 | # Highlight the 'Characters' collection - https://blender.stackexchange.com/a/248563 9 | bpy.context.view_layer.active_layer_collection = \ 10 | bpy.context.view_layer.layer_collection.children['Characters'] 11 | 12 | # Find our character FBX's - https://blender.stackexchange.com/a/253543 13 | folder = bpy.path.abspath("//Characters") 14 | fbxs = [f for f in os.listdir(folder) if f.endswith(".fbx")] 15 | 16 | # Import them 17 | for fbx in fbxs: 18 | bpy.ops.import_scene.fbx( 19 | filepath=os.path.join(folder, fbx), 20 | use_anim=False, 21 | ignore_leaf_bones=True, 22 | force_connect_children=True, 23 | automatic_bone_orientation=True, 24 | ) 25 | 26 | collection = bpy.data.collections["Characters"] 27 | 28 | # Loop through Characters 29 | first_armature = None 30 | for obj in collection.objects: 31 | # Save the first armature object 32 | if first_armature == None and obj.type == 'ARMATURE': 33 | first_armature = obj 34 | continue 35 | 36 | if first_armature != None and obj.type == 'MESH': 37 | if obj.modifiers and obj.modifiers[0].type == 'ARMATURE': 38 | # Set all mesh Armature modifiers to the first armature object 39 | obj.modifiers[0].object = first_armature 40 | # Make our first armature the object parent 41 | obj.parent = first_armature 42 | 43 | # Deselect all 44 | bpy.ops.object.select_all(action='DESELECT') 45 | 46 | 47 | # Remove IK bones 48 | bpy.context.view_layer.objects.active = first_armature 49 | bpy.ops.object.mode_set(mode='EDIT') 50 | # Remove the IK bones 51 | for bone in first_armature.data.edit_bones: 52 | if bone.name.startswith("ik_"): 53 | first_armature.data.edit_bones.remove(bone) 54 | bpy.ops.object.mode_set(mode='OBJECT') 55 | 56 | # Select unnecessary armatures 57 | first_armature = None 58 | for obj in collection.objects: 59 | if obj.type == 'ARMATURE': 60 | if first_armature == None: 61 | first_armature = obj 62 | else: 63 | obj.select_set(True) 64 | 65 | # Delete them 66 | bpy.ops.object.delete() 67 | 68 | # Apply location, rotation, scale to deltas 69 | for obj in collection.all_objects: 70 | obj.delta_location += obj.location 71 | obj.location = (0, 0, 0) 72 | obj.delta_rotation_euler.rotate(obj.rotation_euler) 73 | obj.rotation_euler = (0, 0, 0) 74 | obj.delta_scale = obj.scale 75 | obj.scale = (1, 1, 1) 76 | 77 | first_armature.name = "Characters" 78 | 79 | # Textures pointing to the wrong directory. Fix that 80 | # https://blender.stackexchange.com/a/280804 81 | for image in bpy.data.images.values(): 82 | filename = os.path.basename(image.filepath) 83 | if image.source == "FILE": 84 | if filename == "PolygonWestern_Texture.psd": 85 | # Make absolute 86 | image.filepath = bpy.path.abspath( 87 | "//Textures\\PolygonWestern_Texture_01.png" 88 | ) 89 | else: 90 | # Make absolute 91 | image.filepath = bpy.path.abspath( 92 | "//Textures\\" + filename 93 | ) 94 | 95 | import_characters() -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Synty in Blender Project 2 | 3 |
4 |
5 |
6 |
7 |