Autoexport.py#
Autoexport.py
The script used for the File->Export->Decimate > Auto UV-Map > Export
1import coat
2import os
3
4# Set this to True to enable debug output
5_debugmode = False
6
7# the debug print function
8def _print(*args, **kwargs):
9 if _debugmode:
10 print(*args, **kwargs)
11
12# the autoexport class
13class AutoExport:
14 def __init__(self):
15 # Initialize variables with default values
16 # the texture resolution
17 self.TexRes = 4
18 # the desirable reduced polycount
19 self.ReducedPolycount = 40000
20 self.pReducedPolycount = self.ReducedPolycount
21
22 # the default export preset
24
25 # the default reduction percent
26 self.ReductionPercent = 80
27 self.pReductionPercent = self.ReductionPercent
28 # decumate from all volumes or from the current volume
29 self.DecimateFromAllVolumes = False
30 self.pDecimateFromAllVolumes = False
31
33 self.CurrentPolycount = vol.getPolycount()
34
35 # export each volume to separate asset
36 self.EachVolumeToSeparateAsset = False
37 # center each asset around its bound box
38 self.CenterEachAssetAroundBoundBox = False
39 # export each asset to its own folder
40 self.EachAssetToOwnFolder = False
41 # drop export result directly to blender
42 self.DropToBlender = False
43 # export mesh optimized for UE5
44 self.UE5_optimized = False
45 # the regular export
46 self.RegularExport = True
47 # skip the filename preffix
48 self.SkipFilenamePreffix = True
49 # the scan depth percent (in percents of the diagonal of the bound box)
50 self.ScanDepthPercent = 2.0
51
52 # the mesh export path
53 self.exportMesh = ""
54 # the textures export path (optional)
55 self.texturesExportPath = ""
56
57 # called each frame
58 def process(self):
59 if self.ReductionPercent != self.pReductionPercent:
60 self.ReducedPolycount = int(self.CurrentPolycount * (100.0 - self.ReductionPercent) / 100.0)
61 self.pReducedPolycount = self.ReducedPolycount
62
63 # Update the reduction percent based on the current polycount
64 if self.CurrentPolycount > 0:
65 self.ReductionPercent = int(100.0 - (self.ReducedPolycount * 100.0) / self.CurrentPolycount)
66 if self.ReductionPercent < 0:
67 self.ReductionPercent = 0
68
69 # Check for changes in DecimateFromAllVolumes and update accordingly
70 if self.DecimateFromAllVolumes != self.pDecimateFromAllVolumes:
71 self.pDecimateFromAllVolumes = self.DecimateFromAllVolumes
72
73 # If DecimateFromAllVolumes is true, calculate the total polycount
74 if self.DecimateFromAllVolumes:
75 self.CurrentPolycount = 0
78
79 # Otherwise, use the polycount of the current volume
80 else:
82 self.CurrentPolycount = vol.getPolycount()
83 self.pReductionPercent = self.ReductionPercent
84 self.pReducedPolycount = self.ReducedPolycount
85 return False
86 def UE5_YouTube():
88 def ui(self):
89 self.process()
90 items = []
91 items.append("RegularExport, group1")
92 items.append("DropToBlender, group1")
93 if self.UE5_optimized :
94 items.append("[2 1]")
95
96 items.append("UE5_optimized,group1")
97 if self.UE5_optimized :
98 items.append("UE5_YouTube")
99
100 items.append("---")
101 items.append("EachVolumeToSeparateAsset")
102 items.append("CenterEachAssetAroundBoundBox")
103 if not self.EachVolumeToSeparateAsset :
104 items.append("DecimateFromAllVolumes")
105 else:
106 items.append("EachAssetToOwnFolder")
107 if not self.EachAssetToOwnFolder :
108 items.append("SkipFilenamePreffix")
109
110 # gather all supported export extensions
111 if self.UE5_optimized :
112 items.append("exportMesh,save:*.usdz,*.usd")
113 elif self.DropToBlender :
114 items.append("exportMesh,save:*.fbx")
115 else:
117 items.append("texturesExportPath,folder")
118 items.append("TexRes,[#256x256|#512x512|#1024x1024|#2048x2048|#4096x4096|#8192x8192]")
119
120 if self.RegularExport :
121 items.append("expPreset,[EXPPRESETS|]")
122 items.append("---")
123 items.append("#" + str(self.CurrentPolycount))
124 items.append("ReductionPercent,[0,99]")
125 items.append("[[] 6]")
126 items.append("#{maticon arrow_forward}")
127 items.append("ReducedPolycount")
128 items.append("ScanDepthPercent,[0.01,100]")
129 return items
130
131def RemovePaintObjects():
132 # Get the count of paint objects in the scene
134
135 # Loop through the paint objects and remove them
136 for k in range(n):
138
139def DecimateAutoMapExport():
140 se = AutoExport()
143 # show the parameters dialog
144 if coat.dialog().ok().cancel().text("SimpSculptExportHint").caption("SimpSculptExportHintCap").params(se).show() == 1 :
145 if coat.dialog().ok().cancel().text("RunBakeScriptInfo").caption("SimpSculptExportHintCap").dontShowAgainCheckbox().show() <= 1:
146 # save settings
148 # hide annoying message about textures locking
150 # don't fade background, but keep it' state, auto_keep allows to save that value in the current scope,
153 if se.DropToBlender :
154 # the blender applink toes not support embedding
156 # set blender's specific normalmapping settings
157 # Generally, you may use just English text itens below
158 # but I decided to use the identifiers instead
164 if se.UE5_optimized :
165 # set UE5 specific normalmapping settings
171 # set the export preset
174 # create the list of volumes we want to handle
175 volumes = []
176 if se.EachVolumeToSeparateAsset :
177 se.DecimateFromAllVolumes = False
179 else :
181 # go through all volumes we want to operate
182 for i in range (len(volumes)):
183 vol = volumes[i]
185 # select the volume
186 if se.EachVolumeToSeparateAsset :
187 vol.inScene().selectOne()
188 ab = coat.boundbox()
189 ab.SetEmpty()
190 if not se.DecimateFromAllVolumes :
191 ab = vol.calcWorldSpaceAABB()
192 else :
193 coat.Scene.sculptRoot().iterateVisibleSubtree(lambda e: ab.AddBounds(e.Volume().calcWorldSpaceAABB()))
194 center = ab.GetCenter()
195 _print("Center: " + str(center))
196 def transform(t):
197 if se.CenterEachAssetAroundBoundBox :
198 if not se.DecimateFromAllVolumes :
199 vol.inScene().transform_single(t)
200 else :
203 d = ab.GetDiagonal() * se.ScanDepthPercent / 100.0
204 cp = vol.getPolycount()
205 _print("Polycount: " + str(cp))
206 if se.DecimateFromAllVolumes:
207 cp = 0
208 def accum(e):
209 cp += e.Volume().getPolycount()
210 return False
212 if cp > 0 :
213 # calculating the decimation percent
214 per = 100.0 - se.ReducedPolycount * 100.0 / float(cp)
215 if per > 100.0 :
216 per = 99.0
217 if per < 0.0 :
218 per = 0.0
219 # get the name of the export preset, there is index
220 # of the preset in the se.expPreset
222 RemovePaintObjects()
224 coat.io.step(4)
228 coat.ui.cmd("$ClearTM")
230 coat.ui.cmd("$DecimateAllToRetopo" if se.DecimateFromAllVolumes else "$DecimateToRetopo", lambda: coat.ui.cmd("$DialogButton#1"))
231 # switch to retopo room
233 # refresh UI
234 coat.io.step(4)
235 # closest along normal to keep shape
236 coat.ui.cmd("$SnapToNearestAlongNormal")
237 # relax the mesh, snapping rurned ON
238 coat.ui.cmd("$ApplyTSm")
239 # refresh UI
240 coat.io.step(4)
241 # the string that contains decimation percent
242 texSize = str(256 << se.TexRes)
243 # set the baking scan depth, the depth is auto-cacculated and substituted by Coat
246 # call Bake->Bake with normal map+flat displacement
247 def inbake():
248 # set texture width when the field accessible
249 b1 = coat.ui.cmd("$COMBOBOX_TEXTURE_SIZE_X" + texSize)
250 _print("inbake1: " + str(b1))
251 # set texture height
252 b1 = coat.ui.cmd("$COMBOBOX_TEXTURE_SIZE_Y" + texSize)
253 _print("inbake2: " + str(b1))
254 # press OK at the end
255 coat.ui.cmd("$DialogButton#1")
256 _print("Baking...")
257 coat.ui.cmd("$MergeForDPNM_flatdisp", inbake)
260 # fill the mesh name
261 name = se.exportMesh
262 if se.EachVolumeToSeparateAsset :
263 name, ext = os.path.splitext(name)
264 if se.EachAssetToOwnFolder :
265 name += "/"
266 name += vol.inScene().name()
267 name += "/"
268 name += vol.inScene().name()
269 name += ext
270 else :
271 if se.SkipFilenamePreffix :
272 name = os.path.dirname(name)
273 name += "/"
274 else :
275 name += "_"
276 name += vol.inScene().name()
277 name += ext
278 if se.UE5_optimized :
279 name, ext = os.path.splitext(name)
280 if not ("usd" in ext.lower()):
281 name += ".usdz"
282 if se.DropToBlender :
283 # remove file extension
284 name = os.path.splitext(name)[0]
285 name += ".fbx"
286 # call the export dialog
287 coat.ui.setFileForFileDialog(name)
288 coat.ui.cmd("$Blender", lambda: coat.ui.cmd("$DialogButton#1"))
289 else :
290 def inexport() :
291 # set export preset
292 b1 = coat.ui.cmd("$COMBOBOX_" + preset)
293 _print("inexport: " + "$COMBOBOX_" + preset + " : " + str(b1))
294 coat.io.step(2)
296 _print("inexport: $ExportOpt::ExportMeshName " + name + " : " + str(b1))
297 # enable mesh export
299 _print("inexport: $ExportOpt::ExportTextures : " + str(b1))
300 # enable textures export
302 _print("inexport: $ExportOpt::ExportGeometry : " + str(b1))
303 # set the textures folder path
305 # press Export at the end
306 coat.ui.cmd("$DialogButton#1")
307 _print("inexport: $DialogButton#1 : " + str(b1))
308 _print("Export...")
309 coat.ui.cmd("$EXPORTOBJECT", inexport)
310 # remove all paint objects
311 RemovePaintObjects()
313 coat.io.step(2)
314 # clear all retopo objects
315 coat.ui.cmd("$ClearTM")
318 # restore the background fade state
320
322
323DecimateAutoMapExport()
1 import coat
2 import os
3
4 # Set this to True to enable debug output
5 _debugmode = False
6
7 # the debug print function
8 def _print(*args, **kwargs):
9 if _debugmode:
10 print(*args, **kwargs)
11
12 # the autoexport class
13 class AutoExport:
14 def __init__(self):
15 # Initialize variables with default values
16 # the texture resolution
17 self.TexRes = 4
18 # the desirable reduced polycount
19 self.ReducedPolycount = 40000
20 self.pReducedPolycount = self.ReducedPolycount
21
22 # the default export preset
23 self.expPreset = coat.utils.getEnumValue("EXPPRESETS", "PBR+EXR-displacement",)
24
25 # the default reduction percent
26 self.ReductionPercent = 80
27 self.pReductionPercent = self.ReductionPercent
28 # decumate from all volumes or from the current volume
29 self.DecimateFromAllVolumes = False
30 self.pDecimateFromAllVolumes = False
31
32 vol = coat.Scene.current().Volume()
33 self.CurrentPolycount = vol.getPolycount()
34
35 # export each volume to separate asset
36 self.EachVolumeToSeparateAsset = False
37 # center each asset around its bound box
38 self.CenterEachAssetAroundBoundBox = False
39 # export each asset to its own folder
40 self.EachAssetToOwnFolder = False
41 # drop export result directly to blender
42 self.DropToBlender = False
43 # export mesh optimized for UE5
44 self.UE5_optimized = False
45 # the regular export
46 self.RegularExport = True
47 # skip the filename preffix
48 self.SkipFilenamePreffix = True
49 # the scan depth percent (in percents of the diagonal of the bound box)
50 self.ScanDepthPercent = 2.0
51
52 # the mesh export path
53 self.exportMesh = ""
54 # the textures export path (optional)
55 self.texturesExportPath = ""
56
57 # called each frame
58 def process(self):
59 if self.ReductionPercent != self.pReductionPercent:
60 self.ReducedPolycount = int(self.CurrentPolycount * (100.0 - self.ReductionPercent) / 100.0)
61 self.pReducedPolycount = self.ReducedPolycount
62
63 # Update the reduction percent based on the current polycount
64 if self.CurrentPolycount > 0:
65 self.ReductionPercent = int(100.0 - (self.ReducedPolycount * 100.0) / self.CurrentPolycount)
66 if self.ReductionPercent < 0:
67 self.ReductionPercent = 0
68
69 # Check for changes in DecimateFromAllVolumes and update accordingly
70 if self.DecimateFromAllVolumes != self.pDecimateFromAllVolumes:
71 self.pDecimateFromAllVolumes = self.DecimateFromAllVolumes
72
73 # If DecimateFromAllVolumes is true, calculate the total polycount
74 if self.DecimateFromAllVolumes:
75 self.CurrentPolycount = 0
76 root = coat.Scene.sculptRoot()
77 root.iterateVisibleSubtree(lambda e: self._update_polycount(e))
78
79 # Otherwise, use the polycount of the current volume
80 else:
81 vol = coat.Scene.current().Volume()
82 self.CurrentPolycount = vol.getPolycount()
83 self.pReductionPercent = self.ReductionPercent
84 self.pReducedPolycount = self.ReducedPolycount
85 return False
86 def UE5_YouTube():
87 coat.io.exec(coat.ui.getIdTranslation("UE5_ExportTutorial"))
88 def ui(self):
89 self.process()
90 items = []
91 items.append("RegularExport, group1")
92 items.append("DropToBlender, group1")
93 if self.UE5_optimized :
94 items.append("[2 1]")
95
96 items.append("UE5_optimized,group1")
97 if self.UE5_optimized :
98 items.append("UE5_YouTube")
99
100 items.append("---")
101 items.append("EachVolumeToSeparateAsset")
102 items.append("CenterEachAssetAroundBoundBox")
103 if not self.EachVolumeToSeparateAsset :
104 items.append("DecimateFromAllVolumes")
105 else:
106 items.append("EachAssetToOwnFolder")
107 if not self.EachAssetToOwnFolder :
108 items.append("SkipFilenamePreffix")
109
110 # gather all supported export extensions
111 if self.UE5_optimized :
112 items.append("exportMesh,save:*.usdz,*.usd")
113 elif self.DropToBlender :
114 items.append("exportMesh,save:*.fbx")
115 else:
116 items.append("exportMesh,save:" + coat.io.supportedMeshesFormats())
117 items.append("texturesExportPath,folder")
118 items.append("TexRes,[#256x256|#512x512|#1024x1024|#2048x2048|#4096x4096|#8192x8192]")
119
120 if self.RegularExport :
121 items.append("expPreset,[EXPPRESETS|]")
122 items.append("---")
123 items.append("#" + str(self.CurrentPolycount))
124 items.append("ReductionPercent,[0,99]")
125 items.append("[[] 6]")
126 items.append("#{maticon arrow_forward}")
127 items.append("ReducedPolycount")
128 items.append("ScanDepthPercent,[0.01,100]")
129 return items
130
131 def RemovePaintObjects():
132 # Get the count of paint objects in the scene
133 n = coat.Scene.PaintObjectsCount()
134
135 # Loop through the paint objects and remove them
136 for k in range(n):
137 coat.Scene.RemovePaintObject(0)
138
139 def DecimateAutoMapExport():
140 se = AutoExport()
141 if coat.io.fileExists("UserPrefs/CoreAPI/auto_export_py.json"):
142 coat.io.fromJsonFile(se, "UserPrefs/CoreAPI/auto_export_py.json")
143 # show the parameters dialog
144 if coat.dialog().ok().cancel().text("SimpSculptExportHint").caption("SimpSculptExportHintCap").params(se).show() == 1 :
145 if coat.dialog().ok().cancel().text("RunBakeScriptInfo").caption("SimpSculptExportHintCap").dontShowAgainCheckbox().show() <= 1:
146 # save settings
147 coat.io.toJson(se, "UserPrefs/CoreAPI/auto_export_py.json")
148 # hide annoying message about textures locking
149 coat.ui.hideDontShowAgainMessage("AttachTextureHint")
150 # don't fade background, but keep it' state, auto_keep allows to save that value in the current scope,
151 fade = coat.settings.getBool("GreyOutBgInModalDialogs")
152 coat.settings.setBool("GreyOutBgInModalDialogs", False)
153 if se.DropToBlender :
154 # the blender applink toes not support embedding
155 coat.settings.setBool("EmbedTexturesToFBX", False)
156 # set blender's specific normalmapping settings
157 # Generally, you may use just English text itens below
158 # but I decided to use the identifiers instead
159 coat.settings.setString("SoftwarePreset", "Blender")
160 coat.settings.setString("NormalsCalculationMethod", "nm_AngleWeighed")
161 coat.settings.setString("NMAP_EXPORT_TYPE", "NM_MAYA")
162 coat.settings.setString("TriangulationMethod", "DelaunayTriangulation")
163 coat.settings.setString("TBNMethod", "MikkTSpace")
164 if se.UE5_optimized :
165 # set UE5 specific normalmapping settings
166 coat.settings.setString("SoftwarePreset", "UnrealEngine")
167 coat.settings.setString("NormalsCalculationMethod", "nm_AngleWeighed")
168 coat.settings.setString("NMAP_EXPORT_TYPE", "NM_3DMAX")
169 coat.settings.setString("TriangulationMethod", "NaiveTriangulation")
170 coat.settings.setString("TBNMethod", "MikkTSpace")
171 # set the export preset
172 coat.settings.setString("ExportPreset", "PBR+EXR-displacement")
173 se.expPreset = coat.utils.getEnumValue("EXPPRESETS", "USD (PBR Standard)")
174 # create the list of volumes we want to handle
175 volumes = []
176 if se.EachVolumeToSeparateAsset :
177 se.DecimateFromAllVolumes = False
178 coat.Scene.sculptRoot().iterateVisibleSubtree(lambda e: volumes.append(e.Volume()))
179 else :
180 volumes.append(coat.Scene.current().Volume())
181 # go through all volumes we want to operate
182 for i in range (len(volumes)):
183 vol = volumes[i]
184 _print("Processing volume: " + vol.inScene().name())
185 # select the volume
186 if se.EachVolumeToSeparateAsset :
187 vol.inScene().selectOne()
188 ab = coat.boundbox()
189 ab.SetEmpty()
190 if not se.DecimateFromAllVolumes :
191 ab = vol.calcWorldSpaceAABB()
192 else :
193 coat.Scene.sculptRoot().iterateVisibleSubtree(lambda e: ab.AddBounds(e.Volume().calcWorldSpaceAABB()))
194 center = ab.GetCenter()
195 _print("Center: " + str(center))
196
197 def transform(t):
198 if se.CenterEachAssetAroundBoundBox :
199 if not se.DecimateFromAllVolumes :
200 vol.inScene().transform_single(t)
201 else :
202 coat.Scene.sculptRoot().iterateVisibleSubtree(lambda e: e.transform_single(t))
203 transform(coat.mat4.Translation(-center))
204 d = ab.GetDiagonal() * se.ScanDepthPercent / 100.0
205 cp = vol.getPolycount()
206 _print("Polycount: " + str(cp))
207 if se.DecimateFromAllVolumes:
208 cp = 0
209 def accum(e):
210 cp += e.Volume().getPolycount()
211 return False
212 coat.Scene.sculptRoot().iterateVisibleSubtree(accum)
213 if cp > 0 :
214 # calculating the decimation percent
215 per = 100.0 - se.ReducedPolycount * 100.0 / float(cp)
216 if per > 100.0 :
217 per = 99.0
218 if per < 0.0 :
219 per = 0.0
220 # get the name of the export preset, there is index
221 # of the preset in the se.expPreset
222 preset = coat.utils.getEnumValueByIndex("EXPPRESETS",se.expPreset)
223 RemovePaintObjects()
224 coat.ui.toRoom("Retopo")
225 coat.io.step(4)
226 _print("In room: " + coat.ui.currentRoom())
227 nameCorr = coat.ui.getBoolField("$UseNamesCorrespondence")
228 coat.ui.setBoolValue("$UseNamesCorrespondence", True)
229 coat.ui.cmd("$ClearTM")
230 coat.ui.toRoom("Sculpt")
231 coat.ui.cmd("$DecimateAllToRetopo" if se.DecimateFromAllVolumes else "$DecimateToRetopo", lambda: coat.ui.cmd("$DialogButton#1"))
232 # switch to retopo room
233 coat.ui.toRoom("Retopo")
234 # refresh UI
235 coat.io.step(4)
236 # closest along normal to keep shape
237 coat.ui.cmd("$SnapToNearestAlongNormal")
238 # relax the mesh, snapping rurned ON
239 coat.ui.cmd("$ApplyTSm")
240 # refresh UI
241 coat.io.step(4)
242 # the string that contains decimation percent
243 texSize = str(256 << se.TexRes)
244 # set the baking scan depth, the depth is auto-cacculated and substituted by Coat
245 coat.ui.setOption("BakeScanDepthOut", d)
246 coat.ui.setOption("BakeScanDepthIn", d)
247 # call Bake->Bake with normal map+flat displacement
248 def inbake():
249 # set texture width when the field accessible
250 b1 = coat.ui.cmd("$COMBOBOX_TEXTURE_SIZE_X" + texSize)
251 _print("inbake1: " + str(b1))
252 # set texture height
253 b1 = coat.ui.cmd("$COMBOBOX_TEXTURE_SIZE_Y" + texSize)
254 _print("inbake2: " + str(b1))
255 # press OK at the end
256 coat.ui.cmd("$DialogButton#1")
257 _print("Baking...")
258 coat.ui.cmd("$MergeForDPNM_flatdisp", inbake)
259 coat.ui.setBoolValue("$UseNamesCorrespondence", nameCorr)
260 coat.ui.toRoom("Paint")
261 # fill the mesh name
262 name = se.exportMesh
263 if se.EachVolumeToSeparateAsset :
264 name, ext = os.path.splitext(name)
265 if se.EachAssetToOwnFolder :
266 name += "/"
267 name += vol.inScene().name()
268 name += "/"
269 name += vol.inScene().name()
270 name += ext
271 else :
272 if se.SkipFilenamePreffix :
273 name = os.path.dirname(name)
274 name += "/"
275 else :
276 name += "_"
277 name += vol.inScene().name()
278 name += ext
279 if se.UE5_optimized :
280 name, ext = os.path.splitext(name)
281 if not ("usd" in ext.lower()):
282 name += ".usdz"
283 if se.DropToBlender :
284 # remove file extension
285 name = os.path.splitext(name)[0]
286 name += ".fbx"
287 # call the export dialog
288 coat.ui.setFileForFileDialog(name)
289 coat.ui.cmd("$Blender", lambda: coat.ui.cmd("$DialogButton#1"))
290 else :
291 def inexport() :
292 # set export preset
293 b1 = coat.ui.cmd("$COMBOBOX_" + preset)
294 _print("inexport: " + "$COMBOBOX_" + preset + " : " + str(b1))
295 coat.io.step(2)
296 b1 = coat.ui.setEditBoxValue("$ExportOpt::ExportMeshName", name)
297 _print("inexport: $ExportOpt::ExportMeshName " + name + " : " + str(b1))
298 # enable mesh export
299 b1 = coat.ui.setBoolValue("$ExportOpt::ExportTextures", True)
300 _print("inexport: $ExportOpt::ExportTextures : " + str(b1))
301 # enable textures export
302 b1 = coat.ui.setBoolValue("$ExportOpt::ExportGeometry", True)
303 _print("inexport: $ExportOpt::ExportGeometry : " + str(b1))
304 # set the textures folder path
305 if coat.ui.setEditBoxValue("$ExportOpt::PathForTextures", se.texturesExportPath) :
306 # press Export at the end
307 coat.ui.cmd("$DialogButton#1")
308 _print("inexport: $DialogButton#1 : " + str(b1))
309 _print("Export...")
310 coat.ui.cmd("$EXPORTOBJECT", inexport)
311 # remove all paint objects
312 RemovePaintObjects()
313 coat.ui.toRoom("Retopo")
314 coat.io.step(2)
315 # clear all retopo objects
316 coat.ui.cmd("$ClearTM")
317 coat.ui.toRoom("Sculpt")
318 transform(coat.mat4.Translation(center))
319 # restore the background fade state
320 coat.settings.setBool("GreyOutBgInModalDialogs", fade)
321
322 if _debugmode : coat.io.showPythonConsole()
323
324 DecimateAutoMapExport()