├── .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 |
--------------------------------------------------------------------------------