init_code
This commit is contained in:
413
pygarment/meshgen/datasim_utils.py
Normal file
413
pygarment/meshgen/datasim_utils.py
Normal file
@@ -0,0 +1,413 @@
|
||||
"""Routines to run cloth simulation"""
|
||||
|
||||
# Basic
|
||||
import time
|
||||
import multiprocessing
|
||||
import platform
|
||||
import signal
|
||||
from pathlib import Path
|
||||
|
||||
# BoxMeshGen
|
||||
import pygarment.meshgen.boxmeshgen as bmg
|
||||
from pygarment.meshgen.boxmeshgen import BoxMesh
|
||||
from pygarment.meshgen.sim_config import PathCofig
|
||||
|
||||
# Warp simulation
|
||||
from pygarment.meshgen.simulation import run_sim
|
||||
|
||||
|
||||
def batch_sim(data_path, output_path, dataset_props,
|
||||
run_default_body=False, 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:
|
||||
* data_path -- path to folder with patterns (for given body type)
|
||||
* output_path -- path to folder with the sumulated dataset
|
||||
* dataset_props -- dataset properties. Properties has to be of custom data_config.Properties() class and contain
|
||||
* dataset folder (inside data_path)
|
||||
* type of dataset structure (with/without subfolders for patterns)
|
||||
* list of processed samples if processing of dataset was already attempted
|
||||
* Simulation parameters
|
||||
* Rendering parameters
|
||||
Other needed properties will be files with default values if the corresponding sections
|
||||
are not found in props object
|
||||
* run_default_body -- runs the dataset on the default body (disabled by default)
|
||||
* 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)
|
||||
body_type = 'default_body' if run_default_body else 'random_body'
|
||||
data_props_file = output_path / f'dataset_properties_{body_type}.yaml'
|
||||
pattern_names = _get_pattern_names(data_path)
|
||||
|
||||
# Simulate every template
|
||||
count = 0
|
||||
for pattern_name in pattern_names:
|
||||
# skip processed cases -- in case of resume. First condition needed to skip checking second one on False =)
|
||||
if resume and pattern_name in dataset_props['sim']['stats']['processed']:
|
||||
print(f'Skipped as already processed {pattern_name}')
|
||||
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
|
||||
|
||||
try:
|
||||
paths = PathCofig(
|
||||
in_element_path=data_path / pattern_name,
|
||||
out_path=output_path,
|
||||
in_name=pattern_name,
|
||||
body_name=dataset_props['body_default'],
|
||||
samples_name=dataset_props['body_samples'],
|
||||
default_body=run_default_body
|
||||
)
|
||||
except BaseException as e:
|
||||
# Not all files available
|
||||
print("***Pattern loading failed (paths)***")
|
||||
dataset_props.add_fail('sim', 'crashes', pattern_name)
|
||||
else:
|
||||
template_simulation(paths, dataset_props, caching=caching)
|
||||
|
||||
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(f'\nFinished batch of {data_path}')
|
||||
try:
|
||||
if len(dataset_props['sim']['stats']['processed']) >= len(pattern_names):
|
||||
# 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
|
||||
|
||||
|
||||
def resim_fails(data_path, output_path, dataset_props,
|
||||
run_default_body=False, caching=False):
|
||||
"""Resimulate failure cases -- maybe some of them would get fixed"""
|
||||
|
||||
print('************** RESIMULATING FAILS ****************')
|
||||
|
||||
sim_stats = dataset_props['sim']['stats']
|
||||
|
||||
# Collect fails and remove them from fails list if any
|
||||
fails = sim_stats['fails']
|
||||
to_resim = set()
|
||||
for key in fails:
|
||||
if key not in ['cloth_body_intersection', 'cloth_self_intersection']:
|
||||
for el in fails[key]:
|
||||
to_resim.add(el)
|
||||
fails[key] = [] # NOTE: If nothing to be added in this key, it was already an empty array (and nothing changed)
|
||||
|
||||
if not len(to_resim):
|
||||
# Return previous finished state
|
||||
return dataset_props['frozen'] if 'frozen' in dataset_props else False
|
||||
|
||||
if 'processed' not in sim_stats:
|
||||
sim_stats['processed'] = _get_pattern_names(data_path)
|
||||
dataset_props['frozen'] = False
|
||||
|
||||
# Remove fails from processed to trigger re-simulation
|
||||
for sample in to_resim:
|
||||
sim_stats['processed'].remove(sample)
|
||||
|
||||
# Start simulation again
|
||||
finished = batch_sim(
|
||||
data_path, output_path, dataset_props,
|
||||
run_default_body=run_default_body,
|
||||
num_samples=len(to_resim)+1,
|
||||
caching=caching,
|
||||
force_restart=False
|
||||
)
|
||||
|
||||
return 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 whether current simulation is a resumed last one
|
||||
"""
|
||||
if 'sim' not in props:
|
||||
props.set_section_config(
|
||||
'sim',
|
||||
max_sim_steps=1000, #affects speed
|
||||
max_meshgen_time=20, #in seconds, affects speed
|
||||
max_frame_time= 15, #in seconds, affects speed
|
||||
max_sim_time= 1500, #in seconds, affects speed
|
||||
zero_gravity_steps=10, # 0.01 # depends on the units used, #affects speed
|
||||
static_threshold=0.03, #affects speed
|
||||
non_static_percent=1.5, #affects speed
|
||||
max_body_collisions=0,
|
||||
max_self_collisions=0,
|
||||
resolution_scale=1.0, #affects speed
|
||||
ground=False, # Do not add floor s.t. garment falls infinitely if falls
|
||||
)
|
||||
|
||||
if 'material' not in props['sim']['config']:
|
||||
props['sim']['config']['material'] = {
|
||||
'garment_tri_ka': 10000.0,
|
||||
|
||||
'garment_edge_ke': 1.0,
|
||||
'garment_tri_ke': 10000.0,
|
||||
'spring_ke': 50000.0,
|
||||
|
||||
'garment_edge_kd': 10.0,
|
||||
'garment_tri_kd': 1.0,
|
||||
'spring_kd': 10.0,
|
||||
|
||||
'fabric_density': 1.0,
|
||||
'fabric_thickness': 0.1,
|
||||
'fabric_friction': 0.5
|
||||
|
||||
}
|
||||
|
||||
if 'options' not in props['sim']['config']:
|
||||
props['sim']['config']['options'] = {
|
||||
'enable_particle_particle_collisions': False,
|
||||
'enable_triangle_particle_collisions': True,
|
||||
'enable_edge_edge_collisions': True,
|
||||
'enable_body_collision_filters': True,
|
||||
|
||||
'enable_attachment_constraint': True,
|
||||
'attachment_frames': 400,
|
||||
'attachment_label_names': ['lower_interface'],
|
||||
'attachment_stiffness': [1000.],
|
||||
'attachment_damping': [10.],
|
||||
|
||||
'global_damping_factor': 0.25,
|
||||
'global_damping_effective_velocity': 0.0,
|
||||
'global_max_velocity': 25.0,
|
||||
|
||||
'enable_global_collision_filter': True,
|
||||
'enable_cloth_reference_drag': False,
|
||||
'cloth_reference_margin': 0.1,
|
||||
|
||||
# FIXME Re-writes mesh references causing occasional CUDA errors when referencing meshes other than the body
|
||||
'enable_body_smoothing': False,
|
||||
'smoothing_total_smoothing_factor': 1.0,
|
||||
'smoothing_recover_start_frame': 150,
|
||||
'smoothing_num_steps': 100,
|
||||
'smoothing_frame_gap_between_steps': 1,
|
||||
|
||||
'body_collision_thickness': 0.25,
|
||||
'body_friction': 0.5
|
||||
}
|
||||
|
||||
if 'render' not in props:
|
||||
# init with defaults
|
||||
props.set_section_config(
|
||||
'render',
|
||||
resolution=[800, 800],
|
||||
sides=['front','back'],
|
||||
front_camera_location=None,
|
||||
uv_texture={
|
||||
'seam_width': 0.5,
|
||||
'dpi': 1500,
|
||||
'fabric_grain_texture_path': None,
|
||||
'fabric_grain_resolution': 5,
|
||||
}
|
||||
)
|
||||
|
||||
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]
|
||||
|
||||
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']:
|
||||
# add to simulation failures
|
||||
# Remove last from processed if it did not crash
|
||||
if last_processed not in props['sim']['stats']['stop_over']:
|
||||
props['sim']['stats']['processed'].pop()
|
||||
else:
|
||||
# Already passed here once -> add as crash
|
||||
props['sim']['stats']['fails']['crashes'].append(last_processed)
|
||||
|
||||
props['sim']['stats']['stop_over'].append(last_processed) # indicate resuming dataset simulation
|
||||
|
||||
|
||||
return True
|
||||
|
||||
# else new life
|
||||
# Prepare commulative stats
|
||||
props.set_section_stats('sim',
|
||||
fails={},
|
||||
meshgen_time={},
|
||||
sim_time={},
|
||||
spf={},
|
||||
fin_frame={},
|
||||
face_count={},
|
||||
body_collisions={},
|
||||
self_collisions={})
|
||||
props['sim']['stats']['fails'] = {
|
||||
'crashes': [],
|
||||
'cloth_body_intersection': [],
|
||||
'cloth_self_intersection': [],
|
||||
'static_equilibrium': [],
|
||||
'fast_finish': [],
|
||||
'pattern_loading': [],
|
||||
'multi_stitching': [],
|
||||
'gt_edges_creation': []
|
||||
|
||||
}
|
||||
|
||||
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(paths: PathCofig, props, caching=False):
|
||||
"""
|
||||
Simulate given template within given scene & save log files
|
||||
"""
|
||||
sim_props = props['sim']
|
||||
res = sim_props['config']['resolution_scale']
|
||||
|
||||
garment = BoxMesh(paths.in_g_spec, res)
|
||||
|
||||
print('\n-----------------------------'
|
||||
'\nLoading garment: ', garment.name)
|
||||
|
||||
meshgen_start_time = time.time()
|
||||
timeout_after = int(get_dict_default_value(sim_props['config'], 'max_meshgen_time', 20))
|
||||
|
||||
try:
|
||||
_load_boxmesh_timeout(garment, timeout_after)
|
||||
except TimeoutError as e:
|
||||
print(e)
|
||||
failure_case = 'meshgen-timeout'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
except bmg.PatternLoadingError as e:
|
||||
# record error and skip subequent processing
|
||||
print(e)
|
||||
failure_case = 'pattern_loading'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
except bmg.DegenerateTrianglesError as e:
|
||||
print(e)
|
||||
failure_case = 'degenerate_triangles'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
except bmg.MultiStitchingError as e:
|
||||
print(e)
|
||||
failure_case = 'multi_stitching'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
except bmg.NormError as e:
|
||||
print(e)
|
||||
failure_case = 'norm_error'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
except bmg.StitchingError as e:
|
||||
print(e)
|
||||
failure_case = 'stitching_error'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
except BaseException as e: # Catch the rest of exceptions
|
||||
print("***Pattern loading failed due to unknown error***")
|
||||
print(e)
|
||||
failure_case = 'crashes'
|
||||
props.add_fail('sim', failure_case, garment.name)
|
||||
else:
|
||||
# garment.save_mesh(tag='stitched') # Saving the geometry before eny forces were applied
|
||||
sim_props['stats']['meshgen_time'][garment.name] = time.time() - meshgen_start_time
|
||||
sim_props['stats']['face_count'][garment.name] = len(garment.faces)
|
||||
sim_props_option = sim_props['config']['options']
|
||||
|
||||
vertex_normals = get_dict_default_value(sim_props_option,'store_vertex_normals',False)
|
||||
store_panels = get_dict_default_value(sim_props_option,'store_panels',False)
|
||||
garment.serialize(
|
||||
paths,
|
||||
with_v_norms=vertex_normals,
|
||||
store_panels=store_panels,
|
||||
uv_config=props['render']['config']['uv_texture']
|
||||
)
|
||||
|
||||
run_sim(
|
||||
garment.name,
|
||||
props,
|
||||
paths,
|
||||
save_v_norms=vertex_normals,
|
||||
store_usd=caching, # NOTE: False for fast simulation!,
|
||||
optimize_storage=sim_props['config']['optimize_storage'],
|
||||
verbose=False
|
||||
)
|
||||
|
||||
def _load_boxmesh_timeout(garment, timeout_after):
|
||||
if platform.system() == "Windows":
|
||||
"""https://stackoverflow.com/a/14920854"""
|
||||
p = multiprocessing.Process(target=garment.load(), name="GarmentGeneration")
|
||||
p.start()
|
||||
|
||||
# Wait timeout_after seconds for garment.load()
|
||||
time.sleep(timeout_after)
|
||||
|
||||
# If thread is active
|
||||
if p.is_alive():
|
||||
# Terminate the process
|
||||
p.terminate()
|
||||
p.join()
|
||||
raise TimeoutError
|
||||
|
||||
elif platform.system() in ["Linux", "OSX"]:
|
||||
"""https://code-maven.com/python-timeout"""
|
||||
def alarm_handler(signum, frame):
|
||||
raise TimeoutError
|
||||
|
||||
signal.signal(signal.SIGALRM, alarm_handler)
|
||||
signal.alarm(timeout_after)
|
||||
s_time = time.time()
|
||||
try:
|
||||
garment.load()
|
||||
except TimeoutError as ex:
|
||||
raise TimeoutError
|
||||
else:
|
||||
e_time = time.time() - s_time
|
||||
# print("No timeout error with time: ",e_time)
|
||||
signal.alarm(0)
|
||||
|
||||
|
||||
def get_dict_default_value(props, name, default_value):
|
||||
if name in props:
|
||||
return props[name]
|
||||
return default_value
|
||||
|
||||
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_names(data_path: Path):
|
||||
names = []
|
||||
to_ignore = ['renders'] # special dirs not to include in the pattern list
|
||||
for el in data_path.iterdir():
|
||||
if el.is_dir() and el.stem not in to_ignore:
|
||||
names.append(el.stem)
|
||||
|
||||
return names
|
||||
Reference in New Issue
Block a user