-
Notifications
You must be signed in to change notification settings - Fork 16
/
assets.py
314 lines (263 loc) · 10.8 KB
/
assets.py
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
#
# Copyright (C) 2020-2022 Michael Vigovsky
import os, logging
import bpy, bpy_extras # pylint: disable=import-error
from .lib import fitting, morpher, utils
from .lib.charlib import library, Asset
from .common import manager as mm
logger = logging.getLogger(__name__)
def get_fitter(obj):
if obj is mm.morpher.core.obj:
return mm.morpher.fitter
return morpher.get(obj).fitter
def get_asset_conf(context):
ui = context.window_manager.charmorph_ui
item = ui.fitting_library_asset
if item.startswith("char_"):
obj = ui.fitting_char
char = library.obj_char(obj)
return char.assets.get(item[5:])
if item.startswith("add_"):
return library.additional_assets.get(item[4:])
return None
def do_refit(_ui, _ctx):
f = mm.morpher.fitter
if f:
f.clear_cache()
f.refit_all()
def get_assets(ui, _):
char = library.obj_char(ui.fitting_char)
return [("char_" + k, k, '') for k in sorted(char.assets.keys())]\
+ [("add_" + k, k, '') for k in sorted(library.additional_assets.keys())]
class UIProps:
fitting_char: bpy.props.PointerProperty(
name="Char",
description="Character for fitting",
type=bpy.types.Object,
poll=lambda ui, obj:
utils.visible_mesh_poll(ui, obj)
and ("charmorph_fit_id" not in obj.data or 'cm_alt_topo' in obj.data)
)
fitting_asset: bpy.props.PointerProperty(
name="Local asset",
description="Asset for fitting",
type=bpy.types.Object,
poll=lambda ui, obj: utils.visible_mesh_poll(ui, obj) and ("charmorph_template" not in obj.data))
fitting_binder: bpy.props.EnumProperty(
name="Algorighm",
default="SOFT",
items=[
("SOFT", "Soft", "This algorithm tries to make softer look for clothing but can cause more intersections with character"),
("HARD", "Hard", "This algorighm is better for tight clothing but can cause more artifacts"),
],
update=do_refit,
description="Fitting algorighm")
fitting_mask: bpy.props.EnumProperty(
name="Mask",
default="COMB",
items=[
("NONE", "No mask", "Don't mask character at all"),
("SEPR", "Separate", "Use separate mask vertex groups and modifiers for each asset"),
("COMB", "Combined", "Use combined vertex group and modifier for all character assets"),
],
description="Mask parts of character that are invisible under clothing")
fitting_transforms: bpy.props.BoolProperty(
name="Apply transforms",
default=True,
description="Apply object transforms before fitting")
fitting_weights: bpy.props.EnumProperty(
name="Weights",
default="ORIG",
items=[
("NONE", "None", "Don't transfer weights and armature modifiers to the asset"),
("ORIG", "Original", "Use original weights from character library"),
("OBJ", "Object", "Use weights directly from object"
"(use it if you manually weight-painted the character before fitting the asset)"),
],
description="Select source for armature deform weights")
fitting_weights_ovr: bpy.props.BoolProperty(
name="Weights overwrite",
default=False,
description="Overwrite existing asset weights")
fitting_library_asset: bpy.props.EnumProperty(
name="Library asset",
description="Select asset from library",
items=get_assets)
fitting_library_dir: bpy.props.StringProperty(
name="Library dir",
description="Additional library directory",
update=lambda ui, _: library.update_additional_assets(ui.fitting_library_dir),
subtype='DIR_PATH')
class CHARMORPH_PT_Assets(bpy.types.Panel):
bl_label = "Assets"
bl_parent_id = "VIEW3D_PT_CharMorph"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_options = {"DEFAULT_CLOSED"}
bl_order = 7
@classmethod
def poll(cls, context):
return context.mode == "OBJECT" # is it neccesary?
def draw(self, context):
ui = context.window_manager.charmorph_ui
l = self.layout
col = l.column(align=True)
col.prop(ui, "fitting_char")
col.prop(ui, "fitting_asset")
l.prop(ui, "fitting_binder")
l.prop(ui, "fitting_mask")
col = l.column(align=True)
col.prop(ui, "fitting_weights")
col.prop(ui, "fitting_weights_ovr")
col.prop(ui, "fitting_transforms")
l.separator()
if ui.fitting_asset and 'charmorph_fit_id' in ui.fitting_asset.data:
l.operator("charmorph.unfit")
else:
l.operator("charmorph.fit_local")
l.separator()
l.operator("charmorph.fit_external")
asset = get_asset_conf(context) or fitting.EmptyAsset
col = l.column(align=True)
col.label(text="Author: " + asset.author)
col.label(text="License: " + asset.license)
l.prop(ui, "fitting_library_asset")
l.operator("charmorph.fit_library")
l.prop(ui, "fitting_library_dir")
l.separator()
def mesh_obj(obj):
if obj and obj.type == "MESH":
return obj
return None
def get_char(context):
obj = mesh_obj(context.window_manager.charmorph_ui.fitting_char)
if not obj or ('charmorph_fit_id' in obj.data and 'cm_alt_topo' not in obj.data):
return None
return obj
def fitter_from_ctx(context):
return get_fitter(get_char(context))
def get_asset_obj(context):
return mesh_obj(context.window_manager.charmorph_ui.fitting_asset)
class OpFitLocal(bpy.types.Operator):
bl_idname = "charmorph.fit_local"
bl_label = "Fit local asset"
bl_description = "Fit selected local asset to the character"
bl_options = {"UNDO"}
@classmethod
def poll(cls, context):
if context.mode != "OBJECT":
return False
char = get_char(context)
if not char:
return False
asset = get_asset_obj(context)
if not asset or asset == char:
return False
return True
def execute(self, context): # pylint: disable=no-self-use
char = get_char(context)
asset = get_asset_obj(context)
if mm.morpher.core.obj is asset:
mm.create_charmorphs(char)
if context.window_manager.charmorph_ui.fitting_transforms:
utils.apply_transforms(asset, char)
get_fitter(char).fit_new((asset,))
return {"FINISHED"}
def fitExtPoll(context):
return context.mode == "OBJECT" and get_char(context)
class OpFitExternal(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
bl_idname = "charmorph.fit_external"
bl_label = "Fit from file"
bl_description = "Import and fit an asset from external .blend file"
bl_options = {"UNDO"}
filename_ext = ".blend"
filter_glob: bpy.props.StringProperty(default="*.blend", options={'HIDDEN'})
@classmethod
def poll(cls, context):
return fitExtPoll(context)
def execute(self, context):
name, _ = os.path.splitext(self.filepath)
if fitter_from_ctx(context).fit_import((Asset(name, self.filepath),)):
return {"FINISHED"}
self.report({'ERROR'}, "Import failed")
return {"CANCELLED"}
class OpFitLibrary(bpy.types.Operator):
bl_idname = "charmorph.fit_library"
bl_label = "Fit from library"
bl_description = "Import and fit an asset from library"
bl_options = {"UNDO"}
@classmethod
def poll(cls, context):
return fitExtPoll(context)
def execute(self, context):
asset_data = get_asset_conf(context)
if asset_data is None:
self.report({'ERROR'}, "Asset is not found")
return {"CANCELLED"}
if fitter_from_ctx(context).fit_import((asset_data,)):
return {"FINISHED"}
self.report({'ERROR'}, "Import failed")
return {"CANCELLED"}
class OpUnfit(bpy.types.Operator):
bl_idname = "charmorph.unfit"
bl_label = "Unfit"
bl_options = {"UNDO"}
@classmethod
def poll(cls, context):
asset = get_asset_obj(context)
return context.mode == "OBJECT" and asset and 'charmorph_fit_id' in asset.data
def execute(self, context): # pylint: disable=no-self-use
ui = context.window_manager.charmorph_ui
asset = get_asset_obj(context)
if asset.parent:
if ui.fitting_transforms:
utils.copy_transforms(asset, asset.parent)
asset.parent = asset.parent.parent
if asset.parent and asset.parent.type == "ARMATURE":
if ui.fitting_transforms:
utils.copy_transforms(asset, asset.parent) #FIXME: Make transforms sum
asset.parent = asset.parent.parent
mask = fitting.mask_name(asset)
for char in {asset.parent, ui.fitting_char}: # pylint: disable=use-sequence-for-iteration
if not char or char == asset or 'charmorph_fit_id' in char.data:
continue
f = get_fitter(char)
f.remove_cache(asset)
if mask in char.modifiers:
char.modifiers.remove(char.modifiers[mask])
if mask in char.vertex_groups:
char.vertex_groups.remove(char.vertex_groups[mask])
f.children = None
if "cm_mask_combined" in char.modifiers:
f.recalc_comb_mask()
if char.data.get("charmorph_asset_morphs"):
name = asset.data.get("charmorph_asset")
if name:
f.mcore.remove_asset_morph(name)
f.morpher.update()
try:
del asset.data['charmorph_fit_id']
except KeyError:
pass
if asset.data.shape_keys and "charmorph_fitting" in asset.data.shape_keys.key_blocks:
asset.shape_key_remove(asset.data.shape_keys.key_blocks["charmorph_fitting"])
mm.last_object = asset # Prevent swithing morpher to asset object
return {"FINISHED"}
classes = [OpFitLocal, OpUnfit, OpFitExternal, OpFitLibrary, CHARMORPH_PT_Assets]