"""Routines to run cloth simulation in Maya + Qualoth""" # Basic import time import os # Maya from maya import cmds # My modules from pygarment.pattern.core import BasicPattern import pygarment.mayaqltools as mymaya from pygarment.mayaqltools import qualothwrapper as qw # ----------- High-level requests -------------- # TODO Deprecated def single_file_sim(resources, props, caching=False): """ Simulates the given template and puts the results in original template folder, including config and statistics """ try: # ----- Init ----- init_sim_props(props, True) qw.load_plugin() scene = mymaya.Scene( os.path.join(resources['bodies_path'], props['body']), props['render'], scenes_path=resources['scenes_path']) # Main part template_simulation(os.path.join(resources['templates_path'], props['templates']), scene, props['sim'], caching=caching) # Fin print('\nFinished experiment') try: # remove unnecessaty field del props['sim']['stats']['processed'] except KeyError: pass props.serialize(os.path.join(resources['templates_path'], 'props.json')) except Exception as e: print(e) def batch_sim(resources, data_path, dataset_props, num_samples=None, caching=False, force_restart=False): """ Performs pattern simulation for each example in the dataset given by dataset_props. Batch processing is automatically resumed from the last unporcessed datapoint if restart is not forced. The last example on the processes list is assumed to cause the failure, so it can be later found in failure cases. Parameters: * resources -- dict of paths to needed resoursed: * body_path -- path to folder with body meshes * data_path -- path to folder with the dataset * scenes_path -- path to folder with rendering scenes * dataset_props -- dataset properties. Properties has to be of custom data_config.Properties() class and contain * dataset folder (inside data_path) * name of pattern template * name of body .obj file * type of dataset structure (with/without subfolders for patterns) * list of processed samples if processing of dataset was allready attempted Other needed properties will be filles with default values if the corresponding sections are not found in props object * num_samples -- number of (unprocessed) samples from dataset to process with this run. If None, runs over all unprocessed samples * caching -- enables caching of every frame of simulation (disabled by default) * force_restart -- force restarting the batch processing even if resume conditions are met. """ # ----- Init ----- if 'frozen' in dataset_props and dataset_props['frozen']: # avoid accidential re-runs of data print('WARNING: dataset is frozen, processing is skipped') return True resume = init_sim_props(dataset_props, batch_run=True, force_restart=force_restart) qw.load_plugin() scene = mymaya.Scene( os.path.join(resources['bodies_default_path'], dataset_props['body']), dataset_props['render'], scenes_path=resources['scenes_path']) pattern_specs = _get_pattern_files(data_path, dataset_props) data_props_file = os.path.join(data_path, 'dataset_properties.json') # Simulate every template count = 0 for pattern_spec in pattern_specs: # skip processed cases -- in case of resume. First condition needed to skip checking second one on False =) pattern_spec_norm = os.path.normpath(pattern_spec) pattern_name = BasicPattern.name_from_path(pattern_spec_norm) if resume and pattern_name in dataset_props['sim']['stats']['processed']: print('Skipped as already processed {}'.format(pattern_spec_norm)) continue dataset_props['sim']['stats']['processed'].append(pattern_name) _serialize_props_with_sim_stats(dataset_props, data_props_file) # save info of processed files before potential crash template_simulation(pattern_spec_norm, scene, dataset_props['sim'], delete_on_clean=True, # delete geometry after sim as we don't need it any more caching=caching, save_maya_scene=False) if pattern_name in dataset_props['sim']['stats']['fails']['crashes']: # if we successfully finished simulating crashed example -- it's not a crash any more! print('Crash successfully resimulated!') dataset_props['sim']['stats']['fails']['crashes'].remove(pattern_name) count += 1 # count actively processed cases if num_samples is not None and count >= num_samples: # only process requested number of samples break # Fin print('\nFinished batch of ' + os.path.basename(data_path)) try: if len(dataset_props['sim']['stats']['processed']) >= len(pattern_specs): # processing successfully finished -- no need to resume later del dataset_props['sim']['stats']['processed'] dataset_props['frozen'] = True process_finished = True else: process_finished = False except KeyError: print('KeyError -processed-') process_finished = True pass # Logs _serialize_props_with_sim_stats(dataset_props, data_props_file) return process_finished # ------- Utils ------- def init_sim_props(props, batch_run=False, force_restart=False): """ Add default config values if not given in props & clean-up stats if not resuming previous processing Returns a flag wheter current simulation is a resumed last one """ if 'sim' not in props: props.set_section_config( 'sim', max_sim_steps=500, zero_gravity_steps=5, # time to assembly static_threshold=0.05, # 0.01 # depends on the units used, non_static_percent=1, material={}, body_friction=0.5, resolution_scale=5 ) if 'material' not in props['sim']['config']: props['sim']['config']['material'] = {} if 'render' not in props: # init with defaults props.set_section_config( 'render', resolution=[800, 800] ) if batch_run and 'processed' in props['sim']['stats'] and not force_restart: # resuming existing batch processing -- do not clean stats # Assuming the last example processed example caused the failure last_processed = props['sim']['stats']['processed'][-1] props['sim']['stats']['stop_over'].append(last_processed) # indicate resuming dataset simulation if not any([(name in last_processed) or (last_processed in name) for name in props['render']['stats']['render_time']]): # crash detected -- the last example does not appear in the stats if last_processed not in props['sim']['stats']['fails']['crashes']: # first time to crash here -- try to re-do this example => remove from visited props['sim']['stats']['processed'].pop() props['sim']['stats']['fails']['crashes'].append(last_processed) # else we crashed here before -- do not re-try + leave in crashed list return True # else new life # Prepare commulative stats props.set_section_stats('sim', fails={}, sim_time={}, spf={}, fin_frame={}) props['sim']['stats']['fails'] = { 'crashes': [], 'intersect_colliders': [], 'intersect_self': [], 'static_equilibrium': [], 'fast_finish': [], 'pattern_loading': [] } props.set_section_stats('render', render_time={}) if batch_run: # track batch processing props.set_section_stats('sim', processed=[], stop_over=[]) return False def template_simulation(spec, scene, sim_props, delete_on_clean=False, caching=False, save_maya_scene=False): """ Simulate given template within given scene & save log files """ print('\nGarment load') garment = mymaya.MayaGarment(spec) try: garment.load( shader_group=scene.cloth_SG(), obstacles=[scene.body], # I don't add floor s.t. garment falls infinitely if falls config=sim_props['config'] ) except mymaya.PatternLoadingError as e: # record error and skip subequent processing sim_props['stats']['fails']['pattern_loading'].append(garment.name) else: # garment.save_mesh(tag='stitched') # Saving the geometry before eny forces were applied garment.sim_caching(caching) qw.run_sim(garment, sim_props) # save even if sim failed -- to see what happened! garment.save_mesh(tag='sim') scene.render(garment.path, garment.name) if save_maya_scene: # save current Maya scene cmds.file(rename=os.path.join(garment.path, garment.name + '_scene')) cmds.file(save=True, type='mayaBinary', force=True, defaultExtensions=True) garment.clean(delete_on_clean) def _serialize_props_with_sim_stats(dataset_props, filename): """Compute data processing statistics and serialize props to file""" dataset_props.stats_summary() dataset_props.serialize(filename) def _get_pattern_files(data_path, dataset_props): """ Collects paths to all the pattern files in given folder""" to_ignore = ['renders'] # special dirs not to include in the pattern list pattern_specs = [] root, dirs, files = next(os.walk(data_path)) if dataset_props['to_subfolders']: # https://stackoverflow.com/questions/800197/how-to-get-all-of-the-immediate-subdirectories-in-python for directory in dirs: if directory not in to_ignore: pattern_specs.append(os.path.join(root, directory, 'specification.json')) # cereful for file name changes ^^ else: for file in files: # NOTE filtering might not be very robust if ('.json' in file and 'specification' in file and 'template' not in file): pattern_specs.append(os.path.normpath(os.path.join(root, file))) return pattern_specs