init_code

This commit is contained in:
sky
2025-07-03 17:03:00 +08:00
parent a710c87a2b
commit 89766fe3d1
220 changed files with 479903 additions and 77 deletions

401
gui/gui_pattern.py Normal file
View 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