570 lines
19 KiB
Python
570 lines
19 KiB
Python
|
|
"""
|
||
|
|
Maya interface for editing & testing patterns files
|
||
|
|
* Maya 2022+
|
||
|
|
* Qualoth
|
||
|
|
"""
|
||
|
|
|
||
|
|
# Basic
|
||
|
|
from functools import partial
|
||
|
|
from datetime import datetime
|
||
|
|
import os
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
# Maya
|
||
|
|
from maya import cmds
|
||
|
|
import maya.mel as mel
|
||
|
|
|
||
|
|
# My modules
|
||
|
|
from pygarment import mayaqltools as mymaya
|
||
|
|
from pygarment import data_config
|
||
|
|
|
||
|
|
|
||
|
|
# -------- Main call - Draw the UI -------------
|
||
|
|
def start_GUI():
|
||
|
|
"""Initialize interface"""
|
||
|
|
|
||
|
|
# Init state
|
||
|
|
state = State()
|
||
|
|
|
||
|
|
# init window
|
||
|
|
window_width = 450
|
||
|
|
main_offset = 10
|
||
|
|
win = cmds.window(
|
||
|
|
title="Garment Viewer", width=window_width,
|
||
|
|
closeCommand=win_closed_callback,
|
||
|
|
topEdge=15
|
||
|
|
)
|
||
|
|
cmds.columnLayout(columnAttach=('both', main_offset), rowSpacing=10, adj=1)
|
||
|
|
|
||
|
|
# ------ Draw GUI -------
|
||
|
|
# Pattern load
|
||
|
|
text_button_group(template_field_callback, state, label='Pattern spec: ', button_label='Load')
|
||
|
|
# body load
|
||
|
|
text_button_group(load_body_callback, state, label='Body file: ', button_label='Load')
|
||
|
|
# props load
|
||
|
|
text_button_group(load_props_callback, state, label='Properties: ', button_label='Load')
|
||
|
|
# scene setup load
|
||
|
|
text_button_group(load_scene_callback, state, label='Scene: ', button_label='Load')
|
||
|
|
# separate
|
||
|
|
cmds.separator()
|
||
|
|
|
||
|
|
# Pattern description
|
||
|
|
state.pattern_layout = cmds.columnLayout(
|
||
|
|
columnAttach=('both', 0), rowSpacing=main_offset, adj=1)
|
||
|
|
cmds.text(label='<pattern_here>', al='left')
|
||
|
|
cmds.setParent('..')
|
||
|
|
# separate
|
||
|
|
cmds.separator()
|
||
|
|
|
||
|
|
# Operations
|
||
|
|
equal_rowlayout(5, win_width=window_width, offset=(main_offset / 2))
|
||
|
|
cmds.button(label='Reload Spec', backgroundColor=[255 / 256, 169 / 256, 119 / 256],
|
||
|
|
command=partial(reload_garment_callback, state))
|
||
|
|
sim_button = cmds.button(label='Start Sim', backgroundColor=[227 / 256, 255 / 256, 119 / 256])
|
||
|
|
cmds.button(sim_button, edit=True,
|
||
|
|
command=partial(start_sim_callback, sim_button, state))
|
||
|
|
collisions_button = cmds.button(label='Collisions', backgroundColor=[250 / 256, 200 / 256, 119 / 256])
|
||
|
|
cmds.button(collisions_button, edit=True,
|
||
|
|
command=partial(check_collisions_callback, collisions_button, state))
|
||
|
|
segm_button = cmds.button(label='Segmentation', backgroundColor=[150 / 256, 225 / 256, 80 / 256])
|
||
|
|
cmds.button(segm_button, edit=True,
|
||
|
|
command=partial(display_segmentation_callback, segm_button, state))
|
||
|
|
scan_button = cmds.button(label='3D Scan', backgroundColor=[200 / 256, 225 / 256, 80 / 256])
|
||
|
|
cmds.button(scan_button, edit=True,
|
||
|
|
command=partial(imitate_3D_scan_callback, scan_button, state))
|
||
|
|
|
||
|
|
cmds.setParent('..')
|
||
|
|
# separate
|
||
|
|
cmds.separator()
|
||
|
|
|
||
|
|
# Saving folder
|
||
|
|
saving_to_field = text_button_group(saving_folder_callback, state,
|
||
|
|
label='Saving to: ', button_label='Choose')
|
||
|
|
# saving requests
|
||
|
|
equal_rowlayout(2, win_width=window_width, offset=main_offset)
|
||
|
|
cmds.button(label='Save snapshot', backgroundColor=[227 / 256, 255 / 256, 119 / 256],
|
||
|
|
command=partial(quick_save_callback, saving_to_field, state),
|
||
|
|
ann='Quick save with pattern spec and sim config')
|
||
|
|
cmds.button(label='Save with render', backgroundColor=[255 / 256, 140 / 256, 73 / 256],
|
||
|
|
command=partial(full_save_callback, saving_to_field, state),
|
||
|
|
ann='Full save with pattern spec, sim config, garment mesh & rendering')
|
||
|
|
cmds.setParent('..')
|
||
|
|
|
||
|
|
# Last
|
||
|
|
cmds.text(label='') # offset
|
||
|
|
|
||
|
|
# fin
|
||
|
|
cmds.showWindow(win)
|
||
|
|
|
||
|
|
|
||
|
|
# ----- State -------
|
||
|
|
class State(object):
|
||
|
|
def __init__(self):
|
||
|
|
self.pattern_layout = None # to be set on UI init
|
||
|
|
self.garment = None
|
||
|
|
self.scene = None
|
||
|
|
self.save_to = None
|
||
|
|
self.saving_prefix = None
|
||
|
|
self.body_file = None
|
||
|
|
self.config = data_config.Properties()
|
||
|
|
self.scenes_path = ''
|
||
|
|
self.segmented = False
|
||
|
|
mymaya.simulation.init_sim_props(self.config) # use default setup for simulation -- for now
|
||
|
|
|
||
|
|
def reload_garment(self):
|
||
|
|
"""Reloads garment Geometry & UI with current scene.
|
||
|
|
JSON is NOT loaded from disk as it's on-demand operation"""
|
||
|
|
if self.garment is None:
|
||
|
|
return
|
||
|
|
|
||
|
|
if self.scene is not None:
|
||
|
|
self.garment.load(
|
||
|
|
shader_group=self.scene.cloth_SG(),
|
||
|
|
obstacles=[self.scene.body], # self.scene.floor()],
|
||
|
|
config=self.config['sim']['config']
|
||
|
|
)
|
||
|
|
self.scene.reset_garment_color() # in case there was a segmentation display
|
||
|
|
else:
|
||
|
|
self.garment.load(config=self.config['sim']['config'])
|
||
|
|
|
||
|
|
# calling UI after loading for correct connection of attributes
|
||
|
|
self.garment.drawUI(self.pattern_layout)
|
||
|
|
|
||
|
|
def fetch(self):
|
||
|
|
"""Update info in deendent object from Maya"""
|
||
|
|
if self.scene is not None:
|
||
|
|
self.scene.fetch_props_from_Maya()
|
||
|
|
garment_conf = self.garment.fetchSimProps()
|
||
|
|
self.config.set_section_config(
|
||
|
|
'sim',
|
||
|
|
material=garment_conf['material'],
|
||
|
|
body_friction=garment_conf['body_friction'],
|
||
|
|
collision_thickness=garment_conf['collision_thickness']
|
||
|
|
)
|
||
|
|
|
||
|
|
def serialize(self, directory):
|
||
|
|
"""Serialize text-like objects"""
|
||
|
|
self.config.serialize(os.path.join(directory, 'sim_props.json'))
|
||
|
|
self.garment.serialize(
|
||
|
|
directory,
|
||
|
|
to_subfolder=False,
|
||
|
|
with_3d=False, with_text=False, view_ids=False,
|
||
|
|
empty_ok=True)
|
||
|
|
|
||
|
|
def save_scene(self, directory):
|
||
|
|
"""Save scene objects"""
|
||
|
|
self.garment.save_mesh(directory)
|
||
|
|
self.scene.render(directory, self.garment.name)
|
||
|
|
|
||
|
|
|
||
|
|
# ------- Errors --------
|
||
|
|
class CustomError(Exception):
|
||
|
|
def __init__(self, *args):
|
||
|
|
if args:
|
||
|
|
self.message = args[0]
|
||
|
|
else:
|
||
|
|
self.message = None
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
if self.message:
|
||
|
|
return(self.__class__.__name__ + ', {0} '.format(self.message))
|
||
|
|
else:
|
||
|
|
return(self.__class__.__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class SceneSavingError(CustomError):
|
||
|
|
def __init__(self, *args):
|
||
|
|
super(SceneSavingError, self).__init__(*args)
|
||
|
|
|
||
|
|
|
||
|
|
# --------- UI Drawing ----------
|
||
|
|
def equal_rowlayout(num_columns, win_width, offset):
|
||
|
|
"""Create new layout with given number of columns + extra columns for spacing"""
|
||
|
|
col_width = []
|
||
|
|
for col in range(1, num_columns + 1):
|
||
|
|
col_width.append((col, win_width / num_columns - offset))
|
||
|
|
|
||
|
|
col_attach = [(col, 'both', offset) for col in range(1, num_columns + 1)]
|
||
|
|
|
||
|
|
return cmds.rowLayout(
|
||
|
|
numberOfColumns=num_columns,
|
||
|
|
columnWidth=col_width,
|
||
|
|
columnAttach=col_attach,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def text_button_group(callback, state, label='', button_label='Click'):
|
||
|
|
"""Custom version of textFieldButtonGrp"""
|
||
|
|
cmds.rowLayout(nc=3, adj=2)
|
||
|
|
cmds.text(label=label)
|
||
|
|
text_field = cmds.textField(editable=False)
|
||
|
|
cmds.button(
|
||
|
|
label=button_label,
|
||
|
|
bgc=[0.99, 0.66, 0.46], # backgroundColor=[255 / 256, 169 / 256, 119 / 256],
|
||
|
|
command=partial(callback, text_field, state))
|
||
|
|
cmds.setParent('..')
|
||
|
|
return text_field
|
||
|
|
|
||
|
|
|
||
|
|
# ----- Callbacks -----
|
||
|
|
# -- Loading --
|
||
|
|
def sample_callback(text, *args):
|
||
|
|
print('Called ' + text)
|
||
|
|
|
||
|
|
|
||
|
|
def template_field_callback(view_field, state, *args):
|
||
|
|
"""Get the file with pattern"""
|
||
|
|
|
||
|
|
current_dir = os.path.dirname(cmds.textField(view_field, query=True, text=True))
|
||
|
|
|
||
|
|
multipleFilters = "JSON (*.json);;All Files (*.*)"
|
||
|
|
template_file = cmds.fileDialog2(
|
||
|
|
fileFilter=multipleFilters,
|
||
|
|
dialogStyle=2,
|
||
|
|
fileMode=1,
|
||
|
|
caption='Choose pattern specification file',
|
||
|
|
startingDirectory=current_dir
|
||
|
|
)
|
||
|
|
if not template_file: # do nothing
|
||
|
|
return
|
||
|
|
template_file = template_file[0]
|
||
|
|
|
||
|
|
cmds.textField(view_field, edit=True, text=template_file)
|
||
|
|
|
||
|
|
# create new grament
|
||
|
|
if state.garment is not None:
|
||
|
|
# Cleanup
|
||
|
|
state.garment.clean(delete=True)
|
||
|
|
|
||
|
|
state.garment = mymaya.MayaGarmentWithUI(template_file, True)
|
||
|
|
state.reload_garment()
|
||
|
|
|
||
|
|
|
||
|
|
def load_body_callback(view_field, state, *args):
|
||
|
|
"""Get body file & (re)init scene"""
|
||
|
|
current_dir = os.path.dirname(cmds.textField(view_field, query=True, text=True))
|
||
|
|
multipleFilters = "OBJ (*.obj);;All Files (*.*)"
|
||
|
|
file = cmds.fileDialog2(
|
||
|
|
fileFilter=multipleFilters,
|
||
|
|
dialogStyle=2,
|
||
|
|
fileMode=1,
|
||
|
|
caption='Choose body obj file',
|
||
|
|
startingDirectory=current_dir
|
||
|
|
)
|
||
|
|
if not file: # do nothing
|
||
|
|
return
|
||
|
|
|
||
|
|
file = file[0]
|
||
|
|
cmds.textField(view_field, edit=True, text=file)
|
||
|
|
|
||
|
|
state.config['body'] = os.path.basename(file) # update info
|
||
|
|
state.body_file = file
|
||
|
|
state.scene = mymaya.Scene(file, state.config['render'], scenes_path=state.scenes_path, clean_on_die=True) # previous scene will autodelete
|
||
|
|
state.reload_garment()
|
||
|
|
|
||
|
|
|
||
|
|
def load_props_callback(view_field, state, *args):
|
||
|
|
"""Load sim & renderign properties from file rather then use defaults"""
|
||
|
|
current_dir = os.path.dirname(cmds.textField(view_field, query=True, text=True))
|
||
|
|
multipleFilters = "JSON (*.json);;All Files (*.*)"
|
||
|
|
file = cmds.fileDialog2(
|
||
|
|
fileFilter=multipleFilters,
|
||
|
|
dialogStyle=2,
|
||
|
|
fileMode=1,
|
||
|
|
caption='Choose sim & rendering properties file',
|
||
|
|
startingDirectory=current_dir
|
||
|
|
)
|
||
|
|
if not file: # do nothing
|
||
|
|
return
|
||
|
|
|
||
|
|
file = file[0]
|
||
|
|
cmds.textField(view_field, edit=True, text=file)
|
||
|
|
|
||
|
|
# Edit the incoming config to reflect explicit choiced made in other UI elements
|
||
|
|
in_config = data_config.Properties(file)
|
||
|
|
|
||
|
|
# Use current body info instead of one from config
|
||
|
|
if state.body_file is not None:
|
||
|
|
in_config['body'] = os.path.basename(state.body_file)
|
||
|
|
|
||
|
|
# Use current scene info instead of one from config
|
||
|
|
if 'scene' not in state.config['render']['config']: # remove entirely
|
||
|
|
in_config['render']['config'].pop('scene', None)
|
||
|
|
else:
|
||
|
|
in_config['render']['config']['scene'] = state.config['render']['config']['scene']
|
||
|
|
|
||
|
|
# After the adjustments made, apply the new config to all elements
|
||
|
|
state.config = in_config
|
||
|
|
mymaya.simulation.init_sim_props(state.config) # fill the empty parts
|
||
|
|
|
||
|
|
if state.scene is not None:
|
||
|
|
state.scene = mymaya.Scene(
|
||
|
|
state.body_file, state.config['render'],
|
||
|
|
scenes_path=state.scenes_path, clean_on_die=True)
|
||
|
|
|
||
|
|
if state.garment is not None:
|
||
|
|
state.reload_garment()
|
||
|
|
|
||
|
|
|
||
|
|
def load_scene_callback(view_field, state, *args):
|
||
|
|
"""Load sim & renderign properties from file rather then use defaults"""
|
||
|
|
current_dir = os.path.dirname(cmds.textField(view_field, query=True, text=True))
|
||
|
|
multipleFilters = "MayaBinary (*.mb);;All Files (*.*)"
|
||
|
|
file = cmds.fileDialog2(
|
||
|
|
fileFilter=multipleFilters,
|
||
|
|
dialogStyle=2,
|
||
|
|
fileMode=1,
|
||
|
|
caption='Choose scene setup Maya file',
|
||
|
|
startingDirectory=current_dir
|
||
|
|
)
|
||
|
|
if not file: # do nothing
|
||
|
|
return
|
||
|
|
|
||
|
|
file = file[0]
|
||
|
|
cmds.textField(view_field, edit=True, text=file)
|
||
|
|
|
||
|
|
# Use current scene info instead of one from config
|
||
|
|
state.config['render']['config']['scene'] = os.path.basename(file)
|
||
|
|
state.scenes_path = os.path.dirname(file)
|
||
|
|
|
||
|
|
# Update scene with new config
|
||
|
|
if state.scene is not None:
|
||
|
|
# del state.scene
|
||
|
|
state.scene = mymaya.Scene(
|
||
|
|
state.body_file, state.config['render'],
|
||
|
|
scenes_path=state.scenes_path,
|
||
|
|
clean_on_die=True)
|
||
|
|
state.reload_garment()
|
||
|
|
|
||
|
|
|
||
|
|
def reload_garment_callback(state, *args):
|
||
|
|
"""
|
||
|
|
(re)loads current garment object to Maya if it exists
|
||
|
|
"""
|
||
|
|
if state.garment is not None:
|
||
|
|
state.garment.reloadJSON()
|
||
|
|
state.reload_garment()
|
||
|
|
|
||
|
|
|
||
|
|
# -- Operations --
|
||
|
|
def start_sim_callback(button, state, *args):
|
||
|
|
""" Start simulation """
|
||
|
|
if state.garment is None or state.scene is None:
|
||
|
|
cmds.confirmDialog(title='Error', message='Load pattern specification & body info first')
|
||
|
|
return
|
||
|
|
print('Simulating..')
|
||
|
|
|
||
|
|
# Reload geometry in case something changed
|
||
|
|
state.reload_garment()
|
||
|
|
|
||
|
|
mymaya.qualothwrapper.start_maya_sim(state.garment, state.config['sim'])
|
||
|
|
|
||
|
|
# Update button
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Stop Sim', backgroundColor=[245 / 256, 96 / 256, 66 / 256],
|
||
|
|
command=partial(stop_sim_callback, button, state))
|
||
|
|
|
||
|
|
|
||
|
|
def stop_sim_callback(button, state, *args):
|
||
|
|
"""Stop simulation execution"""
|
||
|
|
# toggle playback
|
||
|
|
cmds.play(state=False)
|
||
|
|
print('Simulation::Stopped')
|
||
|
|
# uppdate button state
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Start Sim', backgroundColor=[227 / 256, 255 / 256, 119 / 256],
|
||
|
|
command=partial(start_sim_callback, button, state))
|
||
|
|
|
||
|
|
cmds.select(state.garment.get_qlcloth_props_obj()) # for props change
|
||
|
|
|
||
|
|
|
||
|
|
def check_collisions_callback(button, state, *args):
|
||
|
|
"""Run removal of faces that might be invisible to 3D scanner"""
|
||
|
|
|
||
|
|
# indicate waiting for imitation finish
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Checking...', backgroundColor=[245 / 256, 96 / 256, 66 / 256],
|
||
|
|
command=partial(stop_sim_callback, button, state))
|
||
|
|
cmds.refresh(currentView=True)
|
||
|
|
|
||
|
|
cmds.confirmDialog(
|
||
|
|
title='Simulation quality info:',
|
||
|
|
message=(
|
||
|
|
'Simulation quality checks: \n\n'
|
||
|
|
'Garment intersect colliders: {} \n'
|
||
|
|
'Garment has self-intersections: {}').format(
|
||
|
|
'Yes' if state.garment.intersect_colliders_3D() else 'No',
|
||
|
|
'Yes' if state.garment.self_intersect_3D(verbose=True) else 'No'),
|
||
|
|
button=['Ok'], defaultButton='Ok', cancelButton='Ok', dismissString='Ok')
|
||
|
|
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Collisions', backgroundColor=[250 / 256, 200 / 256, 119 / 256],
|
||
|
|
command=partial(check_collisions_callback, button, state))
|
||
|
|
|
||
|
|
|
||
|
|
def imitate_3D_scan_callback(button, state, *args):
|
||
|
|
"""Run removal of faces that might be invisible to 3D scanner"""
|
||
|
|
|
||
|
|
# indicate waiting for imitation finish
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Scanning...', backgroundColor=[245 / 256, 96 / 256, 66 / 256])
|
||
|
|
cmds.refresh(currentView=True)
|
||
|
|
|
||
|
|
if 'scan_imitation' in state.config:
|
||
|
|
num_rays = state.config['scan_imitation']['config']['test_rays_num']
|
||
|
|
vis_rays = state.config['scan_imitation']['config']['visible_rays_num']
|
||
|
|
mymaya.scan_imitation.remove_invisible(
|
||
|
|
state.garment.get_qlcloth_geomentry(),
|
||
|
|
[state.scene.body] if state.scene is not None else [],
|
||
|
|
num_rays, vis_rays
|
||
|
|
)
|
||
|
|
else: # go with function defaults
|
||
|
|
mymaya.scan_imitation.remove_invisible(
|
||
|
|
state.garment.get_qlcloth_geomentry(),
|
||
|
|
[state.scene.body] if state.scene is not None else []
|
||
|
|
)
|
||
|
|
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='3D Scan', backgroundColor=[200 / 256, 225 / 256, 80 / 256],
|
||
|
|
command=partial(imitate_3D_scan_callback, button, state))
|
||
|
|
|
||
|
|
|
||
|
|
def display_segmentation_callback(button, state, *args):
|
||
|
|
"""
|
||
|
|
Visualize the segmentation labels
|
||
|
|
"""
|
||
|
|
if not state.segmented:
|
||
|
|
# indicate waiting for imitation finish
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Segmenting...', backgroundColor=[245 / 256, 96 / 256, 66 / 256])
|
||
|
|
cmds.refresh(currentView=True)
|
||
|
|
|
||
|
|
state.garment.display_vertex_segmentation(state.scene.scene['cloth_shader'])
|
||
|
|
|
||
|
|
print('Segmentation displayed!')
|
||
|
|
state.segmented = True
|
||
|
|
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Color', backgroundColor=[150 / 256, 225 / 256, 80 / 256],
|
||
|
|
command=partial(display_segmentation_callback, button, state))
|
||
|
|
else:
|
||
|
|
state.scene.reset_garment_color()
|
||
|
|
state.segmented = False
|
||
|
|
cmds.button(button, edit=True,
|
||
|
|
label='Segmentation', backgroundColor=[150 / 256, 225 / 256, 80 / 256],
|
||
|
|
command=partial(display_segmentation_callback, button, state))
|
||
|
|
|
||
|
|
|
||
|
|
# -- Saving ---
|
||
|
|
def win_closed_callback(*args):
|
||
|
|
"""Clean-up"""
|
||
|
|
# Remove solver objects from the scene
|
||
|
|
cmds.delete(cmds.ls('qlSolver*'))
|
||
|
|
# Other created objects will be automatically deleted through destructors
|
||
|
|
|
||
|
|
|
||
|
|
def saving_folder_callback(view_field, state, *args):
|
||
|
|
"""Choose folder to save files to"""
|
||
|
|
|
||
|
|
current_dir = cmds.textField(view_field, query=True, text=True)
|
||
|
|
|
||
|
|
directory = cmds.fileDialog2(
|
||
|
|
dialogStyle=2,
|
||
|
|
fileMode=3, # directories
|
||
|
|
caption='Choose folder to save snapshots and renderings to',
|
||
|
|
startingDirectory=current_dir
|
||
|
|
)
|
||
|
|
if not directory: # do nothing
|
||
|
|
return
|
||
|
|
|
||
|
|
directory = directory[0]
|
||
|
|
cmds.textField(view_field, edit=True, text=directory)
|
||
|
|
|
||
|
|
state.save_to = directory
|
||
|
|
|
||
|
|
# request saving prefix
|
||
|
|
tag_result = cmds.promptDialog(
|
||
|
|
t='Enter a saving prefix',
|
||
|
|
m='Enter a saving prefix:',
|
||
|
|
button=['OK', 'Cancel'],
|
||
|
|
defaultButton='OK',
|
||
|
|
cancelButton='Cancel',
|
||
|
|
dismissString='Cancel'
|
||
|
|
)
|
||
|
|
if tag_result == 'OK':
|
||
|
|
tag = cmds.promptDialog(query=True, text=True)
|
||
|
|
state.saving_prefix = tag
|
||
|
|
else:
|
||
|
|
state.saving_prefix = None
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def _new_dir(root_dir, tag='snap'):
|
||
|
|
"""create fresh directory for saving files"""
|
||
|
|
folder = tag + '_' + datetime.now().strftime('%y%m%d-%H-%M-%S')
|
||
|
|
path = os.path.join(root_dir, folder)
|
||
|
|
os.makedirs(path)
|
||
|
|
return path
|
||
|
|
|
||
|
|
|
||
|
|
def _create_saving_dir(view_field, state):
|
||
|
|
"""Create directory to save to """
|
||
|
|
|
||
|
|
if state.garment is None:
|
||
|
|
cmds.confirmDialog(title='Error', message='Load pattern specification first')
|
||
|
|
raise SceneSavingError('Garment is not loaded before saving')
|
||
|
|
|
||
|
|
if state.save_to is None:
|
||
|
|
if not saving_folder_callback(view_field, state):
|
||
|
|
raise SceneSavingError('Saving folder not supplied')
|
||
|
|
|
||
|
|
if state.saving_prefix is not None:
|
||
|
|
tag = state.saving_prefix
|
||
|
|
else:
|
||
|
|
tag = state.garment.name
|
||
|
|
|
||
|
|
new_dir = _new_dir(state.save_to, tag)
|
||
|
|
|
||
|
|
return new_dir
|
||
|
|
|
||
|
|
|
||
|
|
def quick_save_callback(view_field, state, *args):
|
||
|
|
"""Quick save with pattern spec and sim config"""
|
||
|
|
try:
|
||
|
|
new_dir = _create_saving_dir(view_field, state)
|
||
|
|
except SceneSavingError:
|
||
|
|
return
|
||
|
|
|
||
|
|
state.fetch()
|
||
|
|
state.serialize(new_dir)
|
||
|
|
|
||
|
|
state.garment.save_mesh(new_dir)
|
||
|
|
|
||
|
|
print('Garment info saved to ' + new_dir)
|
||
|
|
|
||
|
|
|
||
|
|
def full_save_callback(view_field, state, *args):
|
||
|
|
"""Full save with pattern spec, sim config, garment mesh & rendering"""
|
||
|
|
|
||
|
|
if state.garment is None or state.scene is None:
|
||
|
|
cmds.confirmDialog(title='Error', message='Load pattern specification & body info first')
|
||
|
|
return
|
||
|
|
|
||
|
|
# do the same as for quick save
|
||
|
|
try:
|
||
|
|
new_dir = _create_saving_dir(view_field, state)
|
||
|
|
except SceneSavingError:
|
||
|
|
return
|
||
|
|
|
||
|
|
# save scene objects
|
||
|
|
state.save_scene(new_dir)
|
||
|
|
|
||
|
|
# save text properties
|
||
|
|
state.fetch()
|
||
|
|
state.serialize(new_dir)
|
||
|
|
|
||
|
|
print('Pattern spec, props, 3D mesh & render saved to ' + new_dir)
|
||
|
|
|
||
|
|
cmds.select(state.garment.get_qlcloth_props_obj()) # for props change
|