init_code
This commit is contained in:
401
gui/gui_pattern.py
Normal file
401
gui/gui_pattern.py
Normal file
@@ -0,0 +1,401 @@
|
||||
from pathlib import Path
|
||||
import time
|
||||
import yaml
|
||||
import shutil
|
||||
import string
|
||||
import random
|
||||
import trimesh
|
||||
from copy import deepcopy
|
||||
from typing import Optional
|
||||
from lmm_utils.core import MMUA
|
||||
# Custom
|
||||
from assets.garment_programs.meta_garment import MetaGarment
|
||||
from assets.bodies.body_params import BodyParameters
|
||||
import pygarment as pyg
|
||||
from pygarment.meshgen.boxmeshgen import BoxMesh
|
||||
# from pygarment.meshgen.simulation import run_sim
|
||||
import pygarment.data_config as data_config
|
||||
from pygarment.meshgen.sim_config import PathCofig
|
||||
|
||||
|
||||
|
||||
verbose = False
|
||||
|
||||
def _id_generator(size=10, chars=string.ascii_uppercase + string.digits):
|
||||
"""Generate a random string of a given size, see
|
||||
https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits
|
||||
"""
|
||||
return ''.join(random.choices(chars, k=size))
|
||||
|
||||
|
||||
class GUIPattern:
|
||||
def __init__(self) -> None:
|
||||
# Unique id to distiguish tab sessions correctly
|
||||
self.id = _id_generator(20)
|
||||
|
||||
# Paths setup
|
||||
self.save_path_root = Path.cwd() / 'tmp_gui' / 'downloads'
|
||||
self.tmp_path_root = Path.cwd() / 'tmp_gui' / 'display'
|
||||
self.save_path = self.save_path_root / self.id
|
||||
self.svg_filename = None
|
||||
self.saved_garment_archive = ''
|
||||
self.saved_garment_folder = ''
|
||||
self.tmp_path = self.tmp_path_root / self.id
|
||||
self.paths_3d = None
|
||||
|
||||
# create paths
|
||||
self.save_path.mkdir(parents=True, exist_ok=True)
|
||||
self.tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.body_params = None
|
||||
self.design_params = {}
|
||||
self.design_list = []
|
||||
self.design_sampler = pyg.DesignSampler()
|
||||
self.sew_pattern = None
|
||||
|
||||
self.body_id = 'mean_all'
|
||||
self.body_file = None
|
||||
self.design_file = None
|
||||
self._load_body_file(
|
||||
Path.cwd() / f'assets/bodies/{self.body_id}.yaml'
|
||||
)
|
||||
self.default_body_params = deepcopy(self.body_params)
|
||||
self._load_design_file(
|
||||
Path.cwd() / 'assets/design_params/default.yaml'
|
||||
)
|
||||
|
||||
# Status
|
||||
self.is_self_intersecting = False
|
||||
self.is_in_3D = False
|
||||
|
||||
self.reload_garment()
|
||||
# Init Agent
|
||||
self.agent=None
|
||||
|
||||
def parse_chat(self, text_prompt='', img_url='',api_key=None, base_url=None, model=None,text_model=None):
|
||||
print('Prompt: ', text_prompt)
|
||||
print('Image: ', img_url)
|
||||
self.agent.mmua=MMUA(api_key=api_key, base_url=base_url, model=model,text_model=text_model)
|
||||
# self.agent=Agent(api_key=api_key, base_url=base_url, model=model,text_model=text_model)
|
||||
text_prompt = text_prompt.strip()
|
||||
modify_mode = 'modify' in text_prompt or 'm:' in text_prompt
|
||||
stress_mode='stress' in text_prompt or 's:' in text_prompt
|
||||
|
||||
# try:
|
||||
# Modify mode
|
||||
if modify_mode:
|
||||
assert self.design_params and self.design_list, "Must provide a base design under mofify mode."
|
||||
gpt_response,gpt_design_params,gpt_design_list=self.agent.modify_design(self.design_list,text_prompt=text_prompt,design_params=self.design_params)
|
||||
# Stress mode:
|
||||
elif stress_mode:
|
||||
assert self.design_params and self.design_list, "Must provide a base design under mofify mode."
|
||||
gpt_response,gpt_design_params,gpt_design_list=self.agent.stress_design(self.design_list,img_url=img_url,design_params=self.design_params)
|
||||
# Inference from image and text
|
||||
elif text_prompt and img_url:
|
||||
gpt_response,gpt_design_params,gpt_design_list=self.agent.picture_text_design(img_url,text_prompt)
|
||||
|
||||
# Inference from text only
|
||||
elif text_prompt and not img_url:
|
||||
gpt_response,gpt_design_params,gpt_design_list=self.agent.text_design(text_prompt)
|
||||
|
||||
# Inference from image only
|
||||
elif img_url and not text_prompt:
|
||||
gpt_response,gpt_design_params,gpt_design_list=self.agent.picture_design(img_url)
|
||||
else:
|
||||
raise ValueError("At least one design description is required, text prompt or image.")
|
||||
|
||||
self.design_list = gpt_design_list
|
||||
self.set_new_design(gpt_design_params)
|
||||
self.design_params=gpt_design_params
|
||||
print('*** Design params: ', self.design_list, self.design_params)
|
||||
|
||||
return gpt_response
|
||||
|
||||
def release(self):
|
||||
"""Clean up tmp files after the session"""
|
||||
self.clear_previous_download()
|
||||
shutil.rmtree(self.save_path)
|
||||
shutil.rmtree(self.tmp_path)
|
||||
|
||||
def _load_body_file(self, path):
|
||||
self.body_file = path
|
||||
self.body_params = BodyParameters(path)
|
||||
|
||||
def _load_design_file(self, path):
|
||||
self.design_file = path
|
||||
|
||||
# Create values
|
||||
with open(path, 'r') as f:
|
||||
des = yaml.safe_load(f)['design']
|
||||
|
||||
self.design_params.update(des)
|
||||
if 'left' in self.design_params and not self.design_params['left']['enable_asym']['v']:
|
||||
self.sync_left()
|
||||
|
||||
# Update param sampler
|
||||
self.design_sampler.load(path)
|
||||
|
||||
def svg_path(self):
|
||||
return self.tmp_path / self.svg_filename
|
||||
|
||||
def set_new_design(self, design):
|
||||
self._nested_sync(design, self.design_params)
|
||||
|
||||
def set_new_body_params(self, body_params):
|
||||
self.body_params.load_from_dict(body_params)
|
||||
|
||||
def sample_design(self, reload=True):
|
||||
"""Random design parameters"""
|
||||
|
||||
new_design = self.design_sampler.randomize()
|
||||
# NOTE: re-assign the values instead up overwriting them
|
||||
self._nested_sync(new_design, self.design_params)
|
||||
|
||||
if 'left' in self.design_params and not self.design_params['left']['enable_asym']['v']:
|
||||
self.sync_left()
|
||||
|
||||
if reload:
|
||||
self.reload_garment()
|
||||
|
||||
def restore_design(self, reload=True):
|
||||
"""Restore design values to match the current loaded file"""
|
||||
new_design = self.design_sampler.default()
|
||||
# re-assign the values instead up overwriting them
|
||||
self._nested_sync(new_design, self.design_params)
|
||||
|
||||
if reload:
|
||||
self.reload_garment()
|
||||
|
||||
def reload_garment(self):
|
||||
"""Reload sewing pattern with current body and design parameters
|
||||
|
||||
NOTE: loading a pattern might be lagging, execute only when needed!
|
||||
"""
|
||||
self.sew_pattern = MetaGarment(
|
||||
'Configured_design', self.body_params, self.design_params)
|
||||
self.is_self_intersecting = self.sew_pattern.is_self_intersecting()
|
||||
self._view_serialize()
|
||||
|
||||
@staticmethod
|
||||
def _nested_sync(s_from, s_to):
|
||||
if 'v' in s_to:
|
||||
s_to['v'] = s_from['v']
|
||||
else:
|
||||
for key in s_to:
|
||||
if key in s_from:
|
||||
GUIPattern._nested_sync(s_from[key], s_to[key])
|
||||
|
||||
def sync_left(self, with_check=False):
|
||||
"""Synchronize left and right design parameters"""
|
||||
# Check if needed in the first place
|
||||
if with_check and self.design_params['left']['enable_asym']['v']:
|
||||
# Asymmetry enabled, the params should not syncronise
|
||||
return
|
||||
for k in self.design_params['left']:
|
||||
if k != 'enable_asym':
|
||||
# Use proper value assignment instead of deepcopy
|
||||
self._nested_sync(self.design_params[k], self.design_params['left'][k])
|
||||
|
||||
def _view_serialize(self):
|
||||
"""Save a sewing pattern svg representation to tmp folder be used
|
||||
for display"""
|
||||
|
||||
# Get the flat representation
|
||||
pattern = self.sew_pattern.assembly()
|
||||
|
||||
# Clear up the folder from previous version -- it's not needed any more
|
||||
self.clear_previous_svg()
|
||||
try:
|
||||
self.svg_filename = f'pattern_{time.time()}.svg'
|
||||
dwg = pattern.get_svg(self.tmp_path / self.svg_filename,
|
||||
with_text=False,
|
||||
view_ids=False,
|
||||
flat=False,
|
||||
margin=0
|
||||
)
|
||||
dwg.save()
|
||||
|
||||
self.svg_bbox_size = pattern.svg_bbox_size
|
||||
self.svg_bbox = pattern.svg_bbox
|
||||
except pyg.EmptyPatternError:
|
||||
self.svg_filename = ''
|
||||
|
||||
# Cleaning
|
||||
def clear_previous_svg(self):
|
||||
"""Clear previous svg display file"""
|
||||
if self.svg_filename:
|
||||
(self.tmp_path / self.svg_filename).unlink()
|
||||
self.svg_filename = ''
|
||||
|
||||
def clear_previous_download(self):
|
||||
"""Clear previous download package display file"""
|
||||
if self.saved_garment_folder:
|
||||
shutil.rmtree(self.saved_garment_folder)
|
||||
self.saved_garment_folder = ''
|
||||
if self.saved_garment_archive:
|
||||
self.saved_garment_archive.unlink()
|
||||
self.saved_garment_archive = ''
|
||||
|
||||
def clear_3d(self):
|
||||
if self.paths_3d is not None:
|
||||
shutil.rmtree(self.paths_3d.out_el)
|
||||
self.paths_3d = None
|
||||
|
||||
# 3D
|
||||
def drape_3d(self):
|
||||
"""Run the draping of the current frame"""
|
||||
|
||||
# Config setup
|
||||
props = data_config.Properties('./assets/Sim_props/gui_sim_props.yaml') # TODOLOW Parameter?
|
||||
props.set_section_stats('sim', fails={}, sim_time={}, spf={}, fin_frame={}, body_collisions={}, self_collisions={})
|
||||
props.set_section_stats('render', render_time={})
|
||||
|
||||
# Force the design to be fitted to mean body shape
|
||||
# TODOLOW Support body shape estimation from measurements
|
||||
|
||||
def_sew_pattern = MetaGarment(
|
||||
'Configured_design', self.default_body_params, self.design_params)
|
||||
|
||||
# Save the pattern
|
||||
pattern_folder = self.save(False, save_pattern=def_sew_pattern)
|
||||
|
||||
# Paths
|
||||
paths = PathCofig(
|
||||
in_element_path=pattern_folder,
|
||||
out_path=self.save_path,
|
||||
in_name=def_sew_pattern.name,
|
||||
out_name=self.sew_pattern.name + '_3D',
|
||||
body_name=self.body_id, # 'f_smpl_average_A40'
|
||||
smpl_body=False, # NOTE: depends on chosen body model
|
||||
add_timestamp=False
|
||||
)
|
||||
|
||||
# Generate and save garment box mesh (if not existent)
|
||||
garment_box_mesh = BoxMesh(paths.in_g_spec, props['sim']['config']['resolution_scale'])
|
||||
garment_box_mesh.load()
|
||||
garment_box_mesh.serialize(
|
||||
paths, store_panels=False, uv_config=props['render']['config']['uv_texture'])
|
||||
|
||||
# TODOLOW Don't print progress to console with so many lines
|
||||
run_sim(
|
||||
garment_box_mesh.name,
|
||||
props,
|
||||
paths,
|
||||
save_v_norms=False,
|
||||
store_usd=False, # NOTE: False for fast simulation!,
|
||||
optimize_storage=False,
|
||||
verbose=False
|
||||
)
|
||||
|
||||
# Convert to displayable element
|
||||
mesh = trimesh.load_mesh(paths.g_sim)
|
||||
|
||||
# enable double-sided material for nice viewing
|
||||
pbr_material = mesh.visual.material.to_pbr()
|
||||
pbr_material.doubleSided = True
|
||||
mesh.visual.material = pbr_material
|
||||
# export
|
||||
mesh.export(paths.g_sim_glb)
|
||||
|
||||
self.paths_3d = paths
|
||||
self.is_in_3D = True
|
||||
|
||||
return paths.out_el, paths.g_sim_glb.name
|
||||
|
||||
# Current state
|
||||
def is_design_sectioned(self):
|
||||
"""Check if design parameters are grouped by sections:
|
||||
the top level of design dictionary does not contain actual parameters
|
||||
"""
|
||||
for param in self.design_params:
|
||||
if 'v' in self.design_params[param]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_slow_design(self) -> bool:
|
||||
"""Check is parameters that result in slow pattern generation are enabled
|
||||
|
||||
E.g. curved armhole evaluation
|
||||
"""
|
||||
# Pants
|
||||
if (self.design_params['meta']['bottom']['v'] == 'Pants'):
|
||||
return True
|
||||
|
||||
# Upper garment
|
||||
is_not_upper = self.design_params['meta']['upper']['v'] is None
|
||||
if is_not_upper:
|
||||
return False
|
||||
|
||||
# Upper + fitted + strapless
|
||||
is_asymm = self.design_params['left']['enable_asym']['v']
|
||||
is_fitted = 'Fitted' in self.design_params['meta']['upper']['v']
|
||||
is_strapless = self.design_params['fitted_shirt']['strapless']['v']
|
||||
is_asymm_strapless = self.design_params['left']['fitted_shirt']['strapless']['v']
|
||||
|
||||
is_strapless = is_fitted and is_strapless
|
||||
is_asymm_strapless = is_fitted and is_asymm_strapless
|
||||
|
||||
# Has a hoody
|
||||
collar_component = self.design_params['collar']['component']['style']['v']
|
||||
has_hoody = collar_component is not None and 'Hood' in collar_component
|
||||
|
||||
# Sleeve potential setup
|
||||
sleeves = self.design_params['sleeve']
|
||||
is_sleeveless = sleeves['sleeveless']['v']
|
||||
is_curve = sleeves['armhole_shape']['v'] == 'ArmholeCurve'
|
||||
is_curve = not is_sleeveless and is_curve
|
||||
|
||||
is_asym_sleeveless = self.design_params['left']['sleeve']['sleeveless']['v']
|
||||
is_asymm_curve = self.design_params['left']['sleeve']['armhole_shape']['v'] == 'ArmholeCurve'
|
||||
is_asymm_curve = not is_asym_sleeveless and is_asymm_curve
|
||||
|
||||
if is_asymm:
|
||||
right_check = (not is_strapless) and is_curve
|
||||
left_check = (not is_asymm_strapless) and is_asymm_curve
|
||||
return right_check or left_check
|
||||
else:
|
||||
return (not is_strapless) and is_curve or has_hoody
|
||||
|
||||
def save(self, pack=True, save_pattern: Optional[MetaGarment]=None):
|
||||
"""Save current garment design to self.save_path """
|
||||
|
||||
# Save current pattern
|
||||
if save_pattern is None:
|
||||
save_pattern = self.sew_pattern
|
||||
|
||||
pattern = save_pattern.assembly()
|
||||
|
||||
# Save as json file
|
||||
self.saved_garment_folder = pattern.serialize(
|
||||
self.save_path,
|
||||
to_subfolder=True,
|
||||
with_3d=False, with_text=False, view_ids=False,
|
||||
with_printable=True,
|
||||
empty_ok=True
|
||||
)
|
||||
|
||||
self.saved_garment_folder = Path(self.saved_garment_folder)
|
||||
self.body_params.save(self.saved_garment_folder)
|
||||
|
||||
with open(self.saved_garment_folder / 'design_params.yaml', 'w') as f:
|
||||
yaml.dump(
|
||||
{'design': self.design_params},
|
||||
f,
|
||||
default_flow_style=False,
|
||||
sort_keys=False
|
||||
)
|
||||
|
||||
# pack
|
||||
if pack:
|
||||
# Only add geometry if design didn't change since last drape
|
||||
if not self.is_in_3D:
|
||||
self.clear_3d() # Clean any saved 3D if it's not synced with current design
|
||||
self.saved_garment_archive = Path(shutil.make_archive(
|
||||
self.save_path / '..' / f'{self.saved_garment_folder.name}_{self.id}', 'zip',
|
||||
root_dir=self.save_path
|
||||
))
|
||||
|
||||
print(f'Success! {self.sew_pattern.name} saved to {self.saved_garment_folder}')
|
||||
|
||||
return self.saved_garment_archive if pack else self.saved_garment_folder
|
||||
|
||||
Reference in New Issue
Block a user