├── .gitignore ├── Assets └── Objects │ ├── AnimatedMesh │ ├── Door.blend │ ├── door.cga │ ├── door.cga.cryasset │ ├── door.mtl │ ├── door_open.anm │ └── open_open.anm.cryasset │ ├── Characters │ └── Player_SDK │ │ ├── Player_SDK.chr │ │ ├── Player_SDK.chrparams │ │ ├── Player_SDK.mtl │ │ ├── Player_SDK_v_1_0.blend │ │ └── player_sdk.cdf │ ├── Smoothing │ ├── Saturn.mtl │ ├── Smoothing.blend │ ├── saturn_auto_smooth_80_angle.cgf │ ├── saturn_auto_smooth_80_angle_and_sharp_edges.cgf │ ├── saturn_flat.cgf │ └── saturn_smooth.cgf │ └── Vehicles │ ├── HMMWV.mtl │ ├── HMMWV_Simple.blend │ ├── hmmwv.cga │ └── hmmwv.cga.cryasset ├── CHANGELOG.md ├── README.md └── io_bcry_exporter ├── __init__.py ├── configuration.py ├── desc.py ├── exceptions.py ├── export.py ├── export_animations.py ├── export_materials.py ├── icons └── CryEngine.png ├── material_utils.py ├── outpipe.py ├── rc.py ├── udp.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /Assets/Objects/AnimatedMesh/Door.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/AnimatedMesh/Door.blend -------------------------------------------------------------------------------- /Assets/Objects/AnimatedMesh/door.cga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/AnimatedMesh/door.cga -------------------------------------------------------------------------------- /Assets/Objects/AnimatedMesh/door.cga.cryasset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 3 8 | 84 9 | 190 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /Assets/Objects/AnimatedMesh/door.mtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Assets/Objects/AnimatedMesh/door_open.anm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/AnimatedMesh/door_open.anm -------------------------------------------------------------------------------- /Assets/Objects/AnimatedMesh/open_open.anm.cryasset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 0 8 | 0 9 | 0 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /Assets/Objects/Characters/Player_SDK/Player_SDK.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Characters/Player_SDK/Player_SDK.chr -------------------------------------------------------------------------------- /Assets/Objects/Characters/Player_SDK/Player_SDK.chrparams: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /Assets/Objects/Characters/Player_SDK/Player_SDK.mtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Assets/Objects/Characters/Player_SDK/Player_SDK_v_1_0.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Characters/Player_SDK/Player_SDK_v_1_0.blend -------------------------------------------------------------------------------- /Assets/Objects/Characters/Player_SDK/player_sdk.cdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Assets/Objects/Smoothing/Saturn.mtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Assets/Objects/Smoothing/Smoothing.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Smoothing/Smoothing.blend -------------------------------------------------------------------------------- /Assets/Objects/Smoothing/saturn_auto_smooth_80_angle.cgf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Smoothing/saturn_auto_smooth_80_angle.cgf -------------------------------------------------------------------------------- /Assets/Objects/Smoothing/saturn_auto_smooth_80_angle_and_sharp_edges.cgf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Smoothing/saturn_auto_smooth_80_angle_and_sharp_edges.cgf -------------------------------------------------------------------------------- /Assets/Objects/Smoothing/saturn_flat.cgf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Smoothing/saturn_flat.cgf -------------------------------------------------------------------------------- /Assets/Objects/Smoothing/saturn_smooth.cgf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Smoothing/saturn_smooth.cgf -------------------------------------------------------------------------------- /Assets/Objects/Vehicles/HMMWV.mtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Assets/Objects/Vehicles/HMMWV_Simple.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Vehicles/HMMWV_Simple.blend -------------------------------------------------------------------------------- /Assets/Objects/Vehicles/hmmwv.cga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/Assets/Objects/Vehicles/hmmwv.cga -------------------------------------------------------------------------------- /Assets/Objects/Vehicles/hmmwv.cga.cryasset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 6 8 | 48555 9 | 74187 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog: 2 | 3 | ------------------------------------------------------------------------------- 4 | 5 | BCRY Exporter 6 | 7 | ------------------------------------------------------------------------------- 8 | 9 | ## 5.2 10 | #### Compatibility 11 | * Only compatible with CryEngine 3.5 and up. 12 | * Only compatible with Blender 2.7 and up. 13 | * Supports primary assets: CGF, CGA, CHR, SKIN, ANM, I_CAF. 14 | * Compatible with LumberYard. 15 | 16 | #### UI Changes: 17 | * Collapse panel as default. 18 | * New Tool Shelf Panel Design. 19 | * New material utilities name and icons. 20 | 21 | #### New Features: 22 | * New beakable joint window. 23 | * New apply modifiers behavior. 24 | * New Feet On Floor feature. 25 | 26 | #### Improvements/Fixes: 27 | * Material dictionary control fix. 28 | * Locator Locomotion axis fix. 29 | * Using tuple for mesh and scene backup information. 30 | * Auto smooth improvement. 31 | * Generate LODs icon replacing. 32 | 33 | 34 | ## 5.1 35 | #### Compatibility 36 | * Only compatible with CryEngine 3.5 and up. 37 | * Only compatible with Blender 2.7 and up. 38 | * Supports primary assets: CGF, CGA, CHR, SKIN, ANM, I_CAF. 39 | * Compatible with LumberYard. 40 | 41 | #### UI Changes: 42 | * User Defined Properties in Tool Shelf Panel. 43 | 44 | #### New Features: 45 | * Vehicle exporting support. 46 | * Parent-Child relation exporting. 47 | * CGA parent child support. 48 | * Dummy/Empty object exporting. 49 | * Dummy/Empty object UDP support. 50 | * Vehicle hull UDP support. 51 | * Alpha vertex color exporting for Blend Layer. 52 | 53 | #### Improvements/Fixes: 54 | * Custom normals area calculation. 55 | * Material exporting log. 56 | * Material order fix. 57 | * Set Material Name defualt tool fixing. 58 | * UDP properties fixing. 59 | 60 | 61 | ## 5.0 62 | #### Compatibility 63 | * Only compatible with CryEngine 3.5 and up. 64 | * Only compatible with Blender 2.7 and up. 65 | * Supports primary assets: CGF, CGA, CHR, SKIN, ANM, I_CAF. 66 | * Compatible with LumberYard. 67 | 68 | #### UI Changes: 69 | * Timeline Editor choose for Add Animation Node window. 70 | * New Generate LODs menuitem. 71 | * Removed Add Bone Geometry, Remove Bone Geometry, Rename Phys Bones menuitems. 72 | * New Physicalize Skeleton menuitem. 73 | * New Clear Skeleton Physics menuitem. 74 | * New Add Locator Locomotion menuitem. 75 | * New Add Primitive Mesh menuitem. 76 | * Edit Inverse Kinematics menuitem name changed to Set Bone Physic and IKs. 77 | * Removed Remove Old Fakebones menuitem. 78 | * New Generate Materials menuitem. 79 | * Export to Game changed to Export to CryEngine. 80 | * Export to Game icon is changed. 81 | 82 | #### New Features: 83 | * New Smoothing System. 84 | * VCloth 2 exporting. 85 | * Custom Normals. 86 | * Bone Axis Orientations. 87 | * X-Axis Skeleton exporting. 88 | * X-Axis Animated Skeleton exporting. 89 | * Player SDK. 90 | * New Physicalize Skeleton tool. 91 | * New Clear Skeleton Physics tool. 92 | * New Generate LODs tool. 93 | * New Generate Materials tool. 94 | * Color exporting for materials. 95 | * New Add Locator Locomotion tool. 96 | * New Add Primitive Mesh tool. 97 | 98 | #### Improvements/Fixes: 99 | * Geometry writing is completely adapted to BMeshes. 100 | * Input prediction for Add Export Node tool. 101 | * Vertex Colors exporting is fixed. 102 | * Calculate normals using with BMesh tessfaces. 103 | * Now smooth normals are calculated according to face areas. 104 | * Bone axis, length and Tool Shelf panel supporting for Add Root Bone tool. 105 | * Tool Shelf panel supporting for Apply Transformaitons tool. 106 | * SelectGameDirectory wrong folder name fixed. 107 | * Default Physic is changed to None. 108 | * Armature writing enhancements. 109 | * Now animation nodes can be stored with skin and chr nodes. 110 | * Geometry name is rearrenged for Library Controllers. 111 | * Apply Modifiers are fixed. 112 | * Apply Modifiers, Export Selected Nodes supporting. 113 | * Custom Icons interface. 114 | * Multiple Cycles Texture support. 115 | * Descriptions for Set Material Physics tool. 116 | * Logs have been improved. 117 | 118 | 119 | ------------------------------------------------------------------------------- 120 | 121 | CryBlend 122 | 123 | ------------------------------------------------------------------------------- 124 | 125 | ## 5.2 126 | #### Compatibility: 127 | * Only compatible with CryEngine 3.5 and up. 128 | * Only compatible with Blender 2.7 and up. 129 | * Supports primary assets: CGF, CGA, CHR, SKIN, ANM, I_CAF. 130 | * Compatible with LumberYard. 131 | 132 | #### UI Changes: 133 | * Added new Animation Node menuitem to create I_CAF and ANM nodes. 134 | * Added new Export Animation menuitem to separate animation and mesh exporting. 135 | 136 | #### New Features: 137 | * Support multiple I_CAF and ANM animation files exporting. 138 | * Animation and character nodes can be located in same project. 139 | 140 | #### Improvements/Fixes: 141 | * CGA and ANM exporting have been fixed. 142 | * Mesh exporting logs have been improved. 143 | 144 | ## 5.1 145 | #### Compatibility: 146 | * Only compatible with CryEngine 3.5 and up. 147 | * Only compatible with Blender 2.7 and up. 148 | * Supports primary assets: CGF, CGA, CHR, SKIN, ANM, I_CAF. 149 | * Compatible with LumberYard. 150 | 151 | #### UI Changes: 152 | * Apply Scale Animation. 153 | * Edit Inverse Kinematics panel in Bone Utilities. 154 | * Do materials and fix weight default were set to false in export menu. 155 | * Add Property is changed with User Defined Properties menu. 156 | * Same options in quick menu, left tab panel and main menu. 157 | 158 | #### New Features: 159 | * Skeleton Physic. 160 | * New Inverse Kinematics Bone Panel in Bone Utilities. 161 | * Cycles render material support. 162 | * Completely new interactive User Defined Property system. 163 | * Added apply rotation and scale for skeleton that have animation without break bone. 164 | * Delete .animsettings, .caf, .$animsettings files if there are exist for clean reexport animation. 165 | * Set main material name which show in cryengine. 166 | * Change selected material physic in Do Material panel. 167 | * More material notation formats supported. 168 | * New material management options. 169 | * Proxies improvement. 170 | * Export only selected nodes. 171 | * Game folder selection to better management of textures. 172 | * Remove unused vertex groups option. 173 | 174 | #### Improvements/Fixes: 175 | * Texture export fix. 176 | * Skeleton Animation. 177 | * Blender 2.74 and up Skeleton and Animation support. 178 | * Now bone names replace double underscore with whitespace like maya. 179 | * CGF file is processed one time by rc. 180 | * Proxies problems. 181 | * Faster skin export. 182 | * More than 8 bones per vertex fix. 183 | * Residual vertex groups in skins generating problems. 184 | * Problem creating few branches in touch bending. 185 | 186 | ## 5.0 187 | #### Compatibility: 188 | * Only compatible with CryEngine 3.5 and up. 189 | * Only compatible with Blender 2.7 and up. 190 | * Supports primary assets: CGF, CGA, CHR, SKIN, ANM, I_CAF. 191 | 192 | #### UI Changes: 193 | * Added new CryBlend tab using Blender 2.7 tabs UI. 194 | * Added new hotkey activated dropdown menu (SHIFT Q). 195 | * Added properties expandable menu. 196 | * Material physics UI added to Material Properties Tab. 197 | * Fakebones removed from UI. 198 | * AnimNodes removed. 199 | 200 | #### New Features: 201 | * Can now create CGF nodes from object names. 202 | * Added apply transforms tool. 203 | * Added automatic material naming tool. 204 | * Added basic proxy tool. 205 | * Added touch bending tools for adding and naming branches and branch joints. 206 | * Added root bone tool for quickly adding a root bone at the origin to any skeleton. 207 | * New generate scripts tool for generating base CHRPARAMS, CDF, ENT, and LUA files. 208 | * CryExportNodes now have a type which determines the type of node instead of on export. 209 | * Added DAE/RCDONE file toggle, apply modifiers, script generation, and fix weights options to export UI. 210 | 211 | #### Improvements/Fixes: 212 | * Export time significantly reduced. 213 | * Removed naming restrictions. 214 | * Repaired CHR pipeline. 215 | * Vertex alpha repaired? 216 | 217 | #### Code features: 218 | * Thorough code clean up, renamed variables, extracted methods, etc. 219 | * Heavily reworked export.py. 220 | * Sped up export time significantly by revising export of normals (specifically smooth shading). 221 | * Reduced what is printed to the console to reduce export time. 222 | * Reworked major functions, reworked animation export, and moved fakebones from UI to code. 223 | * Deleted obsolete file hbanim.py and increased usage of utils.py. 224 | * ExportNodes now use file extension of asset type instead of CryExportNode_ prefix. 225 | * Animations draw names from ExportNode of animation and frame range from the scene. 226 | * DAE files are now run once through RC and then CGF, CGA, CHR, and SKIN's are run through a second time (two passes). 227 | 228 | ## 4.13 229 | #### Features: 230 | * Improved find weightless vertices utility. 231 | * Smart UV projection used in place of default UV unwrapping for exports missing a UV map. 232 | * Added support for green channel inversion when converting normal maps to DDS. 233 | * Removed find underweight/overweight utilities and replaced them with an automatic weights correction tool. 234 | * Added/Revised tooltips for all operators. 235 | * Added material ID padding to fix assignment problems in CryEngine. 236 | * Only compatible with Blender v2.70 and up. 237 | 238 | #### Code features: 239 | * Thorough code clean up, renamed variables, extracted methods etc. 240 | 241 | ## 4.12.2 242 | #### Fixes: 243 | * Fixed bug #39 (Adding UVs to wrong object). 244 | 245 | ## 4.12.1 246 | * Just version number fix. 247 | 248 | ## 4.12 249 | #### Features: 250 | * Added support for rc 3.4.5 to handle textures conversion to DDS while using rc from 3.5.4. 251 | * Added error message if many texture slots have same texture type. 252 | * Added error message if texture slot has no texture. 253 | 254 | #### Code features: 255 | * Merged functions extract_aniXX. 256 | * Fixed usage of cbPrint(). 257 | * Refactored configuration handler. 258 | * Code clean up, renamed variables, extracted methods etc. 259 | 260 | #### Fixes: 261 | * Layer creation does not crash and maybe generates correct layers. 262 | * Fixed buggy implementation of GUID (UUID) generator. 263 | 264 | ## 4.11.1 265 | #### Fixes: 266 | * Fixed bug in matrix_to_string function (#24). 267 | 268 | ## 4.11.0 269 | #### Features: 270 | * Created new layout for exporting options (#22). 271 | * Improved configuration handling (#20). 272 | * Added converter to DDS (#12). 273 | * Added button to save tiff (DDS exporting) (#18). 274 | * Added "Select textures directory" to CryBlend menu - helps with generating proper texture paths in .mtl files (#16). 275 | * Improved relative paths in .mtl (#16). 276 | * Added option "Profile CryBlend" that helps in tracking performance issues (#16). 277 | * "Find the Resource Compiler" now shows current RC path (#20). 278 | 279 | #### Improvements in code: 280 | * Configuration simplified and moved to separate module (#20). 281 | * Replaced some string concatenation by creating a list of strings (#19). 282 | * Removed useless code (#6). 283 | * Refactored export.py:__export_library_animation_clips_and_animations() to reduce duplicated code. 284 | * And other changes. 285 | 286 | ## 4.9.9 287 | #### Fixes: 288 | * Fixed the previous fixes 289 | * Fixed animation export for .anm 290 | 291 | ## 4.9.8 292 | #### Fixes: 293 | * Fixed incorrect index numbers in the extract_ani functions. 294 | 295 | ## 4.9.7 296 | #### Fixes: 297 | * Fixed the fix of a fix from last time, this is getting stupid. 298 | 299 | ## 4.9.6 300 | #### Fixes: 301 | * Fixed .cga export, again. 302 | 303 | ## 4.9.5 304 | #### Features: 305 | * Refactored some more code 306 | 307 | #### Fixes: 308 | * Fixed that drop-down menu from the last update 309 | 310 | ## 4.9.4 311 | #### Features: 312 | * Added a drop-down menu for file type selection at export time 313 | * Refactored code 314 | 315 | ## 4.9.3 316 | #### Features: 317 | * Added remove bone geometry tool and fixed minor glitch with add bone geometry tool 318 | * Error handling improved. 319 | * It works on wine (Linux/Mac OS). 320 | 321 | #### Fixes: 322 | * Normal maps are now properly exported into mtl file. 323 | * Fixed bug if image path is empty 324 | 325 | ## 4.9.2.1 326 | #### Features: 327 | * Code formatted to match PEP-8. 328 | 329 | ## 4.9.2 330 | #### Fixes: 331 | * Non relative paths to textures in .mtl file. 332 | 333 | ## 4.9.1.1 334 | #### Fixes: 335 | * Spelling issues. 336 | * Trailing white spaces. 337 | 338 | ## 4.9.1 339 | #### Fixes: 340 | * #4 - Few registered class are not unregistered properly. 341 | 342 | ## 4.9 343 | #### Features: 344 | * New tool for locating lines that are connected to too many faces. 345 | * Upgraded the find degenerate faces tool to make it easier to use. 346 | 347 | ## 4.8.3 348 | #### Features: 349 | * The current file directory is now the automatic destination for an export after the first one. 350 | 351 | # Since 4.8.3 CryBlend is hosted on Github 352 | 353 | ## 4.8.2 354 | #### Features 355 | * Removed logging, finally. 356 | 357 | ## 4.8.1 358 | #### Features 359 | * Improved version number handling 360 | * Switched to new better version number system 361 | 362 | #### Fixes: 363 | * Fixed the log sometimes going into the open file directory 364 | 365 | ## 4.8 366 | #### Fixes: 367 | * Fixing the fact that I broke things in 4.7, sorry guys. 368 | 369 | ## 4.7 370 | #### Fixes: 371 | * Another bugfix, this update makes Cryblend compatible with changes relating to context made in the latest Blender development builds. (Courtesy of bitset who figured this out). 372 | 373 | ## 4.6 374 | #### Fixes: 375 | * Just a bug fix release, fixed an issue where the exporter was just recording and longer and longer and longer log file, I had a 100MB log file when I discovered the glitch. The log file will now wipe itself out each time you start Blender ready to receive new information. The log can be found at C:\Program Files\Blender Foundation\Blender\CryBlend Export.log 376 | 377 | ## 4.5 378 | #### Features 379 | * New Find No UVs function for finding objects that lack a UV mesh layout automatically. 380 | * New Remove all FakeBones feature for removing fakebones rapidly in the event you need to change the number of bones in your skeleton. 381 | 382 | ## 4.4 383 | #### Features 384 | * Added extra tools for handling mesh weighting problems. 385 | * Removed unnecessary show console button. 386 | * Changed IK export to be optional via a checkbox at export time. 387 | * Added exporting layers for rapid scene reassembly in the CryEngine 3 (KNOWN ISSUE SEE BELOW). 388 | 389 | #### Fixes: 390 | * Fixed up a few problems causing character export to fail. 391 | 392 | ## 4.3 393 | #### Features 394 | * IK constraints (max, min and damping) are now exported onto the phys skeleton (from the other skeleton). 395 | * Added "Add boneGeometry" button to add boneGeometry objects for the currently selected armatures. 396 | * Added "Rename Phys Bones" button to add "_Phys" to the end of all bone names in armatures whose names contain "_Phys". 397 | 398 | #### Fixes: 399 | * Fixed up the terminal output of CryBlend 400 | * A few miscellaneous bug fixes. 401 | 402 | ## 4.2 403 | #### Features 404 | * Support for physics on exported characters (.chr) 405 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BCRY Exporter 2 | 3 | CryEngine Utilities and Exporter for Blender 4 | 5 | Official Web Site: http://bcry.afcstudio.org
6 | Official documentation: http://bcry.afcstudio.org/documents/ 7 | 8 | --- 9 | 10 | ## Installation 11 | 12 | Copy `io_bcry_exporter` folder to `blender_path\Scripts\Addons` directory. 13 | 14 | Example: D:\Program Files\Steam\SteamApps\common\Blender\2.78\scripts\addons 15 | 16 | --- 17 | 18 | Intallation Document: http://bcry.afcstudio.org/documents/installation/
19 | Configurations Document: http://bcry.afcstudio.org/documents/configurations/ 20 | 21 | --- 22 | 23 | Github: https://github.com/AFCStudio/BCryExporter
24 | Facebook: https://www.facebook.com/afcstudios/
25 | Youtube Channel: https://www.youtube.com/channel/UC94iNgbfEoLEjCWjfaNz0fw 26 | -------------------------------------------------------------------------------- /io_bcry_exporter/configuration.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: configuration.py 3 | # Purpose: Stores CryBlend configuration settings 4 | # 5 | # Author: Mikołaj Milej, 6 | # Angelo J. Miner, Daniel White, Özkan Afacan, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # 9 | # Created: 02/10/2013 10 | # Copyright: (c) Mikołaj Milej 2013 11 | # Licence: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | # 15 | 16 | 17 | import bpy 18 | from io_bcry_exporter.outpipe import bcPrint 19 | from io_bcry_exporter.utils import get_filename 20 | import os 21 | import pickle 22 | 23 | 24 | class __Configuration: 25 | __CONFIG_PATH = bpy.utils.user_resource('CONFIG', 26 | path='scripts', 27 | create=True) 28 | __CONFIG_FILENAME = 'bcry.cfg' 29 | __CONFIG_FILEPATH = os.path.join(__CONFIG_PATH, __CONFIG_FILENAME) 30 | __DEFAULT_CONFIGURATION = {'RC_PATH': r'', 31 | 'TEXTURE_RC_PATH': r'', 32 | 'GAME_DIR': r''} 33 | 34 | def __init__(self): 35 | self.__CONFIG = self.__load({}) 36 | 37 | @property 38 | def rc_path(self): 39 | return self.__CONFIG['RC_PATH'] 40 | 41 | @rc_path.setter 42 | def rc_path(self, value): 43 | self.__CONFIG['RC_PATH'] = value 44 | 45 | @property 46 | def texture_rc_path(self): 47 | if (not self.__CONFIG['TEXTURE_RC_PATH']): 48 | return self.rc_path 49 | 50 | return self.__CONFIG['TEXTURE_RC_PATH'] 51 | 52 | @texture_rc_path.setter 53 | def texture_rc_path(self, value): 54 | self.__CONFIG['TEXTURE_RC_PATH'] = value 55 | 56 | @property 57 | def game_dir(self): 58 | return self.__CONFIG['GAME_DIR'] 59 | 60 | @game_dir.setter 61 | def game_dir(self, value): 62 | self.__CONFIG['GAME_DIR'] = value 63 | 64 | def configured(self): 65 | path = self.__CONFIG['RC_PATH'] 66 | if len(path) > 0 and get_filename(path) == "rc": 67 | return True 68 | 69 | return False 70 | 71 | def save(self): 72 | bcPrint("Saving configuration file.", 'debug') 73 | 74 | if os.path.isdir(self.__CONFIG_PATH): 75 | try: 76 | with open(self.__CONFIG_FILEPATH, 'wb') as f: 77 | pickle.dump(self.__CONFIG, f, -1) 78 | bcPrint("Configuration file saved.") 79 | 80 | bcPrint('Saved {}'.format(self.__CONFIG_FILEPATH)) 81 | 82 | except: 83 | bcPrint( 84 | "[IO] can not write: {}".format( 85 | self.__CONFIG_FILEPATH), 'error') 86 | 87 | else: 88 | bcPrint("Configuration file path is missing {}".format( 89 | self.__CONFIG_PATH), 90 | 'error') 91 | 92 | def __load(self, current_configuration): 93 | new_configuration = {} 94 | new_configuration.update(self.__DEFAULT_CONFIGURATION) 95 | new_configuration.update(current_configuration) 96 | 97 | if os.path.isfile(self.__CONFIG_FILEPATH): 98 | try: 99 | with open(self.__CONFIG_FILEPATH, 'rb') as f: 100 | new_configuration.update(pickle.load(f)) 101 | bcPrint('Configuration file loaded.') 102 | except: 103 | bcPrint("[IO] can not read: {}".format(self.__CONFIG_FILEPATH), 104 | 'error') 105 | 106 | return new_configuration 107 | 108 | 109 | Configuration = __Configuration() 110 | -------------------------------------------------------------------------------- /io_bcry_exporter/desc.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: desc.py 3 | # Purpose: Holds descriptions of UDP and IK properties. 4 | # 5 | # Author: Özkan Afacan 6 | # 7 | # Created: 07/02/2015 8 | # Copyright: (c) Özkan Afacan 2015 9 | # License: GPLv2+ 10 | #------------------------------------------------------------------------------ 11 | 12 | # 13 | 14 | list = {} 15 | 16 | #------------------------------------------------------------------------------ 17 | # Material Physics: 18 | #------------------------------------------------------------------------------ 19 | 20 | list['physDefault'] = """The render geometry is used as physics proxy. This\ 21 | is expensive for complex objects, so use this only for simple objects\ 22 | like cubes or if you really need to fully physicalize an object.""" 23 | 24 | list['physProxyNoDraw'] = """Mesh is used exclusively for collision\ 25 | detection and is not rendered.""" 26 | 27 | list['physNoCollide'] = """Special purpose proxy which is used by the engine\ 28 | to detect player interaction (e.g. for vegetation touch bending).""" 29 | 30 | list['physObstruct'] = """Used for Soft Cover to block AI view\ 31 | (i.e. on dense foliage).""" 32 | 33 | list['physNone'] = """The render geometry have no physic just render it.""" 34 | 35 | #------------------------------------------------------------------------------ 36 | # Inverse Kinematics: 37 | #------------------------------------------------------------------------------ 38 | 39 | list['spring'] = """Stiffness of an angular spring at a joint can be adjusted\ 40 | via the 'Spring Tension' parameter. A value of 1 means acceleration\ 41 | of 1 radian/second2 (1 radian = 57°).""" 42 | 43 | list['damping'] = """The 'dampening' value in the IK Limit options will\ 44 | effect how loose the joint will be in the rag doll simulation of\ 45 | the dead body. Most times you will want the dampening value set at 1,0.""" 46 | 47 | #------------------------------------------------------------------------------ 48 | # Physics Proxy: 49 | #------------------------------------------------------------------------------ 50 | 51 | list['notaprim'] = """Force the engine NOT to convert this proxy to a\ 52 | primitive (for example if the proxy is already naturally box-shaped).""" 53 | 54 | list['no_exp_occlusion'] = """Will allow the force/damage of an explosion to\ 55 | penetrate through the phys proxy.""" 56 | 57 | list['colltpye_player'] = """If a phys proxy node has this string, then:\ 58 | 1 - This node will only receive player collisions, but no hit\ 59 | impacts. 2- If this object contains other phys proxy nodes,\ 60 | then those other nodes will not receive player collisions.""" 61 | 62 | #------------------------------------------------------------------------------ 63 | # Render Mesh: 64 | #------------------------------------------------------------------------------ 65 | 66 | list['is_entity'] = """If the render geometry properties include 'entity', the\ 67 | object will not fade out after being disconnected from the main object.""" 68 | 69 | list['mass'] = """Mass defines the weight of an object based on real world\ 70 | physics in kg. mass=0 sets the object to 'unmovable'.""" 71 | 72 | list['density'] = """The engine automatically calculates the mass for an\ 73 | object based on the density and the bounding box of an object.\ 74 | Can be used alternatively to mass.""" 75 | 76 | list['pieces'] = """Instead of disconnecting the piece when the joint is\ 77 | broken, it will instantly disappear spawning a particle effect\ 78 | depending on the surfacetype of the proxy.""" 79 | 80 | list['is_dynamic'] = """This is a special-case string for dynamically\ 81 | breakable meshes (i.e. glass) – this string flags the object as\ 82 | 'dynamically breakable'. However this string is not required\ 83 | on Glass, Trees, or Cloth, as these are already flagged\ 84 | automatically by the engine (through surface-type system).""" 85 | 86 | list['no_hit_refinement'] = """If the render geometry properties include\ 87 | 'entity', the object will not fade out after being disconnected\ 88 | from the main object.""" 89 | 90 | list['other_rendermesh'] = """(Mostly obsolete now) - This would be required\ 91 | if the phys proxy is a sibling of the rendermesh. Proxies should\ 92 | always be children of the rendermesh however, in which case\ 93 | other_rendermesh is not required.""" 94 | 95 | #------------------------------------------------------------------------------ 96 | # Joint Node: 97 | #------------------------------------------------------------------------------ 98 | 99 | list['limit'] = """Limit is a general value for several different kind of\ 100 | forces applied to the joint. It contains a combination of\ 101 | the values below.""" 102 | 103 | list['bend'] = """Maximum torque around an axis perpendicular to the normal.""" 104 | 105 | list['twist'] = """Maximum torque around the normal.""" 106 | 107 | list['pull'] = """Maximum force applied to the joint's 1st object against\ 108 | the joint normal (the parts are 'pulled together' as a reaction\ 109 | to external forces pulling them apart).""" 110 | 111 | list['push'] = """Maximum force applied to the joint's 1st object along\ 112 | the joint normal; joint normal is the joint's z axis, so for this\ 113 | value to actually be 'push apart', this axis must be directed\ 114 | inside the 1st object.""" 115 | 116 | list['shift'] = """Maximum force in the direction perpendicular to normal.""" 117 | 118 | list['player_can_break'] = """Joints in the entire breakable entity can be\ 119 | broken by the player bumping into them.""" 120 | 121 | list['gameplay_critical'] = """Joints in the entire entity will break, even\ 122 | if jointed breaking is disabled overall.""" 123 | 124 | #------------------------------------------------------------------------------ 125 | # Deformable: 126 | #------------------------------------------------------------------------------ 127 | 128 | list['stiffness'] = """Resilience to bending and shearing (default 10).""" 129 | 130 | list['hardness'] = """Resilience to stretching (default 10).""" 131 | 132 | list['max_stretch'] = """If any edge is stretched more than that, it's length\ 133 | is re-enforced. max_stretch = 0.3 means stretched to 130% of\ 134 | its original length.""" 135 | 136 | list['max_impulse'] = """Upper limit on all applied impulses. Default\ 137 | skeleton's mass*100.""" 138 | 139 | list['skin_dist'] = """Sphere radius in skinning assignment. Default is\ 140 | the minimum of the main mesh's bounding box's dimensions.""" 141 | 142 | list['thickness'] = """Sets the collision thickness for the skeleton.\ 143 | Setting thickness to 0 disables all collisions.""" 144 | 145 | list['explosion_scale'] = """Used to scale down the effect of explosions on\ 146 | the deformable. This lets you have visible deformations from bullet\ 147 | impacts, but without vastly distorting the object too far with explosions.""" 148 | 149 | list['notaprim'] = """A general physics proxy parameter, it keeps the physics\ 150 | mesh from being turned into a primitive (box, cylinder). This is\ 151 | especially important for deformable objects - the skeleton being\ 152 | a primitive will cause a crash!""" 153 | 154 | #------------------------------------------------------------------------------ 155 | # Animation Range Types: 156 | #------------------------------------------------------------------------------ 157 | 158 | list['range_timeline'] = """Animation range is set from Timeline Editor.\ 159 | You may directly change start and end frame from Timeline Editor. This is\ 160 | best choice for single animation per file.""" 161 | 162 | list['range_values'] = """Animation range is stored in custom properties\ 163 | values. You must enter animation range values. You can change start or end 164 | frame from object custom properties. This is ideal for multiple animations\ 165 | in a blender project.""" 166 | 167 | list['range_markers'] = """Animation range is on stored animation markers.\ 168 | Markers can directly be set on Timeline Editor to change frame range.""" 169 | 170 | #------------------------------------------------------------------------------ 171 | # Locator Locomotion: 172 | #------------------------------------------------------------------------------ 173 | 174 | list['locator_length'] = """The Locator Locomotion bone length to represented\ 175 | in 3D view.""" 176 | 177 | list['locator_root'] = """Skeleton Root Bone: The Locator Locomotion bone\ 178 | is going to be linked/parented to that bone.""" 179 | 180 | list['locator_move'] = """Movement Reference Bone: The Locator Locomotion\ 181 | use that bone to copy movements from selected axis.""" 182 | 183 | #------------------------------------------------------------------------------ 184 | # Export: 185 | #------------------------------------------------------------------------------ 186 | 187 | list['merge_all_nodes'] = """Compiles all the geometry from the different\ 188 | nodes into a single node which improves the efficiency. It's supported only\ 189 | for non-skinned geometry. For more information on Merge All Nodes,\ 190 | please refer http://docs.cryengine.com/display/CEMANUAL/Merge+All+Nodes""" 191 | -------------------------------------------------------------------------------- /io_bcry_exporter/exceptions.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: exceptions.py 3 | # Purpose: Holds custom exception classes 4 | # 5 | # Author: Mikołaj Milej, 6 | # Özkan Afacan, Daniel White 7 | # 8 | # Created: 23/06/2013 9 | # Copyright: (c) Mikołaj Milej 2013 10 | # Copyright: (c) Özkan Afacan 2016 11 | # License: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | # 15 | 16 | 17 | class BCryException(RuntimeError): 18 | 19 | def __init__(self, message): 20 | self._message = message 21 | 22 | def __str__(self): 23 | return self.what() 24 | 25 | def what(self): 26 | return self._message 27 | 28 | 29 | class BlendNotSavedException(BCryException): 30 | 31 | def __init__(self): 32 | message = "Blend file has to be saved before exporting." 33 | 34 | BCryException.__init__(self, message) 35 | 36 | 37 | class TextureAndBlendDiskMismatchException(BCryException): 38 | 39 | def __init__(self, blend_path, texture_path): 40 | message = """ 41 | Blend file and all textures have to be placed on the same disk. 42 | It's impossible to create relative paths if they are not. 43 | Blend file: {!r} 44 | Texture file: {!r}""".format(blend_path, texture_path) 45 | 46 | BCryException.__init__(self, message) 47 | 48 | 49 | class NoRcSelectedException(BCryException): 50 | 51 | def __init__(self): 52 | message = """ 53 | Please find Resource Compiler first. 54 | Usually located in 'CryEngine\\Bin32\\rc\\rc.exe' 55 | """ 56 | 57 | BCryException.__init__(self, message) 58 | 59 | 60 | class NoGameDirectorySelected(BCryException): 61 | 62 | def __init__(self): 63 | message = "Please select a Game Directory!" 64 | 65 | BCryException.__init__(self, message) 66 | 67 | 68 | class MarkersNotFound(BCryException): 69 | 70 | def __init__(self): 71 | message = "Start or end marker is less!" 72 | 73 | BCryException.__init__(self, message) 74 | -------------------------------------------------------------------------------- /io_bcry_exporter/export.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: export.py 3 | # Purpose: Main exporter to CryEngine 4 | # 5 | # Author: Özkan Afacan, 6 | # Angelo J. Miner, Mikołaj Milej, Daniel White, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # Some code borrowed from fbx exporter Campbell Barton 9 | # 10 | # Created: 23/01/2012 11 | # Copyright: (c) Angelo J. Miner 2012 12 | # Copyright: (c) Özkan Afacan 2016 13 | # License: GPLv2+ 14 | #------------------------------------------------------------------------------ 15 | 16 | 17 | if "bpy" in locals(): 18 | import imp 19 | imp.reload(utils) 20 | imp.reload(export_materials) 21 | imp.reload(udp) 22 | imp.reload(exceptions) 23 | else: 24 | import bpy 25 | from io_bcry_exporter import utils, export_materials, udp, exceptions 26 | 27 | from io_bcry_exporter.rc import RCInstance 28 | from io_bcry_exporter.outpipe import bcPrint 29 | from io_bcry_exporter.utils import join 30 | 31 | from bpy_extras.io_utils import ExportHelper 32 | from collections import OrderedDict 33 | from datetime import datetime 34 | from mathutils import Matrix, Vector 35 | from time import clock 36 | from xml.dom.minidom import Document, Element, parse, parseString 37 | import bmesh 38 | import copy 39 | import os 40 | import threading 41 | import subprocess 42 | import time 43 | import xml.dom.minidom 44 | 45 | 46 | class CrytekDaeExporter: 47 | 48 | def __init__(self, config): 49 | self._config = config 50 | self._doc = Document() 51 | self._m_exporter = export_materials.CrytekMaterialExporter(config) 52 | 53 | def export(self): 54 | self._prepare_for_export() 55 | 56 | root_element = self._doc.createElement('collada') 57 | root_element.setAttribute( 58 | "xmlns", "http://www.collada.org/2005/11/COLLADASchema") 59 | root_element.setAttribute("version", "1.4.1") 60 | self._doc.appendChild(root_element) 61 | self._create_file_header(root_element) 62 | 63 | if self._config.generate_materials: 64 | self._m_exporter.generate_materials() 65 | 66 | # Just here for future use: 67 | self._export_library_cameras(root_element) 68 | self._export_library_lights(root_element) 69 | ### 70 | 71 | self._export_library_images(root_element) 72 | self._export_library_effects(root_element) 73 | self._export_library_materials(root_element) 74 | self._export_library_geometries(root_element) 75 | 76 | utils.add_fakebones() 77 | try: 78 | self._export_library_controllers(root_element) 79 | self._export_library_animation_clips_and_animations(root_element) 80 | self._export_library_visual_scenes(root_element) 81 | except RuntimeError: 82 | pass 83 | finally: 84 | utils.remove_fakebones() 85 | 86 | self._export_scene(root_element) 87 | 88 | converter = RCInstance(self._config) 89 | converter.convert_dae(self._doc) 90 | 91 | write_scripts(self._config) 92 | 93 | def _prepare_for_export(self): 94 | utils.clean_file(self._config.export_selected_nodes) 95 | 96 | if self._config.fix_weights: 97 | utils.fix_weights() 98 | 99 | def _create_file_header(self, parent_element): 100 | asset = self._doc.createElement('asset') 101 | parent_element.appendChild(asset) 102 | contributor = self._doc.createElement('contributor') 103 | asset.appendChild(contributor) 104 | author = self._doc.createElement('author') 105 | contributor.appendChild(author) 106 | author_name = self._doc.createTextNode('Blender User') 107 | author.appendChild(author_name) 108 | author_tool = self._doc.createElement('authoring_tool') 109 | author_name_text = self._doc.createTextNode( 110 | 'BCry v{}'.format(self._config.bcry_version)) 111 | author_tool.appendChild(author_name_text) 112 | contributor.appendChild(author_tool) 113 | created = self._doc.createElement('created') 114 | created_value = self._doc.createTextNode( 115 | datetime.now().isoformat(' ')) 116 | created.appendChild(created_value) 117 | asset.appendChild(created) 118 | modified = self._doc.createElement('modified') 119 | asset.appendChild(modified) 120 | unit = self._doc.createElement('unit') 121 | unit.setAttribute('name', 'meter') 122 | unit.setAttribute('meter', '1') 123 | asset.appendChild(unit) 124 | up_axis = self._doc.createElement('up_axis') 125 | z_up = self._doc.createTextNode('Z_UP') 126 | up_axis.appendChild(z_up) 127 | asset.appendChild(up_axis) 128 | 129 | #------------------------------------------------------------------ 130 | # Library Cameras: 131 | #------------------------------------------------------------------ 132 | 133 | def _export_library_cameras(self, root_element): 134 | library_cameras = self._doc.createElement('library_cameras') 135 | root_element.appendChild(library_cameras) 136 | 137 | #------------------------------------------------------------------ 138 | # Library Lights: 139 | #------------------------------------------------------------------ 140 | 141 | def _export_library_lights(self, root_element): 142 | library_lights = self._doc.createElement('library_lights') 143 | root_element.appendChild(library_lights) 144 | 145 | #------------------------------------------------------------------ 146 | # Library Images: 147 | #------------------------------------------------------------------ 148 | 149 | def _export_library_images(self, parent_element): 150 | library_images = self._doc.createElement('library_images') 151 | self._m_exporter.export_library_images(library_images) 152 | parent_element.appendChild(library_images) 153 | 154 | #-------------------------------------------------------------- 155 | # Library Effects: 156 | #-------------------------------------------------------------- 157 | 158 | def _export_library_effects(self, parent_element): 159 | library_effects = self._doc.createElement('library_effects') 160 | self._m_exporter.export_library_effects(library_effects) 161 | parent_element.appendChild(library_effects) 162 | 163 | #------------------------------------------------------------------ 164 | # Library Materials: 165 | #------------------------------------------------------------------ 166 | 167 | def _export_library_materials(self, parent_element): 168 | library_materials = self._doc.createElement('library_materials') 169 | self._m_exporter.export_library_materials(library_materials) 170 | parent_element.appendChild(library_materials) 171 | 172 | #------------------------------------------------------------------ 173 | # Library Geometries: 174 | #------------------------------------------------------------------ 175 | 176 | def _export_library_geometries(self, parent_element): 177 | libgeo = self._doc.createElement("library_geometries") 178 | parent_element.appendChild(libgeo) 179 | for group in utils.get_mesh_export_nodes( 180 | self._config.export_selected_nodes): 181 | for object_ in group.objects: 182 | if object_.type != 'MESH': 183 | continue 184 | 185 | apply_modifiers = self._config.apply_modifiers 186 | if utils.get_node_type(group) in ('chr', 'skin'): 187 | apply_modifiers = False 188 | 189 | bmesh_, backup_info = utils.get_bmesh(object_, apply_modifiers) 190 | geometry_node = self._doc.createElement("geometry") 191 | geometry_name = utils.get_geometry_name(group, object_) 192 | geometry_node.setAttribute("id", geometry_name) 193 | mesh_node = self._doc.createElement("mesh") 194 | 195 | print() 196 | bcPrint( 197 | '"{}" object is being processed...'.format( 198 | object_.name)) 199 | 200 | start_time = clock() 201 | self._write_positions(bmesh_, mesh_node, geometry_name) 202 | bcPrint( 203 | 'Positions have been writed {:.4f} seconds.'.format( 204 | clock() - start_time)) 205 | 206 | start_time = clock() 207 | self._write_normals(object_, bmesh_, mesh_node, geometry_name) 208 | bcPrint( 209 | 'Normals have been writed {:.4f} seconds.'.format( 210 | clock() - start_time)) 211 | 212 | start_time = clock() 213 | self._write_uvs(object_, bmesh_, mesh_node, geometry_name) 214 | bcPrint( 215 | 'UVs have been writed {:.4f} seconds.'.format( 216 | clock() - start_time)) 217 | 218 | start_time = clock() 219 | self._write_vertex_colors( 220 | object_, bmesh_, mesh_node, geometry_name) 221 | bcPrint( 222 | 'Vertex colors have been writed {:.4f} seconds.'.format( 223 | clock() - start_time)) 224 | 225 | start_time = clock() 226 | self._write_vertices(mesh_node, geometry_name) 227 | bcPrint( 228 | 'Vertices have been writed {:.4f} seconds.'.format( 229 | clock() - start_time)) 230 | 231 | start_time = clock() 232 | self._write_triangle_list( 233 | object_, bmesh_, mesh_node, geometry_name) 234 | bcPrint( 235 | 'Triangle list have been writed {:.4f} seconds.'.format( 236 | clock() - start_time)) 237 | 238 | extra = self._create_double_sided_extra("MAYA") 239 | mesh_node.appendChild(extra) 240 | geometry_node.appendChild(mesh_node) 241 | libgeo.appendChild(geometry_node) 242 | 243 | utils.clear_bmesh(object_, backup_info) 244 | bcPrint( 245 | '"{}" object has been processed for "{}" node.'.format( 246 | object_.name, group.name)) 247 | 248 | def _write_positions(self, bmesh_, mesh_node, geometry_name): 249 | float_positions = [] 250 | for vertex in bmesh_.verts: 251 | float_positions.extend(vertex.co) 252 | 253 | id_ = "{!s}-pos".format(geometry_name) 254 | source = utils.write_source(id_, "float", float_positions, "XYZ") 255 | mesh_node.appendChild(source) 256 | 257 | def _write_normals(self, object_, bmesh_, mesh_node, geometry_name): 258 | split_angle = 0 259 | use_edge_angle = False 260 | use_edge_sharp = False 261 | 262 | if object_.data.use_auto_smooth: 263 | use_edge_angle = True 264 | use_edge_sharp = True 265 | split_angle = object_.data.auto_smooth_angle 266 | else: 267 | for modifier in object_.modifiers: 268 | if modifier.type == 'EDGE_SPLIT' and modifier.show_viewport: 269 | use_edge_angle = modifier.use_edge_angle 270 | use_edge_sharp = modifier.use_edge_sharp 271 | split_angle = modifier.split_angle 272 | 273 | float_normals = None 274 | if self._config.custom_normals: 275 | float_normals = utils.get_custom_normals(bmesh_, use_edge_angle, 276 | split_angle) 277 | else: 278 | float_normals = utils.get_normal_array(bmesh_, use_edge_angle, 279 | use_edge_sharp, split_angle) 280 | 281 | id_ = "{!s}-normal".format(geometry_name) 282 | source = utils.write_source(id_, "float", float_normals, "XYZ") 283 | mesh_node.appendChild(source) 284 | 285 | def _write_uvs(self, object_, bmesh_, mesh_node, geometry_name): 286 | uv_layer = bmesh_.loops.layers.uv.active 287 | if object_.data.uv_layers.active is None: 288 | bcPrint( 289 | "{} object has no a UV map, creating a default UV...".format( 290 | object_.name)) 291 | uv_layer = bmesh_.loops.layers.uv.new() 292 | 293 | float_uvs = [] 294 | 295 | for face in bmesh_.faces: 296 | for loop in face.loops: 297 | float_uvs.extend(loop[uv_layer].uv) 298 | 299 | id_ = "{!s}-uvs".format(geometry_name) 300 | source = utils.write_source(id_, "float", float_uvs, "ST") 301 | mesh_node.appendChild(source) 302 | 303 | def _write_vertex_colors(self, object_, bmesh_, mesh_node, geometry_name): 304 | float_colors = [] 305 | alpha_found = False 306 | 307 | active_layer = bmesh_.loops.layers.color.active 308 | if object_.data.vertex_colors: 309 | if active_layer.name.lower() == 'alpha': 310 | alpha_found = True 311 | for vert in bmesh_.verts: 312 | loop = vert.link_loops[0] 313 | color = loop[active_layer] 314 | alpha_color = (color[0] + color[1] + color[2]) / 3.0 315 | float_colors.extend([1.0, 1.0, 1.0, alpha_color]) 316 | else: 317 | for vert in bmesh_.verts: 318 | loop = vert.link_loops[0] 319 | float_colors.extend(loop[active_layer]) 320 | 321 | if float_colors: 322 | id_ = "{!s}-vcol".format(geometry_name) 323 | params = ("RGBA" if alpha_found else "RGB") 324 | source = utils.write_source(id_, "float", float_colors, params) 325 | mesh_node.appendChild(source) 326 | 327 | def _write_vertices(self, mesh_node, geometry_name): 328 | vertices = self._doc.createElement("vertices") 329 | vertices.setAttribute("id", "{}-vtx".format(geometry_name)) 330 | input = utils.write_input(geometry_name, None, "pos", "POSITION") 331 | vertices.appendChild(input) 332 | mesh_node.appendChild(vertices) 333 | 334 | def _write_triangle_list(self, object_, bmesh_, mesh_node, geometry_name): 335 | tessfaces = utils.get_tessfaces(bmesh_) 336 | current_material_index = 0 337 | for material, materialname in self._m_exporter.get_materials_for_object( 338 | object_).items(): 339 | triangles = '' 340 | triangle_count = 0 341 | normal_uv_index = 0 342 | for face in bmesh_.faces: 343 | norm_uv_indices = {} 344 | 345 | for index in range(0, len(face.verts)): 346 | norm_uv_indices[ 347 | str(face.verts[index].index)] = normal_uv_index + index 348 | 349 | if face.material_index == current_material_index: 350 | for tessface in tessfaces[face.index]: 351 | triangle_count += 1 352 | for vert in tessface: 353 | normal_uv = norm_uv_indices[str(vert)] 354 | dae_vertex = self._write_vertex_data( 355 | vert, normal_uv, normal_uv, object_.data.vertex_colors) 356 | triangles = join(triangles, dae_vertex) 357 | 358 | normal_uv_index += len(face.verts) 359 | 360 | current_material_index += 1 361 | 362 | if triangle_count == 0: 363 | continue 364 | 365 | triangle_list = self._doc.createElement('triangles') 366 | triangle_list.setAttribute('material', materialname) 367 | triangle_list.setAttribute('count', str(triangle_count)) 368 | 369 | inputs = [] 370 | inputs.append( 371 | utils.write_input( 372 | geometry_name, 373 | 0, 374 | 'vtx', 375 | 'VERTEX')) 376 | inputs.append( 377 | utils.write_input( 378 | geometry_name, 379 | 1, 380 | 'normal', 381 | 'NORMAL')) 382 | inputs.append( 383 | utils.write_input( 384 | geometry_name, 385 | 2, 386 | 'uvs', 387 | 'TEXCOORD')) 388 | if object_.data.vertex_colors: 389 | inputs.append( 390 | utils.write_input( 391 | geometry_name, 392 | 3, 393 | 'vcol', 394 | 'COLOR')) 395 | 396 | for input in inputs: 397 | triangle_list.appendChild(input) 398 | 399 | p = self._doc.createElement('p') 400 | p_text = self._doc.createTextNode(triangles) 401 | p.appendChild(p_text) 402 | 403 | triangle_list.appendChild(p) 404 | mesh_node.appendChild(triangle_list) 405 | 406 | def _write_vertex_data(self, vert, normal, uv, vertex_colors): 407 | if vertex_colors: 408 | return "{:d} {:d} {:d} {:d} ".format( 409 | vert, normal, uv, vert) 410 | else: 411 | return "{:d} {:d} {:d} ".format(vert, normal, uv) 412 | 413 | def _create_double_sided_extra(self, profile): 414 | extra = self._doc.createElement("extra") 415 | technique = self._doc.createElement("technique") 416 | technique.setAttribute("profile", profile) 417 | double_sided = self._doc.createElement("double_sided") 418 | double_sided_value = self._doc.createTextNode("1") 419 | double_sided.appendChild(double_sided_value) 420 | technique.appendChild(double_sided) 421 | extra.appendChild(technique) 422 | 423 | return extra 424 | 425 | # ------------------------------------------------------------------------- 426 | # Library Controllers: --> Skeleton Armature and List of Bone Names 427 | # --> Skin Geometry, Weights, Transform Matrices 428 | # ------------------------------------------------------------------------- 429 | 430 | def _export_library_controllers(self, parent_element): 431 | library_node = self._doc.createElement("library_controllers") 432 | 433 | ALLOWED_NODE_TYPES = ('chr', 'skin') 434 | for group in utils.get_mesh_export_nodes( 435 | self._config.export_selected_nodes): 436 | node_type = utils.get_node_type(group) 437 | if node_type in ALLOWED_NODE_TYPES: 438 | for object_ in group.objects: 439 | if not utils.is_bone_geometry(object_): 440 | armature = utils.get_armature_for_object(object_) 441 | if armature is not None: 442 | self._process_bones(library_node, 443 | group, 444 | object_, 445 | armature) 446 | 447 | parent_element.appendChild(library_node) 448 | 449 | def _process_bones(self, parent_node, group, object_, armature): 450 | id_ = "{!s}_{!s}".format(armature.name, object_.name) 451 | 452 | controller_node = self._doc.createElement("controller") 453 | parent_node.appendChild(controller_node) 454 | controller_node.setAttribute("id", id_) 455 | 456 | skin_node = self._doc.createElement("skin") 457 | skin_node.setAttribute( 458 | "source", "#{!s}".format( 459 | utils.get_geometry_name( 460 | group, object_))) 461 | controller_node.appendChild(skin_node) 462 | 463 | bind_shape_matrix = self._doc.createElement("bind_shape_matrix") 464 | utils.write_matrix(Matrix(), bind_shape_matrix) 465 | skin_node.appendChild(bind_shape_matrix) 466 | 467 | self._process_bone_joints(object_, armature, skin_node, group) 468 | self._process_bone_matrices(object_, armature, skin_node) 469 | self._process_bone_weights(object_, armature, skin_node) 470 | 471 | joints = self._doc.createElement("joints") 472 | input = utils.write_input(id_, None, "joints", "JOINT") 473 | joints.appendChild(input) 474 | input = utils.write_input(id_, None, "matrices", "INV_BIND_MATRIX") 475 | joints.appendChild(input) 476 | skin_node.appendChild(joints) 477 | 478 | def _process_bone_joints(self, object_, armature, skin_node, group): 479 | 480 | bones = utils.get_bones(armature) 481 | id_ = "{!s}_{!s}-joints".format(armature.name, object_.name) 482 | bone_names = [] 483 | for bone in bones: 484 | props_name = self._create_properties_name(bone, group) 485 | bone_name = "{!s}{!s}".format(bone.name, props_name) 486 | bone_names.append(bone_name) 487 | source = utils.write_source(id_, "IDREF", bone_names, []) 488 | skin_node.appendChild(source) 489 | 490 | def _process_bone_matrices(self, object_, armature, skin_node): 491 | 492 | bones = utils.get_bones(armature) 493 | bone_matrices = [] 494 | for bone in armature.pose.bones: 495 | 496 | bone_matrix = utils.transform_bone_matrix(bone) 497 | bone_matrices.extend(utils.matrix_to_array(bone_matrix)) 498 | 499 | id_ = "{!s}_{!s}-matrices".format(armature.name, object_.name) 500 | source = utils.write_source(id_, "float4x4", bone_matrices, []) 501 | skin_node.appendChild(source) 502 | 503 | def _process_bone_weights(self, object_, armature, skin_node): 504 | 505 | bones = utils.get_bones(armature) 506 | group_weights = [] 507 | vw = "" 508 | vertex_groups_lengths = "" 509 | vertex_count = 0 510 | bone_list = {} 511 | 512 | for bone_id, bone in enumerate(bones): 513 | bone_list[bone.name] = bone_id 514 | 515 | for vertex in object_.data.vertices: 516 | vertex_group_count = 0 517 | for group in vertex.groups: 518 | group_name = object_.vertex_groups[group.group].name 519 | if (group.weight == 0 or 520 | group_name not in bone_list): 521 | continue 522 | if vertex_group_count == 8: 523 | bcPrint("Too many bone references in {}:{} vertex group" 524 | .format(object_.name, group_name)) 525 | continue 526 | group_weights.append(group.weight) 527 | vw = "{}{} {} ".format(vw, bone_list[group_name], vertex_count) 528 | vertex_count += 1 529 | vertex_group_count += 1 530 | 531 | vertex_groups_lengths = "{}{} ".format(vertex_groups_lengths, 532 | vertex_group_count) 533 | 534 | id_ = "{!s}_{!s}-weights".format(armature.name, object_.name) 535 | source = utils.write_source(id_, "float", group_weights, []) 536 | skin_node.appendChild(source) 537 | 538 | vertex_weights = self._doc.createElement("vertex_weights") 539 | vertex_weights.setAttribute("count", str(len(object_.data.vertices))) 540 | 541 | id_ = "{!s}_{!s}".format(armature.name, object_.name) 542 | input = utils.write_input(id_, 0, "joints", "JOINT") 543 | vertex_weights.appendChild(input) 544 | input = utils.write_input(id_, 1, "weights", "WEIGHT") 545 | vertex_weights.appendChild(input) 546 | 547 | vcount = self._doc.createElement("vcount") 548 | vcount_text = self._doc.createTextNode(vertex_groups_lengths) 549 | vcount.appendChild(vcount_text) 550 | vertex_weights.appendChild(vcount) 551 | 552 | v = self._doc.createElement("v") 553 | v_text = self._doc.createTextNode(vw) 554 | v.appendChild(v_text) 555 | vertex_weights.appendChild(v) 556 | 557 | skin_node.appendChild(vertex_weights) 558 | 559 | # ----------------------------------------------------------------------------- 560 | # Library Animation and Clips: --> Animations, F-Curves 561 | # ----------------------------------------------------------------------------- 562 | 563 | def _export_library_animation_clips_and_animations(self, parent_element): 564 | libanmcl = self._doc.createElement("library_animation_clips") 565 | libanm = self._doc.createElement("library_animations") 566 | parent_element.appendChild(libanmcl) 567 | parent_element.appendChild(libanm) 568 | 569 | 570 | # --------------------------------------------------------------------- 571 | # Library Visual Scene: --> Skeleton and _Phys bones, Bone 572 | # Transformations, and Instance URL (_boneGeometry) and extras. 573 | # --------------------------------------------------------------------- 574 | 575 | def _export_library_visual_scenes(self, parent_element): 576 | current_element = self._doc.createElement("library_visual_scenes") 577 | visual_scene = self._doc.createElement("visual_scene") 578 | visual_scene.setAttribute("id", "scene") 579 | visual_scene.setAttribute("name", "scene") 580 | current_element.appendChild(visual_scene) 581 | parent_element.appendChild(current_element) 582 | 583 | if utils.get_mesh_export_nodes(self._config.export_selected_nodes): 584 | if utils.are_duplicate_nodes(): 585 | message = "Duplicate Node Names" 586 | bpy.ops.screen.display_error('INVOKE_DEFAULT', message=message) 587 | 588 | for group in utils.get_mesh_export_nodes( 589 | self._config.export_selected_nodes): 590 | self._write_export_node(group, visual_scene) 591 | else: 592 | pass # TODO: Handle No Export Nodes Error 593 | 594 | def _write_export_node(self, group, visual_scene): 595 | if not self._config.export_for_lumberyard: 596 | node_name = "CryExportNode_{}".format(utils.get_node_name(group)) 597 | node = self._doc.createElement("node") 598 | node.setAttribute("id", node_name) 599 | node.setIdAttribute("id") 600 | else: 601 | node_name = "{}".format(utils.get_node_name(group)) 602 | node = self._doc.createElement("node") 603 | node.setAttribute("id", node_name) 604 | node.setAttribute("LumberyardExportNode", "1") 605 | node.setIdAttribute("id") 606 | 607 | root_objects = [] 608 | for object_ in group.objects: 609 | if utils.is_visual_scene_node_writed(object_, group): 610 | root_objects.append(object_) 611 | 612 | node = self._write_visual_scene_node(root_objects, node, group) 613 | 614 | extra = self._create_cryengine_extra(group) 615 | node.appendChild(extra) 616 | visual_scene.appendChild(node) 617 | 618 | def _write_visual_scene_node(self, objects, parent_node, group): 619 | for object_ in objects: 620 | if (object_.type == "MESH" or object_.type == 'EMPTY') \ 621 | and not utils.is_fakebone(object_) \ 622 | and not utils.is_lod_geometry(object_) \ 623 | and not utils.is_there_a_parent_releation(object_, group): 624 | prop_name = object_.name 625 | node_type = utils.get_node_type(group) 626 | if node_type in ('chr', 'skin'): 627 | prop_name = join( 628 | object_.name, self._create_properties_name( 629 | object_, group)) 630 | node = self._doc.createElement("node") 631 | node.setAttribute("id", prop_name) 632 | node.setAttribute("name", prop_name) 633 | node.setIdAttribute("id") 634 | 635 | self._write_transforms(object_, node) 636 | 637 | if not utils.is_dummy(object_): 638 | ALLOWED_NODE_TYPES = ('cgf', 'cga', 'chr', 'skin') 639 | if node_type in ALLOWED_NODE_TYPES: 640 | instance = self._create_instance(group, object_) 641 | if instance is not None: 642 | node.appendChild(instance) 643 | 644 | udp_extra = self._create_user_defined_property(object_) 645 | if udp_extra is not None: 646 | node.appendChild(udp_extra) 647 | 648 | parent_node.appendChild(node) 649 | 650 | if utils.is_has_lod(object_): 651 | sub_node = node 652 | for lod in utils.get_lod_geometries(object_): 653 | sub_node = self._write_lods(lod, sub_node, group) 654 | 655 | if node_type in ('chr', 'skin') and object_.parent \ 656 | and object_.parent.type == "ARMATURE": 657 | armature = object_.parent 658 | self._write_bone_list([utils.get_root_bone( 659 | armature)], object_, parent_node, group) 660 | 661 | armature_physic = utils.get_armature_physic(armature) 662 | if armature_physic: 663 | self._write_bone_list([utils.get_root_bone( 664 | armature_physic)], armature_physic, parent_node, group) 665 | else: 666 | self._write_child_objects(object_, node, group) 667 | 668 | return parent_node 669 | 670 | def _write_child_objects(self, parent_object, parent_node, group): 671 | for child_object in parent_object.children: 672 | if utils.is_lod_geometry(child_object): 673 | continue 674 | if not utils.is_object_in_group(child_object, group): 675 | continue 676 | 677 | prop_name = child_object.name 678 | node_type = utils.get_node_type(group) 679 | node = self._doc.createElement("node") 680 | node.setAttribute("id", prop_name) 681 | node.setAttribute("name", prop_name) 682 | node.setIdAttribute("id") 683 | 684 | self._write_transforms(child_object, node) 685 | 686 | ALLOWED_NODE_TYPES = ('cgf', 'cga', 'chr', 'skin') 687 | if utils.get_node_type(group) in ALLOWED_NODE_TYPES: 688 | instance = self._create_instance(group, child_object) 689 | if instance is not None: 690 | node.appendChild(instance) 691 | 692 | udp_extra = self._create_user_defined_property(child_object) 693 | if udp_extra is not None: 694 | node.appendChild(udp_extra) 695 | 696 | self._write_child_objects(child_object, node, group) 697 | 698 | parent_node.appendChild(node) 699 | 700 | return parent_node 701 | 702 | def _write_lods(self, object_, parent_node, group): 703 | #prop_name = object_.name 704 | prop_name = utils.changed_lod_name(object_.name) 705 | node_type = utils.get_node_type(group) 706 | if node_type in ('chr', 'skin'): 707 | prop_name = join(object_.name, 708 | self._create_properties_name(object_, group)) 709 | node = self._doc.createElement("node") 710 | node.setAttribute("id", prop_name) 711 | node.setAttribute("name", prop_name) 712 | node.setIdAttribute("id") 713 | 714 | self._write_transforms(object_, node) 715 | 716 | ALLOWED_NODE_TYPES = ('cgf', 'cga', 'chr', 'skin') 717 | if utils.get_node_type(group) in ALLOWED_NODE_TYPES: 718 | instance = self._create_instance(group, object_) 719 | if instance is not None: 720 | node.appendChild(instance) 721 | 722 | udp_extra = self._create_user_defined_property(object_) 723 | if udp_extra is not None: 724 | node.appendChild(udp_extra) 725 | 726 | parent_node.appendChild(node) 727 | 728 | return node 729 | 730 | def _write_bone_list(self, bones, object_, parent_node, group): 731 | scene = bpy.context.scene 732 | bone_names = [] 733 | 734 | for bone in bones: 735 | props_name = self._create_properties_name(bone, group) 736 | props_ik = self._create_ik_properties(bone, object_) 737 | bone_name = join(bone.name, props_name, props_ik) 738 | bone_names.append(bone_name) 739 | 740 | node = self._doc.createElement("node") 741 | node.setAttribute("id", bone_name) 742 | node.setAttribute("name", bone_name) 743 | node.setIdAttribute("id") 744 | 745 | fakebone = utils.get_fakebone(bone.name) 746 | if fakebone is not None: 747 | self._write_transforms(fakebone, node) 748 | 749 | bone_geometry = utils.get_bone_geometry(bone) 750 | if bone_geometry is not None: 751 | geo_name = utils.get_geometry_name(group, bone_geometry) 752 | instance = self._create_bone_instance( 753 | bone_geometry, geo_name) 754 | node.appendChild(instance) 755 | 756 | extra = self._create_physic_proxy_for_bone( 757 | object_.parent, bone) 758 | if extra is not None: 759 | node.appendChild(extra) 760 | 761 | elif utils.is_physic_bone(bone): 762 | bone_geometry = utils.get_bone_geometry(bone) 763 | if fakebone is not None: 764 | self._write_transforms(fakebone, node) 765 | 766 | parent_node.appendChild(node) 767 | 768 | if bone.children: 769 | self._write_bone_list(bone.children, object_, node, group) 770 | 771 | def _create_bone_instance(self, bone_geometry, geometry_name): 772 | instance = None 773 | 774 | instance = self._doc.createElement("instance_geometry") 775 | instance.setAttribute("url", "#{}".format(geometry_name)) 776 | bm = self._doc.createElement("bind_material") 777 | tc = self._doc.createElement("technique_common") 778 | 779 | for mat in bone_geometry.material_slots: 780 | im = self._doc.createElement("instance_material") 781 | im.setAttribute("symbol", mat.name) 782 | im.setAttribute("target", "#{}".format(mat.name)) 783 | bvi = self._doc.createElement("bind_vertex_input") 784 | bvi.setAttribute("semantic", "UVMap") 785 | bvi.setAttribute("input_semantic", "TEXCOORD") 786 | bvi.setAttribute("input_set", "0") 787 | im.appendChild(bvi) 788 | tc.appendChild(im) 789 | 790 | bm.appendChild(tc) 791 | instance.appendChild(bm) 792 | 793 | return instance 794 | 795 | def _create_physic_proxy_for_bone(self, object_, bone): 796 | extra = None 797 | try: 798 | bonePhys = object_.pose.bones[bone.name]['phys_proxy'] 799 | bcPrint(bone.name + " physic proxy is " + bonePhys) 800 | 801 | extra = self._doc.createElement("extra") 802 | techcry = self._doc.createElement("technique") 803 | techcry.setAttribute("profile", "CryEngine") 804 | prop2 = self._doc.createElement("properties") 805 | 806 | cryprops = self._doc.createTextNode(bonePhys) 807 | prop2.appendChild(cryprops) 808 | techcry.appendChild(prop2) 809 | extra.appendChild(techcry) 810 | except: 811 | pass 812 | 813 | return extra 814 | 815 | def _write_transforms(self, object_, node): 816 | trans = self._create_translation_node(object_) 817 | rotx, roty, rotz = self._create_rotation_node(object_) 818 | scale = self._create_scale_node(object_) 819 | 820 | node.appendChild(trans) 821 | node.appendChild(rotx) 822 | node.appendChild(roty) 823 | node.appendChild(rotz) 824 | node.appendChild(scale) 825 | 826 | def _create_translation_node(self, object_): 827 | trans = self._doc.createElement("translate") 828 | trans.setAttribute("sid", "translation") 829 | trans_text = self._doc.createTextNode("{:f} {:f} {:f}".format( 830 | * object_.location)) 831 | trans.appendChild(trans_text) 832 | 833 | return trans 834 | 835 | def _create_rotation_node(self, object_): 836 | rotz = self._write_rotation( 837 | "z", "0 0 1 {:f}", object_.rotation_euler[2]) 838 | roty = self._write_rotation( 839 | "y", "0 1 0 {:f}", object_.rotation_euler[1]) 840 | rotx = self._write_rotation( 841 | "x", "1 0 0 {:f}", object_.rotation_euler[0]) 842 | 843 | return rotz, roty, rotx 844 | 845 | def _write_rotation(self, axis, textFormat, rotation): 846 | rot = self._doc.createElement("rotate") 847 | rot.setAttribute("sid", "rotation_{}".format(axis)) 848 | rot_text = self._doc.createTextNode(textFormat.format( 849 | rotation * utils.to_degrees)) 850 | rot.appendChild(rot_text) 851 | 852 | return rot 853 | 854 | def _create_scale_node(self, object_): 855 | scale = self._doc.createElement("scale") 856 | scale.setAttribute("sid", "scale") 857 | scale_text = self._doc.createTextNode( 858 | utils.floats_to_string(object_.scale, " ", "%s")) 859 | scale.appendChild(scale_text) 860 | 861 | return scale 862 | 863 | def _create_instance(self, group, object_): 864 | armature = utils.get_armature_for_object(object_) 865 | node_type = utils.get_node_type(group) 866 | instance = None 867 | if armature and node_type in ('chr', 'skin'): 868 | instance = self._doc.createElement("instance_controller") 869 | # This binds the mesh object to the armature in control of it 870 | instance.setAttribute("url", "#{!s}_{!s}".format( 871 | armature.name, 872 | object_.name)) 873 | elif object_.name[:6] != "_joint" and object_.type == "MESH": 874 | instance = self._doc.createElement("instance_geometry") 875 | instance.setAttribute( 876 | "url", "#{!s}".format( 877 | utils.get_geometry_name( 878 | group, object_))) 879 | 880 | if instance is not None: 881 | bind_material = self._create_bind_material(object_) 882 | instance.appendChild(bind_material) 883 | return instance 884 | 885 | def _create_bind_material(self, object_): 886 | bind_material = self._doc.createElement('bind_material') 887 | technique_common = self._doc.createElement('technique_common') 888 | 889 | for material, materialname in self._m_exporter.get_materials_for_object( 890 | object_).items(): 891 | instance_material = self._doc.createElement( 892 | 'instance_material') 893 | instance_material.setAttribute('symbol', materialname) 894 | instance_material.setAttribute('target', '#{!s}'.format( 895 | materialname)) 896 | 897 | technique_common.appendChild(instance_material) 898 | 899 | bind_material.appendChild(technique_common) 900 | 901 | return bind_material 902 | 903 | def _create_cryengine_extra(self, node): 904 | extra = self._doc.createElement("extra") 905 | technique = self._doc.createElement("technique") 906 | technique.setAttribute("profile", "CryEngine") 907 | properties = self._doc.createElement("properties") 908 | 909 | ALLOWED_NODE_TYPES = ("cgf", "cga", "chr", "skin") 910 | 911 | if utils.is_export_node(node): 912 | node_type = utils.get_node_type(node) 913 | if node_type in ALLOWED_NODE_TYPES: 914 | prop = self._doc.createTextNode( 915 | "fileType={}".format(node_type)) 916 | properties.appendChild(prop) 917 | if not self._config.merge_all_nodes: 918 | prop = self._doc.createTextNode("DoNotMerge") 919 | properties.appendChild(prop) 920 | 921 | prop = self._doc.createTextNode("UseCustomNormals") 922 | properties.appendChild(prop) 923 | 924 | if self._config.vcloth_pre_process and node_type == 'skin': 925 | prop = self._doc.createTextNode("VClothPreProcess") 926 | properties.appendChild(prop) 927 | 928 | prop = self._doc.createTextNode("CustomExportPath=") 929 | properties.appendChild(prop) 930 | else: 931 | if not node.rna_type.id_data.items(): 932 | return 933 | 934 | technique.appendChild(properties) 935 | 936 | extra.appendChild(technique) 937 | extra.appendChild(self._create_xsi_profile(node)) 938 | 939 | return extra 940 | 941 | def _create_xsi_profile(self, node): 942 | technique_xsi = self._doc.createElement("technique") 943 | technique_xsi.setAttribute("profile", "XSI") 944 | 945 | xsi_custom_p_set = self._doc.createElement("XSI_CustomPSet") 946 | xsi_custom_p_set.setAttribute("name", "ExportProperties") 947 | 948 | propagation = self._doc.createElement("propagation") 949 | propagation.appendChild(self._doc.createTextNode("NODE")) 950 | xsi_custom_p_set.appendChild(propagation) 951 | 952 | type_node = self._doc.createElement("type") 953 | type_node.appendChild( 954 | self._doc.createTextNode("CryExportNodeProperties")) 955 | xsi_custom_p_set.appendChild(type_node) 956 | 957 | xsi_parameter = self._doc.createElement("XSI_Parameter") 958 | xsi_parameter.setAttribute("id", "FileType") 959 | xsi_parameter.setAttribute("type", "Integer") 960 | xsi_parameter.setAttribute("value", utils.get_xsi_filetype_value(node)) 961 | xsi_custom_p_set.appendChild(xsi_parameter) 962 | 963 | xsi_parameter = self._doc.createElement("XSI_Parameter") 964 | xsi_parameter.setAttribute("id", "Filename") 965 | xsi_parameter.setAttribute("type", "Text") 966 | xsi_parameter.setAttribute("value", utils.get_node_name(node)) 967 | xsi_custom_p_set.appendChild(xsi_parameter) 968 | 969 | xsi_parameter = self._doc.createElement("XSI_Parameter") 970 | xsi_parameter.setAttribute("id", "Exportable") 971 | xsi_parameter.setAttribute("type", "Boolean") 972 | xsi_parameter.setAttribute("value", "1") 973 | xsi_custom_p_set.appendChild(xsi_parameter) 974 | 975 | xsi_parameter = self._doc.createElement("XSI_Parameter") 976 | xsi_parameter.setAttribute("id", "MergeObjects") 977 | xsi_parameter.setAttribute("type", "Boolean") 978 | xsi_parameter.setAttribute("value", 979 | str(int(self._config.merge_all_nodes))) 980 | xsi_custom_p_set.appendChild(xsi_parameter) 981 | 982 | technique_xsi.appendChild(xsi_custom_p_set) 983 | 984 | return technique_xsi 985 | 986 | def _create_user_defined_property(self, object_): 987 | udp_buffer = "" 988 | for prop in object_.rna_type.id_data.items(): 989 | if prop: 990 | prop_name = prop[0] 991 | if udp.is_user_defined_property(prop_name): 992 | if isinstance(prop[1], str): 993 | udp_buffer += "{!s}\n".format(prop[1]) 994 | else: 995 | udp_buffer += "{!s}={!s}\n".format(prop[0], prop[1]) 996 | 997 | if udp_buffer or utils.is_dummy(object_): 998 | extra = self._doc.createElement("extra") 999 | technique = self._doc.createElement("technique") 1000 | technique.setAttribute("profile", "CryEngine") 1001 | properties = self._doc.createElement("properties") 1002 | buffer = self._doc.createTextNode(udp_buffer) 1003 | properties.appendChild(buffer) 1004 | technique.appendChild(properties) 1005 | if utils.is_dummy(object_): 1006 | helper = self._create_helper_for_dummy(object_) 1007 | technique.appendChild(helper) 1008 | extra.appendChild(technique) 1009 | 1010 | return extra 1011 | else: 1012 | return None 1013 | 1014 | def _create_helper_for_dummy(self, object_): 1015 | x1, y1, z1, x2, y2, z2 = utils.get_bounding_box(object_) 1016 | 1017 | min = self._doc.createElement("bound_box_min") 1018 | min_text = self._doc.createTextNode( 1019 | "{:f} {:f} {:f}".format(x1, y1, z1)) 1020 | min.appendChild(min_text) 1021 | 1022 | max = self._doc.createElement("bound_box_max") 1023 | max_text = self._doc.createTextNode( 1024 | "{:f} {:f} {:f}".format(x2, y2, z2)) 1025 | max.appendChild(max_text) 1026 | 1027 | helper = self._doc.createElement("helper") 1028 | helper.setAttribute("type", "dummy") 1029 | helper.appendChild(min) 1030 | helper.appendChild(max) 1031 | 1032 | return helper 1033 | 1034 | def _create_properties_name(self, bone, group): 1035 | bone_name = bone.name.replace("__", "*") 1036 | node_name = utils.get_node_name(group) 1037 | props_name = '%{!s}%--PRprops_name={!s}'.format(node_name, bone_name) 1038 | 1039 | return props_name 1040 | 1041 | def _create_ik_properties(self, bone, object_): 1042 | props = "" 1043 | if utils.is_physic_bone(bone): 1044 | 1045 | armature_object = bpy.data.objects[object_.name[:-5]] 1046 | pose_bone = armature_object.pose.bones[bone.name[:-5]] 1047 | 1048 | xIK, yIK, zIK = udp.get_bone_ik_max_min(pose_bone) 1049 | 1050 | damping, spring, spring_tension = udp.get_bone_ik_properties( 1051 | pose_bone) 1052 | 1053 | props = join( 1054 | xIK, 1055 | '_xdamping={}'.format(damping[1]), 1056 | '_xspringangle={}'.format(spring[1]), 1057 | '_xspringtension={}'.format(spring_tension[1]), 1058 | 1059 | yIK, 1060 | '_ydamping={}'.format(damping[0]), 1061 | '_yspringangle={}'.format(spring[0]), 1062 | '_yspringtension={}'.format(spring_tension[0]), 1063 | 1064 | zIK, 1065 | '_zdamping={}'.format(damping[2]), 1066 | '_zspringangle={}'.format(spring[2]), 1067 | '_zspringtension={}'.format(spring_tension[2]) 1068 | ) 1069 | 1070 | return props 1071 | 1072 | def _export_scene(self, parent_element): 1073 | scene = self._doc.createElement("scene") 1074 | instance_visual_scene = self._doc.createElement( 1075 | "instance_visual_scene") 1076 | instance_visual_scene.setAttribute("url", "#scene") 1077 | scene.appendChild(instance_visual_scene) 1078 | parent_element.appendChild(scene) 1079 | 1080 | 1081 | def write_scripts(config): 1082 | filepath = bpy.path.ensure_ext(config.filepath, ".dae") 1083 | if not config.make_chrparams and not config.make_cdf: 1084 | return 1085 | 1086 | dae_path = utils.get_absolute_path_for_rc(filepath) 1087 | output_path = os.path.dirname(dae_path) 1088 | 1089 | for chr_name in utils.get_chr_names(self._config.export_selected_nodes): 1090 | if config.make_chrparams: 1091 | filepath = "{}/{}.chrparams".format(output_path, chr_name) 1092 | contents = utils.generate_file_contents("chrparams") 1093 | utils.generate_xml(filepath, contents) 1094 | if config.make_cdf: 1095 | filepath = "{}/{}.cdf".format(output_path, chr_name) 1096 | contents = utils.generate_file_contents("cdf") 1097 | utils.generate_xml(filepath, contents) 1098 | 1099 | 1100 | def save(config): 1101 | # prevent wasting time for exporting if RC was not found 1102 | if not config.disable_rc and not os.path.isfile(config.rc_path): 1103 | raise exceptions.NoRcSelectedException 1104 | 1105 | exporter = CrytekDaeExporter(config) 1106 | exporter.export() 1107 | 1108 | 1109 | def register(): 1110 | bpy.utils.register_class(CrytekDaeExporter) 1111 | 1112 | bpy.utils.register_class(TriangulateMeError) 1113 | bpy.utils.register_class(Error) 1114 | 1115 | 1116 | def unregister(): 1117 | bpy.utils.unregister_class(CrytekDaeExporter) 1118 | bpy.utils.unregister_class(TriangulateMeError) 1119 | bpy.utils.unregister_class(Error) 1120 | 1121 | 1122 | if __name__ == "__main__": 1123 | register() 1124 | 1125 | # test call 1126 | bpy.ops.export_mesh.crytekdae('INVOKE_DEFAULT') 1127 | -------------------------------------------------------------------------------- /io_bcry_exporter/export_animations.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: export_animations.py 3 | # Purpose: Animation exporter to CryEngine 4 | # 5 | # Author: Özkan Afacan, 6 | # Angelo J. Miner, Mikołaj Milej, Daniel White, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # 9 | # Created: 13/06/2016 10 | # Copyright: (c) Özkan Afacan 2016 11 | # License: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | 15 | if "bpy" in locals(): 16 | import imp 17 | imp.reload(utils) 18 | imp.reload(exceptions) 19 | else: 20 | import bpy 21 | from io_bcry_exporter import export, utils, exceptions 22 | 23 | from io_bcry_exporter.rc import RCInstance 24 | from io_bcry_exporter.outpipe import bcPrint 25 | 26 | from xml.dom.minidom import Document, Element, parse, parseString 27 | import xml.dom.minidom 28 | import os 29 | 30 | 31 | AXES = { 32 | 'X': 0, 33 | 'Y': 1, 34 | 'Z': 2, 35 | } 36 | 37 | 38 | class CrytekDaeAnimationExporter(export.CrytekDaeExporter): 39 | 40 | def __init__(self, config): 41 | self._config = config 42 | self._doc = Document() 43 | 44 | def export(self): 45 | self._prepare_for_export() 46 | 47 | root_element = self._doc.createElement('collada') 48 | root_element.setAttribute( 49 | "xmlns", "http://www.collada.org/2005/11/COLLADASchema") 50 | root_element.setAttribute("version", "1.4.1") 51 | self._doc.appendChild(root_element) 52 | self._create_file_header(root_element) 53 | 54 | libanmcl = self._doc.createElement("library_animation_clips") 55 | libanm = self._doc.createElement("library_animations") 56 | root_element.appendChild(libanmcl) 57 | root_element.appendChild(libanm) 58 | 59 | lib_visual_scene = self._doc.createElement("library_visual_scenes") 60 | visual_scene = self._doc.createElement("visual_scene") 61 | visual_scene.setAttribute("id", "scene") 62 | visual_scene.setAttribute("name", "scene") 63 | lib_visual_scene.appendChild(visual_scene) 64 | root_element.appendChild(lib_visual_scene) 65 | 66 | initial_frame_active = bpy.context.scene.frame_current 67 | initial_frame_start = bpy.context.scene.frame_start 68 | initial_frame_end = bpy.context.scene.frame_end 69 | 70 | ALLOWED_NODE_TYPES = ("i_caf", "anm") 71 | for group in utils.get_animation_export_nodes(): 72 | 73 | node_type = utils.get_node_type(group) 74 | node_name = utils.get_node_name(group) 75 | 76 | if node_type in ALLOWED_NODE_TYPES: 77 | object_ = None 78 | layers = None 79 | 80 | if node_type == 'i_caf': 81 | object_ = utils.get_armature_from_node(group) 82 | layers = utils.activate_all_bone_layers(object_) 83 | elif node_type == 'anm': 84 | object_ = group.objects[0] 85 | 86 | frame_start, frame_end = utils.get_animation_node_range( 87 | object_, node_name, initial_frame_start, initial_frame_end) 88 | bpy.context.scene.frame_start = frame_start 89 | bpy.context.scene.frame_end = frame_end 90 | 91 | print('') 92 | bcPrint(group.name) 93 | bcPrint("Animation is being preparing to process.") 94 | bcPrint("Animation frame range are [{} - {}]".format( 95 | frame_start, frame_end)) 96 | 97 | if node_type == 'i_caf': 98 | utils.add_fakebones(group) 99 | try: 100 | self._export_library_animation_clips_and_animations( 101 | libanmcl, libanm, group) 102 | self._export_library_visual_scenes(visual_scene, group) 103 | except RuntimeError: 104 | pass 105 | finally: 106 | if node_type == 'i_caf': 107 | utils.remove_fakebones() 108 | utils.recover_bone_layers(object_, layers) 109 | 110 | bcPrint("Animation has been processed.") 111 | 112 | bpy.context.scene.frame_current = initial_frame_active 113 | bpy.context.scene.frame_start = initial_frame_start 114 | bpy.context.scene.frame_end = initial_frame_end 115 | print('') 116 | 117 | self._export_scene(root_element) 118 | 119 | converter = RCInstance(self._config) 120 | converter.convert_dae(self._doc) 121 | 122 | def _prepare_for_export(self): 123 | utils.clean_file() 124 | 125 | 126 | # ----------------------------------------------------------------------------- 127 | # Library Animations and Clips: --> Animations, F-Curves 128 | # ----------------------------------------------------------------------------- 129 | 130 | def _export_library_animation_clips_and_animations( 131 | self, libanmcl, libanm, group): 132 | 133 | scene = bpy.context.scene 134 | anim_id = utils.get_animation_id(group) 135 | 136 | animation_clip = self._doc.createElement("animation_clip") 137 | animation_clip.setAttribute("id", anim_id) 138 | animation_clip.setAttribute("start", "{:f}".format( 139 | utils.frame_to_time(scene.frame_start))) 140 | animation_clip.setAttribute("end", "{:f}".format( 141 | utils.frame_to_time(scene.frame_end))) 142 | is_animation = False 143 | 144 | for object_ in group.objects: 145 | if (object_.type != 'ARMATURE' and object_.animation_data and 146 | object_.animation_data.action): 147 | 148 | is_animation = True 149 | 150 | props_name = self._create_properties_name(object_, group) 151 | bone_name = "{!s}{!s}".format(object_.name, props_name) 152 | 153 | for axis in iter(AXES): 154 | animation = self._get_animation_location( 155 | object_, bone_name, axis, anim_id) 156 | if animation is not None: 157 | libanm.appendChild(animation) 158 | 159 | for axis in iter(AXES): 160 | animation = self._get_animation_rotation( 161 | object_, bone_name, axis, anim_id) 162 | if animation is not None: 163 | libanm.appendChild(animation) 164 | 165 | self._export_instance_animation_parameters( 166 | object_, animation_clip, anim_id) 167 | 168 | if is_animation: 169 | libanmcl.appendChild(animation_clip) 170 | 171 | def _export_instance_animation_parameters( 172 | self, object_, animation_clip, anim_id): 173 | location_exists = rotation_exists = False 174 | for curve in object_.animation_data.action.fcurves: 175 | for axis in iter(AXES): 176 | if curve.array_index == AXES[axis]: 177 | if curve.data_path == "location": 178 | location_exists = True 179 | if curve.data_path == "rotation_euler": 180 | rotation_exists = True 181 | if location_exists and rotation_exists: 182 | break 183 | 184 | if location_exists: 185 | self._export_instance_parameter( 186 | object_, animation_clip, "location", anim_id) 187 | if rotation_exists: 188 | self._export_instance_parameter( 189 | object_, animation_clip, "rotation_euler", anim_id) 190 | 191 | def _export_instance_parameter( 192 | self, 193 | object_, 194 | animation_clip, 195 | parameter, 196 | anim_id): 197 | for axis in iter(AXES): 198 | inst = self._doc.createElement("instance_animation") 199 | inst.setAttribute( 200 | "url", "#{!s}-{!s}_{!s}_{!s}".format( 201 | anim_id, object_.name, parameter, axis)) 202 | animation_clip.appendChild(inst) 203 | 204 | def _get_animation_location(self, object_, bone_name, axis, anim_id): 205 | attribute_type = "location" 206 | multiplier = 1 207 | target = "{!s}{!s}{!s}".format(bone_name, "/translation.", axis) 208 | 209 | animation_element = self._get_animation_attribute(object_, 210 | axis, 211 | attribute_type, 212 | multiplier, 213 | target, 214 | anim_id) 215 | return animation_element 216 | 217 | def _get_animation_rotation(self, object_, bone_name, axis, anim_id): 218 | attribute_type = "rotation_euler" 219 | multiplier = utils.to_degrees 220 | target = "{!s}{!s}{!s}{!s}".format(bone_name, 221 | "/rotation_", 222 | axis, 223 | ".ANGLE") 224 | 225 | animation_element = self._get_animation_attribute(object_, 226 | axis, 227 | attribute_type, 228 | multiplier, 229 | target, 230 | anim_id) 231 | return animation_element 232 | 233 | def _get_animation_attribute(self, 234 | object_, 235 | axis, 236 | attribute_type, 237 | multiplier, 238 | target, 239 | anim_id): 240 | id_prefix = "{!s}-{!s}_{!s}_{!s}".format(anim_id, object_.name, 241 | attribute_type, axis) 242 | source_prefix = "#{!s}".format(id_prefix) 243 | 244 | for curve in object_.animation_data.action.fcurves: 245 | if (curve.data_path == 246 | attribute_type and curve.array_index == AXES[axis]): 247 | keyframe_points = curve.keyframe_points 248 | sources = { 249 | "input": [], 250 | "output": [], 251 | "interpolation": [], 252 | "intangent": [], 253 | "outangent": [] 254 | } 255 | for keyframe_point in keyframe_points: 256 | khlx = keyframe_point.handle_left[0] 257 | khly = keyframe_point.handle_left[1] 258 | khrx = keyframe_point.handle_right[0] 259 | khry = keyframe_point.handle_right[1] 260 | frame, value = keyframe_point.co 261 | 262 | sources["input"].append(utils.frame_to_time(frame)) 263 | sources["output"].append(value * multiplier) 264 | sources["interpolation"].append( 265 | keyframe_point.interpolation) 266 | sources["intangent"].extend( 267 | [utils.frame_to_time(khlx), khly]) 268 | sources["outangent"].extend( 269 | [utils.frame_to_time(khrx), khry]) 270 | 271 | animation_element = self._doc.createElement("animation") 272 | animation_element.setAttribute("id", id_prefix) 273 | 274 | for type_, data in sources.items(): 275 | anim_node = self._create_animation_node( 276 | type_, data, id_prefix) 277 | animation_element.appendChild(anim_node) 278 | 279 | sampler = self._create_sampler(id_prefix, source_prefix) 280 | channel = self._doc.createElement("channel") 281 | channel.setAttribute( 282 | "source", "{!s}-sampler".format(source_prefix)) 283 | channel.setAttribute("target", target) 284 | 285 | animation_element.appendChild(sampler) 286 | animation_element.appendChild(channel) 287 | 288 | return animation_element 289 | 290 | def _create_animation_node(self, type_, data, id_prefix): 291 | id_ = "{!s}-{!s}".format(id_prefix, type_) 292 | type_map = { 293 | "input": ["float", ["TIME"]], 294 | "output": ["float", ["VALUE"]], 295 | "intangent": ["float", "XY"], 296 | "outangent": ["float", "XY"], 297 | "interpolation": ["name", ["INTERPOLATION"]] 298 | } 299 | 300 | source = utils.write_source( 301 | id_, type_map[type_][0], data, type_map[type_][1]) 302 | 303 | return source 304 | 305 | def _create_sampler(self, id_prefix, source_prefix): 306 | sampler = self._doc.createElement("sampler") 307 | sampler.setAttribute("id", "{!s}-sampler".format(id_prefix)) 308 | 309 | input = self._doc.createElement("input") 310 | input.setAttribute("semantic", "INPUT") 311 | input.setAttribute("source", "{!s}-input".format(source_prefix)) 312 | output = self._doc.createElement("input") 313 | output.setAttribute("semantic", "OUTPUT") 314 | output.setAttribute("source", "{!s}-output".format(source_prefix)) 315 | interpolation = self._doc.createElement("input") 316 | interpolation.setAttribute("semantic", "INTERPOLATION") 317 | interpolation.setAttribute( 318 | "source", "{!s}-interpolation".format(source_prefix)) 319 | intangent = self._doc.createElement("input") 320 | intangent.setAttribute("semantic", "IN_TANGENT") 321 | intangent.setAttribute( 322 | "source", "{!s}-intangent".format(source_prefix)) 323 | outangent = self._doc.createElement("input") 324 | outangent.setAttribute("semantic", "OUT_TANGENT") 325 | outangent.setAttribute( 326 | "source", "{!s}-outangent".format(source_prefix)) 327 | 328 | sampler.appendChild(input) 329 | sampler.appendChild(output) 330 | sampler.appendChild(interpolation) 331 | sampler.appendChild(intangent) 332 | sampler.appendChild(outangent) 333 | 334 | return sampler 335 | 336 | # --------------------------------------------------------------------- 337 | # Library Visual Scene: --> Skeleton and _Phys bones, Bone 338 | # Transformations, and Instance URL (_boneGeometry) and extras. 339 | # --------------------------------------------------------------------- 340 | 341 | def _export_library_visual_scenes(self, visual_scene, group): 342 | if utils.get_animation_export_nodes(): 343 | if utils.are_duplicate_nodes(): 344 | message = "Duplicate Node Names" 345 | bpy.ops.screen.display_error('INVOKE_DEFAULT', message=message) 346 | 347 | self._write_export_node(group, visual_scene) 348 | else: 349 | pass # TODO: Handle No Export Nodes Error 350 | 351 | def _write_export_node(self, group, visual_scene): 352 | if not self._config.export_for_lumberyard: 353 | node_name = "CryExportNode_{}".format(utils.get_node_name(group)) 354 | node = self._doc.createElement("node") 355 | node.setAttribute("id", node_name) 356 | node.setIdAttribute("id") 357 | else: 358 | node_name = "{}".format(utils.get_node_name(group)) 359 | node = self._doc.createElement("node") 360 | node.setAttribute("id", node_name) 361 | node.setAttribute("LumberyardExportNode", "1") 362 | node.setIdAttribute("id") 363 | 364 | bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0)) 365 | self._write_transforms(bpy.context.active_object, node) 366 | bpy.ops.object.delete(use_global=False) 367 | 368 | node = self._write_visual_scene_node(group.objects, node, group) 369 | 370 | extra = self._create_cryengine_extra(group) 371 | node.appendChild(extra) 372 | visual_scene.appendChild(node) 373 | 374 | def _write_visual_scene_node(self, objects, parent_node, group): 375 | node_type = utils.get_node_type(group) 376 | for object_ in objects: 377 | if node_type == 'i_caf' and object_.type == 'ARMATURE': 378 | self._write_bone_list([utils.get_root_bone( 379 | object_)], object_, parent_node, group) 380 | 381 | elif node_type == 'anm' and object_.type == 'MESH': 382 | prop_name = "{}{}".format( 383 | object_.name, self._create_properties_name( 384 | object_, group)) 385 | node = self._doc.createElement("node") 386 | node.setAttribute("id", prop_name) 387 | node.setAttribute("name", prop_name) 388 | node.setIdAttribute("id") 389 | 390 | self._write_transforms(object_, node) 391 | 392 | udp_extra = self._create_user_defined_property(object_) 393 | if udp_extra is not None: 394 | node.appendChild(udp_extra) 395 | 396 | parent_node.appendChild(node) 397 | 398 | return parent_node 399 | 400 | def _create_cryengine_extra(self, node): 401 | extra = self._doc.createElement("extra") 402 | technique = self._doc.createElement("technique") 403 | technique.setAttribute("profile", "CryEngine") 404 | properties = self._doc.createElement("properties") 405 | 406 | node_type = utils.get_node_type(node) 407 | prop = self._doc.createTextNode("fileType={}".format(node_type)) 408 | properties.appendChild(prop) 409 | 410 | prop = self._doc.createTextNode("CustomExportPath=") 411 | properties.appendChild(prop) 412 | 413 | technique.appendChild(properties) 414 | 415 | if (node.name[:6] == "_joint"): 416 | helper = self._create_helper_joint(node) 417 | technique.appendChild(helper) 418 | 419 | extra.appendChild(technique) 420 | extra.appendChild(self._create_xsi_profile(node)) 421 | 422 | return extra 423 | 424 | 425 | # ------------------------------------------------------------------- 426 | 427 | 428 | def save(config): 429 | # prevent wasting time for exporting if RC was not found 430 | if not config.disable_rc and not os.path.isfile(config.rc_path): 431 | raise exceptions.NoRcSelectedException 432 | 433 | exporter = CrytekDaeAnimationExporter(config) 434 | exporter.export() 435 | 436 | 437 | def register(): 438 | bpy.utils.register_class(CrytekDaeAnimationExporter) 439 | 440 | bpy.utils.register_class(TriangulateMeError) 441 | bpy.utils.register_class(Error) 442 | 443 | 444 | def unregister(): 445 | bpy.utils.unregister_class(CrytekDaeAnimationExporter) 446 | bpy.utils.unregister_class(TriangulateMeError) 447 | bpy.utils.unregister_class(Error) 448 | 449 | 450 | if __name__ == "__main__": 451 | register() 452 | 453 | # test call 454 | bpy.ops.export_mesh.crytekdae('INVOKE_DEFAULT') 455 | -------------------------------------------------------------------------------- /io_bcry_exporter/export_materials.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: export_materials.py 3 | # Purpose: Material exporter to CryEngine. 4 | # 5 | # Author: Özkan Afacan, 6 | # Angelo J. Miner, Mikołaj Milej, Daniel White, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # 9 | # Created: 30/09/2016 10 | # Copyright: (c) Özkan Afacan 2016 11 | # License: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | if "bpy" in locals(): 15 | import imp 16 | imp.reload(utils) 17 | imp.reload(material_utils) 18 | else: 19 | import bpy 20 | from io_bcry_exporter import utils, material_utils 21 | 22 | from io_bcry_exporter.outpipe import bcPrint 23 | 24 | from bpy_extras.io_utils import ExportHelper 25 | from collections import OrderedDict 26 | from xml.dom.minidom import Document, Element, parse, parseString 27 | import copy 28 | import os 29 | import threading 30 | import subprocess 31 | import xml.dom.minidom 32 | 33 | 34 | class CrytekMaterialExporter: 35 | 36 | def __init__(self, config): 37 | self._config = config 38 | self._doc = Document() 39 | self._materials = material_utils.get_materials( 40 | config.export_selected_nodes) 41 | 42 | def generate_materials(self): 43 | material_utils.generate_mtl_files(self._config, self._materials) 44 | 45 | def get_materials_for_object(self, object_): 46 | materials = OrderedDict() 47 | for material_name, material in self._materials.items(): 48 | for object_material in object_.data.materials: 49 | if material.name == object_material.name: 50 | materials[material] = material_name 51 | 52 | return materials 53 | 54 | 55 | #------------------------------------------------------------------------------ 56 | # Library Images: 57 | #------------------------------------------------------------------------------ 58 | 59 | def export_library_images(self, library_images): 60 | images = [] 61 | for node in utils.get_export_nodes(): 62 | for material in self._materials: 63 | for image in material_utils.get_textures(material): 64 | if image: 65 | images.append(image) 66 | 67 | self._write_texture_nodes(list(set(images)), library_images) 68 | 69 | def _write_texture_nodes(self, images, library_images): 70 | for image in images: 71 | image_node = self._doc.createElement('image') 72 | image_node.setAttribute("id", image.name) 73 | image_node.setAttribute("name", image.name) 74 | init_form = self._doc.createElement('init_from') 75 | path = material_utils.get_image_path_for_game( 76 | image, self._config.game_dir) 77 | path_node = self._doc.createTextNode(path) 78 | init_form.appendChild(path_node) 79 | image_node.appendChild(init_form) 80 | library_images.appendChild(image_node) 81 | 82 | if self._config.convert_textures: 83 | material_utils.convert_image_to_dds(images, self._config) 84 | 85 | #------------------------------------------------------------------------------ 86 | # Library Effects: 87 | #------------------------------------------------------------------------------ 88 | 89 | def export_library_effects(self, library_effects): 90 | for material_name, material in self._materials.items(): 91 | self._export_library_effects_material( 92 | material, material_name, library_effects) 93 | 94 | def _export_library_effects_material( 95 | self, material, material_name, library_effects): 96 | 97 | images = material_utils.get_textures(material) 98 | 99 | effect_node = self._doc.createElement("effect") 100 | effect_node.setAttribute("id", "{}_fx".format(material_name)) 101 | profile_node = self._doc.createElement("profile_COMMON") 102 | self._write_surface_and_sampler(images, profile_node) 103 | 104 | technique_common = self._doc.createElement("technique") 105 | technique_common.setAttribute("sid", "common") 106 | 107 | self._write_phong_node(material, images, technique_common) 108 | profile_node.appendChild(technique_common) 109 | 110 | extra = self._create_double_sided_extra("GOOGLEEARTH") 111 | profile_node.appendChild(extra) 112 | effect_node.appendChild(profile_node) 113 | 114 | extra = self._create_double_sided_extra("MAX3D") 115 | effect_node.appendChild(extra) 116 | library_effects.appendChild(effect_node) 117 | 118 | def _write_surface_and_sampler(self, images, profile_node): 119 | for image in images: 120 | if image is None: 121 | continue 122 | 123 | surface = self._doc.createElement("newparam") 124 | surface.setAttribute("sid", "{}-surface".format(image.name)) 125 | surface_node = self._doc.createElement("surface") 126 | surface_node.setAttribute("type", "2D") 127 | init_from_node = self._doc.createElement("init_from") 128 | temp_node = self._doc.createTextNode(image.name) 129 | init_from_node.appendChild(temp_node) 130 | surface_node.appendChild(init_from_node) 131 | surface.appendChild(surface_node) 132 | sampler = self._doc.createElement("newparam") 133 | sampler.setAttribute("sid", "{}-sampler".format(image.name)) 134 | sampler_node = self._doc.createElement("sampler2D") 135 | source_node = self._doc.createElement("source") 136 | temp_node = self._doc.createTextNode( 137 | "{}-surface".format(image.name)) 138 | source_node.appendChild(temp_node) 139 | sampler_node.appendChild(source_node) 140 | sampler.appendChild(sampler_node) 141 | 142 | profile_node.appendChild(surface) 143 | profile_node.appendChild(sampler) 144 | 145 | def _write_phong_node(self, material, images, parent_node): 146 | phong = self._doc.createElement("phong") 147 | 148 | emission = self._create_color_node(material, "emission") 149 | ambient = self._create_color_node(material, "ambient") 150 | 151 | if images[0]: 152 | diffuse = self._create_texture_node(images[0].name, "diffuse") 153 | else: 154 | diffuse = self._create_color_node(material, "diffuse") 155 | 156 | if images[1]: 157 | specular = self._create_texture_node(images[1].name, "specular") 158 | else: 159 | specular = self._create_color_node(material, "specular") 160 | 161 | shininess = self._create_attribute_node(material, "shininess") 162 | index_refraction = self._create_attribute_node( 163 | material, "index_refraction") 164 | 165 | phong.appendChild(emission) 166 | phong.appendChild(ambient) 167 | phong.appendChild(diffuse) 168 | phong.appendChild(specular) 169 | phong.appendChild(shininess) 170 | phong.appendChild(index_refraction) 171 | 172 | if images[2]: 173 | normal = self._create_texture_node(images[2].name, "normal") 174 | phong.appendChild(normal) 175 | 176 | parent_node.appendChild(phong) 177 | 178 | def _create_color_node(self, material, type_): 179 | node = self._doc.createElement(type_) 180 | color = self._doc.createElement("color") 181 | color.setAttribute("sid", type_) 182 | col = material_utils.get_material_color(material, type_) 183 | color_text = self._doc.createTextNode(col) 184 | color.appendChild(color_text) 185 | node.appendChild(color) 186 | 187 | return node 188 | 189 | def _create_texture_node(self, image_name, type_): 190 | node = self._doc.createElement(type_) 191 | texture = self._doc.createElement("texture") 192 | texture.setAttribute("texture", "{}-sampler".format(image_name)) 193 | node.appendChild(texture) 194 | 195 | return node 196 | 197 | def _create_attribute_node(self, material, type_): 198 | node = self._doc.createElement(type_) 199 | float = self._doc.createElement("float") 200 | float.setAttribute("sid", type_) 201 | val = material_utils.get_material_attribute(material, type_) 202 | value = self._doc.createTextNode(val) 203 | float.appendChild(value) 204 | node.appendChild(float) 205 | 206 | return node 207 | 208 | def _create_double_sided_extra(self, profile): 209 | extra = self._doc.createElement("extra") 210 | technique = self._doc.createElement("technique") 211 | technique.setAttribute("profile", profile) 212 | double_sided = self._doc.createElement("double_sided") 213 | double_sided_value = self._doc.createTextNode("1") 214 | double_sided.appendChild(double_sided_value) 215 | technique.appendChild(double_sided) 216 | extra.appendChild(technique) 217 | 218 | return extra 219 | 220 | #------------------------------------------------------------------------------ 221 | # Library Materials: 222 | #------------------------------------------------------------------------------ 223 | 224 | def export_library_materials(self, library_materials): 225 | for material_name, material in self._materials.items(): 226 | material_element = self._doc.createElement('material') 227 | material_element.setAttribute('id', material_name) 228 | instance_effect = self._doc.createElement('instance_effect') 229 | instance_effect.setAttribute('url', '#{}_fx'.format(material_name)) 230 | material_element.appendChild(instance_effect) 231 | library_materials.appendChild(material_element) 232 | -------------------------------------------------------------------------------- /io_bcry_exporter/icons/CryEngine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFCStudio/BCRYExporter/0e243d1487e5202d8bc0521aa27b021a5af8a263/io_bcry_exporter/icons/CryEngine.png -------------------------------------------------------------------------------- /io_bcry_exporter/material_utils.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: material_utils.py 3 | # Purpose: Holds material and texture functions. 4 | # 5 | # Author: Özkan Afacan 6 | # Angelo J. Miner, Mikołaj Milej, Daniel White, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # 9 | # Created: 17/09/2016 10 | # Copyright: (c) Özkan Afacan 2016 11 | # License: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | 15 | if "bpy" in locals(): 16 | import imp 17 | imp.reload(utils) 18 | imp.reload(exceptions) 19 | else: 20 | import bpy 21 | from io_bcry_exporter import utils, exceptions 22 | 23 | from io_bcry_exporter.rc import RCInstance 24 | from io_bcry_exporter.outpipe import bcPrint 25 | from collections import OrderedDict 26 | from xml.dom.minidom import Document, Element, parse, parseString 27 | import bpy 28 | import re 29 | import math 30 | import os 31 | import xml.dom.minidom 32 | 33 | 34 | #------------------------------------------------------------------------------ 35 | # Generate Materials: 36 | #------------------------------------------------------------------------------ 37 | 38 | def generate_mtl_files(_config, materials=None): 39 | if materials is None: 40 | materials = get_materials(_config.export_selected_nodes) 41 | 42 | for node in get_material_groups(materials): 43 | _doc = Document() 44 | parent_material = _doc.createElement('Material') 45 | parent_material.setAttribute("MtlFlags", "524544") 46 | parent_material.setAttribute("vertModifType", "0") 47 | sub_material = _doc.createElement('SubMaterials') 48 | parent_material.appendChild(sub_material) 49 | set_public_params(_doc, None, parent_material) 50 | 51 | print() 52 | bcPrint("'{}' material is being processed...".format(node)) 53 | 54 | for material_name, material in materials.items(): 55 | if material_name.split('__')[0] != node: 56 | continue 57 | 58 | print() 59 | write_material_information(material_name) 60 | 61 | material_node = _doc.createElement('Material') 62 | 63 | set_material_attributes(material, material_name, material_node) 64 | add_textures(_doc, material, material_node, _config) 65 | set_public_params(_doc, material, material_node) 66 | 67 | sub_material.appendChild(material_node) 68 | 69 | _doc.appendChild(parent_material) 70 | 71 | filename = "{!s}.mtl".format(node) 72 | filepath = os.path.join(os.path.dirname(_config.filepath), filename) 73 | utils.generate_xml(filepath, _doc, True, 1) 74 | utils.clear_xml_header(filepath) 75 | 76 | print() 77 | bcPrint("'{}' material file has been generated.".format(filename)) 78 | 79 | 80 | def write_material_information(material_name): 81 | parts = material_name.split('__') 82 | bcPrint("Subname: '{}' - Index: '{}' - Physic Type: '{}'".format( 83 | parts[2], parts[1], parts[3])) 84 | 85 | 86 | def get_material_groups(materials): 87 | material_groups = [] 88 | 89 | for material_name, material in materials.items(): 90 | group_name = material_name.split('__')[0] 91 | 92 | if not (group_name in material_groups): 93 | material_groups.append(group_name) 94 | 95 | return material_groups 96 | 97 | 98 | def sort_materials_by_names(unordered_materials): 99 | materials = OrderedDict() 100 | for material_name in sorted(unordered_materials): 101 | materials[material_name] = unordered_materials[material_name] 102 | 103 | return materials 104 | 105 | 106 | def get_materials(just_selected=False): 107 | materials = OrderedDict() 108 | material_counter = {} 109 | 110 | for group in utils.get_mesh_export_nodes(just_selected): 111 | material_counter[group.name] = 0 112 | for object in group.objects: 113 | for i in range(0, len(object.material_slots)): 114 | slot = object.material_slots[i] 115 | material = slot.material 116 | if material is None: 117 | continue 118 | 119 | if material not in materials.values(): 120 | node_name = utils.get_node_name(group) 121 | 122 | material.name = utils.replace_invalid_rc_characters( 123 | material.name) 124 | for image in get_textures(material): 125 | try: 126 | image.name = utils.replace_invalid_rc_characters( 127 | image.name) 128 | except AttributeError: 129 | pass 130 | 131 | node, index, name, physics = get_material_parts( 132 | node_name, slot.material.name) 133 | 134 | # check if material has no position defined 135 | if index == 0: 136 | material_counter[group.name] += 1 137 | index = material_counter[group.name] 138 | 139 | material_name = "{}__{:02d}__{}__{}".format( 140 | node, index, name, physics) 141 | materials[material_name] = material 142 | 143 | return sort_materials_by_names(materials) 144 | 145 | 146 | def set_material_attributes(material, material_name, material_node): 147 | material_node.setAttribute("Name", get_material_name(material_name)) 148 | material_node.setAttribute("MtlFlags", "524416") 149 | 150 | shader = "Illum" 151 | if "physProxyNoDraw" == get_material_physic(material_name): 152 | shader = "Nodraw" 153 | material_node.setAttribute("Shader", shader) 154 | material_node.setAttribute("GenMask", "60400000") 155 | material_node.setAttribute( 156 | "StringGenMask", 157 | "%NORMAL_MAP%SPECULAR_MAP%SUBSURFACE_SCATTERING") 158 | material_node.setAttribute("SurfaceType", "") 159 | material_node.setAttribute("MatTemplate", "") 160 | 161 | material_node.setAttribute( 162 | "Diffuse", color_to_xml_string( 163 | material.diffuse_color)) 164 | material_node.setAttribute( 165 | "Specular", color_to_xml_string( 166 | material.specular_color)) 167 | material_node.setAttribute("Opacity", str(material.alpha)) 168 | material_node.setAttribute("Shininess", str(material.specular_hardness)) 169 | 170 | material_node.setAttribute("vertModifType", "0") 171 | material_node.setAttribute("LayerAct", "1") 172 | 173 | if material.emit: 174 | emit_color = "1,1,1,{}".format(str(int(material.emit * 100))) 175 | material_node.setAttribute("Emittance", emit_color) 176 | 177 | 178 | def set_public_params(_doc, material, material_node): 179 | public_params = _doc.createElement('PublicParams') 180 | public_params.setAttribute("EmittanceMapGamma", "1") 181 | public_params.setAttribute("SSSIndex", "0") 182 | public_params.setAttribute("IndirectColor", "0.25, 0.25, 0.25") 183 | 184 | material_node.appendChild(public_params) 185 | 186 | 187 | def add_textures(_doc, material, material_node, _config): 188 | textures_node = _doc.createElement("Textures") 189 | 190 | diffuse = get_diffuse_texture(material) 191 | specular = get_specular_texture(material) 192 | normal = get_normal_texture(material) 193 | 194 | if diffuse: 195 | texture_node = _doc.createElement('Texture') 196 | texture_node.setAttribute("Map", "Diffuse") 197 | path = get_image_path_for_game(diffuse, _config.game_dir) 198 | texture_node.setAttribute("File", path) 199 | textures_node.appendChild(texture_node) 200 | bcPrint("Diffuse Path: {}.".format(path)) 201 | else: 202 | if "physProxyNoDraw" != get_material_physic(material.name): 203 | texture_node = _doc.createElement('Texture') 204 | texture_node.setAttribute("Map", "Diffuse") 205 | path = "textures/defaults/white.dds" 206 | texture_node.setAttribute("File", path) 207 | textures_node.appendChild(texture_node) 208 | bcPrint("Diffuse Path: {}.".format(path)) 209 | if specular: 210 | texture_node = _doc.createElement('Texture') 211 | texture_node.setAttribute("Map", "Specular") 212 | path = get_image_path_for_game(specular, _config.game_dir) 213 | texture_node.setAttribute("File", path) 214 | textures_node.appendChild(texture_node) 215 | bcPrint("Specular Path: {}.".format(path)) 216 | if normal: 217 | texture_node = _doc.createElement('Texture') 218 | texture_node.setAttribute("Map", "Normal") 219 | path = get_image_path_for_game(normal, _config.game_dir) 220 | texture_node.setAttribute("File", path) 221 | textures_node.appendChild(texture_node) 222 | bcPrint("Normal Path: {}.".format(path)) 223 | 224 | if _config.convert_textures: 225 | convert_image_to_dds([diffuse, specular, normal], _config) 226 | 227 | material_node.appendChild(textures_node) 228 | 229 | 230 | #------------------------------------------------------------------------------ 231 | # Convert DDS: 232 | #------------------------------------------------------------------------------ 233 | 234 | def convert_image_to_dds(images, _config): 235 | converter = RCInstance(_config) 236 | converter.convert_tif(images) 237 | 238 | 239 | #------------------------------------------------------------------------------ 240 | # Collections: 241 | #------------------------------------------------------------------------------ 242 | 243 | def get_textures(material): 244 | images = [] 245 | 246 | images.append(get_diffuse_texture(material)) 247 | images.append(get_specular_texture(material)) 248 | images.append(get_normal_texture(material)) 249 | 250 | return images 251 | 252 | 253 | def get_diffuse_texture(material): 254 | image = None 255 | try: 256 | if bpy.context.scene.render.engine == 'CYCLES': 257 | for node in material.node_tree.nodes: 258 | if node.type == 'TEX_IMAGE': 259 | if node.name == 'Image Texture' or \ 260 | node.name.lower().find('diffuse') != -1: 261 | image = node.image 262 | if is_valid_image(image): 263 | return image 264 | else: 265 | for slot in material.texture_slots: 266 | if slot.texture.type == 'IMAGE': 267 | if slot.use_map_color_diffuse: 268 | image = slot.texture.image 269 | if is_valid_image(image): 270 | return image 271 | except: 272 | pass 273 | 274 | return None 275 | 276 | 277 | def get_specular_texture(material): 278 | image = None 279 | try: 280 | if bpy.context.scene.render.engine == 'CYCLES': 281 | for node in material.node_tree.nodes: 282 | if node.type == 'TEX_IMAGE': 283 | if node.name.lower().find('specular') != -1: 284 | image = node.image 285 | if is_valid_image(image): 286 | return image 287 | else: 288 | for slot in material.texture_slots: 289 | if slot.texture.type == 'IMAGE': 290 | if slot.use_map_color_spec or slot.use_map_specular: 291 | image = slot.texture.image 292 | if is_valid_image(image): 293 | return image 294 | except: 295 | pass 296 | 297 | return None 298 | 299 | 300 | def get_normal_texture(material): 301 | image = None 302 | try: 303 | if bpy.context.scene.render.engine == 'CYCLES': 304 | for node in material.node_tree.nodes: 305 | if node.type == 'TEX_IMAGE': 306 | if node.name.lower().find('normal') != -1: 307 | image = node.image 308 | if is_valid_image(image): 309 | return image 310 | else: 311 | for slot in material.texture_slots: 312 | if slot.texture.type == 'IMAGE': 313 | if slot.use_map_color_normal: 314 | image = slot.texture.image 315 | if is_valid_image(image): 316 | return image 317 | except: 318 | pass 319 | 320 | return None 321 | 322 | 323 | #------------------------------------------------------------------------------ 324 | # Conversions: 325 | #------------------------------------------------------------------------------ 326 | 327 | def color_to_string(color, a): 328 | if type(color) in (float, int): 329 | return "{:f} {:f} {:f} {:f}".format(color, color, color, a) 330 | elif type(color).__name__ == "Color": 331 | return "{:f} {:f} {:f} {:f}".format(color.r, color.g, color.b, a) 332 | 333 | 334 | def color_to_xml_string(color): 335 | if type(color) in (float, int): 336 | return "{:f},{:f},{:f}".format(color, color, color) 337 | elif type(color).__name__ == "Color": 338 | return "{:f},{:f},{:f}".format(color.r, color.g, color.b) 339 | 340 | 341 | #------------------------------------------------------------------------------ 342 | # Materials: 343 | #------------------------------------------------------------------------------ 344 | 345 | def get_material_counter(): 346 | """Returns a dictionary with all CryExportNodes.""" 347 | materialCounter = {} 348 | for group in bpy.data.groups: 349 | if utils.is_export_node(group): 350 | materialCounter[group.name] = 0 351 | return materialCounter 352 | 353 | 354 | def get_material_physics(): 355 | """Returns a dictionary with the physics of all material names.""" 356 | physicsProperties = {} 357 | for material in bpy.data.materials: 358 | properties = extract_bcry_properties(material.name) 359 | if properties: 360 | physicsProperties[properties["Name"]] = properties["Physics"] 361 | return physicsProperties 362 | 363 | 364 | def get_materials_per_group(group): 365 | materials = [] 366 | for _objtmp in bpy.data.groups[group].objects: 367 | for material in _objtmp.data.materials: 368 | if material is not None: 369 | if material.name not in materials: 370 | materials.append(material.name) 371 | return materials 372 | 373 | 374 | def get_material_color(material, type_): 375 | color = 0.0 376 | alpha = 1.0 377 | 378 | if type_ == "emission": 379 | color = material.emit 380 | elif type_ == "ambient": 381 | color = material.ambient 382 | elif type_ == "diffuse": 383 | color = material.diffuse_color 384 | alpha = material.alpha 385 | elif type_ == "specular": 386 | color = material.specular_color 387 | 388 | col = color_to_string(color, alpha) 389 | return col 390 | 391 | 392 | def get_material_attribute(material, type_): 393 | if type_ == "shininess": 394 | float = material.specular_hardness 395 | elif type_ == "index_refraction": 396 | float = material.alpha 397 | 398 | return str(float) 399 | 400 | 401 | def get_material_parts(node, material): 402 | VALID_PHYSICS = ("physDefault", "physProxyNoDraw", "physNoCollide", 403 | "physObstruct", "physNone") 404 | 405 | parts = material.split("__") 406 | count = len(parts) 407 | 408 | group = node 409 | index = 0 410 | name = material 411 | physics = "physNone" 412 | 413 | if count == 1: 414 | # name 415 | index = 0 416 | elif count == 2: 417 | # XXX__name or name__phys 418 | if parts[1] not in VALID_PHYSICS: 419 | # XXX__name 420 | index = int(parts[0]) 421 | name = parts[1] 422 | else: 423 | # name__phys 424 | name = parts[0] 425 | physics = parts[1] 426 | elif count == 3: 427 | # XXX__name__phys 428 | index = int(parts[0]) 429 | name = parts[1] 430 | physics = parts[2] 431 | elif count == 4: 432 | # group__XXX__name__phys 433 | group = parts[0] 434 | index = int(parts[1]) 435 | name = parts[2] 436 | physics = parts[3] 437 | 438 | name = utils.replace_invalid_rc_characters(name) 439 | if physics not in VALID_PHYSICS: 440 | physics = "physNone" 441 | 442 | return group, index, name, physics 443 | 444 | 445 | def extract_bcry_properties(material_name): 446 | """Returns the BCry properties of a material_name as dict or 447 | None if name is invalid. 448 | """ 449 | if is_bcry_material(material_name): 450 | groups = re.findall( 451 | "(.+)__([0-9]+)__(.*)__(phys[A-Za-z0-9]+)", 452 | material_name) 453 | properties = {} 454 | properties["ExportNode"] = groups[0][0] 455 | properties["Number"] = int(groups[0][1]) 456 | properties["Name"] = groups[0][2] 457 | properties["Physics"] = groups[0][3] 458 | return properties 459 | return None 460 | 461 | 462 | def remove_bcry_properties(): 463 | """Removes BCry Exporter properties from all material names.""" 464 | for material in bpy.data.materials: 465 | properties = extract_bcry_properties(material.name) 466 | if properties: 467 | material.name = properties["Name"] 468 | 469 | 470 | def is_bcry_material(material_name): 471 | if re.search(".+__[0-9]+__.*__phys[A-Za-z0-9]+", material_name): 472 | return True 473 | else: 474 | return False 475 | 476 | 477 | def is_bcry_material_with_numbers(material_name): 478 | if re.search("[0-9]+__.*", material_name): 479 | return True 480 | else: 481 | return False 482 | 483 | 484 | def get_material_name(material_name): 485 | try: 486 | return material_name.split('__')[2] 487 | except: 488 | raise exceptions.BCryException( 489 | "Material name is not convenient for BCry!") 490 | 491 | 492 | def get_material_physic(material_name): 493 | index = material_name.find("__phys") 494 | if index != -1: 495 | return material_name[index + 2:] 496 | 497 | return "physNone" 498 | 499 | 500 | def set_material_physic(self, context, phys_name): 501 | if not phys_name.startswith("__"): 502 | phys_name = "__" + phys_name 503 | 504 | me = bpy.context.active_object 505 | if me.active_material: 506 | me.active_material.name = replace_phys_material( 507 | me.active_material.name, phys_name) 508 | 509 | return {'FINISHED'} 510 | 511 | 512 | def replace_phys_material(material_name, phys): 513 | if "__phys" in material_name: 514 | return re.sub(r"__phys.*", phys, material_name) 515 | else: 516 | return "{}{}".format(material_name, phys) 517 | 518 | 519 | #------------------------------------------------------------------------------ 520 | # Textures: 521 | #------------------------------------------------------------------------------ 522 | 523 | def is_valid_image(image): 524 | try: 525 | return image.has_data and image.filepath 526 | except: 527 | return False 528 | 529 | 530 | def get_image_path_for_game(image, game_dir): 531 | if not game_dir or not os.path.isdir(game_dir): 532 | raise exceptions.NoGameDirectorySelected 533 | 534 | image_path = os.path.normpath(bpy.path.abspath(image.filepath)) 535 | image_path = "{}.dds".format(os.path.splitext(image_path)[0]) 536 | image_path = os.path.relpath(image_path, game_dir) 537 | 538 | return image_path 539 | -------------------------------------------------------------------------------- /io_bcry_exporter/outpipe.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: outpipe.py 3 | # Purpose: Pipeline for console output 4 | # 5 | # Author: Angelo J. Miner, 6 | # Mikołaj Milej, Özkan Afacan, Daniel White, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # 9 | # Created: N/A 10 | # Copyright: (c) N/A 11 | # Licence: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | # 15 | 16 | 17 | from io_bcry_exporter import exceptions 18 | from logging import basicConfig, info, debug, warning, DEBUG 19 | 20 | 21 | class OutPipe(): 22 | 23 | def __init__(self): 24 | pass 25 | 26 | def pump(self, message, message_type='info', newline=False): 27 | if newline: 28 | print() 29 | 30 | if message_type == 'info': 31 | print("[Info] BCry: {!r}".format(message)) 32 | 33 | elif message_type == 'debug': 34 | print("[Debug] BCry: {!r}".format(message)) 35 | 36 | elif message_type == 'warning': 37 | print("[Warning] BCry: {!r}".format(message)) 38 | 39 | elif message_type == 'error': 40 | print("[Error] BCry: {!r}".format(message)) 41 | 42 | else: 43 | raise exceptions.BCryException("No such message type {!r}". 44 | format(message_type)) 45 | 46 | 47 | op = OutPipe() 48 | 49 | 50 | def bcPrint(msg, message_type='info', newline=False): 51 | op.pump(msg, message_type, newline) 52 | -------------------------------------------------------------------------------- /io_bcry_exporter/rc.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: rc.py 3 | # Purpose: Resource compiler transactions 4 | # 5 | # Author: Daniel White, 6 | # Angelo J. Miner, Mikołaj Milej, Özkan Afacan, 7 | # Oscar Martin Garcia, Duo Oratar, David Marcelis 8 | # 9 | # Created: 2/12/2016 10 | # Copyright: (c) Daniel White 2016 11 | # Licence: GPLv2+ 12 | #------------------------------------------------------------------------------ 13 | 14 | # 15 | 16 | 17 | if "bpy" in locals(): 18 | import imp 19 | imp.reload(utils) 20 | else: 21 | import bpy 22 | from io_bcry_exporter import utils 23 | 24 | from io_bcry_exporter.outpipe import bcPrint 25 | import fnmatch 26 | import os 27 | import shutil 28 | import subprocess 29 | import threading 30 | import tempfile 31 | 32 | 33 | class RCInstance: 34 | 35 | def __init__(self, config): 36 | self.__config = config 37 | 38 | def convert_tif(self, source): 39 | converter = _TIFConverter(self.__config, source) 40 | conversion_thread = threading.Thread(target=converter) 41 | conversion_thread.start() 42 | 43 | def convert_dae(self, source): 44 | converter = _DAEConverter(self.__config, source) 45 | conversion_thread = threading.Thread(target=converter) 46 | conversion_thread.start() 47 | 48 | 49 | class _DAEConverter: 50 | 51 | def __init__(self, config, source): 52 | self.__config = config 53 | self.__doc = source 54 | 55 | def __call__(self): 56 | filepath = bpy.path.ensure_ext(self.__config.filepath, ".dae") 57 | utils.generate_xml(filepath, self.__doc, overwrite=True) 58 | 59 | dae_path = utils.get_absolute_path_for_rc(filepath) 60 | 61 | if not self.__config.disable_rc: 62 | rc_params = ["/verbose", "/threads=processors", "/refresh"] 63 | if self.__config.vcloth_pre_process: 64 | rc_params.append("/wait=0 /forceVCloth") 65 | 66 | rc_process = run_rc(self.__config.rc_path, dae_path, rc_params) 67 | 68 | if rc_process is not None: 69 | rc_process.wait() 70 | 71 | if not self.__config.is_animation_process: 72 | self.__recompile(dae_path) 73 | else: 74 | self.__rename_anm_files(dae_path) 75 | 76 | if self.__config.make_layer: 77 | lyr_contents = self.__make_layer() 78 | lyr_path = os.path.splitext(filepath)[0] + ".lyr" 79 | utils.generate_file(lyr_path, lyr_contents) 80 | 81 | if not self.__config.save_dae: 82 | rcdone_path = "{}.rcdone".format(dae_path) 83 | utils.remove_file(dae_path) 84 | utils.remove_file(rcdone_path) 85 | 86 | def __recompile(self, dae_path): 87 | name = os.path.basename(dae_path) 88 | output_path = os.path.dirname(dae_path) 89 | ALLOWED_NODE_TYPES = ("chr", "skin") 90 | for group in utils.get_export_nodes(): 91 | node_type = utils.get_node_type(group) 92 | if node_type in ALLOWED_NODE_TYPES: 93 | out_file = os.path.join(output_path, group.name) 94 | args = [ 95 | self.__config.rc_path, 96 | "/refresh", 97 | "/vertexindexformat=u16", 98 | out_file] 99 | rc_second_pass = subprocess.Popen(args) 100 | elif node_type == 'i_caf': 101 | try: 102 | os.remove(os.path.join(output_path, ".animsettings")) 103 | os.remove(os.path.join(output_path, ".caf")) 104 | os.remove(os.path.join(output_path, ".$animsettings")) 105 | except: 106 | pass 107 | 108 | def __rename_anm_files(self, dae_path): 109 | output_path = os.path.dirname(dae_path) 110 | 111 | for group in utils.get_export_nodes(): 112 | if utils.get_node_type(group) == 'anm': 113 | node_name = utils.get_node_name(group) 114 | src_name = "{}_{}".format(node_name, group.name) 115 | src_name = os.path.join(output_path, src_name) 116 | 117 | if os.path.exists(src_name): 118 | dest_name = utils.get_geometry_animation_file_name(group) 119 | dest_name = os.path.join(output_path, dest_name) 120 | 121 | if os.path.exists(dest_name): 122 | os.remove(dest_name) 123 | 124 | os.rename(src_name, dest_name) 125 | 126 | def __get_mtl_files_in_directory(self, directory): 127 | MTL_MATCH_STRING = "*.{!s}".format("mtl") 128 | 129 | mtl_files = [] 130 | for file in os.listdir(directory): 131 | if fnmatch.fnmatch(file, MTL_MATCH_STRING): 132 | filepath = "{!s}/{!s}".format(directory, file) 133 | mtl_files.append(filepath) 134 | 135 | return mtl_files 136 | 137 | def __make_layer(self): 138 | layer_doc = Document() 139 | object_layer = layer_doc.createElement("ObjectLayer") 140 | layer_name = "ExportedLayer" 141 | layer = createAttributes( 142 | 'Layer', 143 | {'name': layer_name, 144 | 'GUID': utils.get_guid(), 145 | 'FullName': layer_name, 146 | 'External': '0', 147 | 'Exportable': '1', 148 | 'ExportLayerPak': '1', 149 | 'DefaultLoaded': '0', 150 | 'HavePhysics': '1', 151 | 'Expanded': '0', 152 | 'IsDefaultColor': '1' 153 | } 154 | ) 155 | 156 | layer_objects = layer_doc.createElement("LayerObjects") 157 | for group in utils.get_export_nodes(): 158 | if len(group.objects) > 1: 159 | origin = 0, 0, 0 160 | rotation = 1, 0, 0, 0 161 | else: 162 | origin = group.objects[0].location 163 | rotation = group.objects[0].delta_rotation_quaternion 164 | 165 | object = createAttributes( 166 | 'Object', 167 | {'name': group.name[14:], 168 | 'Type': 'Entity', 169 | 'Id': utils.get_guid(), 170 | 'LayerGUID': layer.getAttribute('GUID'), 171 | 'Layer': layer_name, 172 | 'Pos': "{}, {}, {}".format(origin[:]), 173 | 'Rotate': "{}, {}, {}, {}".format(rotation[:]), 174 | 'EntityClass': 'BasicEntity', 175 | 'FloorNumber': '-1', 176 | 'RenderNearest': '0', 177 | 'NoStaticDecals': '0', 178 | 'CreatedThroughPool': '0', 179 | 'MatLayersMask': '0', 180 | 'OutdoorOnly': '0', 181 | 'CastShadow': '1', 182 | 'MotionBlurMultiplier': '1', 183 | 'LodRatio': '100', 184 | 'ViewDistRatio': '100', 185 | 'HiddenInGame': '0', 186 | } 187 | ) 188 | properties = createAttributes( 189 | 'Properties', 190 | {'object_Model': '/Objects/{}.cgf'.format(group.name[14:]), 191 | 'bCanTriggerAreas': '0', 192 | 'bExcludeCover': '0', 193 | 'DmgFactorWhenCollidingAI': '1', 194 | 'esFaction': '', 195 | 'bHeavyObject': '0', 196 | 'bInteractLargeObject': '0', 197 | 'bMissionCritical': '0', 198 | 'bPickable': '0', 199 | 'soclasses_SmartObjectClass': '', 200 | 'bUsable': '0', 201 | 'UseMessage': '0', 202 | } 203 | ) 204 | health = createAttributes( 205 | 'Health', 206 | {'bInvulnerable': '1', 207 | 'MaxHealth': '500', 208 | 'bOnlyEnemyFire': '1', 209 | } 210 | ) 211 | interest = createAttributes( 212 | 'Interest', 213 | {'soaction_Action': '', 214 | 'bInteresting': '0', 215 | 'InterestLevel': '1', 216 | 'Pause': '15', 217 | 'Radius': '20', 218 | 'bShared': '0', 219 | } 220 | ) 221 | vOffset = createAttributes( 222 | 'vOffset', 223 | {'x': '0', 224 | 'y': '0', 225 | 'z': '0', 226 | } 227 | ) 228 | 229 | interest.appendChild(vOffset) 230 | properties.appendChild(health) 231 | properties.appendChild(interest) 232 | object.appendChild(properties) 233 | layer_objects.appendChild(object) 234 | 235 | layer.appendChild(layer_objects) 236 | object_layer.appendChild(layer) 237 | layer_doc.appendChild(object_layer) 238 | 239 | return layer_doc.toprettyxml(indent=" ") 240 | 241 | def __createAttributes(self, node_name, attributes): 242 | doc = Document() 243 | node = doc.createElement(node_name) 244 | for name, value in attributes.items(): 245 | node.setAttribute(name, value) 246 | 247 | return node 248 | 249 | 250 | class _TIFConverter: 251 | 252 | def __init__(self, config, source): 253 | self.__config = config 254 | self.__images_to_convert = source 255 | self.__tmp_images = {} 256 | self.__tmp_dir = tempfile.mkdtemp("CryBlend") 257 | 258 | def __call__(self): 259 | for image in self.__images_to_convert: 260 | rc_params = self.__get_rc_params(image.filepath) 261 | tiff_image_path = self.__get_temp_tiff_image_path(image) 262 | 263 | tiff_image_for_rc = utils.get_absolute_path_for_rc(tiff_image_path) 264 | bcPrint(tiff_image_for_rc) 265 | 266 | try: 267 | self.__create_normal_texture() 268 | except: 269 | bcPrint("Failed to invert green channel") 270 | 271 | rc_process = run_rc(self.__config.texture_rc_path, 272 | tiff_image_for_rc, 273 | rc_params) 274 | 275 | # re-save the original image after running the RC to 276 | # prevent the original one from getting lost 277 | try: 278 | if ("_ddn" in image.name): 279 | image.save() 280 | except: 281 | bcPrint("Failed to invert green channel") 282 | 283 | rc_process.wait() 284 | 285 | if self.__config.texture_rc_path: 286 | self.__save_tiffs() 287 | 288 | self.__remove_tmp_files() 289 | 290 | def __create_normal_texture(self): 291 | if ("_ddn" in image.name): 292 | # make a copy to prevent editing the original image 293 | temp_normal_image = image.copy() 294 | self.__invert_green_channel(temp_normal_image) 295 | # save to file and delete the temporary image 296 | new_normal_image_path = "{}_cb_normal.{}".format(os.path.splitext( 297 | temp_normal_image.filepath_raw)[0], 298 | os.path.splitext( 299 | temp_normal_image.filepath_raw)[1]) 300 | temp_normal_image.save_render(filepath=new_normal_image_path) 301 | bpy.data.images.remove(temp_normal_image) 302 | 303 | def __get_rc_params(self, destination_path): 304 | rc_params = ["/verbose", "/threads=cores", "/userdialog=1", "/refresh"] 305 | 306 | image_directory = os.path.dirname(utils.get_absolute_path_for_rc( 307 | destination_path)) 308 | 309 | rc_params.append("/targetroot={!s}".format(image_directory)) 310 | 311 | return rc_params 312 | 313 | def __invert_green_channel(self, image): 314 | override = {'edit_image': bpy.data.images[image.name]} 315 | bpy.ops.image.invert(override, invert_g=True) 316 | image.update() 317 | 318 | def __get_temp_tiff_image_path(self, image): 319 | # check if the image already is a .tif 320 | image_extension = utils.get_extension_from_path(image.filepath) 321 | bcPrint(image_extension) 322 | 323 | if ".tif" == image_extension: 324 | bcPrint( 325 | "Image {!r} is already a tif, not converting".format( 326 | image.name), 'debug') 327 | return image.filepath 328 | 329 | tiff_image_path = utils.get_path_with_new_extension(image.filepath, 330 | "tif") 331 | tiff_image_absolute_path = utils.get_absolute_path(tiff_image_path) 332 | tiff_file_name = os.path.basename(tiff_image_path) 333 | 334 | tmp_file_path = os.path.join(self.__tmp_dir, tiff_file_name) 335 | 336 | if tiff_image_path != image.filepath: 337 | self.__save_as_tiff(image, tmp_file_path) 338 | self.__tmp_images[tmp_file_path] = (tiff_image_absolute_path) 339 | 340 | return tmp_file_path 341 | 342 | def __save_as_tiff(self, image, tiff_file_path): 343 | originalPath = image.filepath 344 | 345 | try: 346 | image.filepath_raw = tiff_file_path 347 | image.file_format = 'TIFF' 348 | image.save() 349 | 350 | finally: 351 | image.filepath = originalPath 352 | 353 | def __save_tiffs(self): 354 | for tmp_image, dest_image in self.__tmp_images.items(): 355 | bcPrint("Moving tmp image: {!r} to {!r}".format(tmp_image, 356 | dest_image), 357 | 'debug') 358 | shutil.move(tmp_image, dest_image) 359 | 360 | def __remove_tmp_files(self): 361 | for tmp_image in self.__tmp_images: 362 | try: 363 | bcPrint("Removing tmp image: {!r}".format(tmp_image), 'debug') 364 | os.remove(tmp_image) 365 | except FileNotFoundError: 366 | pass 367 | 368 | os.removedirs(self.__tmp_dir) 369 | self.__tmp_images.clear() 370 | 371 | 372 | def run_rc(rc_path, files_to_process, params=None): 373 | bcPrint("RC Path: {}".format(os.path.abspath(rc_path)), newline=True) 374 | process_params = [rc_path] 375 | 376 | if isinstance(files_to_process, list): 377 | process_params.extend(files_to_process) 378 | else: 379 | process_params.append(files_to_process) 380 | 381 | process_params.extend(params) 382 | 383 | bcPrint("RC Parameters: {}".format(params)) 384 | bcPrint("Processing File: {}".format(files_to_process)) 385 | 386 | try: 387 | run_object = subprocess.Popen(process_params) 388 | except: 389 | raise exceptions.NoRcSelectedException 390 | 391 | print() 392 | return run_object 393 | -------------------------------------------------------------------------------- /io_bcry_exporter/udp.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Name: udp.py 3 | # Purpose: Holds UDP and IK property functions. 4 | # 5 | # Author: Özkan Afacan 6 | # 7 | # Created: 17/09/2016 8 | # Copyright: (c) Özkan Afacan 2016 9 | # License: GPLv2+ 10 | #------------------------------------------------------------------------------ 11 | 12 | # 13 | 14 | 15 | from bpy.props import * 16 | from bpy_extras.io_utils import ExportHelper 17 | import bpy 18 | import bpy.ops 19 | import bpy_extras 20 | import re 21 | import math 22 | 23 | 24 | #------------------------------------------------------------------------------ 25 | # User Defined Properties: 26 | #------------------------------------------------------------------------------ 27 | 28 | def get_udp(object_, udp_name, udp_value, is_checked=None): 29 | '''Get User Defined Property -- Overloaded function that have two variation''' 30 | 31 | if is_checked is None: 32 | try: 33 | temp_value = object_[udp_name] 34 | udp_value = True 35 | except: 36 | udp_value = False 37 | 38 | return udp_value 39 | 40 | else: 41 | try: 42 | udp_value = object_[udp_name] 43 | is_checked = True 44 | except: 45 | is_checked = False 46 | 47 | return udp_value, is_checked 48 | 49 | 50 | def edit_udp(object_, udp_name, udp_value, is_checked=True): 51 | '''Edit User Defined Property''' 52 | 53 | if is_checked: 54 | object_[udp_name] = udp_value 55 | else: 56 | try: 57 | del object_[udp_name] 58 | except: 59 | pass 60 | 61 | 62 | def is_user_defined_property(property_name): 63 | prop_list = [ 64 | "phys_proxy", 65 | "colltype_player", 66 | "no_explosion_occlusion", 67 | "entity", 68 | "mass", 69 | "density", 70 | "pieces", 71 | "dynamic", 72 | "no_hit_refinement", 73 | "limit", 74 | "bend", 75 | "twist", 76 | "pull", 77 | "push", 78 | "shift", 79 | "player_can_break", 80 | "gameplay_critical", 81 | "constraint_limit", 82 | "constraint_minang", 83 | "consrtaint_maxang", 84 | "constraint_damping", 85 | "constraint_collides", 86 | "stiffness", 87 | "hardness", 88 | "max_stretch", 89 | "max_impulse", 90 | "skin_dist", 91 | "thickness", 92 | "explosion_scale", 93 | "notaprim", 94 | "hull", 95 | "wheel"] 96 | 97 | return property_name in prop_list 98 | 99 | 100 | #------------------------------------------------------------------------------ 101 | # Bone Inverse Kinematics: 102 | #------------------------------------------------------------------------------ 103 | 104 | def get_bone_ik_max_min(pose_bone): 105 | xIK = yIK = zIK = "" 106 | 107 | if pose_bone.lock_ik_x: 108 | xIK = '_xmax={!s}'.format(0.0) + '_xmin={!s}'.format(0.0) 109 | else: 110 | xIK = '_xmax={!s}'.format(math.degrees(-pose_bone.ik_min_y)) \ 111 | + '_xmin={!s}'.format(math.degrees(-pose_bone.ik_max_y)) 112 | 113 | if pose_bone.lock_ik_y: 114 | yIK = '_ymax={!s}'.format(0.0) + '_ymin={!s}'.format(0.0) 115 | else: 116 | yIK = '_ymax={!s}'.format(math.degrees(-pose_bone.ik_min_x)) \ 117 | + '_ymin={!s}'.format(math.degrees(-pose_bone.ik_max_x)) 118 | 119 | if pose_bone.lock_ik_z: 120 | zIK = '_zmax={!s}'.format(0.0) + '_zmin={!s}'.format(0.0) 121 | else: 122 | zIK = '_zmax={!s}'.format(math.degrees(pose_bone.ik_max_z)) \ 123 | + '_zmin={!s}'.format(math.degrees(pose_bone.ik_min_z)) 124 | 125 | return xIK, yIK, zIK 126 | 127 | 128 | def get_bone_ik_properties(pose_bone): 129 | damping = [1.0, 1.0, 1.0] 130 | spring = [0.0, 0.0, 0.0] 131 | spring_tension = [1.0, 1.0, 1.0] 132 | 133 | try: 134 | damping = pose_bone['Damping'] 135 | except: 136 | pass 137 | 138 | try: 139 | spring = pose_bone['Spring'] 140 | except: 141 | pass 142 | 143 | try: 144 | spring_tension = pose_bone['Spring Tension'] 145 | except: 146 | pass 147 | 148 | return damping, spring, spring_tension 149 | --------------------------------------------------------------------------------