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

189
README.md
View File

@@ -1,92 +1,127 @@
# design2garmentcode
# Design2GarmentCode: Programmatic Garment Patterns from Text and Images
[arXiv](https://arxiv.org/abs/2412.08603) | [Project Page](https://style3d.github.io/design2garmentcode/)
## Getting started
Feng Zhou, Ruiyang Liu, ChenLiu, GaofengHe, YongLuLi, XiaogangJin, HuaminWang. *CVPR 2025 .*
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin http://gitlab.linctex.com/ZhouDeal/design2garmentcode.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](http://gitlab.linctex.com/ZhouDeal/design2garmentcode/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
![teaser](assets/img/neural_symbolic-teaser.png)
we propose a novel
sewing pattern generation approach Design2GarmentCode
based on Large Multimodal Models (LMMs), to generate parametric pattern-making programs from multi-modal
design concepts
---
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
### 1. Clone the repository
```bash
git clone https://github.com/your-org/design2garmentcode.git # ← replace with the real URL
cd design2garmentcode
```
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
### 2. Create the Conda environment
An `environment.yml` file is provided in the project root with all required Conda and PyPI dependencies (Python 3.9.19, Torch 2.4.0 + CUDA 12.1, etc.).
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
```bash
conda env create -f environment.yml
conda activate d2g
python -m pip install --upgrade pip # optional: upgrade pip
```
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
### 3. (Optional) Enable 3D simulation
If you need local cloth simulation and 3D visualization, follow the installation instructions for **GarmentCode Warp Simulator**:
<https://github.com/maria-korosteleva/NvidiaWarp-GarmentCode>
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
---
### 4. LanguageModel API
`Design2GarmentCode` communicates with large multimodal models.
Follow the steps **in the given order**:
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
1. **Provide API credentials**
- **Environment variable (recommended)** defaults to *ChatGPT4o*
```bash
export OPENAI_API_KEY="sk..."
```
- **Edit `system.json`** (project root) manually specify `api_key`, `base_url`, and `model` if you prefer a filebased approach.
2. **You should first download the base model**
[`Qwen2-VL-2B-Instruct`]<https://huggingface.co/Qwen/Qwen2-VL-2B-Instruct/tree/main>
and place its full folder in:
`lmm_utils/Qwen/Qwen2-VL-2B-Instruct/`
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
Then make sure that [`model.pth`]<lmm_utils/Qwen/qwen2vl_lora_mlp/model.pth> is placed in the `lmm_utils/Qwen/qwen2vl_lora_mlp/` directory.
This file contains the LoRA+MLP fine-tuned weights.
---
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## Quick GUI Demo
## License
For open source projects, say how it is licensed.
```bash
python gui.py
```
- Input: freeform prompt or an image/sketch
- Output: GarmentCode JSON, preview image, and (optionally) physics simulation
---
## Model Inference
### 1. Text Guided Generation
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Use `test_text_batch.py` to process a list of text descriptions from a JSON file.
```bash
python lmm_utils/test_text_batch.py \
--input assets/test_text/examples.json \
--output assets/test_text_result \
--sim
```
- `--input`: Path to your input JSON file containing multiple garment description texts.
- `--output`: Directory where the output `.json` files will be saved.
- `--sim`: Enable or disable physical simulation output.
Supports physical simulation (enabled by default in script).
---
### 2. Image Guided Generation
Use `test_picture_batch.py` to process all image files in a directory.
```bash
python lmm_utils/test_picture_batch.py \
--input assets/test_img/examples \
--output assets/test_image_result/examples \
--sim
```
- `--input`: Folder containing multiple image files.
- `--output`: Output folder where results will be saved.
- `--sim`: Enable or disable physical simulation output.
---
### 3. Modify Patterns in the GUI
Once a pattern is generated in GUI, you can refine them directly inside the GUI:
1. Focus the **input box** at the bottom.
2. Type `modify: <your-instruction>` .
3. Press **Enter** the system will regenerate the pattern to reflect your changes.
## Get 3D Garment Patterns
### 1. Generate from a pattern.json
After generating the pattern data, you can simulate the corresponding 3D output directly from the pattern's JSON file.
```bash
python lmm_utils/test_garment_sim.py --pattern_spec $OUTPUT_JSON
```
### 2. Generate from gui
You can also run the simulation directly on the GUI to obtain 3D data.
```bash
python gui.py
```
### Citation
```bash
If you find this work useful, please cite:
```bibtex
@article{zhou2024design2garmentcode,
title={Design2GarmentCode: Turning Design Concepts to Tangible Garments Through Program Synthesis},
author={Zhou, Feng and Liu, Ruiyang and Liu, Chen and He, Gaofeng and Li, Yong-Lu and Jin, Xiaogang and Wang, Huamin},
journal={arXiv preprint arXiv:2412.08603},
year={2024}
}
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,732 @@
{
"pattern": {
"panels": {
"left_btorso": {
"translation": [
0.0,
91.4448950181744,
-20.0
],
"rotation": [
0.0,
0.0,
0.0
],
"vertices": [
[
0.0,
0.0
],
[
0.0,
44.26956007084882
],
[
10.8188,
49.91845847814037
],
[
17.2284,
47.37065774294582
],
[
25.0299525,
28.92917774294582
],
[
25.0299525,
0.0
]
],
"edges": [
{
"endpoints": [
0,
1
]
},
{
"endpoints": [
1,
2
],
"curvature": {
"type": "circle",
"params": [
13.184560591816423,
0,
1
]
}
},
{
"endpoints": [
2,
3
]
},
{
"endpoints": [
3,
4
],
"curvature": {
"type": "cubic",
"params": [
[
0.5,
-0.2
],
[
0.8,
-0.35
]
]
}
},
{
"endpoints": [
4,
5
]
},
{
"endpoints": [
5,
0
]
}
],
"label": "body"
},
"left_ftorso": {
"translation": [
0.0,
91.40029082350912,
25.0
],
"rotation": [
0.0,
0.0,
0.0
],
"vertices": [
[
0.0,
0.0
],
[
27.386415000000003,
0.0
],
[
27.386415000000003,
28.973782566623598
],
[
17.228399999999997,
47.415262566623596
],
[
10.818799999999998,
49.963062523006585
],
[
0.0,
34.31450269441045
]
],
"edges": [
{
"endpoints": [
0,
1
]
},
{
"endpoints": [
1,
2
]
},
{
"endpoints": [
2,
3
],
"curvature": {
"type": "cubic",
"params": [
[
0.19999999999999996,
0.35
],
[
0.5,
0.2
]
]
}
},
{
"endpoints": [
3,
4
]
},
{
"endpoints": [
4,
5
],
"curvature": {
"type": "circle",
"params": [
11.564126734773824,
0,
0
]
}
},
{
"endpoints": [
5,
0
]
}
],
"label": "body"
},
"left_hood": {
"translation": [
10.818800000000001,
149.36600318541687,
-11.637599999999999
],
"rotation": [
0.0,
90.0,
0.0
],
"vertices": [
[
0.8187999999999995,
-0.6488984072915587
],
[
-12.16376,
36.7356
],
[
-22.98256,
36.7356
],
[
-21.37098745180261,
-11.459953817131435
],
[
-10.124915390760208,
5.065222977771245
]
],
"edges": [
{
"endpoints": [
0,
1
],
"curvature": {
"type": "quadratic",
"params": [
[
0.8,
-0.5
]
]
}
},
{
"endpoints": [
1,
2
]
},
{
"endpoints": [
2,
3
]
},
{
"endpoints": [
3,
4
],
"curvature": {
"type": "cubic",
"params": [
[
0.2809593298782206,
-0.37546453034800403
],
[
0.7190492879150145,
0.3755072247003444
]
]
}
},
{
"endpoints": [
4,
0
],
"curvature": {
"type": "cubic",
"params": [
[
0.377233550352168,
0.18074617772869186
],
[
0.6225672655140111,
-0.18160966127735645
]
]
}
}
],
"label": "body"
},
"right_hood": {
"translation": [
-10.818800000000001,
149.36600318541687,
-11.637599999999999
],
"rotation": [
0.0,
-90.0,
0.0
],
"vertices": [
[
-0.8187999999999995,
-0.6488984072915587
],
[
10.124915390760208,
5.065222977771245
],
[
21.37098745180261,
-11.459953817131435
],
[
22.98256,
36.7356
],
[
12.16376,
36.7356
]
],
"edges": [
{
"endpoints": [
0,
1
],
"curvature": {
"type": "cubic",
"params": [
[
0.37743273448598885,
-0.18160966127735645
],
[
0.622766449647832,
0.18074617772869186
]
]
}
},
{
"endpoints": [
1,
2
],
"curvature": {
"type": "cubic",
"params": [
[
0.28095071208498545,
0.3755072247003444
],
[
0.7190406701217794,
-0.37546453034800403
]
]
}
},
{
"endpoints": [
2,
3
]
},
{
"endpoints": [
3,
4
]
},
{
"endpoints": [
4,
0
],
"curvature": {
"type": "quadratic",
"params": [
[
0.2,
-0.5
]
]
}
}
],
"label": "body"
},
"right_ftorso": {
"translation": [
0.0,
91.40029082350912,
25.0
],
"rotation": [
0.0,
0.0,
0.0
],
"vertices": [
[
0,
0
],
[
0.0,
34.31450269441045
],
[
-10.818799999999998,
49.963062523006585
],
[
-17.228399999999997,
47.415262566623596
],
[
-27.386415000000003,
28.973782566623598
],
[
-27.386415000000003,
0
]
],
"edges": [
{
"endpoints": [
0,
1
]
},
{
"endpoints": [
1,
2
],
"curvature": {
"type": "circle",
"params": [
11.564126734773824,
0,
0
]
}
},
{
"endpoints": [
2,
3
]
},
{
"endpoints": [
3,
4
],
"curvature": {
"type": "cubic",
"params": [
[
0.5,
0.2
],
[
0.8,
0.35
]
]
}
},
{
"endpoints": [
4,
5
]
},
{
"endpoints": [
5,
0
]
}
],
"label": "body"
},
"right_btorso": {
"translation": [
0.0,
91.4448950181744,
-20.0
],
"rotation": [
0.0,
0.0,
0.0
],
"vertices": [
[
0,
0
],
[
-25.0299525,
0
],
[
-25.0299525,
28.92917774294582
],
[
-17.2284,
47.37065774294582
],
[
-10.8188,
49.91845847814037
],
[
0.0,
44.26956007084882
]
],
"edges": [
{
"endpoints": [
0,
1
]
},
{
"endpoints": [
1,
2
]
},
{
"endpoints": [
2,
3
],
"curvature": {
"type": "cubic",
"params": [
[
0.19999999999999996,
-0.35
],
[
0.5,
-0.2
]
]
}
},
{
"endpoints": [
3,
4
]
},
{
"endpoints": [
4,
5
],
"curvature": {
"type": "circle",
"params": [
13.184560591816423,
0,
1
]
}
},
{
"endpoints": [
5,
0
]
}
],
"label": "body"
}
},
"stitches": [
[
{
"panel": "left_ftorso",
"edge": 4
},
{
"panel": "left_hood",
"edge": 3
}
],
[
{
"panel": "left_btorso",
"edge": 1
},
{
"panel": "left_hood",
"edge": 4
}
],
[
{
"panel": "left_ftorso",
"edge": 3
},
{
"panel": "left_btorso",
"edge": 2
}
],
[
{
"panel": "left_ftorso",
"edge": 1
},
{
"panel": "left_btorso",
"edge": 4
}
],
[
{
"panel": "right_ftorso",
"edge": 1
},
{
"panel": "right_hood",
"edge": 1
}
],
[
{
"panel": "right_btorso",
"edge": 4
},
{
"panel": "right_hood",
"edge": 0
}
],
[
{
"panel": "right_ftorso",
"edge": 2
},
{
"panel": "right_btorso",
"edge": 3
}
],
[
{
"panel": "right_ftorso",
"edge": 4
},
{
"panel": "right_btorso",
"edge": 1
}
],
[
{
"panel": "right_ftorso",
"edge": 0
},
{
"panel": "left_ftorso",
"edge": 5
}
],
[
{
"panel": "right_btorso",
"edge": 5
},
{
"panel": "left_btorso",
"edge": 0
}
],
[
{
"panel": "right_hood",
"edge": 3
},
{
"panel": "left_hood",
"edge": 1
}
],
[
{
"panel": "right_hood",
"edge": 4
},
{
"panel": "left_hood",
"edge": 0
}
]
]
},
"parameters": {},
"parameter_order": [],
"properties": {
"curvature_coords": "relative",
"normalize_panel_translation": false,
"normalized_edge_loops": true,
"units_in_meter": 100
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
sim:
config:
optimize_storage: true
max_sim_steps: 2400
max_frame_time: 60
max_meshgen_time: 60
max_sim_time: 600
static_threshold: 0.03
non_static_percent: 1.5
max_body_collisions: 35
max_self_collisions: 300
zero_gravity_steps: 10
resolution_scale: 1.0
ground: false
material:
garment_tri_ka: 10000.0
garment_edge_ke: 1.0 # Very soft fabric
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
options:
enable_particle_particle_collisions: false
enable_triangle_particle_collisions: true
enable_edge_edge_collisions: true
enable_body_collision_filters: true
enable_global_collision_filter: true
global_damping_factor: 0.25
global_damping_effective_velocity: 0.0
global_max_velocity: 25.0
enable_attachment_constraint: true
attachment_stiffness:
- 1000.
- 1000.
- 1000.
- 1000.
attachment_damping:
- 10.
- 0.
- 0.
- 0.
attachment_frames: 400
attachment_label_names:
- lower_interface
- right_collar
- left_collar
- strapless_top
enable_cloth_reference_drag: true
cloth_reference_margin: 0.1
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
stats:
fails: {}
sim_time: {}
spf: {}
fin_frame: {}
self_collisions: {}
body_collisions: {}
render:
config:
resolution:
- 800
- 800
sides:
- front
- back
front_camera_location:
- 0
- 0.97
- 4.15
uv_texture:
seam_width: 0.5
dpi: 1500
fabric_grain_texture_path: ./assets/img/fabric_texture.png
fabric_grain_resolution: 5
stats:
render_time: {}

View File

@@ -0,0 +1,93 @@
sim:
config:
optimize_storage: true
max_sim_steps: 2400
max_frame_time: null # NOTE: Important for GUI!!
# GUI runs the sim in a thread, which is not supported by timeouts on Linux/MacOS
max_meshgen_time: 60
max_sim_time: 600
static_threshold: 0.03
non_static_percent: 1.5
max_body_collisions: 35
max_self_collisions: 300
zero_gravity_steps: 10
resolution_scale: 1.0
ground: false
material:
garment_tri_ka: 10000.0
garment_edge_ke: 100.0 # Mid-softness fabric
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
options:
enable_particle_particle_collisions: false
enable_triangle_particle_collisions: true
enable_edge_edge_collisions: true
enable_body_collision_filters: true
enable_global_collision_filter: true
global_damping_factor: 0.25
global_damping_effective_velocity: 0.0
global_max_velocity: 25.0
enable_attachment_constraint: true
attachment_stiffness:
- 1000.
- 1000.
- 1000.
- 1000.
attachment_damping:
- 10.
- 0.
- 0.
- 0.
attachment_frames: 400
attachment_label_names:
- lower_interface
- right_collar
- left_collar
- strapless_top
enable_cloth_reference_drag: true
cloth_reference_margin: 0.1
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
stats:
fails: {}
sim_time: {}
spf: {}
fin_frame: {}
self_collisions: {}
body_collisions: {}
render:
config:
resolution:
- 800
- 800
sides:
- front
- back
front_camera_location:
- 0
- 0.97
- 4.15
uv_texture:
seam_width: 0.5
dpi: 1500
fabric_grain_texture_path: ./assets/img/fabric_texture.png
fabric_grain_resolution: 5
stats:
render_time: {}

View File

@@ -0,0 +1,92 @@
sim:
config:
optimize_storage: true
max_sim_steps: 2400
max_frame_time: 60
max_meshgen_time: 60
max_sim_time: 600
static_threshold: 0.03
non_static_percent: 1.5
max_body_collisions: 35
max_self_collisions: 300
zero_gravity_steps: 10
resolution_scale: 1.0
ground: false
material:
garment_tri_ka: 10000.0
garment_edge_ke: 100.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
options:
enable_particle_particle_collisions: false
enable_triangle_particle_collisions: true
enable_edge_edge_collisions: true
enable_body_collision_filters: true
enable_global_collision_filter: true
global_damping_factor: 0.25
global_damping_effective_velocity: 0.0
global_max_velocity: 25.0
enable_attachment_constraint: true
attachment_stiffness:
- 1000.
- 1000.
- 1000.
- 1000.
attachment_damping:
- 10.
- 0.
- 0.
- 0.
attachment_frames: 400
attachment_label_names:
- lower_interface
- right_collar
- left_collar
- strapless_top
enable_cloth_reference_drag: true
cloth_reference_margin: 0.1
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
stats:
fails: {}
sim_time: {}
spf: {}
fin_frame: {}
self_collisions: {}
body_collisions: {}
render:
config:
resolution:
- 800
- 800
sides:
- front
- back
front_camera_location:
- 0
- 0.97
- 4.15
uv_texture:
seam_width: 0.5
dpi: 1500
fabric_grain_texture_path: ./assets/img/fabric_texture.png
fabric_grain_resolution: 5
stats:
render_time: {}

View File

@@ -0,0 +1,92 @@
sim:
config:
optimize_storage: true
max_sim_steps: 2400
max_frame_time: 60
max_meshgen_time: 60
max_sim_time: 600
static_threshold: 0.03
non_static_percent: 1.5
max_body_collisions: 35
max_self_collisions: 300
zero_gravity_steps: 10
resolution_scale: 1.0
ground: false
material:
garment_tri_ka: 10000.0
garment_edge_ke: 500.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
options:
enable_particle_particle_collisions: false
enable_triangle_particle_collisions: true
enable_edge_edge_collisions: true
enable_body_collision_filters: true
enable_global_collision_filter: true
global_damping_factor: 0.25
global_damping_effective_velocity: 0.0
global_max_velocity: 25.0
enable_attachment_constraint: true
attachment_stiffness:
- 1000.
- 1000.
- 1000.
- 1000.
attachment_damping:
- 10.
- 0.
- 0.
- 0.
attachment_frames: 400
attachment_label_names:
- lower_interface
- right_collar
- left_collar
- strapless_top
enable_cloth_reference_drag: true
cloth_reference_margin: 0.1
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
stats:
fails: {}
sim_time: {}
spf: {}
fin_frame: {}
self_collisions: {}
body_collisions: {}
render:
config:
resolution:
- 800
- 800
sides:
- front
- back
front_camera_location:
- 0
- 0.97
- 4.15
uv_texture:
seam_width: 0.5
dpi: 1500
fabric_grain_texture_path: ./assets/img/fabric_texture.png
fabric_grain_resolution: 5
stats:
render_time: {}

View File

@@ -0,0 +1,75 @@
{
"body": "f_average_A40.obj",
"render": {
"config": {
"garment_color": [
0.6359999775886536,
0.43511512875556946,
0.04769999161362648
],
"resolution": [
800,
800
]
},
"stats": {
"render_time": {}
}
},
"scan_imitation": {
"config": {
"test_rays_num": 30,
"visible_rays_num": 4
},
"stats": {}
},
"sim": {
"config": {
"body_friction": 0.25,
"collision_thickness": 0.04,
"material": {
"air_drag": 0.01,
"bend_angle_dropoff": 0.11,
"bend_damp": 0.1,
"bend_damp_dropoff": 0.0,
"bend_plasticity": 0.0,
"bend_resistance": 0.03,
"bend_yield": 0.0,
"compression_resistance": 100.0,
"density": 0.015,
"friction": 0.01,
"length_scale": 1.0,
"pressure": 0.0,
"rubber": 1,
"shear_resistance": 0.4,
"stretch_damp": 0.2,
"stretch_resistance": 25.0,
"viscous_damp": 0.0,
"warp_resistance_scale": 1.0,
"warp_rubber_scale": 1.0,
"weft_resistance_scale": 1.0,
"weft_rubber_scale": 1.0
},
"max_sim_steps": 1500,
"non_static_percent": 1.5,
"object_intersect_border_threshold": 100,
"resolution_scale": 9.0,
"self_intersect_hit_threshold": 0,
"static_threshold": 0.01,
"zero_gravity_steps": 5
},
"stats": {
"fails": {
"crashes": [],
"fast_finish": [],
"intersect_colliders": [],
"intersect_self": [],
"pattern_loading": [],
"static_equilibrium": []
},
"fin_frame": {},
"sim_time": {},
"spf": {}
}
}
}

View File

@@ -0,0 +1,75 @@
{
"body": "f_average_A40.obj",
"render": {
"config": {
"garment_color": [
0.6359999775886536,
0.43511512875556946,
0.04769999161362648
],
"resolution": [
800,
800
]
},
"stats": {
"render_time": {}
}
},
"scan_imitation": {
"config": {
"test_rays_num": 30,
"visible_rays_num": 4
},
"stats": {}
},
"sim": {
"config": {
"body_friction": 0.25,
"collision_thickness": 0.04,
"material": {
"air_drag": 0.01,
"bend_angle_dropoff": 0.11,
"bend_damp": 0.1,
"bend_damp_dropoff": 0.0,
"bend_plasticity": 0.0,
"bend_resistance": 0.03,
"bend_yield": 0.0,
"compression_resistance": 100.0,
"density": 0.015,
"friction": 0.01,
"length_scale": 1.0,
"pressure": 0.0,
"rubber": 1,
"shear_resistance": 0.4,
"stretch_damp": 0.2,
"stretch_resistance": 400.0,
"viscous_damp": 0.0,
"warp_resistance_scale": 1.0,
"warp_rubber_scale": 1.0,
"weft_resistance_scale": 1.0,
"weft_rubber_scale": 1.0
},
"max_sim_steps": 1500,
"non_static_percent": 1.5,
"object_intersect_border_threshold": 100,
"resolution_scale": 9.0,
"self_intersect_hit_threshold": 0,
"static_threshold": 0.01,
"zero_gravity_steps": 5
},
"stats": {
"fails": {
"crashes": [],
"fast_finish": [],
"intersect_colliders": [],
"intersect_self": [],
"pattern_loading": [],
"static_equilibrium": []
},
"fin_frame": {},
"sim_time": {},
"spf": {}
}
}
}

View File

@@ -0,0 +1,75 @@
{
"body": "f_average_A40.obj",
"render": {
"config": {
"garment_color": [
0.6359999775886536,
0.43511512875556946,
0.04769999161362648
],
"resolution": [
800,
800
]
},
"stats": {
"render_time": {}
}
},
"scan_imitation": {
"config": {
"test_rays_num": 30,
"visible_rays_num": 4
},
"stats": {}
},
"sim": {
"config": {
"body_friction": 0.25,
"collision_thickness": 0.04,
"material": {
"air_drag": 0.01,
"bend_angle_dropoff": 0.11,
"bend_damp": 0.1,
"bend_damp_dropoff": 0.0,
"bend_plasticity": 0.0,
"bend_resistance": 2.0,
"bend_yield": 0.0,
"compression_resistance": 100.0,
"density": 0.015,
"friction": 0.01,
"length_scale": 1.0,
"pressure": 0.0,
"rubber": 1,
"shear_resistance": 0.4,
"stretch_damp": 0.2,
"stretch_resistance": 400.0,
"viscous_damp": 0.0,
"warp_resistance_scale": 1.0,
"warp_rubber_scale": 1.0,
"weft_resistance_scale": 1.0,
"weft_rubber_scale": 1.0
},
"max_sim_steps": 1500,
"non_static_percent": 1.5,
"object_intersect_border_threshold": 100,
"resolution_scale": 4.0,
"self_intersect_hit_threshold": 0,
"static_threshold": 0.01,
"zero_gravity_steps": 10
},
"stats": {
"fails": {
"crashes": [],
"fast_finish": [],
"intersect_colliders": [],
"intersect_self": [],
"pattern_loading": [],
"static_equilibrium": []
},
"fin_frame": {},
"sim_time": {},
"spf": {}
}
}
}

7
assets/bodies/Readme.md Normal file
View File

@@ -0,0 +1,7 @@
# Attribution
Body models in this folder with "smpl" in the name are based on the female and male average body models of [SMPL](https://smpl.is.tue.mpg.de/) (licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)).
Body models without "smpl" in the name are based on our own body model, see https://github.com/mbotsch/GarmentMeasurements
### Disclaimer
Due to the restrictions of the SMPL license, we cannot share all 3D models of the body shapes used in GarmentCode paper, except for the base average bodies for male and female versions of SMPL.

View File

@@ -0,0 +1,41 @@
import numpy as np
import pygarment as pyg
class BodyParameters(pyg.BodyParametrizationBase):
"""Custom class that defines calculated body parameters"""
def __init__(self, param_file='') -> None:
super().__init__(param_file)
def eval_dependencies(self, key=None):
super().eval_dependencies(key)
if key in ['height', 'head_l', 'waist_line', 'hips_line', None]:
self.params['_waist_level'] = self.params['height'] - self.params['head_l'] - self.params['waist_line']
self.params['_leg_length'] = self.params['_waist_level'] - self.params['hips_line']
if key in ['shoulder_w', None]:
# Correct sleeve line location is a little closer to the neck
# than the true shoulder width
self.params['_base_sleeve_balance'] = self.params['shoulder_w'] - 2
# Balance for the bust dart location
if key in ['bust_line', 'vert_bust_line', None]:
if 'vert_bust_line' in self.params:
self.params['_bust_line'] = (1 - 1/3) * self.params['vert_bust_line'] + 1/3 * self.params['bust_line']
else:
self.params['_bust_line'] = self.params['bust_line']
# Half of the slopes for use in garment (smoother fabric distribution)
if key in ['hip_inclination', None]:
self.params['_hip_inclination'] = self.params['hip_inclination'] / 2
if key in ['shoulder_incl', None]:
self.params['_shoulder_incl'] = self.params['shoulder_incl']
# Add ease to armhole
if key in ['armscye_depth', None]:
self.params['_armscye_depth'] = self.params['armscye_depth'] + 2.5

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
# Measurments of the body
body:
height: 165
head_l: 25 # nape of the neck to the top -- ok to use some average value
neck_w: 15
shoulder_w: 38
armscye_depth: 15 # Nape of the Neck to armscye depth on the back
shoulder_incl: 10 # degrees
arm_pose_angle: 40 # Degrees
arm_length: 80
wrist: 17
waist: 80 # 82 adjusted for 'dips'
waist_line: 36 # Nape of the Neck to waist (on the back)
waist_over_bust_line: 43 # -- measurement + addition for the shoulder
waist_back_width: 37
bust_line: 23
bust: 90 # adjust for the 'dips' from 96
bust_points: 21 #
underbust: 83
back_width: 45 # 32 + 2 * between-size
hips: 107
hip_back_width: 55
hips_line: 22 # From the waist
hip_inclination: 6
bum_points: 16
crotch_hip_diff: 10.5 # NOTE Waist-crotch = 25 # NOTE vertical projection of the level
leg_circ: 64

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
body:
arm_pose_angle: 40
arm_length: 80
wrist: 25
armscye_depth: 17.5
back_width: 52 # 46
bum_points: 19.3
bust: 100.0
bust_line: 24.4
bust_points: 17
crotch_hip_diff: 13.0
head_l: 25.6
height: 178.1
hips: 100.0
hips_line: 17.0
hip_back_width: 52
hip_inclination: 4
leg_circ: 63
neck_w: 17
shoulder_w: 38.2
shoulder_incl: 10
underbust: 98
waist: 80.0
waist_line: 44.2
waist_over_bust_line: 47
waist_back_width: 38

71252
assets/bodies/mean_all.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
assets/bodies/mean_all.stl Normal file

Binary file not shown.

View File

@@ -0,0 +1,27 @@
body:
arm_length: 53.9697
arm_pose_angle: 45.483
armscye_depth: 12.8679
back_width: 47.6761
bum_points: 18.2342
bust: 99.8407
bust_line: 25.6947
bust_points: 16.9463
crotch_hip_diff: 8.81363
head_l: 26.3262
height: 171.99
hip_back_width: 54.8237
hip_inclination: 9.86489
hips: 103.478
hips_line: 23.4837
leg_circ: 60.2039
neck_w: 18.9328
shoulder_incl: 21.6777
shoulder_w: 36.4568
underbust: 86.2455
vert_bust_line: 21.1388
waist: 84.3338
waist_back_width: 39.1358
waist_line: 36.8913
waist_over_bust_line: 40.5603
wrist: 16.5945

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
#python/object:assets.bodies.body_params.BodyParameters
body:
_armscye_depth: 15.4
_base_sleeve_balance: 34.5
_bust_line: 22.7
_hip_inclination: 4.93
_leg_length: 85.3
_shoulder_incl: 21.7
_waist_level: 108.0
arm_length: 53.0
arm_pose_angle: 45.5
armscye_depth: 12.9
back_width: 47.7
bum_points: 18.2
bust: 99.8
bust_line: 25.7
bust_points: 16.9
crotch_hip_diff: 8.81
head_l: 26.3
height: 171.0
hip_back_width: 54.8
hip_inclination: 9.86
hips: 103.0
hips_line: 23.5
leg_circ: 60.2
neck_w: 18.9
shoulder_incl: 21.7
shoulder_w: 36.5
underbust: 86.2
vert_bust_line: 21.1
waist: 84.3
waist_back_width: 39.1
waist_line: 36.9
waist_over_bust_line: 40.6
wrist: 16.6

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
body:
arm_length: 53.9697
arm_pose_angle: 0.0
armscye_depth: 12.8679
back_width: 47.6761
bum_points: 18.2342
bust: 99.8407
bust_line: 25.6947
bust_points: 16.9463
crotch_hip_diff: 8.81363
head_l: 26.3262
height: 171.99
hip_back_width: 54.8237
hip_inclination: 9.86489
hips: 103.478
hips_line: 23.4837
leg_circ: 60.2039
neck_w: 18.9328
shoulder_incl: 21.6777
shoulder_w: 36.4568
underbust: 86.2455
vert_bust_line: 21.1388
waist: 84.3338
waist_back_width: 39.1358
waist_line: 36.8913
waist_over_bust_line: 40.5603
wrist: 16.5945

71252
assets/bodies/mean_female.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
body:
arm_length: 51.594
arm_pose_angle: 45.487
armscye_depth: 12.6489
back_width: 46.1652
bum_points: 17.6707
bust: 97.4076
bust_line: 25.5656
bust_points: 16.212
crotch_hip_diff: 7.8731
head_l: 25.5328
height: 166.194
hip_back_width: 55.6788
hip_inclination: 12.6783
hips: 104.091
hips_line: 22.6309
leg_circ: 60.6592
neck_w: 17.772
shoulder_incl: 20.905
shoulder_w: 34.7129
underbust: 80.7483
vert_bust_line: 20.7686
waist: 80.3442
waist_back_width: 37.7747
waist_line: 35.7549
waist_over_bust_line: 39.9902
wrist: 15.8636

71252
assets/bodies/mean_male.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
body:
arm_length: 56.8064
arm_pose_angle: 45.4775
armscye_depth: 13.1301
back_width: 50.7175
bum_points: 17.736
bust: 102.787
bust_line: 25.9217
bust_points: 17.8195
crotch_hip_diff: 9.92969
head_l: 27.2653
height: 178.873
hip_back_width: 53.9145
hip_inclination: 6.7451
hips: 102.85
hips_line: 24.4948
leg_circ: 59.9882
neck_w: 20.3361
shoulder_incl: 22.5059
shoulder_w: 38.5276
underbust: 93.0277
vert_bust_line: 21.5796
waist: 89.003
waist_back_width: 40.7068
waist_line: 38.3492
waist_over_bust_line: 41.6141
wrist: 17.4648

File diff suppressed because it is too large Load Diff

926
assets/design_params/default.yaml Executable file
View File

@@ -0,0 +1,926 @@
design:
meta:
upper:
v: null
range:
- FittedShirt
- Shirt
- null
type: select_null
default_prob: 0.3
wb:
v: null
range:
- StraightWB
- FittedWB
- null
type: select_null
default_prob: 0.5
bottom:
v: null
range:
- SkirtCircle
- AsymmSkirtCircle
- GodetSkirt
- Pants
- Skirt2
- SkirtManyPanels
- PencilSkirt
- SkirtLevels
- SkirtLayers
- null
type: select_null
default_prob: 0.3
connected:
v: false
range:
- true
- false
type: bool
waistband:
waist:
v: 1.0
range:
- 1.0
- 2
type: float
default_prob: 0.7
width:
v: 0.2
range:
- 0.1
- 1.0
type: float
default_prob: 0.5
fitted_shirt:
strapless:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
shirt:
length:
v: 1.2
range:
- 0.5
- 3.5
type: float
default_prob: 0.7
width:
v: 1.0
range:
- 1.0
- 1.3
type: float
default_prob: 0.4
flare:
v: 1.0
range:
- 0.7
- 1.6
type: float
default_prob: 0.4
collar:
f_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.4
b_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.8
width:
v: 0.5
range:
- -0.5
- 1
type: float
default_prob: 0.4
fc_depth:
v: 0.15
range:
- 0.3
- 2
type: float
default_prob: 0.3
bc_depth:
v: 0.05
range:
- 0
- 2
type: float
default_prob: 0.4
fc_angle:
v: 95
range:
- 70
- 110
type: int
bc_angle:
v: 95
range:
- 70
- 110
type: int
f_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
b_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
component:
style:
v: null
range:
- Turtle
- SimpleLapel
- Hood2Panels
- null
type: select_null
default_prob: 0.6
depth:
v: 5
range:
- 2
- 8
type: int
lapel_standing:
v: false
range:
- true
- false
type: bool
hood_depth:
v: 1
range:
- 1
- 2
type: float
default_prob: 0.6
hood_length:
v: 1
range:
- 1
- 1.5
type: float
default_prob: 0.6
sleeve:
sleeveless:
v: true
range:
- true
- false
type: bool
default_prob: 0.7
armhole_shape:
v: ArmholeCurve
range:
- ArmholeSquare
- ArmholeAngle
- ArmholeCurve
type: select
default_prob: 0.7
length:
v: 0.2
range:
- 0.1
- 1.15
type: float
connecting_width:
v: 0.2
range:
- 0
- 2
type: float
default_prob: 0.6
end_width:
v: 1.0
range:
- 0.2
- 2
type: float
default_prob: 0.4
sleeve_angle:
v: 11
range:
- 10
- 50
type: int
opening_dir_mix:
v: 0.1
range:
- -0.9
- 0.8
type: float
default_prob: 1.0
standing_shoulder:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
standing_shoulder_len:
v: 4.0
range:
- 4
- 10
type: float
connect_ruffle:
v: 1.0
range:
- 1
- 2
type: float
default_prob: 0.4
smoothing_coeff:
v: 0.25
range:
- 0.1
- 0.4
type: float
default_prob: 0.8
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
top_ruffle:
v: 1
range:
- 1
- 3
type: float
cuff_len:
v: 0.225
range:
- 0.05
- 0.9
type: float
default_prob: 0.7
skirt_fraction:
v: 0.425
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
skirt_flare:
v: 1.0
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float
default_prob: 0.3
left:
enable_asym:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
fitted_shirt:
strapless:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
shirt:
width:
v: 1.0
range:
- 1.0
- 1.3
type: float
default_prob: 0.4
flare:
v: 1.0
range:
- 0.7
- 1.6
type: float
default_prob: 0.4
collar:
f_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.4
b_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.8
width:
v: 0.5
range:
- 0
- 1
type: float
default_prob: 0.4
fc_angle:
v: 95
range:
- 70
- 110
type: int
bc_angle:
v: 95
range:
- 70
- 110
type: int
f_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
b_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
f_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
b_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
sleeve:
sleeveless:
v: true
range:
- true
- false
type: bool
default_prob: 0.7
armhole_shape:
v: ArmholeCurve
range:
- ArmholeSquare
- ArmholeAngle
- ArmholeCurve
type: select
default_prob: 0.7
length:
v: 0.2
range:
- 0.1
- 1.15
type: float
connecting_width:
v: 0.2
range:
- 0
- 2
type: float
default_prob: 0.6
end_width:
v: 1.0
range:
- 0.2
- 2
type: float
default_prob: 0.4
sleeve_angle:
v: 11
range:
- 10
- 50
type: int
opening_dir_mix:
v: 0.1
range:
- -0.9
- 0.8
type: float
default_prob: 1.0
standing_shoulder:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
standing_shoulder_len:
v: 4.0
range:
- 4
- 10
type: float
connect_ruffle:
v: 1.0
range:
- 1
- 2
type: float
default_prob: 0.4
smoothing_coeff:
v: 0.25
range:
- 0.1
- 0.4
type: float
default_prob: 0.8
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
top_ruffle:
v: 1
range:
- 1
- 2
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.7
skirt_fraction:
v: 0.5
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
skirt_flare:
v: 1.2
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float
default_prob: 0.3
skirt:
length:
v: 0.1
range:
- -0.2
- 0.95
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
ruffle:
v: 1.4
range:
- 1
- 2
type: float
default_prob: 0.3
bottom_cut:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.3
flare:
v: 1
range:
- 0
- 20
type: int
default_prob: 0.5
flare-skirt:
length:
v: 0.2
range:
- -0.2
- 0.95
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
suns:
v: 0.75
range:
- 0.1
- 1.95
type: float
skirt-many-panels:
n_panels:
v: 4
range:
- 4
- 15
type: int
panel_curve:
v: 0.15
range:
- -0.35
- -0.25
- -0.15
- 0
- 0.15
- 0.25
- 0.35
- 0.45
type: select
asymm:
front_length:
v: 0.675
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
cut:
add:
v: false
range:
- true
- false
type: bool
default_prob: 0.6
depth:
v: 0.5
range:
- 0.05
- 0.95
type: float
default_prob: 0.6
width:
v: 0.1
range:
- 0.05
- 0.4
type: float
place:
v: -0.5
range:
- -1
- 1
type: float
godet-skirt:
base:
v: PencilSkirt
range:
- Skirt2
- PencilSkirt
type: select
default_prob: 0.7
insert_w:
v: 15
range:
- 10
- 50
type: int
insert_depth:
v: 20
range:
- 10
- 50
type: int
num_inserts:
v: 4
range:
- 4
- 6
- 8
- 10
- 12
type: select
cuts_distance:
v: 5
range:
- 0
- 10
type: int
pencil-skirt:
length:
v: 0.4
range:
- 0.2
- 0.95
type: float
rise:
v: 1
range:
- 0.5
- 1
type: float
default_prob: 0.3
flare:
v: 1.0
range:
- 0.6
- 1.5
type: float
default_prob: 0.3
low_angle:
v: 0
range:
- -30
- 30
type: int
default_prob: 0.7
front_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.4
back_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.4
left_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.6
right_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.6
style_side_cut:
v: null
range:
- Sun
- SIGGRAPH_logo
- null
type: select_null
default_prob: 1.0
levels-skirt:
base:
v: PencilSkirt
range:
- Skirt2
- PencilSkirt
- SkirtCircle
- AsymmSkirtCircle
type: select
level:
v: Skirt2
range:
- Skirt2
- SkirtCircle
- AsymmSkirtCircle
type: select
num_levels:
v: 1
range:
- 1
- 5
type: int
level_ruffle:
v: 1
range:
- 1
- 1.7
type: float
length:
v: 0.5
range:
- 0.2
- 0.95
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
base_length_frac:
v: 0.375
range:
- 0.2
- 0.8
type: float
layers-skirt:
base:
v: SkirtCircle
range:
- SkirtCircle
type: select
num_layers:
v: 2
range:
- 2
- 5
type: int
layer_ruffle:
v: 1
range:
- 1
- 1.7
type: float
length:
v: 0.5
range:
- 0.2
- 5
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
pants:
length:
v: 0.3
range:
- 0.2
- 0.9
type: float
width:
v: 1.0
range:
- 1.0
- 1.5
type: float
default_prob: 0.5
flare:
v: 1.0
range:
- 0.5
- 1.2
type: float
default_prob: 0.3
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
default_prob: 0.5
top_ruffle:
v: 1
range:
- 1
- 2
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.3
skirt_fraction:
v: 0.425
range:
- 0.1
- 0.9
type: float
skirt_flare:
v: 1.0
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float

View File

@@ -0,0 +1,895 @@
design:
meta:
upper:
v: null
range:
- FittedShirt
- Shirt
- null
type: select_null
default_prob: 0.3
wb:
v: null
range:
- StraightWB
- FittedWB
- null
type: select_null
default_prob: 0.5
bottom:
v: null
range:
- SkirtCircle
- AsymmSkirtCircle
- GodetSkirt
- Pants
- Skirt2
- SkirtManyPanels
- PencilSkirt
- SkirtLevels
- SkirtLayers
- null
type: select_null
default_prob: 0.3
connected:
v: false
range:
- true
- false
type: bool
waistband:
waist:
v: 1.0
range:
- 1.0
- 2
type: float
default_prob: 0.7
width:
v: 0.2
range:
- 0.1
- 1.0
type: float
default_prob: 0.5
fitted_shirt:
strapless:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
shirt:
length:
v: 1.2
range:
- 0.5
- 3.5
type: float
default_prob: 0.7
width:
v: 1.0
range:
- 1.0
- 1.3
type: float
default_prob: 0.4
flare:
v: 1.0
range:
- 0.7
- 1.6
type: float
default_prob: 0.4
collar:
f_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.4
b_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.8
width:
v: 0.5
range:
- -0.5
- 1
type: float
default_prob: 0.4
fc_depth:
v: 0.15
range:
- 0.3
- 2
type: float
default_prob: 0.3
bc_depth:
v: 0.05
range:
- 0
- 2
type: float
default_prob: 0.4
fc_angle:
v: 95
range:
- 70
- 110
type: int
bc_angle:
v: 95
range:
- 70
- 110
type: int
f_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
b_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
component:
style:
v: null
range:
- Turtle
- SimpleLapel
- Hood2Panels
- null
type: select_null
default_prob: 0.6
depth:
v: 5
range:
- 2
- 8
type: int
lapel_standing:
v: false
range:
- true
- false
type: bool
hood_depth:
v: 1
range:
- 1
- 2
type: float
default_prob: 0.6
hood_length:
v: 1
range:
- 1
- 1.5
type: float
default_prob: 0.6
sleeve:
sleeveless:
v: true
range:
- true
- false
type: bool
default_prob: 0.7
armhole_shape:
v: ArmholeCurve
range:
- ArmholeSquare
- ArmholeAngle
- ArmholeCurve
type: select
default_prob: 0.7
length:
v: 0.2
range:
- 0.1
- 1.15
type: float
connecting_width:
v: 0.2
range:
- 0
- 2
type: float
default_prob: 0.6
end_width:
v: 1.0
range:
- 0.2
- 2
type: float
default_prob: 0.4
sleeve_angle:
v: 11
range:
- 10
- 50
type: int
opening_dir_mix:
v: 0.1
range:
- -0.9
- 0.8
type: float
default_prob: 1.0
standing_shoulder:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
standing_shoulder_len:
v: 4.0
range:
- 4
- 10
type: float
connect_ruffle:
v: 1.0
range:
- 1
- 2
type: float
default_prob: 0.4
smoothing_coeff:
v: 0.25
range:
- 0.1
- 0.4
type: float
default_prob: 0.8
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
top_ruffle:
v: 1
range:
- 1
- 3
type: float
cuff_len:
v: 0.225
range:
- 0.05
- 0.9
type: float
default_prob: 0.7
skirt_fraction:
v: 0.425
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
skirt_flare:
v: 1.0
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float
default_prob: 0.3
left:
enable_asym:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
fitted_shirt:
strapless:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
shirt:
width:
v: 1.0
range:
- 1.0
- 1.3
type: float
default_prob: 0.4
flare:
v: 1.0
range:
- 0.7
- 1.6
type: float
default_prob: 0.4
collar:
f_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.4
b_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.8
width:
v: 0.5
range:
- 0
- 1
type: float
default_prob: 0.4
fc_angle:
v: 95
range:
- 70
- 110
type: int
bc_angle:
v: 95
range:
- 70
- 110
type: int
f_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
b_bezier_x:
v: 0.175
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_y:
v: 0.175
range:
- 0.05
- 0.95
type: float
f_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
b_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
sleeve:
sleeveless:
v: true
range:
- true
- false
type: bool
default_prob: 0.7
armhole_shape:
v: ArmholeCurve
range:
- ArmholeSquare
- ArmholeAngle
- ArmholeCurve
type: select
default_prob: 0.7
length:
v: 0.2
range:
- 0.1
- 1.15
type: float
connecting_width:
v: 0.2
range:
- 0
- 2
type: float
default_prob: 0.6
end_width:
v: 1.0
range:
- 0.2
- 2
type: float
default_prob: 0.4
sleeve_angle:
v: 11
range:
- 10
- 50
type: int
opening_dir_mix:
v: 0.1
range:
- -0.9
- 0.8
type: float
default_prob: 1.0
standing_shoulder:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
standing_shoulder_len:
v: 4.0
range:
- 4
- 10
type: float
connect_ruffle:
v: 1.0
range:
- 1
- 2
type: float
default_prob: 0.4
smoothing_coeff:
v: 0.25
range:
- 0.1
- 0.4
type: float
default_prob: 0.8
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
top_ruffle:
v: 1
range:
- 1
- 2
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.7
skirt_fraction:
v: 0.5
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
skirt_flare:
v: 1.2
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float
default_prob: 0.3
skirt:
length:
v: 0.1
range:
- -0.2
- 0.95
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
ruffle:
v: 1.4
range:
- 1
- 2
type: float
default_prob: 0.3
bottom_cut:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.3
flare:
v: 1
range:
- 0
- 20
type: int
default_prob: 0.5
flare-skirt:
length:
v: 0.2
range:
- -0.2
- 0.95
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
suns:
v: 0.75
range:
- 0.1
- 1.95
type: float
skirt-many-panels:
n_panels:
v: 4
range:
- 4
- 15
type: int
panel_curve:
v: 0.15
range:
- -0.35
- -0.25
- -0.15
- 0
- 0.15
- 0.25
- 0.35
- 0.45
type: select
asymm:
front_length:
v: 0.675
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
cut:
add:
v: false
range:
- true
- false
type: bool
default_prob: 0.6
depth:
v: 0.5
range:
- 0.05
- 0.95
type: float
default_prob: 0.6
width:
v: 0.1
range:
- 0.05
- 0.4
type: float
place:
v: -0.5
range:
- -1
- 1
type: float
godet-skirt:
base:
v: PencilSkirt
range:
- Skirt2
- PencilSkirt
type: select
default_prob: 0.7
insert_w:
v: 15
range:
- 10
- 50
type: int
insert_depth:
v: 20
range:
- 10
- 50
type: int
num_inserts:
v: 4
range:
- 4
- 6
- 8
- 10
- 12
type: select
cuts_distance:
v: 5
range:
- 0
- 10
type: int
pencil-skirt:
length:
v: 0.4
range:
- 0.2
- 0.95
type: float
rise:
v: 1
range:
- 0.5
- 1
type: float
default_prob: 0.3
flare:
v: 1.0
range:
- 0.6
- 1.5
type: float
default_prob: 0.3
low_angle:
v: 0
range:
- -30
- 30
type: int
default_prob: 0.7
front_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.4
back_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.4
left_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.6
right_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.6
style_side_cut:
v: null
range:
- Sun
- SIGGRAPH_logo
- null
type: select_null
default_prob: 1.0
levels-skirt:
base:
v: PencilSkirt
range:
- Skirt2
- PencilSkirt
- SkirtCircle
- AsymmSkirtCircle
type: select
level:
v: Skirt2
range:
- Skirt2
- SkirtCircle
- AsymmSkirtCircle
type: select
num_levels:
v: 1
range:
- 1
- 5
type: int
level_ruffle:
v: 1
range:
- 1
- 1.7
type: float
length:
v: 0.5
range:
- 0.2
- 0.95
type: float
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
base_length_frac:
v: 0.375
range:
- 0.2
- 0.8
type: float
pants:
length:
v: 0.3
range:
- 0.2
- 0.9
type: float
width:
v: 1.0
range:
- 1.0
- 1.5
type: float
default_prob: 0.5
flare:
v: 1.0
range:
- 0.5
- 1.2
type: float
default_prob: 0.3
rise:
v: 0.5
range:
- 0.5
- 1
type: float
default_prob: 0.3
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
default_prob: 0.5
top_ruffle:
v: 1
range:
- 1
- 2
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.3
skirt_fraction:
v: 0.425
range:
- 0.1
- 0.9
type: float
skirt_flare:
v: 1.0
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,885 @@
design:
meta:
upper:
v: Shirt
range:
- FittedShirt
- Shirt
- null
type: select_null
default_prob: 0.3
wb:
v: null
range:
- StraightWB
- FittedWB
- null
type: select_null
default_prob: 0.5
bottom:
v: null
range:
- SkirtCircle
- AsymmSkirtCircle
- GodetSkirt
- Pants
- Skirt2
- SkirtManyPanels
- PencilSkirt
- SkirtLevels
- null
type: select_null
default_prob: 0.3
waistband:
waist:
v: 1.0
range:
- 1.0
- 2
type: float
default_prob: 0.7
width:
v: 0.2
range:
- 0.1
- 1.
type: float
default_prob: 0.5
shirt:
strapless:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
length:
v: 1.2
range:
- 0.5
- 3.5
type: float
default_prob: 0.7
width:
v: 1.05
range:
- 1.0
- 1.3
type: float
default_prob: 0.4
flare:
v: 1.0
range:
- 0.7
- 1.6
type: float
default_prob: 0.4
collar:
f_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.4
b_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.8
width:
v: 0.2
range:
- -0.5
- 1
type: float
default_prob: 0.4
fc_depth:
v: 0.4
range:
- 0.3
- 2
type: float
default_prob: 0.3
bc_depth:
v: 0
range:
- 0
- 2
type: float
default_prob: 0.4
fc_angle:
v: 95
range:
- 70
- 110
type: int
bc_angle:
v: 95
range:
- 70
- 110
type: int
f_bezier_x:
v: 0.3
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_bezier_y:
v: 0.55
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_x:
v: 0.15
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_y:
v: 0.1
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
b_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
component:
style:
v: null
range:
- Turtle
- SimpleLapel
- Hood2Panels
- null
type: select_null
default_prob: 0.6
depth:
v: 7
range:
- 2
- 8
type: int
lapel_standing:
v: false
range:
- true
- false
type: bool
hood_depth:
v: 1
range:
- 1
- 2
type: float
default_prob: 0.6
hood_length:
v: 1
range:
- 1
- 1.5
type: float
default_prob: 0.6
sleeve:
sleeveless:
v: false
range:
- true
- false
type: bool
default_prob: 0.7
armhole_shape:
v: ArmholeCurve
range:
- ArmholeSquare
- ArmholeAngle
- ArmholeCurve
type: select
default_prob: 0.7
length:
v: 0.3
range:
- 0.1
- 1.15
type: float
connecting_width:
v: 0.2
range:
- 0
- 2
type: float
default_prob: 0.6
end_width:
v: 1.0
range:
- 0.2
- 2
type: float
default_prob: 0.4
sleeve_angle:
v: 10
range:
- 10
- 50
type: int
opening_dir_mix:
v: 0.1
range:
- -0.9
- 0.8
type: float
default_prob: 1.
standing_shoulder:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
standing_shoulder_len:
v: 5.0
range:
- 4
- 10
type: float
connect_ruffle:
v: 1
range:
- 1
- 2
type: float
default_prob: 0.4
smoothing_coeff:
v: 0.25
range:
- 0.1
- 0.4
type: float
default_prob: 0.8
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
top_ruffle:
v: 1
range:
- 1
- 3
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.7
skirt_fraction:
v: 0.5
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
skirt_flare:
v: 1.2
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float
default_prob: 0.3
left:
enable_asym:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
shirt:
strapless:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
width:
v: 1.0
range:
- 1.0
- 1.3
type: float
default_prob: 0.4
flare:
v: 1.0
range:
- 0.7
- 1.6
type: float
default_prob: 0.4
collar:
f_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.4
b_collar:
v: CircleNeckHalf
range:
- CircleNeckHalf
- CurvyNeckHalf
- VNeckHalf
- SquareNeckHalf
- TrapezoidNeckHalf
- CircleArcNeckHalf
- Bezier2NeckHalf
type: select
default_prob: 0.8
width:
v: 0.5
range:
- 0
- 1
type: float
default_prob: 0.4
fc_angle:
v: 95
range:
- 70
- 110
type: int
bc_angle:
v: 95
range:
- 70
- 110
type: int
f_bezier_x:
v: 0.5
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
f_bezier_y:
v: 0.3
range:
- 0.05
- 0.95
type: float
b_bezier_x:
v: 0.5
range:
- 0.05
- 0.95
type: float
default_prob: 0.4
b_bezier_y:
v: 0.3
range:
- 0.05
- 0.95
type: float
f_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
b_flip_curve:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
sleeve:
sleeveless:
v: true
range:
- true
- false
type: bool
default_prob: 0.7
armhole_shape:
v: ArmholeCurve
range:
- ArmholeSquare
- ArmholeAngle
- ArmholeCurve
type: select
default_prob: 0.7
length:
v: 0.3
range:
- 0.1
- 1.15
type: float
connecting_width:
v: 0.2
range:
- 0
- 2
type: float
default_prob: 0.6
end_width:
v: 1.0
range:
- 0.2
- 2
type: float
default_prob: 0.4
sleeve_angle:
v: 10
range:
- 10
- 50
type: int
opening_dir_mix:
v: 0.2
range:
- -0.9
- 0.8
type: float
default_prob: 1.
standing_shoulder:
v: false
range:
- true
- false
type: bool
default_prob: 0.8
standing_shoulder_len:
v: 5.0
range:
- 4
- 10
type: float
connect_ruffle:
v: 1
range:
- 1
- 2
type: float
default_prob: 0.4
smoothing_coeff:
v: 0.25
range:
- 0.1
- 0.4
type: float
default_prob: 0.8
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
top_ruffle:
v: 1
range:
- 1
- 2
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.7
skirt_fraction:
v: 0.5
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
skirt_flare:
v: 1.2
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float
default_prob: 0.3
skirt:
length:
v: 0.2
range:
- -0.2
- 0.95
type: float
rise:
v: 1
range:
- 0.5
- 1
type: float
default_prob: 0.3
ruffle:
v: 1.3
range:
- 1
- 2
type: float
default_prob: 0.3
bottom_cut:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.3
flare:
v: 0
range:
- 0
- 20
type: int
default_prob: 0.5
flare-skirt:
length:
v: 0.2
range:
- -0.2
- 0.95
type: float
rise:
v: 1
range:
- 0.5
- 1
type: float
default_prob: 0.3
suns:
v: 0.75
range:
- 0.1
- 1.95
type: float
skirt-many-panels:
n_panels:
v: 4
range:
- 4
- 15
type: int
panel_curve:
v: 0
range:
- -0.35
- -0.25
- -0.15
- 0
- 0.15
- 0.25
- 0.35
- 0.45
type: select
asymm:
front_length:
v: 0.5
range:
- 0.1
- 0.9
type: float
default_prob: 0.5
cut:
add:
v: false
range:
- true
- false
type: bool
default_prob: 0.6
depth:
v: 0.5
range:
- 0.05
- 0.95
type: float
default_prob: 0.6
width:
v: 0.1
range:
- 0.05
- 0.4
type: float
place:
v: -0.5
range:
- -1
- 1
type: float
godet-skirt:
base:
v: PencilSkirt
range:
- Skirt2
- PencilSkirt
type: select
default_prob: 0.7
insert_w:
v: 15
range:
- 10
- 50
type: int
insert_depth:
v: 20
range:
- 10
- 50
type: int
num_inserts:
v: 4
range:
- 4
- 6
- 8
- 10
- 12
type: select
cuts_distance:
v: 5
range:
- 0
- 10
type: int
pencil-skirt:
length:
v: 0.4
range:
- 0.2
- 0.95
type: float
rise:
v: 1
range:
- 0.5
- 1
type: float
default_prob: 0.3
flare:
v: 1.
range:
- 0.6
- 1.5
type: float
default_prob: 0.3
low_angle:
v: 0
range:
- -30
- 30
type: int
default_prob: 0.7
front_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.4
back_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.4
left_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.6
right_slit:
v: 0
range:
- 0
- 0.9
type: float
default_prob: 0.6
style_side_cut:
v: null
range:
- Sun
- SIGGRAPH_logo
type: select_null
default_prob: 1.
levels-skirt:
base:
v: PencilSkirt
range:
- Skirt2
- PencilSkirt
- SkirtCircle
- AsymmSkirtCircle
type: select
level:
v: Skirt2
range:
- Skirt2
- SkirtCircle
- AsymmSkirtCircle
type: select
num_levels:
v: 1
range:
- 1
- 5
type: int
level_ruffle:
v: 1.0
range:
- 1
- 1.7
type: float
length:
v: 0.5
range:
- 0.2
- 0.95
type: float
rise:
v: 1
range:
- 0.5
- 1
type: float
default_prob: 0.3
base_length_frac:
v: 0.5
range:
- 0.2
- 0.8
type: float
pants:
length:
v: 0.3
range:
- 0.2
- 0.9
type: float
width:
v: 1.0
range:
- 1.0
- 1.5
type: float
default_prob: 0.5
flare:
v: 1.0
range:
- 0.5
- 1.2
type: float
default_prob: 0.3
rise:
v: 1.0
range:
- 0.5
- 1
type: float
default_prob: 0.3
cuff:
type:
v: null
range:
- CuffBand
- CuffSkirt
- CuffBandSkirt
- null
type: select_null
default_prob: 0.5
top_ruffle:
v: 1.0
range:
- 1
- 2
type: float
cuff_len:
v: 0.1
range:
- 0.05
- 0.9
type: float
default_prob: 0.3
skirt_fraction:
v: 0.5
range:
- 0.1
- 0.9
type: float
skirt_flare:
v: 1.2
range:
- 1
- 2
type: float
skirt_ruffle:
v: 1.0
range:
- 1
- 1.5
type: float

View File

@@ -0,0 +1,262 @@
import pygarment as pyg
from assets.garment_programs.circle_skirt import CircleArcPanel
from assets.garment_programs import skirt_paneled
from assets.garment_programs.base_classes import BaseBand
class StraightBandPanel(pyg.Panel):
"""One panel for a panel skirt"""
def __init__(self, name, width, depth, match_int_proportion=None) -> None:
super().__init__(name)
# define edge loop
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0], [0, depth], [width, depth], [width, 0], loop=True)
# define interface
self.interfaces = {
'right': pyg.Interface(self, self.edges[0]),
'top': pyg.Interface(self,
self.edges[1],
ruffle=width / match_int_proportion if match_int_proportion is not None else 1.
).reverse(True),
'left': pyg.Interface(self, self.edges[2]),
'bottom': pyg.Interface(self,
self.edges[3],
ruffle=width / match_int_proportion if match_int_proportion is not None else 1.
)
}
# Default translation
self.top_center_pivot()
self.center_x()
class StraightWB(BaseBand):
"""Simple 2 panel waistband"""
def __init__(self, body, design, rise=1.) -> None:
"""Simple 2 panel waistband
* rise -- the rise value of the bottoms that the WB is attached to
Adapts the shape of the waistband to sit tight on top
of the given rise level (top measurement). If 1. or anything less than waistband width,
the rise is ignored and the StraightWB is created to sit well on the waist
"""
super().__init__(body, design, rise=rise)
# Measurements
self.waist = design['waistband']['waist']['v'] * body['waist']
self.waist_back_frac = body['waist_back_width'] / body['waist']
self.hips = body['hips'] * design['waistband']['waist']['v']
self.hips_back_frac = body['hip_back_width'] / body['hips']
# Params
self.width = design['waistband']['width']['v']
self.rise = rise
# Check correct values
if self.rise + self.width > 1:
self.rise = 1 - self.width
self.top_width = pyg.utils.lin_interpolation(
self.hips, self.waist, self.rise + self.width)
self.top_back_fraction = pyg.utils.lin_interpolation(
self.hips_back_frac, self.waist_back_frac, self.rise + self.width)
self.width = self.width * body['hips_line']
self.define_panels()
self.front.translate_by([0, body['_waist_level'] + 10, 20])
self.back.translate_by([0, body['_waist_level'] + 10, -20])
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['right'], self.back.interfaces['right']),
(self.front.interfaces['left'], self.back.interfaces['left'])
)
self.interfaces = {
'bottom_f': self.front.interfaces['bottom'],
'bottom_b': self.back.interfaces['bottom'],
'top_f': self.front.interfaces['top'],
'top_b': self.back.interfaces['top'],
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['bottom'],
self.back.interfaces['bottom']),
'top': pyg.Interface.from_multiple(
self.front.interfaces['top'],
self.back.interfaces['top']),
}
def define_panels(self):
back_width = self.top_width * self.top_back_fraction
self.front = StraightBandPanel(
'wb_front',
self.top_width - back_width,
self.width,
match_int_proportion=self.body['waist'] - self.body['waist_back_width']
)
self.back = StraightBandPanel(
'wb_back',
back_width,
self.width,
match_int_proportion=self.body['waist_back_width']
)
class FittedWB(StraightWB):
"""Also known as Yoke: a waistband that ~follows the body curvature,
and hence sits tight
Made out of two circular arc panels
"""
def __init__(self, body, design, rise=1.) -> None:
"""A waistband that ~follows the body curvature, and hence sits tight
* rise -- the rise value of the bottoms that the WB is attached to
Adapts the shape of the waistband to sit tight on top
of the given rise level. If 1. or anything less than waistband width,
the rise is ignored and the FittedWB is created to sit well on the waist
"""
self.bottom_width = None
self.bottom_back_fraction = None
super().__init__(body, design, rise)
def define_panels(self):
self.bottom_width = pyg.utils.lin_interpolation(
self.hips, self.waist, self.rise)
self.bottom_back_fraction = pyg.utils.lin_interpolation(
self.hips_back_frac, self.waist_back_frac, self.rise)
self.front = CircleArcPanel.from_all_length(
'wb_front',
self.width,
self.top_width * (1 - self.top_back_fraction),
self.bottom_width * (1 - self.bottom_back_fraction),
match_top_int_proportion=self.body['waist'] - self.body['waist_back_width'],
match_bottom_int_proportion=self.body['waist'] - self.body['waist_back_width']
)
self.back = CircleArcPanel.from_all_length(
'wb_back',
self.width,
self.top_width * self.top_back_fraction,
self.bottom_width * self.bottom_back_fraction,
match_top_int_proportion=self.body['waist_back_width'],
match_bottom_int_proportion=self.body['waist_back_width']
)
class CuffBand(BaseBand):
""" Cuff class for sleeves or pants
band-like piece of fabric with optional "skirt"
"""
def __init__(self, tag, design, length=None) -> None:
super().__init__(body=None, design=design, tag=tag)
self.design = design['cuff']
if length is None:
length = self.design['cuff_len']['v']
self.front = StraightBandPanel(
f'{tag}_cuff_f', self.design['b_width']['v'] / 2, length)
self.front.translate_by([0, 0, 15])
self.back = StraightBandPanel(
f'{tag}_cuff_b', self.design['b_width']['v'] / 2, length)
self.back.translate_by([0, 0, -15])
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['right'], self.back.interfaces['right']),
(self.front.interfaces['left'], self.back.interfaces['left'])
)
self.interfaces = {
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['bottom'],
self.back.interfaces['bottom']),
'top_front': self.front.interfaces['top'],
'top_back': self.back.interfaces['top'],
'top': pyg.Interface.from_multiple(
self.front.interfaces['top'],
self.back.interfaces['top']),
}
class CuffSkirt(BaseBand):
"""A skirt-like flared cuff """
def __init__(self, tag, design, length=None) -> None:
super().__init__(body=None, design=design, tag=tag)
self.design = design['cuff']
width = self.design['b_width']['v']
flare_diff = (self.design['skirt_flare']['v'] - 1) * width / 2
if length is None:
length = self.design['cuff_len']['v']
self.front = skirt_paneled.SkirtPanel(
f'{tag}_cuff_skirt_f', ruffles=self.design['skirt_ruffle']['v'],
waist_length=width / 2, length=length,
flare=flare_diff)
self.front.translate_by([0, 0, 15])
self.back = skirt_paneled.SkirtPanel(
f'{tag}_cuff_skirt_b', ruffles=self.design['skirt_ruffle']['v'],
waist_length=width / 2, length=length,
flare=flare_diff)
self.back.translate_by([0, 0, -15])
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['right'], self.back.interfaces['right']),
(self.front.interfaces['left'], self.back.interfaces['left'])
)
self.interfaces = {
'top': pyg.Interface.from_multiple(
self.front.interfaces['top'], self.back.interfaces['top']),
'top_front': self.front.interfaces['top'],
'top_back': self.back.interfaces['top'],
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['bottom'],
self.back.interfaces['bottom']),
}
class CuffBandSkirt(pyg.Component):
""" Cuff class for sleeves or pants
band-like piece of fabric with optional "skirt"
"""
def __init__(self, tag, design) -> None:
super().__init__(self.__class__.__name__)
self.cuff = CuffBand(
tag,
design,
length=design['cuff']['cuff_len']['v'] * (1 - design['cuff']['skirt_fraction']['v'])
)
self.skirt = CuffSkirt(
tag,
design,
length=design['cuff']['cuff_len']['v'] * design['cuff']['skirt_fraction']['v']
)
# Align
self.skirt.place_below(self.cuff)
self.stitching_rules = pyg.Stitches(
(self.cuff.interfaces['bottom'], self.skirt.interfaces['top']),
)
self.interfaces = {
'top': self.cuff.interfaces['top'],
'top_front': self.cuff.interfaces['top_front'],
'top_back': self.cuff.interfaces['top_back'],
'bottom': self.skirt.interfaces['bottom']
}
def length(self):
return self.cuff.length() + self.skirt.length()

View File

@@ -0,0 +1,122 @@
import pygarment as pyg
class BaseBodicePanel(pyg.Panel):
"""Base class for bodice panels that defines expected interfaces and common functions"""
def __init__(self, name, body, design) -> None:
super().__init__(name)
self.body = body
self.design = design
self.interfaces = {
'outside': object(),
'inside': object(),
'shoulder': object(),
'bottom': object(),
'shoulder_corner': object(),
'collar_corner': object(),
}
def get_width(self, level):
"""Return the panel width at a given level (excluding darts)
* Level is counted from the top of the panel
NOTE: for fitted bodice, the request is only valid for values between 0 and bust_level
"""
# NOTE: this evaluation assumes that the top edge width is the same as bodice shoulder width
side_edge = self.interfaces['outside'].edges[-1]
x = side_edge.end[0] - side_edge.start[0]
y = side_edge.end[1] - side_edge.start[1]
# If the orientation of the edge is "looking down"
# instead of "looking up" as calculations above expect, flip the values
if y < 0:
x, y = -x, -y
return (level * x / y) + self.body['shoulder_w'] / 2
class BaseBottoms(pyg.Component):
"""A base class for all the bottom components.
Defines common elements:
* List of interfaces
* Presence of the rise value
"""
def __init__(self, body, design, tag='', rise=None) -> None:
"""Base bottoms initialization
"""
super().__init__(
self.__class__.__name__ if not tag else f'{self.__class__.__name__}_{tag}')
self.body = body
self.design = design
self.rise = rise
# Set of interfaces that need to be implemented
self.interfaces = {
'top': object()
}
def get_rise(self):
"""Return a rise value for a given component"""
return self.rise
def eval_rise(self, rise):
"""Evaluate updated hip and waist-related measurements,
corresponding to the provided rise value
"""
waist, hips = self.body['waist'], self.body['hips']
hips_level = self.body['hips_line']
self.adj_hips_depth = rise * hips_level
self.adj_waist = pyg.utils.lin_interpolation(hips, waist, rise)
self_adj_back_waist = pyg.utils.lin_interpolation(
self.body['hip_back_width'], self.body['waist_back_width'], rise)
return self.adj_waist, self.adj_hips_depth, self_adj_back_waist
class StackableSkirtComponent(BaseBottoms):
"""
Abstract definition of a skirt that can be stacked with other stackable skirts
(connecting bottom to another StackableSkirtComponent())
"""
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, top_ruffles=True) -> None:
"""Skirt initialization
Extra parameters (length, sleets, top_ruffles)
can be used to overwrite parameters in design dictionary
"""
super().__init__(body, design, tag, rise=rise)
pass
# Set of interfaces that need to be implemented
self.interfaces = {
'top': object(),
'bottom_f': object(),
'bottom_b': object(),
'bottom': object()
}
class BaseBand(pyg.Component):
def __init__(self, body, design, tag='', rise=None) -> None:
"""Base band initialization
"""
super().__init__(
self.__class__.__name__ if not tag else f'{self.__class__.__name__}_{tag}')
self.body = body
self.design = design
self.rise = rise
# Set of interfaces that need to be implemented
self.interfaces = {
'top': object(),
'bottom': object()
}
def length(self):
"""Base length == Length of a first panel"""
return self._get_subcomponents()[0].length()

View File

@@ -0,0 +1,674 @@
from copy import deepcopy
import numpy as np
import pygarment as pyg
from assets.garment_programs.base_classes import BaseBodicePanel
from assets.garment_programs import sleeves
from assets.garment_programs import collars
from assets.garment_programs import tee
from scipy.spatial.transform import Rotation as R
class BodiceFrontHalf(BaseBodicePanel):
def __init__(self, name, body, design) -> None:
super().__init__(name, body, design)
m_bust = body['bust']
m_waist = body['waist']
# sizes
bust_point = body['bust_points'] / 2
front_frac = (body['bust'] - body['back_width']) / 2 / body['bust']
self.width = front_frac * m_bust
waist = (m_waist - body['waist_back_width']) / 2
sh_tan = np.tan(np.deg2rad(body['_shoulder_incl']))
shoulder_incl = sh_tan * self.width
bottom_d_width = (self.width - waist) * 2 / 3
adjustment = sh_tan * (self.width - body['shoulder_w'] / 2)
max_len = body['waist_over_bust_line'] - adjustment
# side length is adjusted due to shoulder inclination
# for the correct sleeve fitting
fb_diff = (front_frac - (0.5 - front_frac)) * body['bust']
back_adjustment = sh_tan * (body['back_width'] / 2 - body['shoulder_w'] / 2)
side_len = body['waist_line'] - back_adjustment - sh_tan * fb_diff
self.edges.append(pyg.EdgeSeqFactory.from_verts(
[0, 0],
[-self.width, 0],
[-self.width, max_len],
[0, max_len + shoulder_incl]
))
self.edges.close_loop()
# Side dart
bust_line = body['waist_line'] - body['_bust_line']
side_d_depth = 0.75 * (self.width - bust_point) # NOTE: calculated value
side_d_width = max_len - side_len
s_edge, side_interface = self.add_dart(
pyg.EdgeSeqFactory.dart_shape(side_d_width, side_d_depth),
self.edges[1],
offset=bust_line + side_d_width / 2)
self.edges.substitute(1, s_edge)
# Take some fabric from the top to match the shoulder width
s_edge[-1].end[0] += (x_upd:=self.width - body['shoulder_w'] / 2)
s_edge[-1].end[1] += (sh_tan * x_upd)
# Bottom dart
b_edge, b_interface = self.add_dart(
pyg.EdgeSeqFactory.dart_shape(bottom_d_width, 0.9 * bust_line),
self.edges[0],
offset=bust_point + bottom_d_width / 2
)
self.edges.substitute(0, b_edge)
# Take some fabric from side in the bottom (!: after side dart insertion)
b_edge[-1].end[0] = - (waist + bottom_d_width)
# Interfaces
self.interfaces = {
'outside': pyg.Interface(self, side_interface), # side_interface, # pyp.Interface(self, [side_interface]), #, self.edges[-3]]),
'inside': pyg.Interface(self, self.edges[-1]),
'shoulder': pyg.Interface(self, self.edges[-2]),
'bottom': pyg.Interface(self, b_interface),
# Reference to the corner for sleeve and collar projections
'shoulder_corner': pyg.Interface(
self, [self.edges[-3], self.edges[-2]]),
'collar_corner': pyg.Interface(
self, [self.edges[-2], self.edges[-1]])
}
# default placement
self.translate_by([0, body['height'] - body['head_l'] - max_len - shoulder_incl, 0])
class BodiceBackHalf(BaseBodicePanel):
"""Panel for the back of basic fitted bodice block"""
def __init__(self, name, body, design) -> None:
super().__init__(name, body, design)
# Overall measurements
self.width = body['back_width'] / 2
waist = body['waist_back_width'] / 2
# NOTE: no inclination on the side, since there is not much to begin with
waist_width = self.width if waist < self.width else waist
shoulder_incl = (sh_tan:=np.tan(np.deg2rad(body['_shoulder_incl']))) * self.width
# Adjust to make sure length is measured from the shoulder
# and not the de-fact side of the garment
back_adjustment = sh_tan * (self.width - body['shoulder_w'] / 2)
length = body['waist_line'] - back_adjustment
# Base edge loop
edge_0 = pyg.CurveEdgeFactory.curve_from_tangents(
start=[0, shoulder_incl / 4], # back a little shorter
end=[-waist_width, 0],
target_tan0=[-1, 0]
)
self.edges.append(edge_0)
self.edges.append(pyg.EdgeSeqFactory.from_verts(
edge_0.end,
[-self.width, body['waist_line'] - body['_bust_line']], # from the bottom
[-self.width, length],
[0, length + shoulder_incl], # Add some fabric for the neck (inclination of shoulders)
))
self.edges.close_loop()
# Take some fabric from the top to match the shoulder width
self.interfaces = {
'outside': pyg.Interface(
self, [self.edges[1], self.edges[2]]),
'inside': pyg.Interface(self, self.edges[-1]),
'shoulder': pyg.Interface(self, self.edges[-2]),
'bottom': pyg.Interface(self, self.edges[0]),
# Reference to the corners for sleeve and collar projections
'shoulder_corner': pyg.Interface(
self, pyg.EdgeSequence(self.edges[-3], self.edges[-2])),
'collar_corner': pyg.Interface(
self, pyg.EdgeSequence(self.edges[-2], self.edges[-1]))
}
# Bottom dart as cutout -- for straight line
if waist < self.get_width(self.edges[2].end[1] - self.edges[2].start[1]):
w_diff = waist_width - waist
side_adj = 0 if w_diff < 4 else w_diff / 6 # NOTE: don't take from sides if the difference is too small
bottom_d_width = w_diff - side_adj
bottom_d_width /= 2 # double darts
bottom_d_depth = 1. * (length - body['_bust_line']) # calculated value
bottom_d_position = body['bum_points'] / 2
# TODOLOW Avoid hardcoding for matching with the bottoms?
dist = bottom_d_position * 0.5 # Dist between darts -> dist between centers
b_edge, b_interface = self.add_dart(
pyg.EdgeSeqFactory.dart_shape(bottom_d_width, 0.9 * bottom_d_depth),
self.edges[0],
offset=bottom_d_position + dist / 2 + bottom_d_width + bottom_d_width / 2,
)
b_edge, b_interface = self.add_dart(
pyg.EdgeSeqFactory.dart_shape(bottom_d_width, bottom_d_depth),
b_edge[0],
offset=bottom_d_position - dist / 2 + bottom_d_width / 2,
edge_seq=b_edge,
int_edge_seq=b_interface,
)
self.edges.substitute(0, b_edge)
self.interfaces['bottom'] = pyg.Interface(self, b_interface)
# Remove fabric from the sides if the diff is big enough
b_edge[-1].end[0] += side_adj
# default placement
self.translate_by([0, body['height'] - body['head_l'] - length - shoulder_incl, 0])
def get_width(self, level):
return self.width
class BodiceHalf(pyg.Component):
"""Definition of a half of an upper garment with sleeves and collars"""
def __init__(self, name, body, design, fitted=True) -> None:
super().__init__(name)
design = deepcopy(design) # Recalculate freely!
# Torso
if fitted:
self.ftorso = BodiceFrontHalf(
f'{name}_ftorso', body, design).translate_by([0, 0, 30])
self.btorso = BodiceBackHalf(
f'{name}_btorso', body, design).translate_by([0, 0, -25])
else:
self.ftorso = tee.TorsoFrontHalfPanel(
f'{name}_ftorso', body, design).translate_by([0, 0, 30])
self.btorso = tee.TorsoBackHalfPanel(
f'{name}_btorso', body, design).translate_by([0, 0, -25])
# Interfaces
self.interfaces.update({
'f_bottom': self.ftorso.interfaces['bottom'],
'b_bottom': self.btorso.interfaces['bottom'],
'front_in': self.ftorso.interfaces['inside'],
'back_in': self.btorso.interfaces['inside']
})
# Sleeves/collar cuts
self.sleeve = None
self.collar_comp = None
self.eval_dep_params(body, design)
if design['fitted_shirt']['strapless']['v'] and fitted: # NOTE: Strapless design only for fitted tops
self.make_strapless(body, design)
else:
# Sleeves and collars
self.add_sleeves(name, body, design)
self.add_collars(name, body, design)
self.stitching_rules.append((
self.ftorso.interfaces['shoulder'],
self.btorso.interfaces['shoulder']
)) # tops
# Main connectivity
self.stitching_rules.append((
self.ftorso.interfaces['outside'], self.btorso.interfaces['outside'])) # sides
def eval_dep_params(self, body, design):
# Sleeves
# NOTE assuming the vertical side is the first argument
max_cwidth = self.ftorso.interfaces['shoulder_corner'].edges[0].length() - 1 # cm
min_cwidth = body['_armscye_depth']
v = design['sleeve']['connecting_width']['v']
design['sleeve']['connecting_width']['v'] = min(min_cwidth + min_cwidth * v, max_cwidth)
# Collars
# NOTE: Assuming the first is the top edge
# Width
# TODOLOW What if sleeve inclination is variable?
# NOTE: Back panel is more narrow, so using it
max_w = body['_base_sleeve_balance'] - 2 # 1 cm from default sleeve
min_w = body['neck_w']
if design['collar']['width']['v'] >= 0:
design['collar']['width']['v'] = width = pyg.utils.lin_interpolation(min_w, max_w, design['collar']['width']['v'])
else:
design['collar']['width']['v'] = width = pyg.utils.lin_interpolation(0, min_w, 1 + design['collar']['width']['v'])
# Depth
# Collar depth is given w.r.t. length.
# adjust for the shoulder inclination
tg = np.tan(np.deg2rad(body['_shoulder_incl']))
f_depth_adj = tg * (self.ftorso.get_width(0) - width / 2)
b_depth_adj = tg * (self.btorso.get_width(0) - width / 2)
max_f_len = self.ftorso.interfaces['collar_corner'].edges[1].length() - tg * self.ftorso.get_width(0) - 1 # cm
max_b_len = self.btorso.interfaces['collar_corner'].edges[1].length() - tg * self.btorso.get_width(0) - 1 # cm
design['collar']['f_strapless_depth'] = {}
design['collar']['f_strapless_depth']['v'] = min(
design['collar']['fc_depth']['v'] * body['_bust_line'],
max_f_len)
design['collar']['fc_depth']['v'] = design['collar']['f_strapless_depth']['v'] + f_depth_adj
design['collar']['b_strapless_depth'] = {}
design['collar']['b_strapless_depth']['v'] = min(
design['collar']['bc_depth']['v'] * body['_bust_line'],
max_b_len)
design['collar']['bc_depth']['v'] = design['collar']['b_strapless_depth']['v'] + b_depth_adj
def add_sleeves(self, name, body, design):
self.sleeve = sleeves.Sleeve(
name, body, design,
front_w=self.ftorso.get_width,
back_w=self.btorso.get_width
)
# self.sleeve = sleeves.OnePieceSleeve(
# name, body, design,
# front_w=self.ftorso.get_width,
# back_w=self.btorso.get_width
# )
_, f_sleeve_int = pyg.ops.cut_corner(
self.sleeve.interfaces['in_front_shape'].edges,
self.ftorso.interfaces['shoulder_corner'],
verbose=self.verbose
)
_, b_sleeve_int = pyg.ops.cut_corner(
self.sleeve.interfaces['in_back_shape'].edges,
self.btorso.interfaces['shoulder_corner'],
verbose=self.verbose
)
if not design['sleeve']['sleeveless']['v']:
# Ordering
bodice_sleeve_int = pyg.Interface.from_multiple(
f_sleeve_int.reverse(with_edge_dir_reverse=True),
b_sleeve_int.reverse(),
)
self.stitching_rules.append((
self.sleeve.interfaces['in'],
bodice_sleeve_int
))
# NOTE: This is a heuristic tuned for arm poses 30 deg-60 deg
# used in the dataset
# FIXME Needs a better general solution
gap = -1 - body['arm_pose_angle'] / 10
self.sleeve.place_by_interface(
self.sleeve.interfaces['in'],
bodice_sleeve_int,
gap=gap,
alignment='top',
)
# Add edge labels
f_sleeve_int.edges.propagate_label(f'{self.name}_armhole')
b_sleeve_int.edges.propagate_label(f'{self.name}_armhole')
def add_collars(self, name, body, design):
# Front
collar_type = getattr(
collars,
str(design['collar']['component']['style']['v']),
collars.NoPanelsCollar
)
self.collar_comp = collar_type(name, body, design)
# Project shape
_, fc_interface = pyg.ops.cut_corner(
self.collar_comp.interfaces['front_proj'].edges,
self.ftorso.interfaces['collar_corner'],
verbose=self.verbose
)
_, bc_interface = pyg.ops.cut_corner(
self.collar_comp.interfaces['back_proj'].edges,
self.btorso.interfaces['collar_corner'],
verbose=self.verbose
)
# Add stitches/interfaces
if 'bottom' in self.collar_comp.interfaces:
self.stitching_rules.append((
pyg.Interface.from_multiple(fc_interface, bc_interface),
self.collar_comp.interfaces['bottom']
))
# Upd front interfaces accordingly
if 'front' in self.collar_comp.interfaces:
self.interfaces['front_collar'] = self.collar_comp.interfaces['front']
self.interfaces['front_in'] = pyg.Interface.from_multiple(
self.ftorso.interfaces['inside'], self.interfaces['front_collar']
)
if 'back' in self.collar_comp.interfaces:
self.interfaces['back_collar'] = self.collar_comp.interfaces['back']
self.interfaces['back_in'] = pyg.Interface.from_multiple(
self.btorso.interfaces['inside'], self.interfaces['back_collar']
)
# Add edge labels
fc_interface.edges.propagate_label(f'{self.name}_collar')
bc_interface.edges.propagate_label(f'{self.name}_collar')
def make_strapless(self, body, design):
out_depth = design['sleeve']['connecting_width']['v']
f_in_depth = design['collar']['f_strapless_depth']['v']
b_in_depth = design['collar']['b_strapless_depth']['v']
# Shoulder adjustment for the back
# TODOLOW Shoulder adj evaluation should be a function
shoulder_angle = np.deg2rad(body['_shoulder_incl'])
sleeve_balance = body['_base_sleeve_balance'] / 2
back_w = self.btorso.get_width(0)
shoulder_adj = np.tan(shoulder_angle) * (back_w - sleeve_balance)
out_depth -= shoulder_adj
# Upd back
self._adjust_top_level(self.btorso, out_depth, b_in_depth)
# Front depth determined by ~compensating for lenght difference
len_back = self.btorso.interfaces['outside'].edges.length()
len_front = self.ftorso.interfaces['outside'].edges.length()
self._adjust_top_level(self.ftorso, out_depth, f_in_depth, target_remove=(len_front - len_back))
# Placement
# NOTE: The commented line places the top a bit higher, increasing the chanced of correct drape
# Surcumvented by attachment constraint, so removed for nicer alignment in asymmetric garments
# self.translate_by([0, out_depth - body['_armscye_depth'] * 0.75, 0]) # adjust for better localisation
# Add a label
self.ftorso.interfaces['shoulder'].edges.propagate_label('strapless_top')
self.btorso.interfaces['shoulder'].edges.propagate_label('strapless_top')
def _adjust_top_level(self, panel, out_level, in_level, target_remove=None):
"""Crops the top of the bodice front/back panel for strapless style
* out_length_diff -- if set, determined the length difference that should be compensates
after cutting the depth
"""
# TODOLOW Should this be the panel's function?
panel_top = panel.interfaces['shoulder'].edges[0]
min_y = min(panel_top.start[1], panel_top.end[1])
# Order vertices
ins, out = panel_top.start, panel_top.end
if panel_top.start[1] < panel_top.end[1]:
ins, out = out, ins
# Inside is a simple vertical line and can be adjusted by chaning Y value
ins[1] = min_y - in_level
# Outside could be inclined, so needs further calculations
outside_edge = panel.interfaces['outside'].edges[-1]
bot, top = outside_edge.start, outside_edge.end
if bot is out:
bot, top = top, bot
if target_remove is not None:
# Adjust the depth to remove this length exactly
angle_sin = abs(out[1] - bot[1]) / outside_edge.length()
curr_remove = out_level / angle_sin
length_diff = target_remove - curr_remove
adjustment = length_diff * angle_sin
out_level += adjustment
angle_cotan = abs(out[0] - bot[0]) / abs(out[1] - bot[1])
out[0] -= out_level * angle_cotan
out[1] = min_y - out_level
def length(self):
return self.btorso.length()
class Shirt(pyg.Component):
"""Panel for the front of upper garments with darts to properly fit it to
the shape"""
def __init__(self, body, design, fitted=False) -> None:
name_with_params = f"{self.__class__.__name__}"
super().__init__(name_with_params)
design = self.eval_dep_params(design)
self.right = BodiceHalf(f'right', body, design, fitted=fitted)
self.left = BodiceHalf(
f'left', body,
design['left'] if design['left']['enable_asym']['v'] else design,
fitted=fitted).mirror()
self.stitching_rules.append((self.right.interfaces['front_in'],
self.left.interfaces['front_in']))
self.stitching_rules.append((self.right.interfaces['back_in'],
self.left.interfaces['back_in']))
# Adjust interface ordering for correct connectivity
self.interfaces = { # Bottom connection
'bottom': pyg.Interface.from_multiple(
self.right.interfaces['f_bottom'].reverse(),
self.left.interfaces['f_bottom'],
self.left.interfaces['b_bottom'].reverse(),
self.right.interfaces['b_bottom'],)
}
def eval_dep_params(self, design):
# NOTE: Support for full collars with partially strapless top
# or combination of paneled collar styles
# requres further development
# TODOLOW enable this one to work
if design['left']['enable_asym']['v']:
# Force no collars since they are not compatible with each other
design = deepcopy(design)
design['collar']['component']['style']['v'] = None
design['left']['collar']['component'] = dict(style=dict(v=None))
# Left-right design compatibility
design['left']['shirt'].update(length={})
design['left']['shirt']['length']['v'] = design['shirt']['length']['v']
design['left']['collar'].update(fc_depth={}, bc_depth={})
design['left']['collar']['fc_depth']['v'] = design['collar']['fc_depth']['v']
design['left']['collar']['bc_depth']['v'] = design['collar']['bc_depth']['v']
return design
def length(self):
return self.right.length()
class FittedShirt(Shirt):
"""Creates fitted shirt
NOTE: Separate class is used for selection convenience.
Even though most of the processing is the same
(hence implemented with the same components except for panels),
design parametrization differs significantly.
With that, we decided to separate the top level names
"""
def __init__(self, body, design) -> None:
super().__init__(body, design, fitted=True)
class PrincessSeamShirt(pyg.Component):
"""Class representing a shirt with princess seams."""
def __init__(self, body, design):
# Initialize the base Component class
super().__init__("PrincessSeamShirt")
# Evaluate design parameters
self.eval_dep_params(design)
# Create front and back panels
self.front_panel = self.create_front_panel(body, design)
self.back_panel = self.create_back_panel(body, design)
# Add panels as components
self.add_component(self.front_panel)
self.add_component(self.back_panel)
# Define stitching rules
self.stitching_rules = [
# Side seams
(self.front_panel.interfaces['side_seam'], self.back_panel.interfaces['side_seam']),
# Shoulder seams
(self.front_panel.interfaces['shoulder_seam'], self.back_panel.interfaces['shoulder_seam']),
# Front princess seams
(self.front_panel.interfaces['princess_seam_left'], self.front_panel.left_princess_panel.interfaces['princess_seam']),
(self.front_panel.interfaces['princess_seam_right'], self.front_panel.right_princess_panel.interfaces['princess_seam']),
# Back princess seams
(self.back_panel.interfaces['princess_seam_left'], self.back_panel.left_princess_panel.interfaces['princess_seam']),
(self.back_panel.interfaces['princess_seam_right'], self.back_panel.right_princess_panel.interfaces['princess_seam']),
]
def eval_dep_params(self, design):
# Placeholder for evaluating dependent design parameters
pass
def create_front_panel(self, body, design):
# Create the front panel
front_panel = pyg.Panel("FrontPanel")
# Body measurements
bust = body['bust']
waist = body['waist']
hips = body['hips']
shoulder_width = body['shoulder_width']
neck_width = body['neck_width']
neck_depth = body['front_neck_depth']
# Calculate panel dimensions
panel_width = bust / 2
princess_seam_pos = bust / 8
# Define key points
points = [
[0, 0], # Bottom left
[0, hips], # Hip point
[0, waist], # Waist point
[princess_seam_pos, bust], # Left bust point
[shoulder_width / 2, bust + neck_depth], # Left shoulder
[panel_width - shoulder_width / 2, bust + neck_depth], # Right shoulder
[panel_width - princess_seam_pos, bust], # Right bust point
[panel_width, waist], # Right waist point
[panel_width, hips], # Right hip point
[panel_width, 0], # Bottom right
]
# Create edges
edges = pyg.EdgeSequence()
for i in range(len(points) - 1):
edges.append(pyg.Edge(points[i], points[i + 1]))
edges.append(pyg.Edge(points[-1], points[0])) # Close shape
# Assign edges to panel
front_panel.edges = edges
# Define interfaces
front_panel.interfaces = {
'side_seam': pyg.Interface(front_panel, [edges[0], edges[8]]),
'shoulder_seam': pyg.Interface(front_panel, [edges[4], edges[5]]),
'princess_seam_left': pyg.Interface(front_panel, [edges[2]]),
'princess_seam_right': pyg.Interface(front_panel, [edges[7]]),
}
# Create princess panels
front_panel.left_princess_panel = self.create_princess_panel(front_panel, edges[2], "FrontLeftPrincessPanel")
front_panel.right_princess_panel = self.create_princess_panel(front_panel, edges[7], "FrontRightPrincessPanel")
return front_panel
def create_back_panel(self, body, design):
# Create the back panel
back_panel = pyg.Panel("BackPanel")
# Body measurements
bust = body['bust']
waist = body['waist']
hips = body['hips']
shoulder_width = body['shoulder_width']
neck_width = body['neck_width']
neck_depth = body['back_neck_depth']
# Calculate panel dimensions
panel_width = bust / 2
princess_seam_pos = bust / 8
# Define key points
points = [
[0, 0], # Bottom left
[0, hips], # Hip point
[0, waist], # Waist point
[princess_seam_pos, bust], # Left bust point
[shoulder_width / 2, bust + neck_depth], # Left shoulder
[panel_width - shoulder_width / 2, bust + neck_depth], # Right shoulder
[panel_width - princess_seam_pos, bust], # Right bust point
[panel_width, waist], # Right waist point
[panel_width, hips], # Right hip point
[panel_width, 0], # Bottom right
]
# Create edges
edges = pyg.EdgeSequence()
for i in range(len(points) - 1):
edges.append(pyg.Edge(points[i], points[i + 1]))
edges.append(pyg.Edge(points[-1], points[0])) # Close shape
# Assign edges to panel
back_panel.edges = edges
# Define interfaces
back_panel.interfaces = {
'side_seam': pyg.Interface(back_panel, [edges[0], edges[8]]),
'shoulder_seam': pyg.Interface(back_panel, [edges[4], edges[5]]),
'princess_seam_left': pyg.Interface(back_panel, [edges[2]]),
'princess_seam_right': pyg.Interface(back_panel, [edges[7]]),
}
# Create princess panels
back_panel.left_princess_panel = self.create_princess_panel(back_panel, edges[2], "BackLeftPrincessPanel")
back_panel.right_princess_panel = self.create_princess_panel(back_panel, edges[7], "BackRightPrincessPanel")
return back_panel
def create_princess_panel(self, parent_panel, seam_edge, panel_name):
# Create a princess panel along the seam
princess_panel = pyg.Panel(panel_name)
# Seam edge points
start_point = seam_edge.start
end_point = seam_edge.end
# Panel offset
offset = 5 # Width of the princess panel
# Calculate new points
offset_start = [start_point[0] + offset, start_point[1]]
offset_end = [end_point[0] + offset, end_point[1]]
# Create edges
edges = pyg.EdgeSequence()
edges.append(pyg.Edge(start_point, end_point)) # Seam edge
edges.append(pyg.Edge(end_point, offset_end)) # Top edge
edges.append(pyg.Edge(offset_end, offset_start)) # Outer edge
edges.append(pyg.Edge(offset_start, start_point)) # Bottom edge
# Close shape
edges.close_loop()
# Assign edges to panel
princess_panel.edges = edges
# Define interfaces
princess_panel.interfaces = {
'princess_seam': pyg.Interface(princess_panel, [edges[0]]),
'outer_seam': pyg.Interface(princess_panel, [edges[2]]),
}
return princess_panel

View File

@@ -0,0 +1,234 @@
import numpy as np
import pygarment as pyg
from assets.garment_programs.base_classes import StackableSkirtComponent
class CircleArcPanel(pyg.Panel):
"""One panel circle skirt"""
def __init__(self,
name,
top_rad, length, angle,
match_top_int_proportion : bool = None,
match_bottom_int_proportion=None
) -> None:
super().__init__(name)
halfarc = angle / 2
dist_w = 2 * top_rad * np.sin(halfarc)
dist_out = 2 * (top_rad + length) * np.sin(halfarc)
vert_len = length * np.cos(halfarc)
# top
self.edges.append(pyg.CircleEdgeFactory.from_points_radius(
[-dist_w/2, 0], [dist_w/2, 0],
radius=top_rad, large_arc=halfarc > np.pi / 2))
self.edges.append(pyg.Edge(
self.edges[-1].end, [dist_out / 2, -vert_len]))
# Bottom
self.edges.append(pyg.CircleEdgeFactory.from_points_radius(
self.edges[-1].end, [- dist_out / 2, -vert_len],
radius=top_rad + length,
large_arc=halfarc > np.pi / 2, right=False))
self.edges.close_loop()
# Interfaces
self.interfaces = {
'top': pyg.Interface(self, self.edges[0],
ruffle=self.edges[0].length() / match_top_int_proportion if match_top_int_proportion is not None else 1.
).reverse(True),
'bottom': pyg.Interface(self, self.edges[2],
ruffle=self.edges[2].length() / match_bottom_int_proportion if match_bottom_int_proportion is not None else 1.
),
'left': pyg.Interface(self, self.edges[1]),
'right': pyg.Interface(self, self.edges[3])
}
def length(self, *args):
return self.interfaces['right'].edges.length()
@staticmethod
def from_w_length_suns(name, length, top_width, sun_fraction, **kwargs):
arc = sun_fraction * 2 * np.pi
rad = top_width / arc
return CircleArcPanel(name, rad, length, arc, **kwargs)
@staticmethod
def from_all_length(name, length, top_width, bottom_width, **kwargs):
diff = bottom_width - top_width
arc = diff / length
rad = top_width / arc
return CircleArcPanel(name, rad, length, arc, **kwargs)
@staticmethod
def from_length_rad(name, length, top_width, rad, **kwargs):
arc = top_width / rad
return CircleArcPanel(name, rad, length, arc, **kwargs)
class AsymHalfCirclePanel(pyg.Panel):
"""Panel for a asymmetrci circle skirt"""
def __init__(self,
name,
top_rad, length_f, length_s,
match_top_int_proportion=None,
match_bottom_int_proportion=None
) -> None:
""" Half a shifted arc section
"""
super().__init__(name)
dist_w = 2 * top_rad
dist_out = 2 * (top_rad + length_s)
# top
self.edges.append(pyg.CircleEdgeFactory.from_points_radius(
[-dist_w/2, 0], [dist_w/2, 0],
radius=top_rad, large_arc=False))
self.edges.append(pyg.Edge(
self.edges[-1].end, [dist_out / 2, 0]))
# Bottom
self.edges.append(
pyg.CircleEdgeFactory.from_three_points(
self.edges[-1].end, [- dist_out / 2, 0],
point_on_arc=[0, -(top_rad + length_f)]
)
)
self.edges.close_loop()
# Interfaces
self.interfaces = {
'top': pyg.Interface(self, self.edges[0],
ruffle=self.edges[0].length() / match_top_int_proportion if match_top_int_proportion is not None else 1.
).reverse(True),
'bottom': pyg.Interface(self, self.edges[2],
ruffle=self.edges[2].length() / match_bottom_int_proportion if match_bottom_int_proportion is not None else 1.
),
'left': pyg.Interface(self, self.edges[1]),
'right': pyg.Interface(self, self.edges[3])
}
def length(self, *args):
return self.interfaces['right'].edges.length()
class SkirtCircle(StackableSkirtComponent):
"""Simple circle skirt"""
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, asymm=False, min_len=5, **kwargs) -> None:
super().__init__(body, design, tag)
design = design['flare-skirt']
suns = design['suns']['v']
self.rise = design['rise']['v'] if rise is None else rise
waist, hips_depth, _ = self.eval_rise(self.rise)
if length is None: # take from design parameters
length = hips_depth + design['length']['v'] * body['_leg_length']
# NOTE: with some combinations of rise and length parameters length may become too small/negative
# Hence putting a min positive value here
length = max(length, min_len)
# panels
if not asymm: # Typical symmetric skirt
self.front = CircleArcPanel.from_w_length_suns(
f'skirt_front_{tag}' if tag else 'skirt_front',
length, waist / 2, suns / 2,
match_top_int_proportion=self.body['waist'] - self.body['waist_back_width'],
).translate_by([0, body['_waist_level'], 15])
self.back = CircleArcPanel.from_w_length_suns(
f'skirt_back_{tag}' if tag else 'skirt_back',
length, waist / 2, suns / 2,
match_top_int_proportion=self.body['waist_back_width'],
).translate_by([0, body['_waist_level'], -15])
else:
# NOTE: Asymmetic front/back is only defined on full skirt (1 sun)
w_rad = waist / 2 / np.pi
f_length = design['asymm']['front_length']['v'] * length
tot_len = w_rad * 2 + length + f_length
del_r = tot_len / 2 - f_length - w_rad
s_length = np.sqrt((tot_len / 2)**2 - del_r**2) - w_rad
self.front = AsymHalfCirclePanel(
f'skirt_front_{tag}' if tag else 'skirt_front',
w_rad, f_length, s_length,
match_top_int_proportion=self.body['waist'] - self.body['waist_back_width'],
).translate_by([0, body['_waist_level'], 15])
self.back = AsymHalfCirclePanel(
f'skirt_back_{tag}' if tag else 'skirt_back',
w_rad, length, s_length,
match_top_int_proportion=self.body['waist_back_width'],
).translate_by([0, body['_waist_level'], -15])
# Add a cut
if design['cut']['add']['v'] and slit:
self.add_cut(
self.front if design['cut']['place']['v'] > 0 else self.back,
design, length)
# Stitches
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['right'], self.back.interfaces['right']),
(self.front.interfaces['left'], self.back.interfaces['left'])
)
# Interfaces
self.interfaces = {
'top': pyg.Interface.from_multiple(self.front.interfaces['top'], self.back.interfaces['top']),
'bottom_f': self.front.interfaces['bottom'],
'bottom_b': self.back.interfaces['bottom'],
'bottom': pyg.Interface.from_multiple(self.front.interfaces['bottom'], self.back.interfaces['bottom'])
}
def add_cut(self, panel, design, sk_length):
"""Add a cut to the skirt"""
width, depth = design['cut']['width']['v'] * sk_length, design['cut']['depth']['v'] * sk_length
target_edge = panel.interfaces['bottom'].edges[0]
t_len = target_edge.length()
offset = abs(design['cut']['place']['v'] * t_len)
# Respect the placement boundaries
offset = max(offset, width / 2)
offset = min(offset, t_len - width / 2)
# NOTE: heuristic is specific for the panels that we use
right = target_edge.start[0] > target_edge.end[0]
# Make a cut
cut_shape = pyg.EdgeSeqFactory.dart_shape(width, depth=depth)
new_edges, _, interf_edges = pyg.ops.cut_into_edge(
cut_shape, target_edge,
offset=offset,
right=right
)
panel.edges.substitute(target_edge, new_edges)
panel.interfaces['bottom'].substitute(
target_edge, interf_edges,
[panel for _ in range(len(interf_edges))])
def length(self, *args):
return self.front.length()
class AsymmSkirtCircle(SkirtCircle):
"""Front/back asymmetric skirt"""
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, **kwargs):
super().__init__(body, design, tag, length, rise, slit, asymm=True)

View File

@@ -0,0 +1,370 @@
import numpy as np
from scipy.spatial.transform import Rotation as R
import pygarment as pyg
from assets.garment_programs.bands import StraightBandPanel
from assets.garment_programs.circle_skirt import CircleArcPanel
# # ------ Collar shapes withough extra panels ------
def VNeckHalf(depth, width, **kwargs):
"""Simple VNeck design"""
edges = pyg.EdgeSequence(pyg.Edge([0, 0], [width / 2, -depth]))
return edges
def SquareNeckHalf(depth, width, **kwargs):
"""Square design"""
edges = pyg.EdgeSeqFactory.from_verts([0, 0], [0, -depth], [width / 2, -depth])
return edges
def TrapezoidNeckHalf(depth, width, angle=90, verbose=True, **kwargs):
"""Trapesoid neck design"""
# Special case when angle = 180 (sin = 0)
if (pyg.utils.close_enough(angle, 180, tol=1)
or pyg.utils.close_enough(angle, 0, tol=1)):
# degrades into VNeck
return VNeckHalf(depth, width)
rad_angle = np.deg2rad(angle)
bottom_x = -depth * np.cos(rad_angle) / np.sin(rad_angle)
if bottom_x > width / 2: # Invalid angle/depth/width combination resulted in invalid shape
if verbose:
print('TrapezoidNeckHalf::WARNING::Parameters are invalid and create overlap: '
f'{bottom_x} > {width / 2}. '
'The collar is reverted to VNeck')
return VNeckHalf(depth, width)
edges = pyg.EdgeSeqFactory.from_verts([0, 0], [bottom_x, -depth], [width / 2, -depth])
return edges
def CurvyNeckHalf(depth, width, flip=False, **kwargs):
"""Testing Curvy Collar design"""
sign = -1 if flip else 1
edges = pyg.EdgeSequence(pyg.CurveEdge(
[0, 0], [width / 2,-depth],
[[0.4, sign * 0.3], [0.8, sign * -0.3]]))
return edges
def CircleArcNeckHalf(depth, width, angle=90, flip=False, **kwargs):
"""Collar with a side represented by a circle arc"""
# 1/4 of a circle
edges = pyg.EdgeSequence(pyg.CircleEdgeFactory.from_points_angle(
[0, 0], [width / 2,-depth], arc_angle=np.deg2rad(angle),
right=(not flip)
))
return edges
def CircleNeckHalf(depth, width, **kwargs):
"""Collar that forms a perfect circle arc when halfs are stitched"""
# Take a full desired arc and half it!
circle = pyg.CircleEdgeFactory.from_three_points(
[0, 0],
[width, 0],
[width / 2, -depth])
subdiv = circle.subdivide_len([0.5, 0.5])
return pyg.EdgeSequence(subdiv[0])
def Bezier2NeckHalf(depth, width, flip=False, x=0.5, y=0.3, **kwargs):
"""2d degree Bezier curve as neckline"""
sign = 1 if flip else -1
edges = pyg.EdgeSequence(pyg.CurveEdge(
[0, 0], [width / 2,-depth],
[[x, sign*y]]))
return edges
# # ------ Collars with panels ------
class NoPanelsCollar(pyg.Component):
"""Face collar class that only forms the projected shapes """
def __init__(self, name, body, design) -> None:
super().__init__(name)
# Front
collar_type = globals()[design['collar']['f_collar']['v']]
f_collar = collar_type(
design['collar']['fc_depth']['v'],
design['collar']['width']['v'],
angle=design['collar']['fc_angle']['v'],
flip=design['collar']['f_flip_curve']['v'],
x=design['collar']['f_bezier_x']['v'],
y=design['collar']['f_bezier_y']['v'],
verbose=self.verbose
)
# Back
collar_type = globals()[design['collar']['b_collar']['v']]
b_collar = collar_type(
design['collar']['bc_depth']['v'],
design['collar']['width']['v'],
angle=design['collar']['bc_angle']['v'],
flip=design['collar']['b_flip_curve']['v'],
x=design['collar']['b_bezier_x']['v'],
y=design['collar']['b_bezier_y']['v'],
verbose=self.verbose
)
self.interfaces = {
'front_proj': pyg.Interface(self, f_collar),
'back_proj': pyg.Interface(self, b_collar)
}
def length(self):
return 0
class Turtle(pyg.Component):
def __init__(self, tag, body, design) -> None:
super().__init__(f'Turtle_{tag}')
depth = design['collar']['component']['depth']['v']
# --Projecting shapes--
f_collar = CircleNeckHalf(
design['collar']['fc_depth']['v'],
design['collar']['width']['v'])
b_collar = CircleNeckHalf(
design['collar']['bc_depth']['v'],
design['collar']['width']['v'])
self.interfaces = {
'front_proj': pyg.Interface(self, f_collar),
'back_proj': pyg.Interface(self, b_collar)
}
# -- Panels --
length_f, length_b = f_collar.length(), b_collar.length()
height_p = body['height'] - body['head_l'] + depth
self.front = StraightBandPanel(
f'{tag}_turtle_front', length_f, depth).translate_by(
[-length_f / 2, height_p, 10])
self.back = StraightBandPanel(
f'{tag}_turtle_back', length_b, depth).translate_by(
[-length_b / 2, height_p, -10])
self.stitching_rules.append((
self.front.interfaces['right'],
self.back.interfaces['right']
))
self.interfaces.update({
'front': self.front.interfaces['left'],
'back': self.back.interfaces['left'],
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['bottom'],
self.back.interfaces['bottom']
)
})
def length(self):
return self.interfaces['back'].edges.length()
class SimpleLapelPanel(pyg.Panel):
"""A panel for the front part of simple Lapel"""
def __init__(self, name, length, max_depth) -> None:
super().__init__(name)
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0], [max_depth, 0], [max_depth, -length]
)
self.edges.append(
pyg.CurveEdge(
self.edges[-1].end,
self.edges[0].start,
[[0.7, 0.2]]
)
)
self.interfaces = {
'to_collar': pyg.Interface(self, self.edges[0]),
'to_bodice': pyg.Interface(self, self.edges[1])
}
class SimpleLapel(pyg.Component):
def __init__(self, tag, body, design) -> None:
super().__init__(f'Turtle_{tag}')
depth = design['collar']['component']['depth']['v']
standing = design['collar']['component']['lapel_standing']['v']
# --Projecting shapes--
# Any front one!
collar_type = globals()[design['collar']['f_collar']['v']]
f_collar = collar_type(
design['collar']['fc_depth']['v'],
design['collar']['width']['v'],
angle=design['collar']['fc_angle']['v'],
flip=design['collar']['f_flip_curve']['v'])
b_collar = CircleNeckHalf(
design['collar']['bc_depth']['v'],
design['collar']['width']['v'])
self.interfaces = {
'front_proj': pyg.Interface(self, f_collar),
'back_proj': pyg.Interface(self, b_collar)
}
# -- Panels --
length_f, length_b = f_collar.length(), b_collar.length()
height_p = body['height'] - body['head_l'] + depth * 2
self.front = SimpleLapelPanel(
f'{tag}_lapel_front', length_f, depth).translate_by(
[-depth * 2, height_p, 35]) # TODOLOW This should be related with the bodice panels' placement
if standing:
self.back = StraightBandPanel(
f'{tag}_lapel_back', length_b, depth).translate_by(
[-length_b / 2, height_p, -10])
else:
# A curved back panel that follows the collar opening
rad, angle, _ = b_collar[0].as_radius_angle()
self.back = CircleArcPanel(
f'{tag}_lapel_back', rad, depth, angle
).translate_by([-length_b, height_p, -10])
self.back.rotate_by(R.from_euler('XYZ', [90, 45, 0], degrees=True))
if standing:
self.back.interfaces['right'].set_right_wrong(True)
self.stitching_rules.append((
self.front.interfaces['to_collar'],
self.back.interfaces['right']
))
self.interfaces.update({
#'front': NOTE: no front interface here
'back': self.back.interfaces['left'],
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['to_bodice'].set_right_wrong(True),
self.back.interfaces['bottom'] if standing else self.back.interfaces['top'].set_right_wrong(True),
)
})
def length(self):
return self.interfaces['back'].edges.length()
class HoodPanel(pyg.Panel):
"""A panel for the side of the hood"""
def __init__(self, name, f_depth, b_depth, f_length, b_length, width, in_length, depth) -> None:
super().__init__(name)
width = width / 2 # Panel covers one half only
length = in_length + width / 2
# Bottom-back
bottom_back_in = pyg.CurveEdge(
[-width, -b_depth],
[0, 0],
[[0.3, -0.2], [0.6, 0.2]]
)
bottom_back = pyg.ops.curve_match_tangents(
bottom_back_in.as_curve(),
[1, 0], # Full opening is vertically aligned
[1, 0],
target_len=b_length,
return_as_edge=True,
verbose=self.verbose
)
self.edges.append(bottom_back)
# Bottom front
bottom_front_in = pyg.CurveEdge(
self.edges[-1].end,
[width, -f_depth],
[[0.3, 0.2], [0.6, -0.2]]
)
bottom_front = pyg.ops.curve_match_tangents(
bottom_front_in.as_curve(),
[1, 0], # Full opening is vertically aligned
[1, 0],
target_len=f_length,
return_as_edge=True,
verbose=self.verbose
)
self.edges.append(bottom_front)
# Front-top straight section
self.edges.append(pyg.EdgeSeqFactory.from_verts(
self.edges[-1].end,
[width * 1.2, length], [width * 1.2 - depth, length]
))
# Back of the hood
self.edges.append(
pyg.CurveEdge(
self.edges[-1].end,
self.edges[0].start,
[[0.2, -0.5]]
)
)
self.interfaces = {
'to_other_side': pyg.Interface(self, self.edges[-2:]),
'to_bodice': pyg.Interface(self, self.edges[0:2]).reverse()
}
self.rotate_by(R.from_euler('XYZ', [0, -90, 0], degrees=True))
self.translate_by([-width*2, 0, 0])
class Hood2Panels(pyg.Component):
def __init__(self, tag, body, design) -> None:
super().__init__(f'Hood_{tag}')
# --Projecting shapes--
width = design['collar']['width']['v']
f_collar = CircleNeckHalf(
design['collar']['fc_depth']['v'],
design['collar']['width']['v'])
b_collar = CircleNeckHalf(
design['collar']['bc_depth']['v'],
design['collar']['width']['v'])
self.interfaces = {
'front_proj': pyg.Interface(self, f_collar),
'back_proj': pyg.Interface(self, b_collar)
}
# -- Panel --
self.panel = HoodPanel(
f'{tag}_hood',
design['collar']['fc_depth']['v'],
design['collar']['bc_depth']['v'],
f_length=f_collar.length(),
b_length=b_collar.length(),
width=width,
in_length=body['head_l'] * design['collar']['component']['hood_length']['v'],
depth=width / 2 * design['collar']['component']['hood_depth']['v']
).translate_by(
[0, body['height'] - body['head_l'] + 10, 0])
self.interfaces.update({
#'front': NOTE: no front interface here
'back': self.panel.interfaces['to_other_side'],
'bottom': self.panel.interfaces['to_bodice']
})
def length(self):
return self.panel.length()

View File

@@ -0,0 +1,121 @@
import math
import numpy as np
import pygarment as pyg
from assets.garment_programs.base_classes import BaseBottoms
from assets.garment_programs import skirt_paneled as skirts
class Insert(pyg.Panel):
def __init__(self, id, width=30, depth=30) -> None:
super().__init__(f'Insert_{id}')
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0],
[width/2, depth],
[width, 0], loop=True)
self.interfaces = [
pyg.Interface(self, self.edges[:2])
]
self.top_center_pivot()
self.center_x()
class GodetSkirt(BaseBottoms):
def __init__(self, body, design, rise=None) -> None:
super().__init__(body, design, rise=rise)
gdesign = design['godet-skirt']
ins_w = gdesign['insert_w']['v']
ins_depth = gdesign['insert_depth']['v']
base_skirt = getattr(skirts, gdesign['base']['v'])
# NOTE: godets currently don't like slits on the front/back
# of the base skirt => Forcing to remove any slits
self.base = base_skirt(body, design, rise=rise, slit=False)
bintr = self.base.interfaces['bottom']
for edge, panel in zip(bintr.edges, bintr.panel):
self.inserts(
edge, panel, ins_w, ins_depth,
num_inserts=gdesign['num_inserts']['v'] / len(bintr),
cuts_dist=gdesign['cuts_distance']['v'])
self.interfaces = {
'top': self.base.interfaces['top']
}
def inserts(
self, bottom_edge, panel, ins_w, ins_depth,
num_inserts=3, cuts_dist=0):
"""Create insert panels, add cuts to the skirt panel,
and connect created insert panels with them
"""
num_inserts = int(num_inserts)
bottom_len = bottom_edge.length()
pbbox = panel.bbox3D()
z_transl = panel.translation[-1] + np.sign(panel.translation[-1]) * 5
y_base = pbbox[0][1] # min Y
x_shift = (pbbox[0][0] + pbbox[1][0]) / 2
cut_width = (bottom_len - cuts_dist * num_inserts) / num_inserts
if cut_width < 1:
cut_width = 1 # 1 cm
cuts_dist_req = cuts_dist
cuts_dist = (bottom_len - cut_width * num_inserts) / num_inserts
if self.verbose:
print(f'{self.__class__.__name__}::WARNING:: Cannot place {num_inserts} cuts '
f'with requested distance between cuts ({cuts_dist_req}). '
f'Using the maximum possible distance ({cuts_dist})')
# Insert panels
insert = Insert(0, width=ins_w, depth=ins_depth).translate_by([
x_shift - num_inserts * ins_w / 2 + ins_w / 2, y_base + ins_depth, z_transl])
self.subs += pyg.ops.distribute_horisontally(
insert, num_inserts, -ins_w, 'ins_' + panel.name)
# make appropriate cuts and stitches
side_len = math.sqrt((ins_w / 2)**2 + ins_depth**2) # should be the same on the skirt and the insert
if side_len > cut_width / 2: # Normal case
cut_depth = math.sqrt(side_len**2 - (cut_width / 2)**2)
else:
old_cut_width = cut_width
cut_depth = 1
cut_width = 2 * math.sqrt(side_len**2 - cut_depth**2)
if self.verbose:
print(f'{self.__class__.__name__}::WARNING::Requested cut_width ({old_cut_width:.2f}) '
'is too wide for given inserts. '
f'Using the maximum possible width ({cut_width:.2f})')
cut_shape = pyg.EdgeSeqFactory.from_verts(
[0, 0], [cut_width / 2, cut_depth], [cut_width, 0])
right = z_transl < 0 # NOTE: heuristic corresponding to skirts in our collection
for i in range(num_inserts):
offset = cut_width / 2 + (cuts_dist / 2 if i == 0 else cuts_dist) # start_offest + i * stride
new_bottom, cutted, _ = pyg.ops.cut_into_edge(
cut_shape, bottom_edge, offset=offset, right=right)
panel.edges.substitute(bottom_edge, new_bottom)
bottom_edge = new_bottom[-1] # New edge that needs to be cutted -- on the next step
cut_interface = pyg.Interface(panel, cutted)
if right:
cut_interface.reverse()
self.stitching_rules.append(
(self.subs[-1-i if right else -(num_inserts-i)].interfaces[0],
cut_interface))
def get_rise(self):
return self.base.get_rise()
def length(self):
return self.base.length()

View File

@@ -0,0 +1,195 @@
from assets.garment_programs.tee import *
from assets.garment_programs.godet import *
from assets.garment_programs.bodice import *
from assets.garment_programs.pants import *
from assets.garment_programs.bands import *
from assets.garment_programs.skirt_paneled import *
from assets.garment_programs.skirt_levels import *
from assets.garment_programs.circle_skirt import *
from assets.garment_programs.sleeves import *
import yaml
class TotalLengthError(BaseException):
"""Error indicating that the total length of a garment goes beyond
the floor length for a given person"""
pass
class IncorrectElementConfiguration(BaseException):
"""Error indicating that given pattern is an empty garment"""
pass
class MetaGarment(pyg.Component):
"""Meta garment component
Depending on parameter values it can generate sewing patterns
for various dresses and jumpsuit styles and fit them to the body
measurements
"""
def __init__(self, name, body, design) -> None:
super().__init__(name)
self.body = body
self.design = design
# with open('assets/bodies/mean_all_full.yaml', 'w') as f:
# yaml.dump(body, f, default_flow_style=False)
# Elements
self.upper_name = design['meta']['upper']['v']
self.lower_name = design['meta']['bottom']['v']
self.belt_name = design['meta']['wb']['v']
# Upper garment
if self.upper_name:
upper = globals()[self.upper_name]
self.subs = [upper(body, design)]
# Set a label
self.subs[-1].set_panel_label('body', overwrite=False)
#Pan up a bit
self.subs[-1].translate_by((0, 5, 0))
# Here are all the garments that are not connected to have a belt
if self.belt_name == None:
if design['meta']['connected']['v'] == False and design['meta']['bottom']['v'] != None:
if design['meta']['bottom']['v'] != 'Pants':
design['meta']['wb']['v'] = "FittedWB"
design['waistband']['waist']['v'] = 0.7
design['waistband']['width']['v'] = 0.1
if design['meta']['bottom']['v'] == 'Pants':
design['meta']['wb']['v'] = 'FittedWB'
design['waistband']['waist']['v'] = 1
design['waistband']['width']['v'] = 0.2
self.belt_name = design['meta']['wb']['v']
# Define Lower garment
if self.lower_name:
Lower_class = globals()[self.lower_name]
# NOTE: full rise for fitted tops
Lower = Lower_class(body, design, rise=1. if self.upper_name and 'Fitted' in self.upper_name else None)
else:
Lower = None
# Belt (or not)
# TODO Adapt the rise of the lower garment to the width of the belt for correct matching
if self.belt_name:
Belt_class = globals()[self.belt_name]
# Adjust rise to match the Lower garment if needed
Belt = Belt_class(body, design, Lower.get_rise() if Lower else 1.)
self.subs.append(Belt)
# Place below the upper garment
if len(self.subs) > 1:
self.subs[-1].place_by_interface(
self.subs[-1].interfaces['top'],
self.subs[-2].interfaces['bottom'],
gap=5
)
if design['meta']['connected']['v'] : # If you need to connect, you need to connect the belt to the top
self.stitching_rules.append(
(self.subs[-2].interfaces['bottom'],
self.subs[-1].interfaces['top']))
# Add waist label
self.subs[-1].interfaces['top'].edges.propagate_label('lower_interface')
# Set panel segmentation labels
self.subs[-1].set_panel_label('body', overwrite=False)
if self.lower_name:
self.subs.append(Lower)
# Place below the upper garment or self.wb
if len(self.subs) > 1:
self.subs[-1].place_by_interface(
self.subs[-1].interfaces['top'],
self.subs[-2].interfaces['bottom'],
gap=5
)
#There must be a belt now, so here's how to connect the belt to the bottom
self.stitching_rules.append(
(self.subs[-2].interfaces['bottom'],
self.subs[-1].interfaces['top']))
#To deal with the simulation impact caused by the lack of connection, the main thing is to adjust the position and all the next translationby is to simulate better and reduce the problem situation.
# The specific tranlateby values are mainly tried
if not design['meta']['connected']['v']:
self.handle_disconnected_position_influence(design)
# Add waist label
if not self.belt_name:
self.subs[-1].interfaces['top'].edges.propagate_label('lower_interface')
# Set panel segmentation labels
self.subs[-1].set_panel_label('leg', overwrite=False)
def handle_disconnected_position_influence(self, design):
'''deal with the influence of disconnected garments on the simulation by translateby .
which is value is tried out'''
self.subs[-1].translate_by((0, 6, 0))
# Gets a specific clothing object
pant_flag = False
pant = None
circleskirt = None
circleskirt_flag = False
shirt = None
shirt_flag = False
for sub in self.subs:
if isinstance(sub, Pants):
pant_flag = True
pant = sub
if isinstance(sub, SkirtCircle):
circleskirt = sub
circleskirt_flag = True
if isinstance(sub, Shirt):
shirt = sub
shirt_flag = True
if pant_flag and shirt_flag:
# If the top is short<=1.2, you need to pan the pants downward.
if design['shirt']['length']['v'] <= 1.2:
# pass
pant.translate_by((0, -13, 0))
# For clothes that are too long, pull them up
if design['shirt']['length'][
'v'] > 1.2:
pant.translate_by((0, 25, 0))
translate_d = 8
shirt.right.ftorso.translate_by((0, 0, translate_d))
shirt.right.btorso.translate_by((0, 0, -translate_d))
shirt.left.ftorso.translate_by((0, 0, translate_d))
shirt.left.btorso.translate_by((0, 0, -translate_d))
# For the handling that is not underwear
if design['meta']['bottom']['v'] is not None and design['meta']['bottom']['v'] != 'Pants':
translate_d = 10
bottom_garment = self.subs[-1]
# if design['meta']['bottom']['v'] != "SkirtManyPanels":
# bottom_garment.translate_by((0, 0, 0))
# Deal with a single class first, for multiple skirts such as level, this is not processed, and later, at present, multi-layer group skirts do not need to be processed
if (design['meta']['bottom']['v'] == "Skirt2 " or design['meta']['bottom']['v'] == "SkirtCircle"
or design['meta']['bottom']['v'] == "AssymmSkirtCircle"
or design['meta']['bottom']['v'] == "PencilSkirt"):
bottom_garment.front.translate_by((0, 0, translate_d))
bottom_garment.back.translate_by((0, 0, -translate_d))
bottom_garment.translate_by((0, -8, 0))
# For the handling of the pants
if pant_flag:
pant.translate_by((0, -8, 0))
def assert_total_length(self, tol=1):
"""Check the total length of components"""
# Check that the total length of the components are less that body height
length = self.length()
floor = self.body['height'] - self.body['head_l']
if length > floor + tol:
raise TotalLengthError(f'{self.__class__.__name__}::{self.name}::ERROR:'
f':Total length {length} exceeds the floor length {floor}')
# TODO these checks don't require initialization of the pattern!
def assert_non_empty(self, filter_belts=True):
"""Check that the garment is non-empty
* filter_wb -- if set, then garments consisting only of waistbands are considered empty
"""
if not self.upper_name and not self.lower_name:
if filter_belts or not self.belt_name:
raise IncorrectElementConfiguration()
def assert_skirt_waistband(self):
"""Check if a generated heavy skirt is created with a waistband"""
if self.lower_name and self.lower_name in ['SkirtCircle', 'AsymmSkirtCircle', 'SkirtManyPanels']:
if not (self.belt_name or self.upper_name):
raise IncorrectElementConfiguration()

View File

@@ -0,0 +1,312 @@
from copy import deepcopy
import numpy as np
import pygarment as pyg
from assets.garment_programs.base_classes import BaseBottoms
from assets.garment_programs import bands
class PantPanel(pyg.Panel):
def __init__(
self, name, body, design,
length,
waist,
hips,
hips_depth,
crotch_width,
dart_position,
match_top_int_to=None,
hipline_ext=1,
double_dart=False) -> None:
"""
Basic pant panel with option to be fitted (with darts)
"""
super().__init__(name)
flare = body['leg_circ'] * (design['flare']['v'] - 1) / 4
hips_depth = hips_depth * hipline_ext
hip_side_incl = np.deg2rad(body['_hip_inclination'])
dart_depth = hips_depth * 0.8
# Crotch cotrols
crotch_depth_diff = body['crotch_hip_diff']
crotch_extention = crotch_width
# eval pants shape
# TODO Return ruffle opportunity?
# amount of extra fabric at waist
w_diff = hips - waist # Assume its positive since waist is smaller then hips
# We distribute w_diff among the side angle and a dart
hw_shift = np.tan(hip_side_incl) * hips_depth
# Small difference
if hw_shift > w_diff:
hw_shift = w_diff
# --- Edges definition ---
# Right
if pyg.utils.close_enough(design['flare']['v'], 1): # skip optimization
right_bottom = pyg.Edge(
[-flare, 0],
[0, length]
)
else:
right_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
[-flare, 0],
[0, length],
target_tan1=np.array([0, 1]),
# initial guess places control point closer to the hips
initial_guess=[0.75, 0]
)
right_top = pyg.CurveEdgeFactory.curve_from_tangents(
right_bottom.end,
[hw_shift, length + hips_depth],
target_tan0=np.array([0, 1]),
initial_guess=[0.5, 0]
)
top = pyg.Edge(
right_top.end,
[w_diff + waist, length + hips_depth]
)
crotch_top = pyg.Edge(
top.end,
[hips, length + 0.45 * hips_depth] # A bit higher than hip line
# NOTE: The point should be lower than the minimum rise value (0.5)
)
crotch_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
crotch_top.end,
[hips + crotch_extention, length - crotch_depth_diff],
target_tan0=np.array([0, -1]),
target_tan1=np.array([1, 0]),
initial_guess=[0.5, -0.5]
)
left = pyg.CurveEdgeFactory.curve_from_tangents(
crotch_bottom.end,
[
# NOTE "Magic value" (-2 cm) which we use to define default width:
# just a little behing the crotch point
# NOTE: Ensuring same distance from the crotch point in both
# front and back for matching curves
crotch_bottom.end[0] - 2 + flare,
# NOTE: The inside edge either matches the length of the outside (0, normal case)
# or when the inteded length is smaller than crotch depth,
# inside edge covers of the inside leg a bit below the crotch (panties-like shorts)
y:=min(0, length - crotch_depth_diff * 1.5)
],
target_tan1=[flare, y - crotch_bottom.end[1]],
initial_guess=[0.3, 0]
)
self.edges = pyg.EdgeSequence(
right_bottom, right_top, top, crotch_top, crotch_bottom, left
).close_loop()
bottom = self.edges[-1]
# Default placement
self.set_pivot(crotch_bottom.end)
self.translation = [-0.5, - hips_depth - crotch_depth_diff + 5, 0]
# Out interfaces (easier to define before adding a dart)
self.interfaces = {
'outside': pyg.Interface(
self,
pyg.EdgeSequence(right_bottom, right_top),
ruffle=[1, hipline_ext]),
'crotch': pyg.Interface(self, pyg.EdgeSequence(crotch_top, crotch_bottom)),
'inside': pyg.Interface(self, left),
'bottom': pyg.Interface(self, bottom)
}
# Add top dart
# NOTE: Ruffle indicator to match to waistline proportion for correct balance line matching
dart_width = w_diff - hw_shift
if w_diff > hw_shift:
top_edges, int_edges = self.add_darts(
top, dart_width, dart_depth, dart_position, double_dart=double_dart)
self.interfaces['top'] = pyg.Interface(
self, int_edges,
ruffle=waist / match_top_int_to if match_top_int_to is not None else 1.
)
self.edges.substitute(top, top_edges)
else:
self.interfaces['top'] = pyg.Interface(
self, top,
ruffle=waist / match_top_int_to if match_top_int_to is not None else 1.
)
def add_darts(self, top, dart_width, dart_depth, dart_position, double_dart=False):
if double_dart:
# TODOLOW Avoid hardcoding for matching with the top?
dist = dart_position * 0.5 # Dist between darts -> dist between centers
offsets_mid = [
- (dart_position + dist / 2 + dart_width / 2 + dart_width / 4),
- (dart_position - dist / 2) - dart_width / 4,
]
darts = [
pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth * 0.9), # smaller
pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth)
]
else:
offsets_mid = [
- dart_position - dart_width / 2,
]
darts = [
pyg.EdgeSeqFactory.dart_shape(dart_width, dart_depth)
]
top_edges, int_edges = pyg.EdgeSequence(top), pyg.EdgeSequence(top)
for off, dart in zip(offsets_mid, darts):
left_edge_len = top_edges[-1].length()
top_edges, int_edges = self.add_dart(
dart,
top_edges[-1],
offset=left_edge_len + off,
edge_seq=top_edges,
int_edge_seq=int_edges
)
return top_edges, int_edges
class PantsHalf(BaseBottoms):
def __init__(self, tag, body, design, rise=None) -> None:
super().__init__(body, design, tag, rise=rise)
design = design['pants']
self.rise = design['rise']['v'] if rise is None else rise
waist, hips_depth, waist_back = self.eval_rise(self.rise)
# NOTE: min value = full sum > leg curcumference
# Max: pant leg falls flat from the back
# Mostly from the back side
# => This controls the foundation width of the pant
min_ext = body['leg_circ'] - body['hips'] / 2 + 5 # 2 inch ease: from pattern making book
front_hip = (body['hips'] - body['hip_back_width']) / 2
crotch_extention = min_ext * design['width']['v']
front_extention = front_hip / 4 # From pattern making book
back_extention = crotch_extention - front_extention
length, cuff_len = design['length']['v'], design['cuff']['cuff_len']['v']
if design['cuff']['type']['v']:
if length - cuff_len < design['length']['range'][0]: # Min length from paramss
# Cannot be longer then a pant
cuff_len = length - design['length']['range'][0]
# Include the cuff into the overall length,
# unless the requested length is too short to fit the cuff
# (to avoid negative length)
length -= cuff_len
length *= body['_leg_length']
cuff_len *= body['_leg_length']
self.front = PantPanel(
f'pant_f_{tag}', body, design,
length=length,
waist=(waist - waist_back) / 2,
hips=(body['hips'] - body['hip_back_width']) / 2,
hips_depth=hips_depth,
dart_position = body['bust_points'] / 2,
crotch_width=front_extention,
match_top_int_to=(body['waist'] - body['waist_back_width']) / 2
).translate_by([0, body['_waist_level'] - 5, 25])
self.back = PantPanel(
f'pant_b_{tag}', body, design,
length=length,
waist=waist_back / 2,
hips=body['hip_back_width'] / 2,
hips_depth=hips_depth,
hipline_ext=1.1,
dart_position = body['bum_points'] / 2,
crotch_width=back_extention,
match_top_int_to=body['waist_back_width'] / 2,
double_dart=True
).translate_by([0, body['_waist_level'] - 5, -20])
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['outside'], self.back.interfaces['outside']),
(self.front.interfaces['inside'], self.back.interfaces['inside'])
)
# add a cuff
# TODOLOW This process is the same for sleeves -- make a function?
if design['cuff']['type']['v']:
pant_bottom = pyg.Interface.from_multiple(
self.front.interfaces['bottom'],
self.back.interfaces['bottom'])
# Copy to avoid editing original design dict
cdesign = deepcopy(design)
cdesign['cuff']['b_width'] = {}
cdesign['cuff']['b_width']['v'] = pant_bottom.edges.length() / design['cuff']['top_ruffle']['v']
cdesign['cuff']['cuff_len']['v'] = cuff_len
# Init
cuff_class = getattr(bands, cdesign['cuff']['type']['v'])
self.cuff = cuff_class(f'pant_{tag}', cdesign)
# Position
self.cuff.place_by_interface(
self.cuff.interfaces['top'],
pant_bottom,
gap=5,
alignment='left'
)
# Stitch
self.stitching_rules.append((
pant_bottom,
self.cuff.interfaces['top'])
)
self.interfaces = {
'crotch_f': self.front.interfaces['crotch'],
'crotch_b': self.back.interfaces['crotch'],
'top_f': self.front.interfaces['top'],
'top_b': self.back.interfaces['top']
}
def length(self):
if self.design['pants']['cuff']['type']['v']:
return self.front.length() + self.cuff.length()
return self.front.length()
class Pants(BaseBottoms):
def __init__(self, body, design, rise=None) -> None:
super().__init__(body, design)
self.right = PantsHalf('r', body, design, rise)
self.left = PantsHalf('l', body, design, rise).mirror()
self.stitching_rules = pyg.Stitches(
(self.right.interfaces['crotch_f'], self.left.interfaces['crotch_f']),
(self.right.interfaces['crotch_b'], self.left.interfaces['crotch_b']),
)
self.interfaces = {
'top_f': pyg.Interface.from_multiple(
self.right.interfaces['top_f'], self.left.interfaces['top_f']),
'top_b': pyg.Interface.from_multiple(
self.right.interfaces['top_b'], self.left.interfaces['top_b']),
# Some are reversed for correct connection
'top': pyg.Interface.from_multiple( # around the body starting from front right
self.right.interfaces['top_f'].flip_edges(),
self.left.interfaces['top_f'].reverse(with_edge_dir_reverse=True),
self.left.interfaces['top_b'].flip_edges(),
self.right.interfaces['top_b'].reverse(with_edge_dir_reverse=True), # Flips the edges and restores the direction
)
}
def get_rise(self):
return self.right.get_rise()
def length(self):
return self.right.length()

View File

@@ -0,0 +1,64 @@
"""A decorative shapes"""
import pygarment as pyg
def sample_arc(curve, length, stride, n_points, shift=0):
ts = [(shift + i*stride) / length for i in range(n_points)]
verts = [curve.point(t) for t in ts]
for i in range(len(verts)):
verts[i] = [verts[i].real, verts[i].imag]
return verts
def Sun(width, depth, n_rays=8, d_rays=5, **kwargs):
"""Sun-like mark"""
# Outer arc
out_arc = pyg.CircleEdgeFactory.from_three_points(
[0, 0], [width, 0], [width/2, depth]
)
in_arc = pyg.CircleEdgeFactory.from_three_points(
[d_rays, 0], [width - d_rays, 0], [width/2, depth - d_rays]
)
out_curve = out_arc.as_curve()
in_curve = in_arc.as_curve()
# Sample with stride
out_stride = out_arc.length() / n_rays
in_stride = in_arc.length() / n_rays
out_verts = sample_arc(out_curve, out_arc.length(),
out_stride, n_rays, out_stride / 2)
in_verts = sample_arc(in_curve, in_arc.length(), in_stride, n_rays + 1, 0)
# Mix the vertices in the right order
verts = out_verts
for i in range(len(in_verts)):
verts.insert(i*2, in_verts[i])
shape = pyg.EdgeSeqFactory.from_verts(*verts)
return shape, shape
def SIGGRAPH_logo(width, depth=None, **kwargs):
"""Shape of SIGGRAPH Logo (split vertically)"""
filename='./assets/img/siggraph_logo_thick_connection.svg' # NOTE assumes the script is run from the root
# TODOLOW path w.r.t. current file
left_seq, right_seq = pyg.EdgeSeqFactory.halfs_from_svg(
filename, target_height=width)
return left_seq, right_seq
def SVGFile(width, filename, depth=None, **kwargs):
"""Shape loaded from any svg file:
The shape is expected to consist of non-nested loops
each passing through OY once
"""
left_seq, right_seq = pyg.EdgeSeqFactory.halfs_from_svg(
filename, target_height=width)
return left_seq, right_seq

View File

@@ -0,0 +1,151 @@
from assets.garment_programs.circle_skirt import *
from assets.garment_programs.skirt_paneled import *
from copy import deepcopy
class SkirtLevels(BaseBottoms):
"""Skirt constiting of multuple stitched skirts"""
def __init__(self, body, design, rise=None) -> None:
super().__init__(body, design, rise=rise)
ldesign = design['levels-skirt']
lbody = deepcopy(body) # We will modify the values, so need a copy
n_levels = ldesign['num_levels']['v']
ruffle = ldesign['level_ruffle']['v']
# Adjust length to the common denominators
self.eval_length(ldesign, body)
# Definitions
self.rise = ldesign['rise']['v'] if rise is None else rise
base_skirt_class = globals()[ldesign['base']['v']]
self.subs.append(base_skirt_class(
body,
design,
length=self.base_len,
rise=self.rise,
slit=False))
if (hasattr(base := self.subs[0], 'design')
and 'low_angle' in base.design):
self.angle = base.design['low_angle']['v']
else:
self.angle = 0
# Place the levels
level_skirt_class = globals()[ldesign['level']['v']]
for i in range(n_levels):
# Adjust the mesurement to trick skirts into producing correct width
# TODOLOW More elegant overwrite
lbody['waist'] = ruffle * self.subs[-1].interfaces['bottom'].edges.length()
lbody['waist_back_width'] = ruffle * self.subs[-1].interfaces['bottom_b'].edges.length()
self.subs.append(level_skirt_class(
lbody,
design,
tag=str(i),
length=self.level_len,
slit=False,
top_ruffles=False))
# Placement
# Rotation if base is assymetric
self.subs[-1].rotate_by(R.from_euler(
'XYZ', [0, 0, -self.angle], degrees=True))
self.subs[-1].place_by_interface(
self.subs[-1].interfaces['top'],
self.subs[-2].interfaces['bottom'],
gap=5
)
# Stitch
self.stitching_rules.append((
self.subs[-2].interfaces['bottom'],
self.subs[-1].interfaces['top']
))
self.interfaces = {
'top': self.subs[0].interfaces['top']
}
def eval_length(self, ldesign, body):
# With convertion to absolute values
total_length = ldesign['length']['v'] * body['_leg_length']
self.base_len = total_length * ldesign['base_length_frac']['v']
self.level_len = (total_length - self.base_len) / ldesign['num_levels']['v']
# Add hip_line (== zero length)
self.base_len = body['hips_line'] * ldesign['rise']['v'] + self.base_len
class SkirtLayers(BaseBottoms):
"""Skirt consisting of multiple layered skirts stitched at the waistline"""
def __init__(self, body, design, rise=None):
super().__init__(body, design, rise=rise)
ldesign = design['layers-skirt']
lbody = deepcopy(body) # We will modify the values, so need a copy
n_layers = ldesign['num_layers']['v']
ruffle = ldesign['layer_ruffle']['v']
# Definitions
self.rise = ldesign['rise']['v'] if rise is None else rise
base_skirt_class = globals()[ldesign['base']['v']]
total_length = ldesign['length']['v'] * body['_leg_length']
layer_lengths = self.eval_layer_lengths(ldesign, total_length, n_layers)
# Place the levels
self.layers = []
for i in range(n_layers):
# Adjust the measurements to produce correct width with ruffle
waist_multiplier = 1 + ruffle * i
lbody['waist'] = self.body['waist'] * waist_multiplier
lbody['waist_back_width'] = self.body['waist_back_width'] * waist_multiplier
layer_length = layer_lengths[i]
skirt_layer = base_skirt_class(
lbody,
design,
tag=str(i),
length=layer_length,
rise=self.rise,
slit=False,
top_ruffles=False)
# Place the layer at the waistline
skirt_layer.translate_by([0, self.body['_waist_level'], 0])
self.layers.append(skirt_layer)
# Interfaces
self.interfaces = {
'top': self.layers[0].interfaces['top'],
'bottom': self.layers[-1].interfaces['bottom']
}
# Stitching rules: stitch each outer layer's top interface
# to the 'top' interface of the component (waistline)
for layer in self.layers[1:]:
self.stitching_rules.append(
(self.interfaces['top'], layer.interfaces['top'])
)
# Add layers to subcomponents
self.subs.extend(self.layers)
for id,layer in enumerate(self.layers):
layer.front.translate_by([0,0, 5*id])
layer.back.translate_by([0, 0, -5*id])
def eval_layer_lengths(self, ldesign, total_length, n_layers):
"""Calculate lengths for each layer"""
if 'layer_lengths' in ldesign:
# If specific lengths are provided for each layer
layer_lengths = [ldesign['layer_lengths'][i]['v'] * total_length for i in range(n_layers)]
else:
# Distribute the total length among layers, possibly making outer layers longer
base_length = total_length / n_layers
layer_lengths = [base_length * (1 + 0.1 * i) for i in range(n_layers)]
return layer_lengths

View File

@@ -0,0 +1,512 @@
import numpy as np
from scipy.spatial.transform import Rotation as R
import pygarment as pyg
from assets.garment_programs.base_classes import StackableSkirtComponent
from assets.garment_programs.base_classes import BaseBottoms
from assets.garment_programs import shapes
class SkirtPanel(pyg.Panel):
"""One panel of a panel skirt with ruffles on the waist"""
def __init__(self,
name,
waist_length=70, length=70,
ruffles=1,
match_top_int_to=None,
bottom_cut=0,
flare=0
) -> None:
super().__init__(name)
base_width = waist_length
top_width = base_width * ruffles
low_width = top_width + 2*flare
x_shift_top = (low_width - top_width) / 2 # to account for flare at the bottom
# define edge loop
self.right = pyg.EdgeSeqFactory.side_with_cut(
[0, 0],
[x_shift_top, length],
start_cut=bottom_cut / length) if bottom_cut else pyg.EdgeSequence(
pyg.Edge([0, 0], [x_shift_top, length]))
self.waist = pyg.Edge(
self.right[-1].end, [x_shift_top + top_width, length])
self.left = pyg.EdgeSeqFactory.side_with_cut(
self.waist.end, [low_width, 0],
end_cut=bottom_cut / length) if bottom_cut else pyg.EdgeSequence(
pyg.Edge(self.waist.end, [low_width, 0]))
self.bottom = pyg.Edge(self.left[-1].end, self.right[0].start)
# define interface
self.interfaces = {
'right': pyg.Interface(self, self.right[-1]),
'top': pyg.Interface(self, self.waist,
ruffle=self.waist.length() / match_top_int_to if match_top_int_to is not None else ruffles
).reverse(True),
'left': pyg.Interface(self, self.left[0]),
'bottom': pyg.Interface(self, self.bottom)
}
# Single sequence for correct assembly
self.edges = self.right
self.edges.append(self.waist) # on the waist
self.edges.append(self.left)
self.edges.append(self.bottom)
# default placement
self.top_center_pivot()
self.center_x() # Already know that this panel should be centered over Y
class ThinSkirtPanel(pyg.Panel):
"""One panel of a panel skirt"""
def __init__(self, name, top_width=10, bottom_width=20, length=70, b_curvature=0) -> None:
super().__init__(name)
# define edge loop
self.flare = (bottom_width - top_width) / 2
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0], [self.flare, length], [self.flare + top_width, length],
[self.flare * 2 + top_width, 0])
if pyg.utils.close_enough(b_curvature, 0):
self.edges.close_loop()
else:
self.edges.append(
pyg.CircleEdgeFactory.from_three_points(
self.edges[-1].end,
self.edges[0].start,
[0.5, b_curvature],
relative=True
)
)
# w.r.t. top left point
self.set_pivot(self.edges[0].end)
self.interfaces = {
'right': pyg.Interface(self, self.edges[0]),
'top': pyg.Interface(self, self.edges[1]),
'left': pyg.Interface(self, self.edges[2]),
'bottom': pyg.Interface(self, self.edges[-1])
}
class FittedSkirtPanel(pyg.Panel):
"""Fitted panel for a pencil skirt"""
def __init__(
self, name, body, design,
waist, hips, hips_depth, # TODOLOW Half measurement instead of a quarter
length,
hipline_ext=1,
dart_position=None, dart_frac=0.5, double_dart=False,
match_top_int_to=None,
slit=0, left_slit=0, right_slit=0,
side_cut=None, flip_side_cut=False
) -> None:
""" Fitted panel for a pencil skirt
Body/design values that differ between front and back panels are supplied as parameters,
the rest are taken from the body and design dictionaries
"""
super().__init__(name)
# Shared params
low_angle = design['low_angle']['v']
hip_side_incl = np.deg2rad(body['_hip_inclination'])
flare = design['flare']['v']
low_width = body['hips'] * (flare - 1) / 4 + hips # Distribute the difference equally
# between front and back
# adjust for a rise
adj_hips_depth = hips_depth * hipline_ext
dart_depth = hips_depth * dart_frac
dart_depth = max(dart_depth - (hips_depth - adj_hips_depth), 0)
# amount of extra fabric
w_diff = hips - waist # Assume its positive since waist is smaller then hips
# We distribute w_diff among the side angle and a dart
hw_shift = np.tan(hip_side_incl) * adj_hips_depth
# Small difference
if hw_shift > w_diff:
hw_shift = w_diff
# Adjust the bottom edge to the desired angle
angle_shift = np.tan(np.deg2rad(low_angle)) * low_width
# --- Edges definition ---
# Right
if pyg.utils.close_enough(flare, 1): # skip optimization
right_bottom = pyg.Edge(
[hips - low_width, angle_shift],
[0, length]
)
else:
right_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
[hips - low_width, angle_shift],
[0, length],
target_tan1=np.array([0, 1]),
# initial guess places control point closer to the hips
initial_guess=[0.75, 0]
)
right_top = pyg.CurveEdgeFactory.curve_from_tangents(
right_bottom.end,
[hw_shift, length + adj_hips_depth],
target_tan0=np.array([0, 1]),
initial_guess=[0.5, 0]
)
right = pyg.EdgeSequence(right_bottom, right_top)
# top
top = pyg.Edge(right[-1].end, [hips * 2 - hw_shift, length + adj_hips_depth])
# left
left_top = pyg.CurveEdgeFactory.curve_from_tangents(
top.end,
[hips * 2, length],
target_tan1=np.array([0, -1]),
initial_guess=[0.5, 0]
)
if pyg.utils.close_enough(flare, 1): # skip optimization for straight skirt
left_bottom = pyg.Edge(
left_top.end,
[hips + low_width, -angle_shift],
)
else:
left_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
left_top.end,
[hips + low_width, -angle_shift],
target_tan0=np.array([0, -1]),
# initial guess places control point closer to the hips
initial_guess=[0.25, 0]
)
left = pyg.EdgeSequence(left_top, left_bottom)
# fin
self.edges = pyg.EdgeSequence(right, top, left).close_loop()
bottom = self.edges[-1]
if slit: # add a slit
# Use long and thin disconnected dart for a cutout
new_edges, _, int_edges = pyg.ops.cut_into_edge(
pyg.EdgeSeqFactory.dart_shape(2, depth=slit * length), # a very thin cutout
bottom,
offset=bottom.length() / 2,
right=True)
self.edges.substitute(bottom, new_edges)
bottom = int_edges
if left_slit:
frac = left_slit
new_left_bottom = left_bottom.subdivide_len([1 - frac, frac])
left.substitute(left_bottom, new_left_bottom[0])
self.edges.substitute(left_bottom, new_left_bottom)
left_bottom = new_left_bottom[0]
if right_slit:
frac = right_slit
new_rbottom = right_bottom.subdivide_len([frac, 1 - frac])
right.substitute(right_bottom, new_rbottom[1])
self.edges.substitute(right_bottom, new_rbottom)
right_bottom = new_rbottom[1]
if side_cut is not None:
try:
# Add a stylistic cutout to the skirt
new_edges, _, int_edges = pyg.ops.cut_into_edge(
side_cut, left_bottom,
offset=left_bottom.length() / 2,
right=True, flip_target=flip_side_cut)
except:
# Skip adding the cut if it doesn't fit (e.g. because of the slit)
pass
else:
self.edges.substitute(left_bottom, new_edges)
left.substitute(left_bottom, new_edges)
# Default placement
self.top_center_pivot()
self.translation = [-hips / 2, 5, 0]
# Out interfaces (easier to define before adding a dart)
# Adding ruffle factor on the hip line extention (used in back panel)
self.interfaces = {
'bottom': pyg.Interface(self, bottom),
'right': pyg.Interface(self, right, [1] * (len(right) - 1) + [hipline_ext]),
'left': pyg.Interface(self, left, [hipline_ext] + [1] * (len(left) - 1)),
}
self.interfaces['left'].edges_flipping[0] = True
self.interfaces['right'].edges_flipping[-1] = True
# Add top darts
if w_diff > hw_shift:
dart_width = w_diff - hw_shift
top_edges, int_edges = self.add_darts(top, dart_width, dart_depth, dart_position, double_dart=double_dart)
self.interfaces['top'] = pyg.Interface(
self, int_edges,
ruffle=int_edges.length() / match_top_int_to if match_top_int_to is not None else 1.
)
self.edges.substitute(top, top_edges)
else:
self.interfaces['top'] = pyg.Interface(
self, top,
ruffle=top.length() / match_top_int_to if match_top_int_to is not None else 1.
)
def add_darts(self, top, dart_width, dart_depth, dart_position, double_dart=False):
top_edge_len = top.length()
if double_dart:
# TODOLOW Avoid hardcoding for matching with the top?
dist = dart_position * 0.5 # Dist between darts -> dist between centers
offsets_mid = [
- (dart_position + dist / 2 + dart_width / 2) - dart_width / 4,
- (dart_position - dist / 2) - dart_width / 4,
dart_position - dist / 2 + dart_width / 4,
dart_position + dist / 2 + dart_width / 2 + dart_width / 4,
]
# dart_shape = pyp.EdgeSeqFactory.dart_shape(dart_width, dart_depth)
dart_shape_full = pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth)
dart_shape_small = pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth * 0.9)
darts = [
dart_shape_small,
dart_shape_full,
dart_shape_full,
dart_shape_small,
]
else:
offsets_mid = [
- dart_position - dart_width / 2,
dart_position + dart_width / 2,
]
dart_shape = pyg.EdgeSeqFactory.dart_shape(dart_width, dart_depth)
darts = [
dart_shape,
dart_shape,
]
top_edges, int_edges = pyg.EdgeSequence(top), pyg.EdgeSequence(top)
for off, dart in zip(offsets_mid, darts):
left_edge_len = top_edges[-1].length()
top_edges, int_edges = self.add_dart(
dart,
top_edges[-1],
offset=(left_edge_len - top_edge_len / 2) + off,
edge_seq=top_edges,
int_edge_seq=int_edges
)
return top_edges, int_edges
# Full garments - Components
class PencilSkirt(StackableSkirtComponent):
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, **kwargs) -> None:
super().__init__(body, design, tag)
design = design['pencil-skirt']
self.design = design # Make accessible from outside
# condition
if design['style_side_cut']['v'] is not None:
depth = 0.7 * (body['hips'] / 4 - body['bust_points'] / 2)
shape_class = getattr(shapes, design['style_side_cut']['v'])
style_shape_l, style_shape_r = shape_class(
width=depth * 1.5,
depth=depth, n_rays=6, d_rays=depth*0.2,
filename=design['style_side_file']['v'] if 'style_side_file' in design else None
)
else:
style_shape_l, style_shape_r = None, None
# Force from arguments if given
self.rise = design['rise']['v'] if rise is None else rise
waist, hips_depth, back_waist = self.eval_rise(self.rise)
if length is None:
length = design['length']['v'] * body['_leg_length'] # Depends on leg length
else:
length = length - hips_depth
self.front = FittedSkirtPanel(
'skirt_front',
body,
design,
(waist - back_waist) / 2,
(body['hips'] - body['hip_back_width']) / 2,
hips_depth=hips_depth,
length=length,
dart_position=body['bust_points'] / 2,
dart_frac=0.8, # Diff for front and back
match_top_int_to=(body['waist'] - body['waist_back_width']),
slit=design['front_slit']['v'] if slit else 0,
left_slit=design['left_slit']['v'] if slit else 0,
right_slit=design['right_slit']['v'] if slit else 0,
side_cut=style_shape_l
).translate_to([0, body['_waist_level'], 25])
self.back = FittedSkirtPanel(
'skirt_back',
body,
design,
back_waist / 2,
body['hip_back_width'] / 2,
length=length,
hips_depth=hips_depth,
hipline_ext=1.05,
dart_position=body['bum_points'] / 2,
dart_frac=0.85,
double_dart=True,
match_top_int_to=body['waist_back_width'],
slit=design['back_slit']['v'] if slit else 0,
left_slit=design['left_slit']['v'] if slit else 0,
right_slit=design['right_slit']['v'] if slit else 0,
side_cut=style_shape_r,
flip_side_cut=False,
).translate_to([0, body['_waist_level'], -20])
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['right'], self.back.interfaces['right']),
(self.front.interfaces['left'], self.back.interfaces['left'])
)
# Reusing interfaces of sub-panels as interfaces of this component
self.interfaces = {
'top_f': self.front.interfaces['top'],
'top_b': self.back.interfaces['top'],
'top': pyg.Interface.from_multiple(
self.front.interfaces['top'].flip_edges(),
self.back.interfaces['top'].reverse(with_edge_dir_reverse=True)
),
'bottom_f': self.front.interfaces['bottom'],
'bottom_b': self.back.interfaces['bottom'],
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['bottom'], self.back.interfaces['bottom']
)
}
def length(self):
return self.front.length()
class Skirt2(StackableSkirtComponent):
"""Simple 2 panel skirt"""
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, top_ruffles=True, min_len=5) -> None:
super().__init__(body, design, tag)
design = design['skirt']
self.rise = design['rise']['v'] if rise is None else rise
waist, hip_line, back_waist = self.eval_rise(self.rise)
# Force from arguments if given
if length is None:
length = hip_line + design['length']['v'] * body['_leg_length'] # Depends on leg length
# NOTE: with some combinations of rise and length parameters length may become too small/negative
# Hence putting a min positive value here
length = max(length, min_len)
self.front = SkirtPanel(
f'skirt_front_{tag}' if tag else 'skirt_front',
waist_length=waist - back_waist,
length=length,
ruffles=design['ruffle']['v'] if top_ruffles else 1, # Only if on waistband
flare=design['flare']['v'],
bottom_cut=design['bottom_cut']['v'] * design['length']['v'] if slit else 0,
match_top_int_to=(body['waist'] - body['waist_back_width'])
).translate_to([0, body['_waist_level'], 25])
self.back = SkirtPanel(
f'skirt_back_{tag}' if tag else 'skirt_back',
waist_length=back_waist,
length=length,
ruffles=design['ruffle']['v'] if top_ruffles else 1, # Only if on waistband
flare=design['flare']['v'],
bottom_cut=design['bottom_cut']['v'] * design['length']['v'] if slit else 0,
match_top_int_to=body['waist_back_width']
).translate_to([0, body['_waist_level'], -20])
self.stitching_rules = pyg.Stitches(
(self.front.interfaces['right'], self.back.interfaces['right']),
(self.front.interfaces['left'], self.back.interfaces['left'])
)
# Reusing interfaces of sub-panels as interfaces of this component
self.interfaces = {
'top_f': self.front.interfaces['top'],
'top_b': self.back.interfaces['top'],
'top': pyg.Interface.from_multiple(
self.front.interfaces['top'], self.back.interfaces['top']
),
'bottom_f': self.front.interfaces['bottom'],
'bottom_b': self.back.interfaces['bottom'],
'bottom': pyg.Interface.from_multiple(
self.front.interfaces['bottom'], self.back.interfaces['bottom']
)
}
def length(self):
return self.front.length()
class SkirtManyPanels(BaseBottoms):
"""Round Skirt with many panels"""
def __init__(self, body, design, tag='', rise=None, min_len=5) -> None:
tag_extra = str(design['flare-skirt']['skirt-many-panels']['n_panels']['v'])
tag = f'{tag}_{tag_extra}' if tag else tag_extra
super().__init__(body, design, tag=tag, rise=rise)
design = design['flare-skirt']
self.rise = design['rise']['v'] if rise is None else rise
waist, hip_line, _ = self.eval_rise(self.rise)
n_panels = design['skirt-many-panels']['n_panels']['v']
# Length is dependent on length of legs
length = hip_line + design['length']['v'] * body['_leg_length']
# NOTE: with some combinations of rise and length parameters, length may become too small/negative
# Hence putting a min positive value here
length = max(length, min_len)
flare_coeff_pi = 1 + design['suns']['v'] * length * 2 * np.pi / waist
self.front = ThinSkirtPanel('front',
panel_w := waist / n_panels,
bottom_width=panel_w * flare_coeff_pi,
length=length,
b_curvature=design['skirt-many-panels']['panel_curve']['v'])
# Move far enough s.t. the widest part of the panels fit on the circle
dist = self.front.interfaces['bottom'].edges.length() / (2 * np.tan(np.pi / n_panels))
self.front.translate_to([-dist, body['_waist_level'], 0])
# Align orientation with a body
self.front.rotate_by(R.from_euler('XYZ', [0, -90, 0], degrees=True))
self.front.rotate_align([-dist, 0, panel_w / 2])
# Upd interface orientation
self.front.interfaces['top'].reverse(True)
# Create new panels
self.subs = pyg.ops.distribute_Y(self.front, n_panels, name_tag='skirt_panel')
# Stitch new components
for i in range(1, n_panels):
self.stitching_rules.append((self.subs[i - 1].interfaces['left'],
self.subs[i].interfaces['right']))
self.stitching_rules.append((self.subs[-1].interfaces['left'],
self.subs[0].interfaces['right']))
# Define the interface
self.interfaces = {
'top': pyg.Interface.from_multiple(*[sub.interfaces['top']
for sub in self.subs])
}
def length(self):
return self.front.length()

View File

@@ -0,0 +1,373 @@
from copy import deepcopy
import numpy as np
from scipy.spatial.transform import Rotation as R
from assets.garment_programs import bands
import pygarment as pyg
# ------ Armhole shapes ------
def ArmholeSquare(incl, width, angle, invert=True, **kwargs):
"""Simple square armhole cut-out
Not recommended to use for sleeves, stitching in 3D might be hard
if angle is provided, it also calculated the shape of the sleeve interface to attach
returns edge sequence and part to be preserved inverted
"""
edges = pyg.EdgeSeqFactory.from_verts([0, 0], [incl, 0], [incl, width])
if not invert:
return edges, None
sina, cosa = np.sin(angle), np.cos(angle)
l = edges[0].length()
sleeve_edges = pyg.EdgeSeqFactory.from_verts(
[incl + l*sina, - l*cosa],
[incl, 0], [incl, width])
# TODOLOW Bend instead of rotating to avoid sharp connection
sleeve_edges.rotate(angle=-angle)
return edges, sleeve_edges
def ArmholeAngle(incl, width, angle, incl_coeff=0.2, w_coeff=0.2,
invert=True, **kwargs):
"""Piece-wise smooth armhole shape"""
diff_incl = incl * (1 - incl_coeff)
edges = pyg.EdgeSeqFactory.from_verts(
[0, 0], [diff_incl, w_coeff * width], [incl, width])
if not invert:
return edges, None
sina, cosa = np.sin(angle), np.cos(angle)
l = edges[0].length()
sleeve_edges = pyg.EdgeSeqFactory.from_verts(
[diff_incl + l*sina, w_coeff * width - l*cosa],
[diff_incl, w_coeff * width], [incl, width])
# TODOLOW Bend instead of rotating to avoid sharp connection
sleeve_edges.rotate(angle=-angle)
return edges, sleeve_edges
def ArmholeCurve(incl, width, angle, bottom_angle_mix=0, invert=True, verbose=False, **kwargs):
""" Classic sleeve opening on Cubic Bezier curves
"""
# Curvature as parameters?
cps = [[0.5, 0.2], [0.8, 0.35]]
edge = pyg.CurveEdge([incl, width], [0, 0], cps)
edge_as_seq = pyg.EdgeSequence(edge.reverse())
if not invert:
return edge_as_seq, None
# Initialize inverse (initial guess)
# Agle == 0
down_direction = np.array([0, -1]) # Full opening is vertically aligned
inv_cps = deepcopy(cps)
inv_cps[-1][1] *= -1 # Invert the last
inv_edge = pyg.CurveEdge(
start=[incl, width],
end=(np.array([incl, width]) + down_direction * edge._straight_len()).tolist(),
control_points=inv_cps
)
# Rotate by desired angle (usually desired sleeve rest angle)
inv_edge.rotate(angle=-angle)
# Optimize the inverse shape to be nice
shortcut = inv_edge.shortcut()
rotated_direction = shortcut[-1] - shortcut[0]
rotated_direction /= np.linalg.norm(rotated_direction)
left_direction = np.array([-1, 0])
mix_factor = bottom_angle_mix
dir = (1 - mix_factor) * rotated_direction + (
mix_factor * down_direction if mix_factor > 0 else (- mix_factor * left_direction))
# TODOLOW Remember relative curvature results and reuse them? (speed)
fin_inv_edge = pyg.ops.curve_match_tangents(
inv_edge.as_curve(),
down_direction, # Full opening is vertically aligned
dir,
target_len=edge.length(),
return_as_edge=True,
verbose=verbose
)
return edge_as_seq, pyg.EdgeSequence(fin_inv_edge.reverse())
# -------- New sleeve definitions -------
class SleevePanel(pyg.Panel):
"""Trying proper sleeve panel"""
def __init__(self, name, body, design, open_shape, length_shift=0, _standing_margin=5):
"""Define a standard sleeve panel (half a sleeve)
* length_shift -- force upd sleeve length by this amount.
Can be used to adjust length evaluation to fit the cuff
"""
super().__init__(name)
MIN_LENGTH = 5 # Minimum sleeve length
shoulder_angle = np.deg2rad(body['_shoulder_incl'])
rest_angle = max(np.deg2rad(design['sleeve_angle']['v']),
shoulder_angle)
standing = design['standing_shoulder']['v']
# Calculating extension size & end size before applying ruffles
# Since ruffles add to pattern length & width, but not to de-facto
# sleeve length in 3D
end_width = design['end_width']['v'] * abs(open_shape[0].start[1] - open_shape[-1].end[1])
# Ensure it fits regardless of parameters
end_width = max(end_width, body['wrist'] / 2)
# Ruffles at opening
if not pyg.utils.close_enough(design['connect_ruffle']['v'], 1):
open_shape.extend(design['connect_ruffle']['v'])
# -- Main body of a sleeve --
opening_length = abs(open_shape[0].start[0] - open_shape[-1].end[0])
arm_width = abs(open_shape[0].start[1] - open_shape[-1].end[1])
# Length from the border of the opening to the end of the sleeve
length = design['length']['v'] * (body['arm_length'] - opening_length)
# NOTE: Asked to reduce by too much: reduce as much as possible
length = max(length + length_shift, MIN_LENGTH)
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0], [0, -end_width], [length, -arm_width]
)
# Align the opening
open_shape.snap_to(self.edges[-1].end)
open_shape[0].start = self.edges[-1].end # chain
self.edges.append(open_shape)
# Fin
self.edges.close_loop()
if standing:
if rest_angle > (shoulder_angle + np.deg2rad(_standing_margin)): # Add a "shelve" to create square shoulder appearance
top_edge = self.edges[-1]
start = top_edge.start
len = design['standing_shoulder_len']['v']
x_shift = len * np.cos(rest_angle - shoulder_angle)
y_shift = len * np.sin(rest_angle - shoulder_angle)
standing_edge = pyg.Edge(
start=start,
end=[start[0] - x_shift, start[1] + y_shift]
)
top_edge.start = standing_edge.end
self.edges.substitute(top_edge, [standing_edge, top_edge])
else:
if self.verbose:
print(f'{self.__class__.__name__}::WARNING::'
f'Sleeve rest angle {np.rad2deg(rest_angle):.3f} should be '
f'larger than shoulder angle {body["_shoulder_incl"]} by '
f'at least {_standing_margin} deg to enable '
'standing shoulder. Standing shoulder ignored')
standing = False
# Interfaces
self.interfaces = {
# NOTE: interface needs reversing because the open_shape was reversed for construction
'in': pyg.Interface(self, open_shape, ruffle=design['connect_ruffle']['v']),
'out': pyg.Interface(self, self.edges[0], ruffle=design['cuff']['top_ruffle']['v']),
'top': pyg.Interface(self, self.edges[-2:] if standing else self.edges[-1]),
'bottom': pyg.Interface(self, self.edges[1])
}
# Default placement
self.set_pivot(self.edges[-1].start)
self.translate_to([
- body['shoulder_w'] / 2,
body['height'] - body['head_l'],
0,
])
def length(self, longest_dim=False):
return self.interfaces['bottom'].edges.length()
class Sleeve(pyg.Component):
"""Trying to do a proper sleeve"""
def __init__(self, tag, body, design, front_w, back_w):
"""Defintion of a sleeve:
* front_w, back_w: the width front and the back of the top
the sleeve will attach to -- needed for correct share calculations
They may be
* Specified as scalar numbers
* Specified as functions w.r.t. the requested vertical level (=>
calculated width of a horizontal slice)
"""
super().__init__(f'{self.__class__.__name__}_{tag}')
design = design['sleeve']
self.design = design
self.body = body
sleeve_balance = body['_base_sleeve_balance'] / 2
rest_angle = max(np.deg2rad(design['sleeve_angle']['v']),
np.deg2rad(body['_shoulder_incl']))
connecting_width = design['connecting_width']['v']
smoothing_coeff = design['smoothing_coeff']['v']
front_w = front_w(connecting_width) if callable(front_w) else front_w
back_w = back_w(connecting_width) if callable(back_w) else back_w
# --- Define sleeve opening shapes ----
# NOTE: Non-trad armholes only for sleeveless styles due to
# unclear inversion and stitching errors (see below)
armhole = globals()[design['armhole_shape']['v']] if design['sleeveless']['v'] else ArmholeCurve
front_project, front_opening = armhole(
front_w - sleeve_balance,
connecting_width,
angle=rest_angle,
incl_coeff=smoothing_coeff,
w_coeff=smoothing_coeff,
invert=not design['sleeveless']['v'],
bottom_angle_mix=design['opening_dir_mix']['v'],
verbose=self.verbose
)
back_project, back_opening = armhole(
back_w - sleeve_balance,
connecting_width,
angle=rest_angle,
incl_coeff=smoothing_coeff,
w_coeff=smoothing_coeff,
invert=not design['sleeveless']['v'],
bottom_angle_mix=design['opening_dir_mix']['v']
)
self.interfaces = {
'in_front_shape': pyg.Interface(self, front_project),
'in_back_shape': pyg.Interface(self, back_project)
}
if design['sleeveless']['v']:
# The rest is not needed!
return
if front_w != back_w:
front_opening, back_opening = pyg.ops.even_armhole_openings(
front_opening, back_opening,
tol=0.2 / front_opening.length(), # ~2mm tolerance as a fraction of length
verbose=self.verbose
)
# --- Eval length adjustment for cuffs (if any) ----
cuff_len_adj = self._cuff_len_adj()
# # ----- Get sleeve panels -------
self.f_sleeve = SleevePanel(
f'{tag}_sleeve_f', body, design, front_opening,
length_shift=-cuff_len_adj
).translate_by([5, 0, 15])
# self.f_sleeve = SleevePanel(
# f'{tag}_sleeve_f', body, design, front_opening,
# length_shift=-cuff_len_adj
# )
self.b_sleeve = SleevePanel(
f'{tag}_sleeve_b', body, design, back_opening,
length_shift=-cuff_len_adj
).translate_by([5, 0, -15])
# self.b_sleeve = SleevePanel(
# f'{tag}_sleeve_b', body, design, back_opening,
# length_shift=-cuff_len_adj
# )
# Connect panels
self.stitching_rules = pyg.Stitches(
(self.f_sleeve.interfaces['top'],
self.b_sleeve.interfaces['top']),
(self.f_sleeve.interfaces['bottom'],
self.b_sleeve.interfaces['bottom']),
)
# Interfaces
self.interfaces.update({
'in': pyg.Interface.from_multiple(
self.f_sleeve.interfaces['in'],
self.b_sleeve.interfaces['in'].reverse(with_edge_dir_reverse=True)
),
'out': pyg.Interface.from_multiple(
self.f_sleeve.interfaces['out'],
self.b_sleeve.interfaces['out']
),
})
# Cuff
if design['cuff']['type']['v']:
# Class
# Copy to avoid editing original design dict
cdesign = deepcopy(design)
cuff_circ = self.interfaces['out'].edges.length() / design['cuff']['top_ruffle']['v']
# Ensure it fits regardless of parameters
cuff_circ = max(cuff_circ, body['wrist'])
cdesign['cuff']['b_width'] = dict(v=cuff_circ)
cdesign['cuff']['cuff_len']['v'] = cuff_len_adj
cuff_class = getattr(bands, cdesign['cuff']['type']['v'])
self.cuff = cuff_class(f'sl_{tag}', cdesign)
# Position
self.cuff.rotate_by(
R.from_euler(
'XYZ',
[0, 0, -90], # from -Ox direction
degrees=True
)
)
self.cuff.place_by_interface(
self.cuff.interfaces['top'],
self.interfaces['out'],
gap=2,
alignment='top'
)
self.stitching_rules.append(
(
self.cuff.interfaces['top'],
self.interfaces['out']
)
)
# UPD out interface!
self.interfaces['out'] = self.cuff.interfaces['bottom']
# Final rotation of sleeve piece
# print('*** arm_pose_angle', body['arm_pose_angle'])
self.rotate_by(R.from_euler(
'XYZ', [0, 0, body['arm_pose_angle']], degrees=True))
# Set label
self.set_panel_label('arm')
def _cuff_len_adj(self):
"""Eval sleeve length adjustment due to cuffs (if any)"""
if not self.design['cuff']['type']['v']:
return 0
cuff_len_adj = self.design['cuff']['cuff_len']['v'] * self.body['arm_length']
max_len = self.design['length']['v'] * self.body['arm_length']
if cuff_len_adj > max_len * 0.7:
cuff_len_adj = max_len * 0.7
return cuff_len_adj
def length(self):
if self.design['sleeveless']['v']:
return 0
if self.design['cuff']['type']['v']:
return self.f_sleeve.length() + self.cuff.length()
return self.f_sleeve.length()

View File

@@ -0,0 +1,150 @@
"""Design analysis routines to supply intresting stats"""
import pygarment.pattern.core as pattern
import yaml
# panels
def count_panels(pattern:pattern.BasicPattern, props, verbose=False):
n_panels = len(pattern.pattern['panels'].keys())
if verbose:
print(pattern.name, ' Panel count ', n_panels)
props['generator']['stats']['panel_count'][pattern.name] = n_panels
# Type determination
def bottom_length(design):
meta = design['meta']
if meta['bottom']['v']:
bottom_section = None
if meta['bottom']['v'] in ['SkirtCircle', 'AsymmSkirtCircle', 'SkirtManyPanels']:
bottom_section = 'flare-skirt'
elif meta['bottom']['v'] == 'Pants':
bottom_section = 'pants'
elif meta['bottom']['v'] == 'Skirt2':
bottom_section = 'skirt'
elif meta['bottom']['v'] == 'PencilSkirt':
bottom_section = 'pencil-skirt'
elif meta['bottom']['v'] == 'SkirtLevels':
bottom_section = 'levels-skirt'
elif meta['bottom']['v'] == 'GodetSkirt':
base = design['godet-skirt']['base']['v']
if base == 'Skirt2':
bottom_section = 'skirt'
else: # One other option
bottom_section = 'pencil-skirt'
else:
raise(ValueError(f'Unknown bottoms type {meta["bottom"]["v"]}'))
return design[bottom_section]['length']['v']
else:
return 0
def sleeve_length(design):
# Sleeve length
sleeve_len_r = design['sleeve']['length']['v'] if not design['sleeve']['sleeveless']['v'] else 0
sleeve_len_l = design['left']['sleeve']['length']['v'] if design['left']['enable_asym']['v'] and not design['left']['sleeve']['sleeveless']['v'] else 0
return max(sleeve_len_r, sleeve_len_l)
def top_length(design):
if design['meta']['upper']['v'] == 'FittedShirt':
return 1.
elif design['meta']['upper']['v'] == 'Shirt':
return design['shirt']['length']['v']
else:
return 0.
def vertical_len(design):
# NOTE: this will give very approximate result since
# the units of mesurement are slightly different
wb_len = design['waistband']['width']['v'] if design['meta']['wb']['v'] else 0
return top_length(design) + wb_len + bottom_length(design)
def garment_type(el_name, design, props, verbose=False):
main_type = None
add_types = []
# Main:
# + upper garment (short skirt - top or dress?)
# + skirt
# + pants
# + dress
# + jumpsuit
# Additional labels:
# + asymmetrical top
# + Hoody?
# + maxi/midi/mini
# + sleeve/less
# + long sleeve / short sleeve?
meta = design['meta']
if meta['upper']['v']:
if meta['bottom']['v'] and 'Pants' in meta['bottom']['v']:
main_type = 'jumpsuit'
elif vertical_len(design) < 1.4: # NOTE: very approximate division
main_type = 'upper_garment'
else:
main_type = 'dress'
else:
if 'Pants' in meta['bottom']['v']:
main_type = 'pants'
else:
main_type = 'skirt'
# Additional types
if meta['upper']['v']:
if design['left']['enable_asym']['v']:
add_types.append('asymmetric_top')
if (not design['left']['enable_asym']['v']
and design['collar']['component']['style']['v']
and 'Hood' in design['collar']['component']['style']['v']):
add_types.append('hoodie')
if (design['sleeve']['sleeveless']['v']
and (design['left']['sleeve']['sleeveless']['v'] if design['left']['enable_asym']['v'] else True)):
add_types.append('sleeveless')
else:
add_types.append('with_sleeves')
sleeve_len = sleeve_length(design)
if sleeve_len < 0.5:
add_types.append('short_sleeve')
else:
add_types.append('long_sleeve')
# Mini/Midi/Maxi
if meta['bottom']['v']:
length = bottom_length(design)
if length < 0.3:
add_types.append('mini')
elif length < 0.5:
add_types.append('knee_len')
elif length < 0.65:
add_types.append('midi')
else:
add_types.append('maxi')
if verbose:
print(el_name, ' types ', main_type, add_types)
props['generator']['stats']['garment_types'][el_name] = {
'main': main_type,
'styles': add_types
}
# Summary
summary = props['generator']['stats']['garment_types_summary']
if main_type not in summary['main']:
summary['main'][main_type] = 1
else:
summary['main'][main_type] += 1
for style_t in add_types:
if style_t not in summary['style']:
summary['style'][style_t] = {'total': 0}
summary['style'][style_t]['total'] += 1
if main_type not in summary['style'][style_t]:
summary['style'][style_t][main_type] = 1
else:
summary['style'][style_t][main_type] += 1

View File

@@ -0,0 +1,116 @@
""" Panels for a straight upper garment (T-shirt)
Note that the code is very similar to Bodice.
"""
import numpy as np
import pygarment as pyg
from assets.garment_programs.base_classes import BaseBodicePanel
class TorsoFrontHalfPanel(BaseBodicePanel):
"""Half of a simple non-fitted upper garment (e.g. T-Shirt)
Fits to the bust size
"""
def __init__(self, name, body, design) -> None:
""" Front = True, provides the adjustments necessary for the front panel
"""
super().__init__(name, body, design)
design = design['shirt']
# width
m_width = design['width']['v'] * body['bust']
b_width = design['flare']['v'] * m_width
# sizes
body_width = (body['bust'] - body['back_width']) / 2
frac = body_width / body['bust']
self.width = frac * m_width
b_width = frac * b_width
sh_tan = np.tan(np.deg2rad(body['_shoulder_incl']))
shoulder_incl = sh_tan * self.width
length = design['length']['v'] * body['waist_line']
# length in the front panel is adjusted due to shoulder inclination
# for the correct sleeve fitting
fb_diff = (frac - (0.5 - frac)) * body['bust']
length = length - sh_tan * fb_diff
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0],
[-b_width, 0],
[-self.width, length],
[0, length + shoulder_incl],
loop=True
)
# Interfaces
self.interfaces = {
'outside': pyg.Interface(self, self.edges[1]),
'inside': pyg.Interface(self, self.edges[-1]),
'shoulder': pyg.Interface(self, self.edges[-2]),
'bottom': pyg.Interface(self, self.edges[0], ruffle=self.width / ((body['waist'] - body['waist_back_width']) / 2)),
# Reference to the corner for sleeve and collar projections
'shoulder_corner': pyg.Interface(self, [self.edges[-3], self.edges[-2]]),
'collar_corner': pyg.Interface(self, [self.edges[-2], self.edges[-1]])
}
# default placement
self.translate_by([0, body['height'] - body['head_l'] - length - shoulder_incl, 0])
def get_width(self, level):
return super().get_width(level) + self.width - self.body['shoulder_w'] / 2
class TorsoBackHalfPanel(BaseBodicePanel):
"""Half of a simple non-fitted upper garment (e.g. T-Shirt)
Fits to the bust size
"""
def __init__(self, name, body, design) -> None:
""" Front = True, provides the adjustments necessary for the front panel
"""
super().__init__(name, body, design)
design = design['shirt']
# account for ease in basic measurements
m_width = design['width']['v'] * body['bust']
b_width = design['flare']['v'] * m_width
# sizes
body_width = body['back_width'] / 2
frac = body_width / body['bust']
self.width = frac * m_width
b_width = frac * b_width
shoulder_incl = (np.tan(np.deg2rad(body['_shoulder_incl']))) * self.width
length = design['length']['v'] * body['waist_line']
self.edges = pyg.EdgeSeqFactory.from_verts(
[0, 0],
[-b_width, 0],
[-self.width, length],
[0, length + shoulder_incl],
loop=True
)
# Interfaces
self.interfaces = {
'outside': pyg.Interface(self, self.edges[1]),
'inside': pyg.Interface(self, self.edges[-1]),
'shoulder': pyg.Interface(self, self.edges[-2]),
'bottom': pyg.Interface(self, self.edges[0], ruffle=self.width / (body['waist_back_width'] / 2)),
# Reference to the corner for sleeve and collar projections
'shoulder_corner': pyg.Interface(self, [self.edges[-3], self.edges[-2]]),
'collar_corner': pyg.Interface(self, [self.edges[-2], self.edges[-1]])
}
# default placement
self.translate_by([0, body['height'] - body['head_l'] - length - shoulder_incl, 0])
def get_width(self, level):
return super().get_width(level) + self.width - self.body['shoulder_w'] / 2

BIN
assets/img/artist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

BIN
assets/img/err_js.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 KiB

BIN
assets/img/err_regency.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
assets/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/img/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 184.75 187.06">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #231f20;
stroke-miterlimit: 10;
stroke-width: .25px;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<path class="cls-1" d="m2.94,111.46C-6.29,77.1,7.5,38.35,39.92,16.21c32.8-22.39,74.5-20.81,103.18,1.03-21-2.64-55.7,9.64-86.52,32.53C26.51,72.1,7.63,94.28,2.94,111.46Z"/>
<path class="cls-1" d="m171.8,53.37c-.17-.3-.41-.63-.6-.91-8.93-13.07-37.91-8.81-75.25,14.45,26.14-13.4,42.43-12.73,49.06-2.94,8.46,12.49-12.51,41.73-46.82,65.33-29.12,20.03-59.07,31.1-71.66,24.21-1.08-.59-2.03-1.42-2.74-2.38h-.03c.25.37.53.67.79,1.03h0c10.57,13.3,53.75,4.89,95.58-26.19,39.92-29.65,61.05-57.68,51.66-72.59"/>
<path class="cls-1" d="m13.42,134.09c.17.3.41.63.6.91,8.93,13.07,37.91,8.81,75.25-14.45-26.14,13.4-42.43,12.73-49.06,2.94-8.46-12.49,12.51-41.73,46.82-65.33,29.12-20.03,59.07-31.1,71.66-24.21,1.08.59,2.03,1.42,2.74,2.38h.03c-.25-.37-.53-.67-.79-1.03h0c-10.57-13.3-53.75-4.89-95.58,26.19C25.16,91.15,4.03,119.18,13.42,134.09"/>
<path class="cls-1" d="m181.81,75.6c9.23,34.36-4.56,73.12-36.98,95.25-32.8,22.39-74.5,20.81-103.18-1.03,21,2.64,55.7-9.64,86.52-32.53,30.06-22.33,48.95-44.51,53.64-61.69Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

View File

@@ -0,0 +1,8 @@
[
"shirt, sleeveless, regular length, crew neck",
"dress, A-line, knee-length, regular fit, boat neck, short sleeves",
"jumpsuit, straight leg, relaxed fit, turtleneck, regular length, long sleeves",
"dress, sleeveless, tight fit, level skirt, boat neck, mini-length",
"dress, with a hood",
"dress, one-shoulder"
]

View File

@@ -0,0 +1,54 @@
"""Aling body models s.t. they stand exactly on the plane y=0 and save as a new data"""
import igl
import numpy as np
from pathlib import Path
import trimesh
from pygarment.data_config import Properties
def load_mesh(path):
v, f = igl.read_triangle_mesh(str(path))
return v, f.flatten(), f
def get_shift_param(body_vertices):
v_body_arr = np.array(body_vertices)
min_y = (min(v_body_arr[:, 1]))
if min_y < 0:
return abs(min_y)
return 0.0
def save_mesh(path, v, f):
igl.write_triangle_mesh(str(path), v=v, f=f, force_ascii=False)
def process_body(path_in, path_out):
body_vertices, _, body_faces = load_mesh(path_in) # self.paths.in_body_obj)
shift_y = get_shift_param(body_vertices)
# body_vertices = body_vertices * b_scale
if shift_y:
body_vertices[:, 1] = body_vertices[:, 1] + shift_y
save_mesh(path_out, body_vertices, body_faces)
if __name__ == "__main__":
system_paths = Properties('./system.json')
# body_folder_path = Path(system_paths['body_samples_path']) / 'body_shapes_and_measures_2023-12-30'
# body_objs_path = body_folder_path / 'meshes'
body_objs_path = Path('./assets/bodies')
# out_path = body_folder_path / 'meshes_aligned'
out_path = Path('./assets/bodies_aligned')
out_path.mkdir(parents=True, exist_ok=True)
# loop over all meshes
for file in body_objs_path.iterdir():
if '.obj' in file.name:
process_body(file, out_path / file.name)
print(file.name)

View File

@@ -0,0 +1,25 @@
"""In simulated dataset, gather all the scene images in one folder"""
import pygarment.data_config as config
from pathlib import Path
import shutil
from pattern_data_sim import gather_renders
system_props = config.Properties('./system.json')
dataset = 'unpacking_test'
datapaths = [
Path(system_props['output']) / dataset / 'default_body',
Path(system_props['output']) / dataset / 'random_body'
]
for datapath in datapaths:
# Check packing
tar_path = datapath / 'data.tar.gz'
if tar_path.exists():
shutil.unpack_archive(tar_path, datapath)
# Finally -- clean up
tar_path.unlink()
gather_renders(datapath)

View File

@@ -0,0 +1,117 @@
"""
Run or Resume simulation of a pattern dataset with MayaPy standalone mode
Note that this module is executed in Maya (or by mayapy)
How to use:
* fill out system.json with approppriate paths
Running itself:
./datasim.py --data <dataset folder name> --minibatch <size> --config <simulation_rendering_configuration.json>
"""
import argparse
import sys
import shutil
from pathlib import Path
# My modules
import pygarment.data_config as data_config
import pygarment.meshgen.datasim_utils as sim
def get_command_args():
"""command line arguments to control the run"""
# https://stackoverflow.com/questions/40001892/reading-named-command-arguments
parser = argparse.ArgumentParser()
parser.add_argument('--data', '-d', help='name of dataset folder', type=str)
parser.add_argument('--config', '-c', help='name of .json file with desired simulation&rendering config', type=str,
default=None)
parser.add_argument('--minibatch', '-b', help='number of examples to simulate in this run', type=int, default=None)
parser.add_argument('--default_body', action='store_true', help='run dataset on default body')
parser.add_argument('--caching', action='store_true', help='cache intermediate simulation')
parser.add_argument('--rewrite_config', action='store_true', help='cache intermediate simulation')
args = parser.parse_args()
print(args)
return args
def gather_renders(out_data_path: Path, verbose=False):
renders_path = out_data_path / 'renders'
renders_path.mkdir(exist_ok=True)
render_files = list(out_data_path.glob('**/*render*.png'))
for file in render_files:
try:
shutil.copy(str(file), str(renders_path))
except shutil.SameFileError:
if verbose:
print(f'File {file} already exists')
pass
if __name__ == "__main__":
command_args = get_command_args()
system_config = data_config.Properties('./system.json')
# ------ Dataset ------
dataset = command_args.data
datapath = Path(system_config['datasets_path']) / dataset
init_dataset_file = datapath / 'dataset_properties.yaml'
# Create dataset_file in correct folder (default_body or random_body)
body_type = 'default_body' if command_args.default_body else 'random_body'
datapath = datapath / body_type / 'data' # Overwrite datapath to specific body type
output_path = Path(system_config['datasets_sim']) / dataset / body_type
output_path.mkdir(parents=True, exist_ok=True)
dataset_file_body = output_path / f'dataset_properties_{body_type}.yaml'
if not dataset_file_body.exists():
shutil.copy(str(init_dataset_file), str(dataset_file_body))
dataset_file = dataset_file_body
props = data_config.Properties(dataset_file_body)
if 'frozen' in props and props['frozen']: #Where is this set?
# avoid accidential re-runs of data
print('Warning: dataset is frozen, processing is skipped')
sys.exit(0)
# ------- Defining sim props -----
props.set_basic(data_folder=dataset) # in case data properties are from other dataset/folder, update info
if command_args.config is not None:
props.merge(
Path(system_config['sim_configs_path']) / command_args.config,
re_write=command_args.rewrite_config) # Re-write sim config only explicitly
# ----- Main loop ----------
finished = sim.batch_sim(
datapath,
output_path,
props,
run_default_body=command_args.default_body,
num_samples=command_args.minibatch, # run in mini-batch if requested
caching=command_args.caching, force_restart=False)
# ----- Try and resim fails once -----
if finished:
# NOTE: Could be larger than a regular batch
finished = sim.resim_fails(
datapath,
output_path,
props,
run_default_body=command_args.default_body,
caching=command_args.caching)
props.add_sys_info() # Save system information
props.serialize(dataset_file)
# ------ Gather renders -------
gather_renders(output_path)
# -------- fin --------
if finished:
# finished processing the dataset
print('Dataset processing finished')
sys.exit(0)
else:
sys.exit(1) # not finished dataset processing

View File

@@ -0,0 +1,34 @@
#!/bin/bash
# This script is needed to autorestart execution of simulating datapoints for a dataset
# in case of crashes and/or using mini-batches
# sh ./datasim_runner.sh 3>&1 2>&1 > C:\Users\out.txt (path to output file)
dataset_name=my_dataset
config=default_sim_props.yaml
sim_default_bodies=false
batch_size=100
# -- Main calls --
ret_code=1
STARTTIME=$(date +%s)
while [ $ret_code != 0 ] # failed for any reason
do
if [ "$sim_default_bodies" = "true" ]; then
python ./pattern_data_sim.py --data $dataset_name --default_body --config $config -b $batch_size
else
python ./pattern_data_sim.py --data $dataset_name --config $config -b $batch_size
fi
ret_code=$?
if [ $ret_code -eq 0 ]; then
echo "The execution completed successfully."
else
echo "The execution failed with an error (ret_code: $ret_code)."
fi
ENDTIME=$(date +%s)
T=$(($ENDTIME - $STARTTIME))
echo "It took ${T} seconds to complete this task so far..."
printf "Pretty format: %02dd %02dh %02dm %02ds\n" "$(($T/86400))" "$(($T/3600%24))" "$(($T/60%60))" "$(($T%60))"
done

View File

@@ -0,0 +1,229 @@
"""
Fitting one sewing pattern design to a set of various body shapes
"""
from datetime import datetime
from pathlib import Path
import yaml
import shutil
import time
import traceback
import argparse
# Custom
from pygarment.data_config import Properties
from assets.garment_programs.meta_garment import MetaGarment
from assets.bodies.body_params import BodyParameters
def get_command_args():
"""command line arguments to control the run"""
# https://stackoverflow.com/questions/40001892/reading-named-command-arguments
parser = argparse.ArgumentParser()
parser.add_argument('design_file', help='Path to design parameters file to be used to fit to the bodies', type=str)
parser.add_argument('--batch_id', '-b', help='id of a sampling batch', type=int, default=None)
parser.add_argument('--size', '-s', help='size of a sample', type=int, default=10)
parser.add_argument('--name', '-n', help='Name of the dataset', type=str, default='design_fit')
parser.add_argument('--replicate', '-re', help='Name of the dataset to re-generate. If set, other arguments are ignored', type=str, default=None)
args = parser.parse_args()
print('Commandline arguments: ', args)
return args
def _create_data_folder(properties, path=Path('')):
""" Create a new directory to put dataset in
& generate appropriate name & update dataset properties
"""
if 'data_folder' in properties: # will this work?
# => regenerating from existing data
properties['name'] = properties['data_folder'] + '_regen'
data_folder = properties['name']
else:
data_folder = properties['name']
# make unique
data_folder += '_' + datetime.now().strftime('%y%m%d-%H-%M-%S')
properties['data_folder'] = data_folder
path_with_dataset = path / data_folder
path_with_dataset.mkdir(parents=True)
default_folder = path_with_dataset / 'default_body'
body_folder = path_with_dataset / 'random_body'
default_folder.mkdir(parents=True, exist_ok=True)
body_folder.mkdir(parents=True, exist_ok=True)
return path_with_dataset, default_folder, body_folder
def _gather_body_options(body_path: Path):
objs_path = body_path / 'measurements'
bodies = []
for file in objs_path.iterdir():
# Get name
b_name = file.stem.split('_')[0]
bodies.append({})
# Get obj options
bodies[-1]['objs'] = dict(
straight=f'meshes/{b_name}_straight.obj',
apart=f'meshes/{b_name}_apart.obj', )
# Get measurements
bodies[-1]['mes'] = f'measurements/{b_name}.yaml'
return bodies
def body_sample(idx, bodies: dict, path: Path, straight=True):
body_i = bodies[idx]
mes_file = body_i['mes']
obj_file = body_i['objs']['straight'] if straight else body_i['objs']['apart']
body = BodyParameters(path / mes_file)
body.params['body_sample'] = (path / obj_file).stem
return body
def _save_sample(piece, body, new_design, folder, verbose=False):
pattern = piece.assembly()
# Save as json file
folder = pattern.serialize(
folder,
tag='',
to_subfolder=True,
with_3d=False, with_text=False, view_ids=False)
body.save(folder)
with open(Path(folder) / 'design_params.yaml', 'w') as f:
yaml.dump(
{'design': new_design},
f,
default_flow_style=False,
sort_keys=False
)
if verbose:
print(f'Saved {piece.name}')
def generate(path, properties, sys_paths, verbose=False):
"""Generates a synthetic dataset of patterns with given properties
Params:
path : path to folder to put a new dataset into
props : an instance of DatasetProperties class
requested properties of the dataset
"""
path = Path(path)
gen_config = properties['generator']['config']
gen_stats = properties['generator']['stats']
body_samples_path = Path(sys_paths['body_samples_path']) / properties['body_samples']
body_options = _gather_body_options(body_samples_path)
# create data folder
data_folder, default_path, body_sample_path = _create_data_folder(properties, path)
default_sample_data = default_path / 'data'
body_sample_data = body_sample_path / 'data'
# generate data
start_time = time.time()
# Load design
with open(properties['design_file'], 'r') as f:
design = yaml.safe_load(f)['design']
# On default body
default_body = BodyParameters(Path(sys_paths['bodies_default_path']) / (properties['body_default'] + '.yaml'))
piece_default = MetaGarment(properties['body_default'], default_body, design)
_save_sample(piece_default, default_body, design, default_sample_data, verbose=verbose)
for i in range(properties['size']):
# log properties every time
properties.serialize(data_folder / 'dataset_properties.yaml')
try:
# On random body shape
rand_body = body_sample(
i + properties['body_sample_start_id'],
body_options,
body_samples_path,
straight='Pants' != design['meta']['bottom']['v'])
name = rand_body.params['body_sample']
piece_shaped = MetaGarment(name, rand_body, design)
# Save samples
_save_sample(piece_shaped, rand_body, design, body_sample_data, verbose=verbose)
except KeyboardInterrupt: # Return immediately with whatever is ready
return default_path, body_sample_path
except BaseException as e:
print(f'{name} failed')
traceback.print_exc()
print(e)
continue
elapsed = time.time() - start_time
gen_stats['generation_time'] = f'{elapsed:.3f} s'
# log properties
properties.serialize(data_folder / 'dataset_properties.yaml')
return default_path, body_sample_path
def gather_visuals(path, verbose=False):
vis_path = Path(path) / 'patterns_vis'
vis_path.mkdir(parents=True, exist_ok=True)
for p in path.rglob("*.png"):
try:
shutil.copy(p, vis_path)
except shutil.SameFileError:
if verbose:
print('File {} already exists'.format(p.name))
pass
if __name__ == '__main__':
system_props = Properties('./system.json')
args = get_command_args()
if args.replicate is not None:
props = Properties(
Path(system_props['datasets_path']) / args.replicate / 'dataset_properties.yaml',
True)
else:
props = Properties()
props.set_basic(
design_file=args.design_file,
body_default='mean_all',
body_samples='5000_body_shapes_and_measures',
body_sample_start_id=0,
name=f'{args.name}_{args.size}' if not args.batch_id else f'{args.name}_{args.size}_{args.batch_id}',
size=args.size,
to_subfolders=True)
props.set_section_config('generator')
# Generator
default_path, body_sample_path = generate(
system_props['datasets_path'], props, system_props, verbose=False)
# Gather the pattern images separately
gather_visuals(default_path)
gather_visuals(body_sample_path)
# At the end -- it takes some time to gather the info
props.add_sys_info()
print('Data generation completed!')

View File

@@ -0,0 +1,336 @@
"""
Create a random sample of sewing pattern designs and fit each
to a neutral and a random body shape
"""
from datetime import datetime
from pathlib import Path
import yaml
import shutil
import time
import random
import string
import traceback
import argparse
# Custom
from pygarment.data_config import Properties
from assets.garment_programs.meta_garment import MetaGarment, IncorrectElementConfiguration
from assets.bodies.body_params import BodyParameters
import pygarment as pyg
import assets.garment_programs.stats_utils as stats_utils
def get_command_args():
"""command line arguments to control the run"""
# https://stackoverflow.com/questions/40001892/reading-named-command-arguments
parser = argparse.ArgumentParser()
parser.add_argument('--batch_id', '-b', help='id of a sampling batch', type=int, default=None)
parser.add_argument('--size', '-s', help='size of a sample', type=int, default=10)
parser.add_argument('--name', '-n', help='Name of the dataset', type=str, default='data')
parser.add_argument('--replicate', '-re', help='Name of the dataset to re-generate. If set, other arguments are ignored', type=str, default=None)
args = parser.parse_args()
print('Commandline arguments: ', args)
return args
# Utils
def _create_data_folder(properties, path=Path('')):
""" Create a new directory to put dataset in
& generate appropriate name & update dataset properties
"""
if 'data_folder' in properties: # will this work?
# => regenerating from existing data
properties['name'] = properties['data_folder'] + '_regen'
data_folder = properties['name']
else:
data_folder = properties['name']
# make unique
data_folder += '_' + datetime.now().strftime('%y%m%d-%H-%M-%S')
properties['data_folder'] = data_folder
path_with_dataset = path / data_folder
path_with_dataset.mkdir(parents=True)
default_folder = path_with_dataset / 'default_body'
body_folder = path_with_dataset / 'random_body'
default_folder.mkdir(parents=True, exist_ok=True)
body_folder.mkdir(parents=True, exist_ok=True)
return path_with_dataset, default_folder, body_folder
def gather_body_options(body_path: Path):
objs_path = body_path / 'measurements'
bodies = {}
for file in objs_path.iterdir():
# Get name
b_name = file.stem.split('_')[0]
bodies[b_name] = {}
# Get obj options
bodies[b_name]['objs'] = dict(
straight=f'meshes/{b_name}_straight.obj',
apart=f'meshes/{b_name}_apart.obj', )
# Get measurements
bodies[b_name]['mes'] = f'measurements/{b_name}.yaml'
return bodies
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))
def body_sample(bodies: dict, path: Path, straight=True):
rand_name = random.sample(list(bodies.keys()), k=1)
body_i = bodies[rand_name[0]]
mes_file = body_i['mes']
obj_file = body_i['objs']['straight'] if straight else body_i['objs']['apart']
body = BodyParameters(path / mes_file)
body.params['body_sample'] = (path / obj_file).stem
return body
def _save_sample(piece, body, new_design, folder, verbose=False):
pattern = piece.assembly()
# Save as json file
folder = pattern.serialize(
folder,
tag='',
to_subfolder=True,
with_3d=False, with_text=False, view_ids=False)
body.save(folder)
with open(Path(folder) / 'design_params.yaml', 'w') as f:
yaml.dump(
{'design': new_design},
f,
default_flow_style=False,
sort_keys=False
)
if verbose:
print(f'Saved {piece.name}')
return pattern
def has_pants(design):
return 'Pants' == design['meta']['bottom']['v']
def gather_visuals(path, verbose=False):
vis_path = Path(path) / 'patterns_vis'
vis_path.mkdir(parents=True, exist_ok=True)
for p in path.rglob("*.png"):
try:
shutil.copy(p, vis_path)
except shutil.SameFileError:
if verbose:
print('File {} already exists'.format(p.name))
pass
# Quality filter
def assert_param_combinations(design, filter_belts=True):
"""Check for some known invalid parameter combinations cases"""
upper_name = design['meta']['upper']['v']
lower_name = design['meta']['bottom']['v']
belt_name = design['meta']['wb']['v']
if upper_name: # No issues with garments that can hang on shoulders
return
# Empty patterns and singular belts
if not lower_name:
if filter_belts or not belt_name:
raise IncorrectElementConfiguration('ERROR::IncorrectParams::Empty pattern or singular belt')
return
# Cases when lower name is present (and maybe a belt):
# All pants and pencils are okay
if lower_name in ['Pants', 'PencilSkirt']:
return
# -- Sliding issues --
# NOTE: Checks are conservative, so some sliding issues might be present nontheless
# Skirt 2 & skirts of top of it -- uses ruffles and belt is too wide if even present
if (lower_name == 'Skirt2'
or lower_name == 'GodetSkirt' and design['godet-skirt']['base']['v'] == 'Skirt2'
or lower_name == 'SkirtLevels' and design['levels-skirt']['base']['v'] == 'Skirt2'
):
if (design['skirt']['ruffle']['v'] > 1 and (not belt_name or design['waistband']['waist']['v'] > 1.)):
raise IncorrectElementConfiguration('ERROR::IncorrectParams::Skirt2 ruffles + belt')
# Flare skirts & skirts on top of it -- no belt + too wide / too long
flare_skirts = ['SkirtCircle', 'AsymmSkirtCircle', 'SkirtManyPanels']
if (lower_name in flare_skirts
or lower_name == 'SkirtLevels' and design['levels-skirt']['base']['v'] in flare_skirts
):
# if Fitted belt of enough width not present -- check if "heavy"
if (not belt_name
or design['waistband']['waist']['v'] > 1.
or design['waistband']['width']['v'] <= 0.25
):
length_param = design['levels-skirt' if lower_name == 'SkirtLevels' else 'flare-skirt']['length']['v']
if length_param > 0.5 or design['flare-skirt']['suns']['v'] > 0.75:
raise IncorrectElementConfiguration('ERROR::IncorrectParams::Flare skirts + belt')
# Generation loop
def generate(path, properties, sys_paths, verbose=False):
"""Generates a synthetic dataset of patterns with given properties
Params:
path : path to folder to put a new dataset into
props : an instance of DatasetProperties class
requested properties of the dataset
"""
path = Path(path)
gen_config = properties['generator']['config']
gen_stats = properties['generator']['stats']
body_samples_path = Path(sys_paths['body_samples_path']) / properties['body_samples']
body_options = gather_body_options(body_samples_path)
# create data folder
data_folder, default_path, body_sample_path = _create_data_folder(properties, path)
default_sample_data = default_path / 'data'
body_sample_data = body_sample_path / 'data'
# init random seed
if 'random_seed' not in gen_config or gen_config['random_seed'] is None:
gen_config['random_seed'] = int(time.time())
print(f'Random seed is {gen_config["random_seed"]}')
random.seed(gen_config['random_seed'])
# generate data
start_time = time.time()
default_body = BodyParameters(Path(sys_paths['bodies_default_path']) / (properties['body_default'] + '.yaml'))
sampler = pyg.DesignSampler(properties['design_file'])
for i in range(properties['size']):
# log properties every time
properties.serialize(data_folder / 'dataset_properties.yaml')
# Redo sampling untill success
for _ in range(100): # Putting a limit on re-tries to avoid infinite loops
new_design = sampler.randomize()
name = f'rand_{_id_generator()}'
try:
if verbose:
print(f'{name} saving design params for debug')
with open(Path('./Logs') / f'{name}_design_params.yaml', 'w') as f:
yaml.dump(
{'design': new_design},
f,
default_flow_style=False,
sort_keys=False
)
# Preliminary checks
assert_param_combinations(new_design)
# On default body
piece_default = MetaGarment(name, default_body, new_design)
piece_default.assert_total_length() # Check final length correctnesss
# Straight/apart legs pose
def_obj_name = properties['body_default']
if has_pants(new_design):
def_obj_name += '_apart'
default_body.params['body_sample'] = def_obj_name
# On random body shape
rand_body = body_sample(
body_options,
body_samples_path,
straight=not has_pants(new_design))
piece_shaped = MetaGarment(name, rand_body, new_design)
piece_shaped.assert_total_length() # Check final length correctness
if piece_default.is_self_intersecting() or piece_shaped.is_self_intersecting():
if verbose:
print(f'{piece_default.name} is self-intersecting!!')
continue # Redo the randomization
# Save samples
pattern = _save_sample(piece_default, default_body, new_design, default_sample_data, verbose=verbose)
_save_sample(piece_shaped, rand_body, new_design, body_sample_data, verbose=verbose)
stats_utils.count_panels(pattern, props)
stats_utils.garment_type(name, new_design, props)
break # Stop generation
except KeyboardInterrupt: # Return immediately with whatever is ready
return default_path, body_sample_path
except BaseException as e:
print(f'{name} failed')
if verbose:
traceback.print_exc()
print(e)
# Check empty folder
if (default_sample_data / name).exists():
print('Generate::Info::Removed empty folder after unsuccessful sampling attempt', default_sample_data / name)
shutil.rmtree(default_sample_data / name, ignore_errors=True)
if (body_sample_data / name).exists():
print('Generate::Info::Removed empty folder after unsuccessful sampling attempt', body_sample_data / name)
shutil.rmtree(body_sample_data / name, ignore_errors=True)
continue
elapsed = time.time() - start_time
gen_stats['generation_time'] = f'{elapsed:.3f} s'
# log properties
props.stats_summary()
properties.serialize(data_folder / 'dataset_properties.yaml')
return default_path, body_sample_path
if __name__ == '__main__':
system_props = Properties('./system.json')
args = get_command_args()
if args.replicate is not None:
props = Properties(
Path(system_props['datasets_path']) / args.replicate / 'dataset_properties.yaml',
True)
else: # New sample
props = Properties()
props.set_basic(
design_file='./assets/design_params/default.yaml',
body_default='mean_all',
body_samples='5000_body_shapes_and_measures',
size=args.size,
name=f'{args.name}_{args.size}' if not args.batch_id else f'{args.name}_{args.size}_{args.batch_id}',
to_subfolders=True)
props.set_section_config('generator')
props.set_section_stats(
'generator',
panel_count={},
garment_types={},
garment_types_summary=dict(main={}, style={})
)
# Generator
default_path, body_sample_path = generate(
system_props['datasets_path'], props, system_props, verbose=False)
# Gather the pattern images separately
gather_visuals(default_path)
gather_visuals(body_sample_path)
print('Data generation completed!')

40
environment.yml Normal file
View File

@@ -0,0 +1,40 @@
name: d2g
channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
dependencies:
- python=3.9.19
- numpy=1.26.4
- scipy=1.13.1
- pyyaml=6.0.2
- svgwrite=1.4.3
- psutil=6.0.0
- matplotlib
- svgpathtools
- cairosvg=2.7.1
- nicegui=2.15.0
- trimesh
- cgal
- pip
- pip:
- --extra-index-url https://download.pytorch.org/whl/cu121
- torch==2.4.0+cu121
- torchvision==0.19.0+cu121
- torchaudio==2.4.0+cu121
- transformers==4.46.2
- tokenizers==0.20.3
- accelerate==1.1.1
- datasets==2.18.0
- huggingface-hub==0.29.2
- safetensors==0.5.3
- tiktoken==0.9.0
- peft==0.13.2
- qwen-vl-utils==0.0.8
- modelscope==1.18.0
- pyrender==0.1.45
- libigl==2.5.1
- cgal==6.0.1.post202410241521
- openai==1.54.4

46
gui.py Normal file
View File

@@ -0,0 +1,46 @@
import argparse
from nicegui import ui, Client
# Custom
from gui.callbacks import GUIState
import gui.error_pages
# GPT
from lmm_utils.agent import Agent
import asyncio
agent=Agent(model_init=True)
@ui.page('/')
async def index(client: Client):
global agent
# Start the interface!
gui_st = GUIState()
gui_st.pattern_state.agent=agent
# Connection end
# https://github.com/zauberzeug/nicegui/discussions/1379
await client.disconnected()
print('Closed connection ', gui_st.pattern_state.id, '. Deleting files...')
gui_st.release()
if __name__ == '__main__':
parser=argparse.ArgumentParser()
parser.add_argument(
'--host',
help='Host address to start the gui server ,defaults to "127.0.0.0"',
type=str,
default='0.0.0.0'
)
parser.add_argument(
'--port',
help='Use this port',
type=str,
default="8080"
)
args=parser.parse_args()
ui.run(
host=args.host,
port=args.port,
reload=False,
favicon='assets/img/favicon.ico',
title='Design2GarmentCode: Turning Design Concepts to Tangible Garments Through Program Synthesis'
)

0
gui/__init__.py Normal file
View File

918
gui/callbacks.py Normal file
View File

@@ -0,0 +1,918 @@
"""Callback functions & State info for Sewing Pattern Configurator """
# NOTE: NiceGUI reference: https://nicegui.io/
import asyncio
import shutil
import traceback
from argparse import Namespace
# Async execution of regular functions
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from pathlib import Path
from typing import List, Tuple
from uuid import uuid4
import numpy as np
import yaml
from nicegui import ui, app, events
# Custom
from .gui_pattern import GUIPattern
icon_github = """
<svg viewbox="0 0 98 96" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0
21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362
0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015
4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283
0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571
12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015
13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89
2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/>
</svg>
"""
icon_arxiv = """<svg id="primary_logo_-_single_color_-_white" data-name="primary logo - single color - white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 246.978 110.119"><path d="M492.976,269.5l24.36-29.89c1.492-1.989,2.2-3.03,1.492-4.723a5.142,5.142,0,0,0-4.481-3.161h0a4.024,4.024,0,0,0-3.008,1.108L485.2,261.094Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M526.273,325.341,493.91,287.058l-.972,1.033-7.789-9.214-7.743-9.357-4.695,5.076a4.769,4.769,0,0,0,.015,6.53L520.512,332.2a3.913,3.913,0,0,0,3.137,1.192,4.394,4.394,0,0,0,4.027-2.818C528.4,328.844,527.6,327.133,526.273,325.341Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M479.215,288.087l6.052,6.485L458.714,322.7a2.98,2.98,0,0,1-2.275,1.194,3.449,3.449,0,0,1-3.241-2.144c-.513-1.231.166-3.15,1.122-4.168l.023-.024.021-.026,24.851-29.448m-.047-1.882-25.76,30.524c-1.286,1.372-2.084,3.777-1.365,5.5a4.705,4.705,0,0,0,4.4,2.914,4.191,4.191,0,0,0,3.161-1.563l27.382-29.007-7.814-8.372Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M427.571,255.154c1.859,0,3.1,1.24,3.985,3.453,1.062-2.213,2.568-3.453,4.694-3.453h14.878a4.062,4.062,0,0,1,4.074,4.074v7.828c0,2.656-1.327,4.074-4.074,4.074-2.656,0-4.074-1.418-4.074-4.074V263.3H436.515a2.411,2.411,0,0,0-2.656,2.745v27.188h10.007c2.658,0,4.074,1.329,4.074,4.074s-1.416,4.074-4.074,4.074h-26.39c-2.659,0-3.986-1.328-3.986-4.074s1.327-4.074,3.986-4.074h8.236V263.3h-7.263c-2.656,0-3.985-1.329-3.985-4.074,0-2.658,1.329-4.074,3.985-4.074Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M539.233,255.154c2.656,0,4.074,1.416,4.074,4.074v34.007h10.1c2.746,0,4.074,1.329,4.074,4.074s-1.328,4.074-4.074,4.074H524.8c-2.656,0-4.074-1.328-4.074-4.074s1.418-4.074,4.074-4.074h10.362V263.3h-8.533c-2.744,0-4.073-1.329-4.073-4.074,0-2.658,1.329-4.074,4.073-4.074Zm4.22-17.615a5.859,5.859,0,1,1-5.819-5.819A5.9,5.9,0,0,1,543.453,237.539Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M605.143,259.228a4.589,4.589,0,0,1-.267,1.594L590,298.9a3.722,3.722,0,0,1-3.721,2.48h-5.933a3.689,3.689,0,0,1-3.808-2.48l-15.055-38.081a3.23,3.23,0,0,1-.355-1.594,4.084,4.084,0,0,1,4.164-4.074,3.8,3.8,0,0,1,3.718,2.656l14.348,36.134,13.9-36.134a3.8,3.8,0,0,1,3.72-2.656A4.084,4.084,0,0,1,605.143,259.228Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M390.61,255.154c5.018,0,8.206,3.312,8.206,8.4v37.831H363.308a4.813,4.813,0,0,1-5.143-4.929V283.427a8.256,8.256,0,0,1,7-8.148l25.507-3.572v-8.4H362.306a4.014,4.014,0,0,1-4.141-4.074c0-2.87,2.143-4.074,4.355-4.074Zm.059,38.081V279.942l-24.354,3.4v9.9Z" transform="translate(-358.165 -223.27)" fill="#fff"/><path d="M448.538,224.52h.077c1,.024,2.236,1.245,2.589,1.669l.023.028.024.026,46.664,50.433a3.173,3.173,0,0,1-.034,4.336l-4.893,5.2-6.876-8.134L446.652,230.4c-1.508-2.166-1.617-2.836-1.191-3.858a3.353,3.353,0,0,1,3.077-2.02m0-1.25a4.606,4.606,0,0,0-4.231,2.789c-.705,1.692-.2,2.88,1.349,5.1l39.493,47.722,7.789,9.214,5.853-6.221a4.417,4.417,0,0,0,.042-6.042L452.169,225.4s-1.713-2.08-3.524-2.124Z" transform="translate(-358.165 -223.27)" fill="#fff"/></svg>"""
# # Dracula color theme
# theme_colors = Namespace(
# primary='#BD93F9', # Light purple (primary accent in Dracula theme)
# secondary='#FF79C6', # Pink
# accent='#FFB86C', # Orange
# dark='#282A36', # Dracula background (dark)
# positive='#50FA7B', # Green
# negative='#FF5555', # Red
# info='#8BE9FD', # Cyan (info color)
# warning='#F1FA8C' # Yellow
# )
theme_colors = Namespace(
primary='#5C5D71',
secondary='#333333',
accent='#a82c64',
dark='#4d1f48',
positive='#22ba38',
negative='#f50000',
info='#31CCEC',
warning='#9333ea'
)
messages: List[Tuple[str, str, str, str, str]] = []
@ui.refreshable
def chat_messages(own_id: str) -> None:
if messages:
for user_id, avatar, text, stamp, img_url in messages:
if img_url:
with ui.chat_message(text=text, stamp=stamp, avatar=avatar, sent=own_id==user_id).classes('w-full'):
ui.image(img_url).classes('w-64')
else:
ui.chat_message(text=text, stamp=stamp, avatar=avatar, sent=own_id==user_id).classes('w-full')
else:
ui.label('').classes('mx-auto my-36')
ui.run_javascript('window.scrollTo(0, document.body.scrollHeight)')
# State of GUI
class GUIState:
"""State of GUI-related objects
NOTE: "#" is used as a separator in GUI keys to avoid confusion with
symbols that can be (typically) used in body/design parameter names
('_', '-', etc.)
"""
def __init__(self) -> None:
self.window = None
# Pattern
self.pattern_state = GUIPattern()
# Pattern display constants
self.canvas_aspect_ratio = 1500. / 900 # Millimiter paper
self.w_rel_body_size = 0.5 # Body size as fraction of horisontal canvas axis
self.h_rel_body_size = 0.95
self.background_body_scale = 1 / 171.99 # Inverse of the mean_all body height from GGG
self.background_body_canvas_center = 0.273 # Fraction of the canvas (millimiter paper)
self.w_canvas_pad, self.h_canvas_pad = 0.011, 0.04
self.body_outline_classes = '' # Application of pattern&body scaling when it overflows
# Paths setup
# Static images for GUI
self.path_static_img = '/img'
app.add_static_files(self.path_static_img, './assets/img')
# 3D updates
self.path_static_3d = '/geo'
self.garm_3d_filename = f'garm_3d_{self.pattern_state.id}.glb'
self.local_path_3d = Path('./tmp_gui/garm_3d')
self.local_path_3d.mkdir(parents=True, exist_ok=True)
app.add_static_files(self.path_static_3d, self.local_path_3d)
app.add_static_files('/body', './assets/bodies')
#model api
self.baseurl = None
self.api = None
self.model = None
self.text_model = None
# Elements
self.ui_design_subtabs = {}
self.ui_pattern_display = None
self._async_executor = ThreadPoolExecutor(1)
self.pattern_state.reload_garment()
self.stylings()
self.layout()
def release(self):
"""Clean-up after the sesssion"""
self.pattern_state.release()
(self.local_path_3d / self.garm_3d_filename).unlink(missing_ok=True)
# Initial definitions
def stylings(self):
"""Theme definition"""
# Theme
# Here: https://quasar.dev/style/theme-builder
ui.colors(
primary=theme_colors.primary,
secondary=theme_colors.secondary,
accent=theme_colors.accent,
dark=theme_colors.dark,
positive=theme_colors.positive,
negative=theme_colors.negative,
info=theme_colors.info,
warning=theme_colors.warning
)
# SECTION Top level layout
def layout(self):
"""Overall page layout"""
# as % of viewport width/height
self.h_header = 5
self.h_params_content = 88
self.h_garment_display = 74
self.w_garment_display = 65
self.w_splitter_design = 32
self.scene_base_resoltion = (1024, 800)
# Helpers
self.def_pattern_waiting()
# TODOLOW One dialog for both?
self.def_design_file_dialog()
self.def_body_file_dialog()
# Configurator GUI
with ui.row(wrap=False).classes(f'w-full h-[{self.h_params_content}dvh] p-0 m-0 '):
# Tabs
self.def_param_tabs_layout()
# Pattern visual
self.view_tabs_layout()
# Overall wrapping
# NOTE: https://nicegui.io/documentation/section_pages_routing#page_layout
with ui.header(elevated=True, fixed=False).classes(f'h-[{self.h_header}vh] items-center justify-end py-0 px-4 m-0'):
ui.label('Design2GarmentCode').classes('mr-auto').style('font-size: 150%; font-weight: 400')
ui.button(
'Project',
on_click=lambda: ui.navigate.to('https://style3d.github.io/design2garmentcode/', new_tab=True)
).props('flat color=white')
with ui.link(target='http://arxiv.org/abs/2412.08603', new_tab=True):
ui.html(icon_arxiv).classes('w-16 bg-transparent')
with ui.link(target='https://github.com/Style3D/SXDGarmentCode', new_tab=True):
ui.html(icon_github).classes('w-8 bg-transparent')
# NOTE No ui.left_drawer(), no ui.right_drawer()
with ui.footer(fixed=False, elevated=True).classes('items-center justify-center p-0 m-0'):
# https://www.termsfeed.com/blog/sample-copyright-notices/
ui.link(
'© 2025 Style3D Research',
'https://www.linctex.com/',
new_tab=True
).classes('text-white')
def view_tabs_layout(self):
"""2D/3D view tabs"""
with ui.column(wrap=False).classes(f'h-[{self.h_params_content}vh] w-full items-center'):
with ui.tabs() as tabs:
self.ui_2d_tab = ui.tab('Sewing Pattern')
self.ui_3d_tab = ui.tab('3D view')
with ui.tab_panels(tabs, value=self.ui_2d_tab, animated=True).classes('w-full h-full items-center'):
with ui.tab_panel(self.ui_2d_tab).classes('w-full h-full items-center justify-center p-0 m-0'):
self.def_pattern_display()
with ui.tab_panel(self.ui_3d_tab).classes('w-full h-full items-center p-0 m-0'):
self.def_3d_scene()
ui.button('Download Current Garment', on_click=lambda: self.state_download()).classes('justify-self-end')
def def_param_tabs_layout(self):
"""Layout of tabs with parameters"""
with ui.column(wrap=False).classes(f'h-[{self.h_params_content}vh]'):
with ui.tabs().classes('w-full').props('dense') as tabs:
self.ui_lmm_tab = ui.tab('Parse Design').props('icon=chat').classes('text-sm')
self.ui_design_tab = ui.tab('Design Parameters').props('icon=checkroom').classes('text-sm')
self.ui_body_tab = ui.tab('Body Measurement').props('icon=accessibility_new').classes('text-sm')
with ui.tab_panels(tabs, value=self.ui_design_tab, animated=True).classes('w-full h-full items-center'):
with ui.tab_panel(self.ui_lmm_tab).classes('w-full h-full items-center p-0 m-0'):
self.def_lmm_tab()
with ui.tab_panel(self.ui_design_tab).classes('w-full h-full items-center p-0 m-0'):
self.def_design_tab()
with ui.tab_panel(self.ui_body_tab).classes('w-full h-full items-center p-0 m-0'):
self.def_body_tab()
def def_lmm_tab(self):
self.own_id = str(uuid4())
self.chat_msg = ''
self.chat_img_url = ''
def toggle_inputs():
if 'hidden' in self.api_input_fields.classes:
self.api_input_fields.classes.remove('hidden') # display
else:
self.api_input_fields.classes.append('hidden') # hidden
self.api_input_fields.update() # Update the UI
def submit_api_data():
self.baseurl = self.baseurl_input.value
self.api = self.api_input.value
self.model = self.model_input.value
self.text_model = self.text_model_input.value
toggle_inputs()
with ui.column().classes('w-full h-full'):
with ui.element('div').classes('w-full h-full overflow-auto'):
chat_messages(self.own_id)
with ui.row().classes('w-full mt-2'):
self.chat_input = ui.input(
placeholder='Describe your design or upload a reference image...').props('autofocus').classes('flex-grow').on('keydown.enter', self.handle_chat_input)
ui.button('Send', on_click=self.handle_chat_input).props('icon=send flat').classes('mt-2')
with ui.column().classes('hidden').style('margin-left: auto;') as self.api_input_fields:
self.baseurl_input = ui.input(placeholder='Base URL').classes('w-full')
self.api_input = ui.input(placeholder='API Key').classes('w-full')
self.model_input = ui.input(placeholder='Model Name').classes('w-full')
self.text_model_input = ui.input(placeholder='Text Model Name').classes('w-full')
ui.button('Submit', on_click=submit_api_data).classes('icon=send')
ui.button('open/close input baseurl api model', on_click=toggle_inputs).classes('icon=send')
with self.chat_input.add_slot('prepend'):
ui.icon('add_photo_alternate').on('click', self.open_image_upload_dialog).classes('cursor-pointer').style('margin-left: 8px;')
with ui.dialog() as self.image_upload_dialog:
with ui.card():
ui.upload(on_upload=self.handle_image_upload).props('accept="image/*"').classes('w-full')
ui.button('Close', on_click=self.image_upload_dialog.close).classes('mt-2')
async def handle_chat_input(self, e=None):
self.chat_msg = self.chat_input.value.strip()
if self.chat_msg:
timestamp = datetime.now().strftime('%H:%M')
messages.append((self.own_id, f'{self.path_static_img}/artist.png', self.chat_msg, timestamp, self.chat_img_url))
chat_messages.refresh(self.own_id)
await self.parse_design(self.chat_msg, self.chat_img_url,api_key=self.api, base_url=self.baseurl, model=self.model,text_model=self.text_model)
self.chat_input.value = ''
self.chat_img_url = ''
def open_image_upload_dialog(self):
self.image_upload_dialog.open()
async def handle_image_upload(self, e: events.UploadEventArguments):
image_name = e.name
image_path = f'{self.path_static_img}/{image_name}'
with open(f'./assets/img/{image_name}', 'wb') as f:
f.write(e.content.read())
self.chat_img_url = image_path
self.image_upload_dialog.close()
self.chat_msg = self.chat_input.value.strip()
timestamp = datetime.now().strftime('%H:%M')
messages.append((self.own_id, f'{self.path_static_img}/artist.png', self.chat_msg, timestamp, self.chat_img_url))
chat_messages.refresh(self.own_id)
await self.parse_design(self.chat_msg, f'./assets/img/{image_name}',api_key=self.api, base_url=self.baseurl, model=self.model,text_model=self.text_model)
self.chat_input.value = ''
self.chat_img_url = ''
def def_body_tab(self):
# Set of buttons
with ui.row():
ui.button('Upload', on_click=self.ui_body_dialog.open)
self.ui_active_body_refs = {}
self.ui_passive_body_refs = {}
with ui.scroll_area().classes('w-full h-full p-0 m-0'): # NOTE: p-0 m-0 gap-0 dont' seem to have effect
body = self.pattern_state.body_params
for param in body:
param_name = param.replace('_', ' ').capitalize()
elem = ui.number(
label=param_name,
value=str(body[param]),
format='%.2f',
precision=2,
step=0.5,
).classes('text-[0.85rem]')
if param[0] == '_': # Info elements for calculatable parameters
elem.disable()
self.ui_passive_body_refs[param] = elem
else: # active elements accepting input
# NOTE: e.sender == UI object, e.value == new value
elem.on_value_change(lambda e, dic=body, param=param: self.update_pattern_ui_state(
dic, param, e.value, body_param=True
))
self.ui_active_body_refs[param] = elem
def def_flat_design_subtab(self, ui_elems, design_params, use_collapsible=False):
"""Group of design parameters"""
for param in design_params:
param_name = param.replace('_', ' ').capitalize()
if 'v' not in design_params[param]:
ui_elems[param] = {}
if use_collapsible:
with ui.expansion().classes('w-full p-0 m-0') as expansion:
with expansion.add_slot('header'):
ui.label(f'{param_name}').classes('text-base self-center w-full h-full p-0 m-0')
with ui.row().classes('w-full h-full p-0 m-0'): # Ensures correct application of style classes for children
self.def_flat_design_subtab(ui_elems[param], design_params[param])
else:
with ui.card().classes('w-full shadow-md border m-0 rounded-md'):
ui.label(f'{param_name}').classes('text-base self-center w-full h-full p-0 m-0')
self.def_flat_design_subtab(ui_elems[param], design_params[param])
else:
# Leaf value
p_type = design_params[param]['type']
val = design_params[param]['v']
p_range = design_params[param]['range']
if 'select' in p_type:
values = design_params[param]['range']
if 'null' in p_type and None not in values:
values.append(None) # NOTE: Displayable value
ui.label(param_name).classes('p-0 m-0 mt-2 text-stone-500 text-[0.85rem]')
ui_elems[param] = ui.select(
values, value=val,
on_change=lambda e, dic=design_params, param=param: self.update_pattern_ui_state(dic, param, e.value)
).classes('w-full')
elif p_type == 'bool':
ui_elems[param] = ui.switch(
param_name, value=val,
on_change=lambda e, dic=design_params, param=param: self.update_pattern_ui_state(dic, param, e.value)
).classes('text-stone-500')
elif p_type == 'float' or p_type == 'int':
ui.label(param_name).classes('p-0 m-0 mt-2 text-stone-500 text-[0.85rem]')
ui_elems[param] = ui.slider(
value=val,
min=p_range[0],
max=p_range[1],
step=0.025 if p_type == 'float' else 1,
).props('snap label').classes('w-full') \
.on('update:model-value',
lambda e, dic=design_params, param=param: self.update_pattern_ui_state(dic, param, e.args),
throttle=0.5, leading_events=False)
# NOTE Events control: https://nicegui.io/documentation/slider#throttle_events_with_leading_and_trailing_options
elif 'file' in p_type:
print(f'GUI::NotImplementedERROR::{param}::'
'"file" parameter type is not yet supported in Web GarmentCode. '
'Creation of corresponding UI element skipped'
)
else:
print(f'GUI::WARNING::Unknown parameter type: {p_type}')
ui_elems[param] = ui.input(label=param_name, value=val, placeholder='Type the value',
validation={'Input too long': lambda value: len(value) < 20},
on_change=lambda e, dic=design_params, param=param: self.update_pattern_ui_state(dic, param, e.value)
).classes('w-full')
def def_design_tab(self):
# Set of buttons
with ui.row():
ui.button('Random', on_click=self.random)
ui.button('Default', on_click=self.default)
ui.button('Upload', on_click=self.ui_design_dialog.open)
# Design parameters
design_params = self.pattern_state.design_params
self.ui_design_refs = {}
if self.pattern_state.is_design_sectioned():
# Use tabs to represent top-level sections
with ui.splitter(value=self.w_splitter_design).classes('w-full h-full p-0 m-0') as splitter:
with splitter.before:
with ui.tabs().props('vertical').classes('w-full h-full') as tabs:
for param in design_params:
# Tab
self.ui_design_subtabs[param] = ui.tab(param)
self.ui_design_refs[param] = {}
with splitter.after:
with ui.tab_panels(tabs, value=self.ui_design_subtabs['meta']).props('vertical').classes('w-full h-full p-0 m-0'):
for param, tab_elem in self.ui_design_subtabs.items():
with ui.tab_panel(tab_elem).classes('w-full h-full p-0 m-0').style('gap: 0px'):
with ui.scroll_area().classes('w-full h-full p-0 m-0').style('gap: 0px'):
self.def_flat_design_subtab(
self.ui_design_refs[param],
design_params[param],
use_collapsible=(param == 'left')
)
else:
# Simplified display of designs
with ui.scroll_area().classes('w-full h-full p-0 m-0'):
self.def_flat_design_subtab(
self.ui_design_refs,
design_params,
use_collapsible=True
)
# !SECTION
# SECTION -- Pattern visuals
def def_pattern_display(self):
"""Prepare pattern display area"""
with ui.column().classes('h-full p-0 m-0'):
with ui.row().classes('w-full p-0 m-0 justify-between'):
switch = ui.switch(
'Body Silhouette', value=True,
).props('dense left-label').classes('text-stone-800')
self.ui_self_intersect = ui.label(
'WARNING: Garment panels are self-intersecting!'
).classes('font-semibold text-purple-600 border-purple-600 border py-0 px-1.5 rounded-md') \
.bind_visibility(self.pattern_state, 'is_self_intersecting')
with ui.image(
f'{self.path_static_img}/millimiter_paper_1500_900.png'
).classes(f'aspect-[{self.canvas_aspect_ratio}] h-[95%] p-0 m-0') as self.ui_pattern_bg:
# NOTE: Positioning: https://github.com/zauberzeug/nicegui/discussions/957
with ui.row().classes('w-full h-full p-0 m-0 bg-transparent relative top-[0%] left-[0%]'):
self.body_outline_classes = 'bg-transparent h-full absolute top-[0%] left-[0%] p-0 m-0'
try:
self.ui_body_outline = ui.image(f'{self.path_static_img}/ggg_outline_{self.pattern_state.body_id}.svg').classes(replace=self.body_outline_classes)
switch.bind_value(self.ui_body_outline, 'visible')
except Exception as e:
print('GUI::WARNING::Body silhouette not found, using mean_all', str(e))
self.ui_body_outline = ui.image(f'{self.path_static_img}/ggg_outline_mean_all.svg').classes(self.body_outline_classes)
switch.bind_value(self.ui_body_outline, 'visible')
# NOTE: ui.row allows for correct classes application (e.g. no padding on svg pattern)
with ui.row().classes('w-full h-full p-0 m-0 bg-transparent relative'):
# Automatically updates from source
self.ui_pattern_display = ui.interactive_image(
''
).classes('bg-transparent p-0 m-0')
# !SECTION
# SECTION 3D view
def create_lights(self, scene:ui.scene, intensity=30.0):
light_positions = np.array([
[1.60614, 1.23701, 1.5341,],
[1.31844, -2.52238, 1.92831],
[-2.80522, 2.34624, 1.2594],
[0.160261, 3.52215, 1.81789],
[-2.65752, -1.26328, 1.41194]
])
light_colors = [
'#ffffff',
'#ffffff',
'#ffffff',
'#ffffff',
'#ffffff'
]
z_dirs = np.arctan2(light_positions[:, 1], light_positions[:, 0])
# Add lights to the scene
for i in range(len(light_positions)):
scene.spot_light(
color=light_colors[i], intensity=intensity,
angle=np.pi,
).rotate(0., 0., -z_dirs[i]).move(light_positions[i][0], light_positions[i][1], light_positions[i][2])
def create_camera(self, cam_location, fov, scale=1.):
camera = ui.scene.perspective_camera(fov=fov)
camera.x = cam_location[0] * scale
camera.y = cam_location[1] * scale
camera.z = cam_location[2] * scale
# direction
camera.look_at_x = 0
camera.look_at_y = 0
camera.look_at_z = cam_location[2] * scale * 2/3
return camera
def def_3d_scene(self):
y_fov = 30 # Degrees == np.pi / 6. rad FOV
camera_location = [0, -4.15, 1.25]
bg_color='#ffffff'
def body_visibility(value):
self.ui_body_3d.visible(value)
with ui.row().classes('w-full p-0 m-0 justify-between items-center'):
self.ui_body_3d_switch = ui.switch(
'Body Silhouette',
value=True,
on_change=lambda e: body_visibility(e.value)
).props('dense left-label').classes('text-stone-800')
ui.button('Drape current design', on_click=lambda: self.update_3d_scene())
ui.label(
'INFO: it takes a few minutes'
).classes(f'font-semibold text-[{theme_colors.primary}] border-[{theme_colors.primary}] '
'border py-0 px-1.5 rounded-md')
camera = self.create_camera(camera_location, y_fov)
with ui.scene(
width=self.scene_base_resoltion[0],
height=self.scene_base_resoltion[1],
camera=camera,
grid=False,
background_color=bg_color
).classes(f'w-[{self.w_garment_display}vw] h-[90%] p-0 m-0') as self.ui_3d_scene:
# Lights setup
self.create_lights(self.ui_3d_scene, intensity=60.)
# NOTE: texture is there, just needs a better setup
self.ui_garment_3d = None
# TODOLOW Update body model to a correct shape
try:
self.ui_body_3d = self.ui_3d_scene.stl(
f'/body/{self.pattern_state.body_id}.stl'
).rotate(np.pi / 2, 0., 0.).material(color='#000000')
except Exception as e:
print(str(e))
self.ui_body_3d = self.ui_3d_scene.stl(
'/body/mean_all.stl'
).rotate(np.pi / 2, 0., 0.).material(color='#000000')
# !SECTION
# SECTION -- Other UI details
def def_pattern_waiting(self):
"""Define the waiting splashcreen with spinner
(e.g. waiting for a pattern to update)"""
# NOTE: the screen darkens because of the shadow
with ui.dialog(value=False).props(
'persistent maximized'
) as self.spin_dialog, ui.card().classes('bg-transparent'):
# Styles https://quasar.dev/vue-components/spinners
ui.spinner('hearts', size='15em').classes('fixed-center') # NOTE: 'dots' 'ball'
def def_body_file_dialog(self):
""" Dialog for loading parameter files (body)
"""
async def handle_upload(e: events.UploadEventArguments):
param_dict = yaml.safe_load(e.content.read())['body']
self.toggle_param_update_events(self.ui_active_body_refs)
self.pattern_state.body_id = e.name.split('.')[0]
self.pattern_state.set_new_body_params(param_dict)
self.update_body_params_ui_state(self.ui_active_body_refs)
await self.update_pattern_ui_state()
if self.ui_body_3d is not None: self.ui_body_3d.delete()
try:
print(f'INFO::Body silhouette update ggg_outline_{self.pattern_state.body_id}.svg')
self.ui_body_3d = self.ui_3d_scene.stl(
f'/body/{self.pattern_state.body_id}.stl'
).rotate(np.pi / 2, 0., 0.).material(color='#000000')
except Exception as e:
print('GUI::WARNING::3D Body mesh not found, using mean_all', str(e))
self.ui_body_3d = self.ui_3d_scene.stl(
'/body/mean_all.stl'
).rotate(np.pi / 2, 0., 0.).material(color='#000000')
try:
print(f'INFO::Body silhouette update ggg_outline_{self.pattern_state.body_id}.svg')
print('*** img_url: ', self.ui_body_outline.source)
self.ui_body_outline.set_source(f'{self.path_static_img}/ggg_outline_{self.pattern_state.body_id}.svg')
self.ui_body_outline.update()
except Exception as e:
print('GUI::WARNING::Body silhouette not found, using mean_all', str(e))
self.toggle_param_update_events(self.ui_active_body_refs)
ui.notify(f'Successfully applied {e.name}')
self.ui_body_dialog.close()
with ui.dialog() as self.ui_body_dialog, ui.card().classes('items-center'):
# NOTE: https://www.reddit.com/r/nicegui/comments/1393i2f/file_upload_with_restricted_types/
ui.upload(
label='Body parameters .yaml or .json',
on_upload=handle_upload
).classes('max-w-full').props('accept=".yaml,.json"')
ui.button('Close without upload', on_click=self.ui_body_dialog.close)
def def_design_file_dialog(self):
""" Dialog for loading parameter files (design)
"""
async def handle_upload(e: events.UploadEventArguments):
param_dict = yaml.safe_load(e.content.read())['design']
self.toggle_param_update_events(self.ui_design_refs) # Don't react to value updates
self.pattern_state.set_new_design(param_dict)
self.update_design_params_ui_state(self.ui_design_refs, self.pattern_state.design_params)
await self.update_pattern_ui_state()
self.toggle_param_update_events(self.ui_design_refs) # Re-enable reaction to value updates
ui.notify(f'Successfully applied {e.name}')
self.ui_design_dialog.close()
with ui.dialog() as self.ui_design_dialog, ui.card().classes('items-center'):
# NOTE: https://www.reddit.com/r/nicegui/comments/1393i2f/file_upload_with_restricted_types/
ui.upload(
label='Design parameters .yaml or .json',
on_upload=handle_upload
).classes('max-w-full').props('accept=".yaml,.json"')
ui.button('Close without upload', on_click=self.ui_design_dialog.close)
# !SECTION
# SECTION -- Event callbacks
async def update_pattern_ui_state(self, param_dict=None, param=None, new_value=None, body_param=False):
"""UI was updated -- update the state of the pattern parameters and visuals"""
# NOTE: Fixing to the "same value" issue in lambdas
# https://github.com/zauberzeug/nicegui/wiki/FAQs#why-have-all-my-elements-the-same-value
print('INFO::Updating pattern...')
# Update the values
if param_dict is not None:
if body_param:
param_dict[param] = new_value
else:
param_dict[param]['v'] = new_value
self.pattern_state.is_in_3D = False # Design param changes -> 3D model is not synced with the param
try:
if not self.pattern_state.is_slow_design():
# Quick update
self._sync_update_state()
return
# Display waiting spinner untill getting the result
# NOTE Splashscreen solution to block users from modifying params while updating
# https://github.com/zauberzeug/nicegui/discussions/1988
self.spin_dialog.open()
# NOTE: Using threads for async call
# https://stackoverflow.com/questions/49822552/python-asyncio-typeerror-object-dict-cant-be-used-in-await-expression
self.loop = asyncio.get_event_loop()
await self.loop.run_in_executor(self._async_executor, self._sync_update_state)
self.spin_dialog.close()
except KeyboardInterrupt as e:
raise e
except BaseException as e:
traceback.print_exc()
print(e)
self.spin_dialog.close() # If open
ui.notify(
'Failed to generate pattern correctly. Try different parameter values',
type='negative',
close_button=True,
position='center'
)
def _sync_update_state(self):
# Update derivative body values (just in case)
# TODOLOW only do that on body value updates
self.pattern_state.body_params.eval_dependencies()
self.update_body_params_ui_state(self.ui_passive_body_refs) # Display evaluated dependencies
# Update the garment
# Sync left-right for easier editing
self.pattern_state.sync_left(with_check=True)
# NOTE This is the slow part
self.pattern_state.reload_garment()
# TODOLOW the pattern is floating around when collars are added..
# Update display
if self.ui_pattern_display is not None:
if self.pattern_state.svg_filename:
# Re-align the canvas and body with the new pattern
p_bbox_size = self.pattern_state.svg_bbox_size
p_bbox = self.pattern_state.svg_bbox
# Margin calculations w.r.t. canvas size
# s.t. the pattern scales correctly
w_shift = abs(p_bbox[0]) # Body feet location in width direction w.r.t top-left corner of the pattern
m_top = (1. - abs(p_bbox[2]) * self.background_body_scale) * self.h_rel_body_size + (1. - self.h_rel_body_size) / 2
m_left = self.background_body_canvas_center - w_shift * self.background_body_scale * self.w_rel_body_size
m_right = 1 - m_left - p_bbox_size[0] * self.background_body_scale * self.w_rel_body_size
m_bottom = 1 - m_top - p_bbox_size[1] * self.background_body_scale * self.h_rel_body_size
# Canvas padding adjustment
m_top -= self.h_canvas_pad
m_left -= self.w_canvas_pad
m_right += self.w_canvas_pad # preserve evaluated width
m_bottom -= self.h_canvas_pad
# New placement
if m_top < 0 or m_bottom < 0 or m_left < 0 or m_right < 0:
# Calculate the fraction
scale_margin = 1.2
y_top_scale = abs(min(m_top * scale_margin, 0.)) + 1.
y_bot_scale = 1. + abs(min(m_bottom * scale_margin, 0.))
x_left_scale = abs(min(m_left * scale_margin, 0.)) + 1.
x_right_scale = abs(min(m_right * scale_margin, 0.)) + 1.
scale = min(1. / y_top_scale, 1. / y_bot_scale, 1. / x_left_scale, 1. / x_right_scale)
# Rescale the body
self.ui_body_outline.classes(
replace=self.body_outline_classes + f' origin-center scale-[{scale}]'
)
# Recalculate positioning & width
body_center = 0.5 - self.background_body_canvas_center
m_top = (1. - abs(p_bbox[2]) * self.background_body_scale) * self.h_rel_body_size * scale + (1. - self.h_rel_body_size * scale) / 2
m_left = (0.5 - body_center * scale) - w_shift * self.background_body_scale * self.w_rel_body_size * scale
m_right = 1 - m_left - p_bbox_size[0] * self.background_body_scale * self.w_rel_body_size * scale
# Canvas padding adjustment
# TODOLOW For some reason top adjustment is not needed here: m_top -= self.h_canvas_pad * scale
m_left -= self.w_canvas_pad * scale
m_right += self.w_canvas_pad * scale
else: # Display normally
# Remove body transforms if any were applied
self.ui_body_outline.classes(replace=self.body_outline_classes)
# New pattern image
self.ui_pattern_display.set_source(
str(self.pattern_state.svg_path()) if self.pattern_state.svg_filename else '')
self.ui_pattern_display.classes(
replace=f"""bg-transparent p-0 m-0
absolute
left-[{m_left * 100}%]
top-[{m_top * 100}%]
w-[{(1. - m_right - m_left) * 100}%]
height-auto
""")
else:
# Restore default state
self.ui_pattern_display.set_source('')
self.ui_body_outline.classes(replace=self.body_outline_classes)
def update_design_params_ui_state(self, ui_elems, design_params):
"""Sync ui params with the current state of the design params"""
for param in design_params:
if 'v' not in design_params[param]:
self.update_design_params_ui_state(ui_elems[param], design_params[param])
else:
ui_elems[param].value = design_params[param]['v']
def toggle_param_update_events(self, ui_elems):
"""Enable/disable event handling on the ui elements related to GarmentCode parameters"""
for param in ui_elems:
if isinstance(ui_elems[param], dict):
self.toggle_param_update_events(ui_elems[param])
else:
if ui_elems[param].is_ignoring_events: # -> disabled
ui_elems[param].enable()
else:
ui_elems[param].disable()
def update_body_params_ui_state(self, ui_body_refs):
"""Sync ui params with the current state of the body params"""
for param in ui_body_refs:
ui_body_refs[param].value = self.pattern_state.body_params[param]
async def update_3d_scene(self):
"""According the whatever pattern current state"""
print('INFO::Updating 3D...')
# Cleanup
if self.ui_garment_3d is not None:
self.ui_garment_3d.delete()
self.ui_garment_3d = None
if not self.pattern_state.svg_filename:
print('INFO::Current garment is empty, skipped 3D update')
ui.notify('Current garment is empty. Chose a design to start simulating!')
self.ui_body_3d.visible(True)
self.ui_body_3d_switch.set_value(True)
return
try:
# Display waiting spinner untill getting the result
# NOTE Splashscreen solution to block users from modifying params while updating
# https://github.com/zauberzeug/nicegui/discussions/1988
self.spin_dialog.open()
# NOTE: Using threads for async call
# https://stackoverflow.com/questions/49822552/python-asyncio-typeerror-object-dict-cant-be-used-in-await-expression
self.loop = asyncio.get_event_loop()
await self.loop.run_in_executor(self._async_executor, self._sync_update_3d)
# Update ui
# https://github.com/zauberzeug/nicegui/discussions/1269
with self.ui_3d_scene:
# NOTE: material is defined in the glb file
self.ui_garment_3d = self.ui_3d_scene.gltf(
f'geo/{self.garm_3d_filename}',
).scale(0.01).rotate(np.pi / 2, 0., 0.)
# Show the result! =)
self.spin_dialog.close()
except KeyboardInterrupt as e:
raise e
except BaseException as e:
traceback.print_exc()
print(e)
self.ui_3d_scene.set_visibility(True)
self.spin_dialog.close() # If open
ui.notify(
'Failed to generate 3D model correctly. Try different parameter values',
type='negative',
close_button=True,
position='center'
)
def _sync_update_3d(self):
"""Update 3d model"""
# Run simulation
path, filename = self.pattern_state.drape_3d()
# NOTE: The files will be available publically at the static point
# However, we cannot do much about it, since it won't be available for the interface otherwise
shutil.copy2(path / filename, self.local_path_3d / self.garm_3d_filename)
# Design buttons updates
async def design_sample(self):
"""Run design sampling"""
self.loop = asyncio.get_event_loop()
await self.loop.run_in_executor(self._async_executor, self.pattern_state.sample_design)
async def random(self):
# Sampling could be slow, so add spin always
self.spin_dialog.open()
self.toggle_param_update_events(self.ui_design_refs) # Don't react to value updates
await self.design_sample()
self.update_design_params_ui_state(self.ui_design_refs, self.pattern_state.design_params)
await self.update_pattern_ui_state()
self.toggle_param_update_events(self.ui_design_refs) # Re-do reaction to value updates
self.spin_dialog.close()
async def default(self):
self.toggle_param_update_events(self.ui_design_refs)
self.pattern_state.restore_design(False)
self.update_design_params_ui_state(self.ui_design_refs, self.pattern_state.design_params)
await self.update_pattern_ui_state()
self.toggle_param_update_events(self.ui_design_refs)
async def parse_design(self, text_prompt='', img_url='',api_key=None, base_url=None, model=None,text_model=None):
"""Parse design from text or image"""
def _sync_parse_design():
response = self.pattern_state.parse_chat(text_prompt, img_url,api_key, base_url, model,text_model)
return response
self.spin_dialog.open()
self.toggle_param_update_events(self.ui_design_refs)
self.loop = asyncio.get_event_loop()
response = await self.loop.run_in_executor(self._async_executor, _sync_parse_design)
# response = self.pattern_state.parse_chat(text_prompt, img_url)
print('GPT response: ', response)
self.update_design_params_ui_state(self.ui_design_refs, self.pattern_state.design_params)
await self.update_pattern_ui_state()
self.toggle_param_update_events(self.ui_design_refs)
self.spin_dialog.close()
# !SECTION
def state_download(self):
"""Download current state of a garment"""
archive_path = self.pattern_state.save()
ui.download(archive_path, f'Configured_design_{datetime.now().strftime("%y%m%d-%H-%M-%S")}.zip')

68
gui/error_pages.py Normal file
View File

@@ -0,0 +1,68 @@
from nicegui import ui, app
from nicegui import Client
from nicegui.page import page
import random
from gui.callbacks import theme_colors
# dresses selection =)
error_icons = [
'./assets/img/err_dress_20s.png',
'./assets/img/err_dress_30s.png',
'./assets/img/err_dress_50s.png',
'./assets/img/err_js.png',
'./assets/img/err_red_modern.png',
'./assets/img/err_regency.png'
]
# https://github.com/zauberzeug/nicegui/discussions/883#discussioncomment-5801636
def error_handler(err_type, text, exception: Exception):
"""Base error page, with customizable error messages"""
with ui.column().classes('h-[95vh] w-[95vw] items-center justify-top space-y-8 self-center'):
img = random.choice(error_icons)
ui.image(img).classes('h-[45vh]').props('fit="scale-down"')
with ui.column().classes('h-fit w-fit py-4 px-10 items-center justify-center space-y-8 '
f'border border-[{theme_colors.primary}] rounded-md '
f'shadow-lg shadow-[{theme_colors.secondary}]'):
ui.label(err_type).classes('text-3xl')
if text:
ui.label(text).classes('text-2xl')
ui.label(str(exception)).classes('text-xl text-stone-500')
# https://www.pixelfish.com.au/blog/most-common-website-errors/
@app.exception_handler(404)
async def exception_handler_404(request, exception: Exception):
with Client(page(''), request=None) as client:
error_handler('404', 'You are looking for something that doesn\'t exist', exception)
return client.build_response(request, 404)
@app.exception_handler(500)
async def exception_handler_500(request, exception: Exception):
with Client(page(''), request=None) as client:
error_handler('500', 'Oops! Server error. We are fixing it ASAP =)', exception)
return client.build_response(request, 500)
@app.exception_handler(400)
async def exception_handler_400(request, exception: Exception):
with Client(page(''), request=None) as client:
error_handler('400', 'Oh no, bad request', exception)
return client.build_response(request, 400)
@app.exception_handler(401)
async def exception_handler_401(request, exception: Exception):
with Client(page(''), request=None) as client:
error_handler('401', 'You don\'t have access to this place', exception)
return client.build_response(request, 401)
@app.exception_handler(403)
async def exception_handler_403(request, exception: Exception):
with Client(page(''), request=None) as client:
error_handler('403', 'Sorry, you cannot come here', exception)
return client.build_response(request, 403)
@app.exception_handler(503)
async def exception_handler_503(request, exception: Exception):
with Client(page(''), request=None) as client:
error_handler('503', 'We are unavailable, but will be back soon!', exception)
return client.build_response(request, 503)

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

24
gui/maya_garmentviewer.py Normal file
View File

@@ -0,0 +1,24 @@
"""
Loads maya interface for editing & testing template files
* Maya 2022+
* Qualoth
"""
from maya import cmds
from importlib import reload
# My modules
from pygarment import mayaqltools as mymaya
reload(mymaya)
# -------------- Main -------------
if __name__ == "__main__":
print('Load plugins')
mymaya.qualothwrapper.load_plugin()
cmds.loadPlugin('mtoa.mll') # https://stackoverflow.com/questions/50422566/how-to-register-arnold-render
cmds.loadPlugin('objExport.mll') # same as in https://forums.autodesk.com/t5/maya-programming/invalid-file-type-specified-atomimport/td-p/9121166
try:
mymaya.garmentUI.start_GUI()
except Exception as e:
print(e)

File diff suppressed because it is too large Load Diff

0
lmm_utils/__init__.py Normal file
View File

135
lmm_utils/agent.py Normal file
View File

@@ -0,0 +1,135 @@
import copy
from lmm_utils.core import MMUA
from lmm_utils.projector import input_caption2random_default_cption
from lmm_utils.predict_garmentcode_picture import Predictor
class Agent():
def __init__(self,api_key=None, base_url=None, model=None,text_model=None,model_init=True):
self.mmua=MMUA(api_key=api_key, base_url=base_url, model=model,text_model=text_model)
self.dsl_ga= Predictor(model_init=model_init)
def modify_design(self, design_list, text_prompt,design_params):
gpt_design_list=None
gpt_response=None
gpt_design_params=None
try:
gpt_design_list, gpt_response = self.mmua.text_forusermodel_gpt(design_list,
user_input=text_prompt)
except Exception as e:
print("modify_fail,trying again.please wait")
try:
gpt_design_list, gpt_response = self.mmua.text_forusermodel_gpt(design_list,
user_input=text_prompt)
except Exception as e:
print("modify_fail,pleae input prompt again")
gpt_response = "modify_fail,pleae input prompt again"
if gpt_design_list is not None:
gpt_design_list = input_caption2random_default_cption(gpt_design_list)
more_caption_list = set(gpt_design_list)-set(design_list)
gpt_design_params = self.dsl_ga.caption2yaml(more_caption_list,modify=True,cache_input_design_data=copy.deepcopy(design_params))
return gpt_response, gpt_design_params, gpt_design_list
def stress_design(self, design_list, img_url,design_params):
gpt_design_list=None
gpt_response=None
gpt_design_params=None
try:
gpt_design_list, gpt_response = self.mmua.picture_caption_gpt_red(img_url, caption=design_list)
except Exception as e:
print("stress_fail,trying again.please wait")
try:
gpt_design_list, gpt_response = self.mmua.picture_caption_gpt_red(img_url, caption=design_list)
except Exception as e:
print("stress_fail,please input prompt and picture again")
gpt_response = "stress_fail,please input prompt and picture again"
if gpt_design_list is not None:
gpt_design_list = input_caption2random_default_cption(gpt_design_list)
more_caption_list = set(gpt_design_list) - set(design_list)
gpt_design_params = self.dsl_ga.caption2yaml(more_caption_list, modify=True,
cache_input_design_data=copy.deepcopy(design_params))
return gpt_response, gpt_design_params, gpt_design_list
def picture_text_design(self, img_url,text_prompt):
gpt_design_list=None
gpt_response=None
gpt_design_params=None
recognize_picture_bool = True
try:
gpt_design_list, gpt_response = self.mmua.picture_gpt(img_url)
except Exception as e:
print(f"GPT_UTIL::Generation_FAILURE::Failed for image [I]{img_url} and [T]{text_prompt}"
f" due to {str(e)}, trying again...")
try:
gpt_design_list, gpt_response = self.mmua.picture_gpt(img_url)
except Exception as e:
print(f"GPT_UTIL::PARSE_FAILURE::Failed to parse image [I]{img_url} "
f"and [T]{text_prompt} again due to {str(e)}, give up.")
gpt_response = "Generation failed, please try another image or prompt."
recognize_picture_bool = False
if recognize_picture_bool:
try:
gpt_design_list, gpt_response = self.mmua.text_forusermodel_gpt(
caption=gpt_design_list, user_input=text_prompt)
except Exception as e:
print(f"GPT_UTIL::AUTHORING_FAILURE::Failed to understand instruction {text_prompt} "
f"due to {str(e)}, trying again...")
try:
gpt_design_list, gpt_response = self.mmua.text_forusermodel_gpt(
caption=gpt_design_list, user_input=text_prompt)
except Exception as e:
print(f"GPT_UTIL::AUTHORING_FAILURE::Failed to understand instruction {text_prompt}"
f" due to {str(e)}, give up.")
gpt_response = "Authoring failed, please try another instruction."
if gpt_design_list is not None:
gpt_design_list = input_caption2random_default_cption(gpt_design_list)
gpt_design_params = self.dsl_ga.caption2yaml(gpt_design_list)
return gpt_response, gpt_design_params, gpt_design_list
def picture_design(self, img_url):
gpt_design_list=None
gpt_response=None
gpt_design_params=None
try:
gpt_design_list, gpt_response = self.mmua.picture_gpt(img_url)
except Exception as e:
print(f"GPT_UTIL::PARSE_IMAGE_FAILURE::Failed for image {img_url} due to {str(e)}, trying again...")
try:
gpt_design_list, gpt_response = self.mmua.picture_gpt(img_url)
except Exception as e:
print(f"GPT_UTIL::PARSE_IMAGE_FAILURE::Failed for image {img_url} due to {str(e)}, give up.")
gpt_response = "Generation failed, please try another image."
if gpt_design_list is not None:
gpt_design_list = input_caption2random_default_cption(gpt_design_list)
gpt_design_params = self.dsl_ga.caption2yaml(gpt_design_list,image_path=img_url)
return gpt_response, gpt_design_params, gpt_design_list
def text_design(self, text_prompt):
gpt_design_list=None
gpt_response=None
gpt_design_params=None
try:
gpt_design_list, gpt_response = self.mmua.text_gpt(text_prompt)
except Exception as e:
print(f"GPT_UTIL::PARSE_TEXT_FAILURE::Failed to parse {text_prompt} due to {str(e)}, trying again...")
try:
gpt_design_list, gpt_response = self.mmua.text_gpt(text_prompt)
except Exception as e:
print(f"GPT_UTIL::PARSE_TEXT_FAILURE::Failed to parse {text_prompt} due to {str(e)}, give up.")
gpt_response="Generation failed, please try another prompt."
if gpt_design_list is not None:
gpt_design_list = input_caption2random_default_cption(gpt_design_list)
gpt_design_params = self.dsl_ga.caption2yaml(gpt_design_list)
return gpt_response, gpt_design_params, gpt_design_list

2260
lmm_utils/core.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
import torch
import torch.nn as nn
from transformers import AutoModel, AutoConfig
from peft import PeftModel, PeftConfig
import torch
from datasets import Dataset
from modelscope import snapshot_download, AutoTokenizer
from qwen_vl_utils import process_vision_info
from peft import LoraConfig, TaskType, get_peft_model, PeftModel
from transformers import (
TrainingArguments,
Trainer,
DataCollatorForSeq2Seq,
# Qwen2VLForConditionalGeneration,
AutoProcessor,
PreTrainedModel
)
from lmm_utils.Qwen.qwen2vl_lora_mlp.qwen2vl_modify_modeling_qwen2_vl import Qwen2VLForConditionalGeneration
import json
# Qwen2VLForConditionalGeneration
class LoRAWithMLP(nn.Module):
def __init__(self, base_model_name, mlp_hidden_size=512, num_mlp_layers=2,device='cuda:0'):
super().__init__()
self.device=device
self.base_model = Qwen2VLForConditionalGeneration.from_pretrained("./lmm_utils/Qwen/Qwen2-VL-2B-Instruct/", device_map=device,
torch_dtype=torch.bfloat16, trust_remote_code=True, )
self.base_model.enable_input_require_grads() # This method is performed when gradient checkpoints are turned on
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
# target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
inference_mode=False, # Training mode
r=64,
lora_alpha=16,
lora_dropout=0.05,
bias="none",
)
# Get the LoRA model
self.lora_model = get_peft_model(self.base_model, config)
mlp_layers = []
input_dim = self.lora_model.config.hidden_size # Inherit large model hidden_size
for _ in range(num_mlp_layers):
mlp_layers.append(nn.Linear(input_dim, mlp_hidden_size,dtype=torch.bfloat16))
mlp_layers.append(nn.ReLU())
input_dim = mlp_hidden_size
mlp_layers.append(nn.Linear(mlp_hidden_size, 123,dtype=torch.bfloat16)) #Output size = hidden_size
self.mlp = nn.Sequential(*mlp_layers)
def forward(self, input_ids=None,
attention_mask=None,
inputs_embeds=None,
labels=None,
output_attentions=None,
output_hidden_states=None,
return_dict=None,
task_ids=None,
**kwargs,):
# Calculate the output of the large model after LoRA adaptation
lora_output = self.lora_model(input_ids=input_ids,attention_mask=attention_mask,inputs_embeds=inputs_embeds,labels=None,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
return_dict=return_dict,
task_ids=task_ids,
)
# Calculate the output of MLP additional processing
mlp_output = self.mlp(lora_output.hidden_states[:,-1])
return mlp_output # Let MLP adjust the output of LoRA
def save_checkpoint(self, path,epoch,optimizer,scheduler,best_valid_loss,avg_train_loss):
filtered_dict = {name: param for name, param in self.state_dict().items() if
'base_model' not in name or 'lora' in name}
checkpoint_dict = {
'epoch': epoch,
'model_state_dict': filtered_dict,
'optimizer_state_dict': optimizer.state_dict(),
'best_valid_loss': best_valid_loss,
'avg_train_loss': avg_train_loss,
"scheduler_state_dict":scheduler.state_dict(),
}
torch.save(checkpoint_dict, path)
def load_checkpoint(self, path, optimizer,scheduler, device):
"""
Load the checkpoint and restore the model and optimizer state
:p aram model: The model that needs to be restored
:p aram optimizer: The optimizer that needs to be restored
:p aram path: checkpoint file path
:p aram device: Runtime device (default GPU)
:return: epoch of training, best validation loss, training loss
"""
checkpoint = torch.load(path, map_location=device,optimizer=None,) # Load checkpoint
self.load_state_dict(checkpoint['model_state_dict'], strict=False) # Load the model parameters
optimizer.load_state_dict(checkpoint['optimizer_state_dict']) # Load optimizer parameters
scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
epoch = checkpoint.get('epoch', 0) # Get the epoch
best_valid_loss = checkpoint.get('best_valid_loss', float('inf')) # Get the best verification loss
avg_train_loss = checkpoint.get('avg_train_loss', float('inf')) # Get the average training loss
return epoch, best_valid_loss
def save_weights(self, path):
""" Only LoRA + MLP weights are saved, and the original Qwen2VL model is not included """
# for name, param in self.state_dict().items():
# print(name, param.requires_grad)
filtered_dict = {name: param for name, param in self.state_dict().items() if 'base_model' not in name or 'lora' in name}
torch.save(filtered_dict, path)
# torch.save(self.state_dict(), path)
def load_weights(self, path):
""" Load LoRA + MLP weights (need to initialize the model first) """
state_dict = torch.load(path, map_location=self.device)
self.load_state_dict(state_dict, strict=False) # strict=False Some layers are allowed to be missing

153
lmm_utils/helper.py Normal file
View File

@@ -0,0 +1,153 @@
import os
from lmm_utils.core import MMUA
import shutil
from lmm_utils.projector import input_caption2random_default_cption
import json
import time
from sim_utils import modelandreturn_picture_path, garmentyaml_folder2json_folder
from lmm_utils.predict_garmentcode_picture import Predictor
def category2yaml2json(
category,
category_data,
final_json_path=None,
sim_bool=False,
id="root",
api_key=None,
base_url=None,
model=None,
dsl_ga=None,
):
"""Specify the different types of data, pass in the data, and get a json file to generate the result for the final boilerplate.
Args:
category(string): Specify the type of input 'picture' 'text' 'list'
category_data: List or ImagePath(String) or Text(String) Enter different specific data depending on the type
final_json_path (string): specifies the final JSON path,
At the same time, the json file of the template data, the template image, the converted stylexd format, and the template generation time will be generated in the json folder
sim_bool (bool): Specifies whether simulation is required
id(string): Create a folder under the user_data based on the ID you entered temp_user_folder_for{id}gpt contains all the data related to the template generated this time.
api_key (string): Specifies the API to access the model, if the input is None, the default API will be called
base_url (string): Specifies the URL to be visited, if the input is None, the default API will be called
model(string): Specifies the model to be accessed, if the input is None, the default model will be called
Return
json_list (list): the list selected by the large model
response(string): the reply of the large model, removing the list content in the reply
"""
mmua_llm = MMUA(api_key=api_key, base_url=base_url, model=model)
if dsl_ga is None:
dsl_ga = Predictor()
gpt_respond = None
start_time = time.time()
json_list = []
picture_path = None
if category == "picture": # The corresponding category_data is the image path
json_list, gpt_respond = mmua_llm.picture_gpt(category_data)
picture_path = category_data
if category == "text": # The text corresponding to the input
json_list, gpt_respond = mmua_llm.text_gpt(category_data)
if category == "list": # It's the list of captions
json_list = category_data
caption_json_list = json_list
json_list = input_caption2random_default_cption(json_list)
dsl_ga.caption_json(caption=json_list, id=id,picture_path=picture_path)
end_time = time.time()
pattern_generate_time = end_time - start_time
print(pattern_generate_time)
temp_json_file_path = (
f"user_data/temp_user_folder_for{id}gpt/now_{id}/now_{id}_specification.json"
)
if final_json_path is not None:
final_json_dirname = os.path.dirname(final_json_path)
os.makedirs(final_json_dirname, exist_ok=True)
with open(f"{final_json_dirname}/pattern_generate_time.txt", "a") as f:
f.write(f"pattern_generate_time{pattern_generate_time:.4f} s\n")
with open(f"{final_json_dirname}/caption.json", "w") as file:
json.dump(caption_json_list, file, indent=4)
with open(f"{final_json_dirname}/gpt_respond.txt", "w") as file:
file.write(gpt_respond)
f"user_data/temp_user_folder_for{id}gpt"
# Define the source file path
print("temp_json_file_path", temp_json_file_path)
# print("temp_json_file_path",temp_json_file_path)
# Define the target file path, including the new file name
destination = final_json_path
# Copy the file and retain the metadata, while modifying the file name
shutil.copy2(temp_json_file_path, destination)
if category == "text":
with open(f"{final_json_dirname}/user_input.txt", "w") as file:
# Write text
file.write(category_data)
if category == "picture":
image_path = category_data
# Next, save the original image to the folder where the json path is located, and name it the same as this json file, but with a different extension
_, picture_file_extension = os.path.splitext(image_path)
picture_file_path, _ = os.path.splitext(final_json_path)
# Composite the new image path, here the name of the image is changed
picture_path = picture_file_path + picture_file_extension
shutil.copy2(image_path, picture_path)
filename = os.path.splitext(os.path.basename(final_json_path))[0]
if category == "list": pass
if sim_bool:
modelandreturn_picture_path(temp_json_file_path)
model_png = f"user_data/temp_user_folder_for{id}gpt/now_{id}/now_{id}/now_{id}_render_front.png"
#Copy the mockup to the destination folder
model_png_end_path = final_json_dirname + "/sim_garment_front.png"
shutil.copy2(model_png, model_png_end_path)
model_png = f"user_data/temp_user_folder_for{id}gpt/now_{id}/now_{id}/now_{id}_render_back.png"
# Copy the mockup to the destination folder
model_png_end_path = final_json_dirname + "/sim_garment_back.png"
shutil.copy2(model_png, model_png_end_path)
# Duplicate the PNG image of the plate
pattern_png = (
f"user_data/temp_user_folder_for{id}gpt/now_{id}/now_{id}_pattern.png"
)
pattern_png_end_path = final_json_dirname + f"/{filename}_pattern.png"
shutil.copy2(pattern_png, pattern_png_end_path)
# Copy the yaml file
yaml_file = f"user_data/temp_user_folder_for{id}gpt//now_{id}/now_{id}.yaml"
yaml_end_path = final_json_dirname + f"/{filename}.yaml"
shutil.copy2(yaml_file, yaml_end_path)
if final_json_path is None:
temp_json_dirname = os.path.dirname(temp_json_file_path)
with open(f"{temp_json_dirname}/pattern_generate_time.txt", "a") as f:
f.write(f"pattern_generate_time{pattern_generate_time:.4f} s\n")
with open(f"{temp_json_dirname}/caption.json", "w") as file:
json.dump(caption_json_list, file, indent=4)
with open(f"{temp_json_dirname}/gpt_respond.txt", "w") as file:
file.write(gpt_respond)
if category == "text":
with open(f"{temp_json_dirname}/user_input.txt", "w") as file:
# Write text
file.write(category_data)
if category == "picture":
image_path = category_data
# Next, save the original image to the folder where the json path is located, and name it the same as this json file, but with a different extension
_, picture_file_extension = os.path.splitext(image_path)
picture_file_path, _ = os.path.splitext(temp_json_file_path)
# Composite the new image path, here the name of the image is changed
picture_path = picture_file_path + picture_file_extension
shutil.copy2(image_path, picture_path)
if sim_bool:
modelandreturn_picture_path(temp_json_file_path)
return json_list, gpt_respond

View File

@@ -0,0 +1,478 @@
from torch.utils.data import DataLoader
import torch
from modelscope import AutoTokenizer
from qwen_vl_utils import process_vision_info
from transformers import AutoProcessor
import json
from lmm_utils.fintuned_qwen2vl_model import LoRAWithMLP
from lmm_utils.projector import vec_2_pattern_yaml
from lmm_utils.projector import save_design2yaml
import yaml
import os
import numpy as np
from lmm_utils.sim_utils import garmentyaml_folder2json_folder
from pathlib import Path
def load_system_config():
root_path = Path(__file__).resolve().parent.parent # Navigate to the project's home directory
config_path = root_path / "system.json"
with open(config_path, "r") as f:
return json.load(f)
_config = load_system_config()
class Predictor:
def __init__(self, model_path="./lmm_utils/Qwen/Qwen2-VL-2B-Instruct", device=None,model_init=True):
self.model_init = model_init
if not model_init:
return
if device is None:
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
else:
self.device = device
self.model =LoRAWithMLP(base_model_name=model_path, mlp_hidden_size=512, num_mlp_layers=2,
device=device)
self.tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
self.processor = AutoProcessor.from_pretrained(model_path)
mask_list = torch.tensor([1, 1, 1, 1, 0, 0,
1, # fitted
0, 0, 0, # shirt
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, # collar b_beizer_y
1, 1, 1, 0, 1, 0, 0, # collar
1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, # sleeve
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, # lfet sleeve _cuff
0, 0, 0, 0, 0, # skirt
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, # flare-skirt
1, 0, 0, 1, 0, # godet-skirt
0, 0, 0, 0, 0, 0, 0, 0, 1, # peicl-skirt
1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0]).to(device=self.device) # 1 represents the discrete value identified by the MMUA as the final result.
self.mask_list = 1 - mask_list
checkpoint = torch.load(
_config['param_model'],
map_location='cpu')
self.model.load_state_dict(checkpoint['model_state_dict'], strict=False) # Load the model parameters
self.model.to(self.device)
def predict(self,img_path,caption):
messages_list = [[
{
"role": "user",
"content": [
{
"type": "image",
"image": f"{img_path}",
"resized_height": 280,
"resized_width": 280,
},
{"type": "text", "text": f"garmentcode Yes:{caption}"},
],
}
]]
"""
The dataset is preprocessed
"""
MAX_LENGTH = 8192
input_ids, attention_mask, labels = [], [], []
msgs = messages_list
texts = self.processor.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
# def process_func(self, conversation):
image_inputs, video_inputs = process_vision_info(msgs) # Get data (preprocessed)
inputs = self.processor(
text=texts,
images=image_inputs,
videos=video_inputs,
padding=True,
return_tensors="pt",
)
inputs['image_grid_thw'] = inputs['image_grid_thw'] # Transform from (1,h,w,c) to (h,w,c)
input_ids = inputs['input_ids']
attention_mask = inputs['attention_mask']
labels = None
batch = {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels,
"pixel_values": inputs['pixel_values'], "image_grid_thw": inputs['image_grid_thw']}
batch = {k: v.to(self.device) for k, v in batch.items() if isinstance(v, torch.Tensor)}
with torch.no_grad():
outputs = self.model(**batch)
outputs = outputs * self.mask_list
return outputs
def caption2yaml(self,caption, yaml_path='assets/design_params/default_text_value.yaml', new_yaml_path=None,
return_template_yaml='assets/design_params/default_template.yaml',
body_param_files="assets/bodies/mean_all_full.yaml", modify=False, image_path=None,
cache_input_design_data=None):
'''Map the caption to a yaml file.
Args
caption(list): the list generated by the large model.
yaml_path (string): The basic YAML file used to fill in the value represented by the caption into the YAML file,
which is a specially marked YAML file.
new_yaml_path (string): Save this yaml file to a new path
modify=False and text_bool=False, which are used to control the processing of the length of the lower body.
Return
design(dict): the data of the processed YAML file
'''
with open(body_param_files, 'r', encoding='utf-8') as yaml_file:
body_param = yaml.safe_load(yaml_file)
body = body_param['body']
with open(yaml_path, 'r', encoding='utf-8') as yaml_file:
default_yaml = yaml.safe_load(yaml_file)
design = default_yaml['design']
with open(return_template_yaml, 'r', encoding='utf-8') as yaml_file:
default_template_yaml = yaml.safe_load(yaml_file)
design_template = default_template_yaml['design']
if cache_input_design_data is not None:
design_template = cache_input_design_data
for design_item in caption:
design_item_list = design_item.split('__')
temp = design
template = design_template
for i in range(len(design_item_list)):
if i == (len(design_item_list) - 1):
final_text = design_item_list[i]
if final_text.isdigit():
final_text = int(final_text)
if final_text == 'None':
final_text = None
if final_text == 'True':
final_text = True
if final_text == 'False':
final_text = False
if isinstance(temp['range'][0], dict):
for item in temp['range']:
if final_text in item:
final_text = item[final_text]
temp['v'] = final_text
template['v'] = final_text
else:
try:
temp = temp[design_item_list[i]]
template = template[design_item_list[i]]
except Exception as e:
print(temp)
design = design_template
if 'meta__upper__None' in caption:
design['skirt']['rise']['v'] = 0.5
design['flare-skirt']['rise']['v'] = 0.5
design['pencil-skirt']['rise']['v'] = 0.5
design['levels-skirt']['rise']['v'] = 0.5
if "meta__bottom__SkirtManyPanels" in caption:
if "flare-skirt__length__micro" in caption:
design['flare-skirt']['length']['v'] = 0.15
if "flare-skirt__length__mini" in caption:
design['flare-skirt']['length']['v'] = 0.2
if "flare-skirt__length__above-knee" in caption:
design['flare-skirt']['length']['v'] = 0.3
if "flare-skirt__length__knee-length" in caption:
design['flare-skirt']['length']['v'] = 0.35
if "flare-skirt__length__midi" in caption:
design['flare-skirt']['length']['v'] = 0.45
if "flare-skirt__length__floor-length" in caption:
design['flare-skirt']['length']['v'] = 0.6
if not modify:
shirt_length = 0
waist_length = 0
if 'meta__upper__Shirt' in caption:
front_frac = (body['bust'] - body['back_width']) / 2 / body['bust']
fb_diff = (front_frac - (0.5 - front_frac)) * body['bust']
sh_tan = float(np.tan(np.deg2rad(body['_shoulder_incl'])))
shirt_length = design['shirt']['length']['v'] * body['waist_line'] - sh_tan * fb_diff
if 'meta__upper__FittedShirt' in caption:
m_bust = body['bust']
front_frac = (body['bust'] - body['back_width']) / 2 / body['bust']
sh_tan = float(np.tan(np.deg2rad(body['_shoulder_incl'])))
width = front_frac * m_bust
adjustment = sh_tan * (width - body['shoulder_w'] / 2)
fitted_shirt_length = body['waist_over_bust_line'] - adjustment
shirt_length = fitted_shirt_length
if "meta__wb__None" not in caption:
waist_length = design['waistband']['width']['v'] * body["hips_line"]
if ("meta__bottom__None" not in caption and 'meta__bottom__Pants' not in caption
and 'meta__upper__None' not in caption and "meta__connected__True" in caption):
if "meta__bottom__Skirt2" in caption:
if "skirt__length__micro" in caption:
all_length = 63.99739360159472
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__mini" in caption:
all_length = 70.38289360159473
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__above-knee" in caption:
all_length = 83.15389360159473
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__knee-length" in caption:
all_length = 98.05339360159472
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__midi" in caption:
all_length = 108.69589360159473
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__floor-length" in caption:
all_length = 121.46689360159472
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif ("meta__bottom__SkirtCircle" in caption or "meta__bottom__SkirtManyPanels" in caption
or "meta__bottom__AsymmSkirtCircle" in caption):
if "flare-skirt__length__micro" in caption:
all_length = 63.99739360159472
design['flare-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['flare-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "flare-skirt__length__mini" in caption:
all_length = 70.38289360159473
design['flare-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['flare-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "flare-skirt__length__above-knee" in caption:
all_length = 83.15389360159473
design['flare-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['flare-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "flare-skirt__length__knee-length" in caption:
all_length = 98.05339360159472
design['flare-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['flare-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "flare-skirt__length__midi" in caption:
all_length = 108.69589360159473
design['flare-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['flare-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "flare-skirt__length__floor-length" in caption:
all_length = 121.46689360159472
design['flare-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['flare-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "meta__bottom__GodetSkirt" in caption:
if "godet-skirt__base__Skirt2" in caption:
if "skirt__length__micro" in caption:
all_length = 63.99739360159472
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__mini" in caption:
all_length = 70.38289360159473
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__above-knee" in caption:
all_length = 83.15389360159473
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__knee-length" in caption:
all_length = 98.05339360159472
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__midi" in caption:
all_length = 108.69589360159473
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "skirt__length__floor-length" in caption:
all_length = 121.46689360159472
design['skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['skirt']['rise']['v'] * body["hips_line"]) / \
body["_leg_length"]
elif "godet-skirt__base__PencilSkirt" in caption:
if "pencil-skirt__length__micro" in caption:
all_length = 63.99739360159472
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__mini" in caption:
all_length = 70.38289360159473
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__above-knee" in caption:
all_length = 83.15389360159473
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__knee-length" in caption:
all_length = 98.05339360159472
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__midi" in caption:
all_length = 108.69589360159473
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__floor-length" in caption:
all_length = 121.46689360159472
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "meta__bottom__PencilSkirt" in caption:
if "pencil-skirt__length__micro" in caption:
all_length = 63.99739360159472
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__mini" in caption:
all_length = 70.38289360159473
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__above-knee" in caption:
all_length = 83.15389360159473
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length -
design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__knee-length" in caption:
all_length = 98.05339360159472
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length -
design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__midi" in caption:
all_length = 108.69589360159473
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length -
design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "pencil-skirt__length__floor-length" in caption:
all_length = 121.46689360159472
design['pencil-skirt']['length']['v'] = (all_length - shirt_length - waist_length -
design['pencil-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "meta__bottom__SkirtLevels" in caption:
if "levels-skirt__length__micro" in caption:
all_length = 63.99739360159472
design['levels-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['levels-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "levels-skirt__length__mini" in caption:
all_length = 63.99739360159472
design['levels-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['levels-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "levels-skirt__length__above-knee" in caption:
all_length = 83.15389360159473
design['levels-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['levels-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "levels-skirt__length__knee-length" in caption:
all_length = 98.05339360159472
design['levels-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['levels-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "levels-skirt__length__midi" in caption:
all_length = 108.69589360159473
design['levels-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['levels-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
elif "levels-skirt__length__floor-length" in caption:
all_length = 121.46689360159472
design['levels-skirt']['length']['v'] = (all_length - shirt_length - waist_length
- design['levels-skirt']['rise']['v'] * body[
"hips_line"]) / \
body["_leg_length"]
if image_path and os.path.exists(image_path) and self.model_init:
temp_cwd = os.getcwd()
image_path = temp_cwd + '/' + image_path
param_vec = self.predict(img_path=image_path, caption=caption)
param_vec = param_vec[0].float().cpu().numpy()
design = vec_2_pattern_yaml(design, param_vec, self.mask_list.tolist())
if new_yaml_path is not None: save_design2yaml(design, new_yaml_path)
return design
def caption_json(self,caption, id="root",picture_path=None,dsl_ga=None):
os.makedirs(f"user_data/temp_user_folder_for{id}gpt", exist_ok=True)
self.caption2yaml(
caption=caption,
new_yaml_path=f"user_data/temp_user_folder_for{id}gpt/now_{id}.yaml",
yaml_path="assets/design_params/default_text_value.yaml",
image_path=picture_path
)
# In this case, a folder with the same name as the yaml file will be generated
# under the corresponding output folder based on the file name of the yaml file,
# and a json file with the same name as the yaml file will be the final result.
# Here the output will be the now_picture file in the now_picture folder
garmentyaml_folder2json_folder(
input_folder=f"user_data/temp_user_folder_for{id}gpt",
output_folder=f"user_data/temp_user_folder_for{id}gpt",
)

785
lmm_utils/projector.py Normal file
View File

@@ -0,0 +1,785 @@
import os
import uuid
import json
import yaml
import random
import numpy as np
from collections import OrderedDict
# from predict_garmentcode_picture import predict
CONNECT_TAG = '__'
all_text_dict={
"meta__upper": [
"meta__upper__FittedShirt",
"meta__upper__Shirt",
"meta__upper__None"
],
"meta__wb": [
"meta__wb__StraightWB",
"meta__wb__FittedWB",
"meta__wb__None"
],
"meta__bottom": [
"meta__bottom__SkirtCircle",
"meta__bottom__AsymmSkirtCircle",
"meta__bottom__GodetSkirt",
"meta__bottom__Pants",
"meta__bottom__Skirt2",
"meta__bottom__SkirtManyPanels",
"meta__bottom__PencilSkirt",
"meta__bottom__SkirtLevels",
"meta__bottom__None"
],
"meta__connected": [
"meta__connected__True",
"meta__connected__False"
],
"waistband__waist": [
"waistband__waist__fitted",
"waistband__waist__slightly-loose",
"waistband__waist__loose"
],
"waistband__width": [
"waistband__width__narrow",
"waistband__width__medium",
"waistband__width__wide"
],
"fitted_shirt__strapless": [
"fitted_shirt__strapless__True",
"fitted_shirt__strapless__False"
],
"shirt__length": [
"shirt__length__super-cropped",
"shirt__length__regular",
"shirt__length__long"
],
"shirt__width": [
"shirt__width__normal",
"shirt__width__relaxed"
],
"shirt__flare": [
"shirt__flare__tight",
"shirt__flare__straight",
"shirt__flare__flared",
"shirt__flare__very-flared"
],
"collar__f_collar": [
"collar__f_collar__CircleNeckHalf",
"collar__f_collar__CurvyNeckHalf",
"collar__f_collar__VNeckHalf",
"collar__f_collar__SquareNeckHalf",
"collar__f_collar__TrapezoidNeckHalf",
"collar__f_collar__CircleArcNeckHalf",
"collar__f_collar__Bezier2NeckHalf"
],
"collar__b_collar": [
"collar__b_collar__CircleNeckHalf",
"collar__b_collar__CurvyNeckHalf",
"collar__b_collar__VNeckHalf",
"collar__b_collar__SquareNeckHalf",
"collar__b_collar__TrapezoidNeckHalf",
"collar__b_collar__CircleArcNeckHalf",
"collar__b_collar__Bezier2NeckHalf"
],
"collar__width": [
"collar__width__very-narrow",
"collar__width__medium",
"collar__width__wide"
],
"collar__fc_depth": [
"collar__fc_depth__shallow",
"collar__fc_depth__medium",
"collar__fc_depth__deep"
],
"collar__bc_depth": [
"collar__bc_depth__shallow",
"collar__bc_depth__medium",
"collar__bc_depth__deep"
],
"collar__fc_angle": [
"collar__fc_angle__acute",
"collar__fc_angle__standard",
"collar__fc_angle__obtuse"
],
"collar__bc_angle": [
"collar__bc_angle__acute",
"collar__bc_angle__standard",
"collar__bc_angle__obtuse"
],
"collar__f_bezier_x": [
"collar__f_bezier_x__left",
"collar__f_bezier_x__center",
"collar__f_bezier_x__right"
],
"collar__f_bezier_y": [
"collar__f_bezier_y__top",
"collar__f_bezier_y__center",
"collar__f_bezier_y__bottom"
],
"collar__b_bezier_x": [
"collar__b_bezier_x__left",
"collar__b_bezier_x__center",
"collar__b_bezier_x__right"
],
"collar__b_bezier_y": [
"collar__b_bezier_y__top",
"collar__b_bezier_y__center",
"collar__b_bezier_y__bottom"
],
"collar__f_flip_curve": [
"collar__f_flip_curve__True",
"collar__f_flip_curve__False"
],
"collar__b_flip_curve": [
"collar__b_flip_curve__True",
"collar__b_flip_curve__False"
],
"collar__component__style": [
"collar__component__style__Turtle",
"collar__component__style__SimpleLapel",
"collar__component__style__Hood2Panels",
"collar__component__style__None"
],
"collar__component__depth": [
"collar__component__depth__shallow",
"collar__component__depth__medium",
"collar__component__depth__deep"
],
"collar__component__lapel_standing": [
"collar__component__lapel_standing__True",
"collar__component__lapel_standing__False"
],
"collar__component__hood_depth": [
"collar__component__hood_depth__shallow",
"collar__component__hood_depth__medium",
"collar__component__hood_depth__deep"
],
"collar__component__hood_length": [
"collar__component__hood_length__short",
"collar__component__hood_length__medium",
"collar__component__hood_length__long"
],
"sleeve__sleeveless": [
"sleeve__sleeveless__True",
"sleeve__sleeveless__False"
],
"sleeve__armhole_shape": [
"sleeve__armhole_shape__ArmholeSquare",
"sleeve__armhole_shape__ArmholeAngle",
"sleeve__armhole_shape__ArmholeCurve"
],
"sleeve__length": [
"sleeve__length__short",
"sleeve__length__half",
"sleeve__length__three-quarter",
"sleeve__length__long",
"sleeve__length__full"
],
"sleeve__connecting_width": [
"sleeve__connecting_width__narrow",
"sleeve__connecting_width__medium",
"sleeve__connecting_width__loose",
"sleeve__connecting_width__very-loose"
],
"sleeve__end_width": [
"sleeve__end_width__closing",
"sleeve__end_width__straight",
"sleeve__end_width__opening"
],
"sleeve__sleeve_angle": [
"sleeve__sleeve_angle__small",
"sleeve__sleeve_angle__medium",
"sleeve__sleeve_angle__large"
],
"sleeve__opening_dir_mix": [
"sleeve__opening_dir_mix__negative-twist",
"sleeve__opening_dir_mix__standard",
"sleeve__opening_dir_mix__positive-twist"
],
"sleeve__standing_shoulder": [
"sleeve__standing_shoulder__True",
"sleeve__standing_shoulder__False"
],
"sleeve__standing_shoulder_len": [
"sleeve__standing_shoulder_len__short",
"sleeve__standing_shoulder_len__medium",
"sleeve__standing_shoulder_len__long"
],
"sleeve__connect_ruffle": [
"sleeve__connect_ruffle__none",
"sleeve__connect_ruffle__some",
"sleeve__connect_ruffle__obvious"
],
"sleeve__smoothing_coeff": [
"sleeve__smoothing_coeff__very-smooth",
"sleeve__smoothing_coeff__moderate",
"sleeve__smoothing_coeff__less-smooth"
],
"sleeve__cuff__type": [
"sleeve__cuff__type__CuffBand",
"sleeve__cuff__type__CuffSkirt",
"sleeve__cuff__type__CuffBandSkirt",
"sleeve__cuff__type__None"
],
"sleeve__cuff__top_ruffle": [
"sleeve__cuff__top_ruffle__straight",
"sleeve__cuff__top_ruffle__tapered",
"sleeve__cuff__top_ruffle__very_tapered"
],
"sleeve__cuff__cuff_len": [
"sleeve__cuff__cuff_len__short",
"sleeve__cuff__cuff_len__medium",
"sleeve__cuff__cuff_len__long"
],
"sleeve__cuff__skirt_fraction": [
"sleeve__cuff__skirt_fraction__small",
"sleeve__cuff__skirt_fraction__medium",
"sleeve__cuff__skirt_fraction__large"
],
"sleeve__cuff__skirt_flare": [
"sleeve__cuff__skirt_flare__slight",
"sleeve__cuff__skirt_flare__moderate",
"sleeve__cuff__skirt_flare__significant"
],
"sleeve__cuff__skirt_ruffle": [
"sleeve__cuff__skirt_ruffle__none",
"sleeve__cuff__skirt_ruffle__some"
],
"left__enable_asym": [
"left__enable_asym__True",
"left__enable_asym__False"
],
"left__fitted_shirt__strapless": [
"left__fitted_shirt__strapless__True",
"left__fitted_shirt__strapless__False"
],
"left__shirt__width": [
"left__shirt__width__normal",
"left__shirt__width__relaxed"
],
"left__shirt__flare": [
"left__shirt__flare__tight",
"left__shirt__flare__straight",
"left__shirt__flare__flared",
"left__shirt__flare__very-flared"
],
"left__collar__f_collar": [
"left__collar__f_collar__CircleNeckHalf",
"left__collar__f_collar__CurvyNeckHalf",
"left__collar__f_collar__VNeckHalf",
"left__collar__f_collar__SquareNeckHalf",
"left__collar__f_collar__TrapezoidNeckHalf",
"left__collar__f_collar__CircleArcNeckHalf",
"left__collar__f_collar__Bezier2NeckHalf"
],
"left__collar__b_collar": [
"left__collar__b_collar__CircleNeckHalf",
"left__collar__b_collar__CurvyNeckHalf",
"left__collar__b_collar__VNeckHalf",
"left__collar__b_collar__SquareNeckHalf",
"left__collar__b_collar__TrapezoidNeckHalf",
"left__collar__b_collar__CircleArcNeckHalf",
"left__collar__b_collar__Bezier2NeckHalf"
],
"left__collar__width": [
"left__collar__width__narrow",
"left__collar__width__medium",
"left__collar__width__wide"
],
"left__collar__fc_angle": [
"left__collar__fc_angle__acute",
"left__collar__fc_angle__standard",
"left__collar__fc_angle__obtuse"
],
"left__collar__bc_angle": [
"left__collar__bc_angle__acute",
"left__collar__bc_angle__standard",
"left__collar__bc_angle__obtuse"
],
"left__collar__f_bezier_x": [
"left__collar__f_bezier_x__left",
"left__collar__f_bezier_x__center",
"left__collar__f_bezier_x__right"
],
"left__collar__f_bezier_y": [
"left__collar__f_bezier_y__top",
"left__collar__f_bezier_y__center",
"left__collar__f_bezier_y__bottom"
],
"left__collar__b_bezier_x": [
"left__collar__b_bezier_x__left",
"left__collar__b_bezier_x__center",
"left__collar__b_bezier_x__right"
],
"left__collar__b_bezier_y": [
"left__collar__b_bezier_y__top",
"left__collar__b_bezier_y__center",
"left__collar__b_bezier_y__bottom"
],
"left__collar__f_flip_curve": [
"left__collar__f_flip_curve__True",
"left__collar__f_flip_curve__False"
],
"left__collar__b_flip_curve": [
"left__collar__b_flip_curve__True",
"left__collar__b_flip_curve__False"
],
"left__sleeve__sleeveless": [
"left__sleeve__sleeveless__True",
"left__sleeve__sleeveless__False"
],
"left__sleeve__armhole_shape": [
"left__sleeve__armhole_shape__ArmholeSquare",
"left__sleeve__armhole_shape__ArmholeAngle",
"left__sleeve__armhole_shape__ArmholeCurve"
],
"left__sleeve__length": [
"left__sleeve__length__short",
"left__sleeve__length__half",
"left__sleeve__length__three-quarter",
"left__sleeve__length__long",
"left__sleeve__length__full"
],
"left__sleeve__connecting_width": [
"left__sleeve__connecting_width__narrow",
"left__sleeve__connecting_width__medium",
"left__sleeve__connecting_width__loose",
"left__sleeve__connecting_width__very-loose"
],
"left__sleeve__end_width": [
"left__sleeve__end_width__closing",
"left__sleeve__end_width__straight",
"left__sleeve__end_width__opening"
],
"left__sleeve__sleeve_angle": [
"left__sleeve__sleeve_angle__small",
"left__sleeve__sleeve_angle__medium",
"left__sleeve__sleeve_angle__large"
],
"left__sleeve__opening_dir_mix": [
"left__sleeve__opening_dir_mix__negative-twist",
"left__sleeve__opening_dir_mix__standard",
"left__sleeve__opening_dir_mix__positive-twist"
],
"left__sleeve__standing_shoulder": [
"left__sleeve__standing_shoulder__True",
"left__sleeve__standing_shoulder__False"
],
"left__sleeve__standing_shoulder_len": [
"left__sleeve__standing_shoulder_len__short",
"left__sleeve__standing_shoulder_len__medium",
"left__sleeve__standing_shoulder_len__long"
],
"left__sleeve__connect_ruffle": [
"left__sleeve__connect_ruffle__none",
"left__sleeve__connect_ruffle__some",
"left__sleeve__connect_ruffle__obvious"
],
"left__sleeve__smoothing_coeff": [
"left__sleeve__smoothing_coeff__very-smooth",
"left__sleeve__smoothing_coeff__moderate",
"left__sleeve__smoothing_coeff__less-smooth"
],
"left__sleeve__cuff__type": [
"left__sleeve__cuff__type__CuffBand",
"left__sleeve__cuff__type__CuffSkirt",
"left__sleeve__cuff__type__CuffBandSkirt",
"left__sleeve__cuff__type__None"
],
"left__sleeve__cuff__top_ruffle": [
"left__sleeve__cuff__top_ruffle__none",
"left__sleeve__cuff__top_ruffle__moderate",
"left__sleeve__cuff__top_ruffle__obvious"
],
"left__sleeve__cuff__cuff_len": [
"left__sleeve__cuff__cuff_len__short",
"left__sleeve__cuff__cuff_len__medium",
"left__sleeve__cuff__cuff_len__long"
],
"left__sleeve__cuff__skirt_fraction": [
"left__sleeve__cuff__skirt_fraction__small",
"left__sleeve__cuff__skirt_fraction__medium",
"left__sleeve__cuff__skirt_fraction__large"
],
"left__sleeve__cuff__skirt_flare": [
"left__sleeve__cuff__skirt_flare__slight",
"left__sleeve__cuff__skirt_flare__moderate",
"left__sleeve__cuff__skirt_flare__significant"
],
"left__sleeve__cuff__skirt_ruffle": [
"left__sleeve__cuff__skirt_ruffle__none",
"left__sleeve__cuff__skirt_ruffle__some"
],
"skirt__length": [
"skirt__length__micro",
"skirt__length__mini",
"skirt__length__above-knee",
"skirt__length__knee-length",
"skirt__length__midi",
"skirt__length__floor-length"
],
"skirt__rise": [
"skirt__rise__low",
"skirt__rise__mid",
"skirt__rise__high"
],
"skirt__ruffle": [
"skirt__ruffle__none",
"skirt__ruffle__moderate",
"skirt__ruffle__rich"
],
"skirt__bottom_cut": [
"skirt__bottom_cut__none",
"skirt__bottom_cut__shallow",
"skirt__bottom_cut__deep"
],
"skirt__flare": [
"skirt__flare__small",
"skirt__flare__medium",
"skirt__flare__large"
],
"flare-skirt__length": [
"flare-skirt__length__micro",
"flare-skirt__length__mini",
"flare-skirt__length__above-knee",
"flare-skirt__length__knee-length",
"flare-skirt__length__midi",
"flare-skirt__length__floor-length"
],
"flare-skirt__rise": [
"flare-skirt__rise__low",
"flare-skirt__rise__mid",
"flare-skirt__rise__high"
],
"flare-skirt__suns": [
"flare-skirt__suns__slight",
"flare-skirt__suns__moderate",
"flare-skirt__suns__significant"
],
"flare-skirt__skirt-many-panels__n_panels": [
"flare-skirt__skirt-many-panels__n_panels__few",
"flare-skirt__skirt-many-panels__n_panels__medium",
"flare-skirt__skirt-many-panels__n_panels__many"
],
"flare-skirt__skirt-many-panels__panel_curve": [
"flare-skirt__skirt-many-panels__panel_curve__inward",
"flare-skirt__skirt-many-panels__panel_curve__straight",
"flare-skirt__skirt-many-panels__panel_curve__outward"
],
"flare-skirt__asymm__front_length": [
"flare-skirt__asymm__front_length__highly-asymmetric",
"flare-skirt__asymm__front_length__strongly-asymmetric",
"flare-skirt__asymm__front_length__moderately-asymmetric",
"flare-skirt__asymm__front_length__slightly-asymmetric",
"flare-skirt__asymm__front_length__symmetric"
],
"flare-skirt__cut__add": [
"flare-skirt__cut__add__True",
"flare-skirt__cut__add__False"
],
"flare-skirt__cut__depth": [
"flare-skirt__cut__depth__shallow",
"flare-skirt__cut__depth__medium",
"flare-skirt__cut__depth__deep"
],
"flare-skirt__cut__width": [
"flare-skirt__cut__width__narrow",
"flare-skirt__cut__width__medium",
"flare-skirt__cut__width__wide"
],
"flare-skirt__cut__place": [
"flare-skirt__cut__place__back_left",
"flare-skirt__cut__place__back_center",
"flare-skirt__cut__place__back_right",
"flare-skirt__cut__place__front_left",
"flare-skirt__cut__place__front_center",
"flare-skirt__cut__place__front_right",
],
"godet-skirt__base": [
"godet-skirt__base__Skirt2",
"godet-skirt__base__PencilSkirt"
],
"godet-skirt__insert_w": [
"godet-skirt__insert_w__narrow",
"godet-skirt__insert_w__medium",
"godet-skirt__insert_w__wide"
],
"godet-skirt__insert_depth": [
"godet-skirt__insert_depth__shallow",
"godet-skirt__insert_depth__medium",
"godet-skirt__insert_depth__deep"
],
"godet-skirt__num_inserts": [
"godet-skirt__num_inserts__4",
"godet-skirt__num_inserts__6",
"godet-skirt__num_inserts__8",
"godet-skirt__num_inserts__10",
"godet-skirt__num_inserts__12"
],
"godet-skirt__cuts_distance": [
"godet-skirt__cuts_distance__close",
"godet-skirt__cuts_distance__medium",
"godet-skirt__cuts_distance__far"
],
"pencil-skirt__length": [
"pencil-skirt__length__micro",
"pencil-skirt__length__mini",
"pencil-skirt__length__above-knee",
"pencil-skirt__length__knee-length",
"pencil-skirt__length__midi",
"pencil-skirt__length__floor-length"
],
"pencil-skirt__rise": [
"pencil-skirt__rise__low",
"pencil-skirt__rise__mid",
"pencil-skirt__rise__high"
],
"pencil-skirt__flare": [
"pencil-skirt__flare__tight",
"pencil-skirt__flare__straight",
"pencil-skirt__flare__slight-flare"
],
"pencil-skirt__low_angle": [
"pencil-skirt__low_angle__inward",
"pencil-skirt__low_angle__straight",
"pencil-skirt__low_angle__outward"
],
"pencil-skirt__front_slit": [
"pencil-skirt__front_slit__none",
"pencil-skirt__front_slit__shallow",
"pencil-skirt__front_slit__deep"
],
"pencil-skirt__back_slit": [
"pencil-skirt__back_slit__none",
"pencil-skirt__back_slit__shallow",
"pencil-skirt__back_slit__deep"
],
"pencil-skirt__left_slit": [
"pencil-skirt__left_slit__none",
"pencil-skirt__left_slit__shallow",
"pencil-skirt__left_slit__deep"
],
"pencil-skirt__right_slit": [
"pencil-skirt__right_slit__none",
"pencil-skirt__right_slit__shallow",
"pencil-skirt__right_slit__deep"
],
"pencil-skirt__style_side_cut": [
"pencil-skirt__style_side_cut__Sun",
"pencil-skirt__style_side_cut__SIGGRAPH_logo",
"pencil-skirt__style_side_cut__None"
],
"levels-skirt__base": [
"levels-skirt__base__Skirt2",
"levels-skirt__base__PencilSkirt",
"levels-skirt__base__SkirtCircle",
"levels-skirt__base__AsymmSkirtCircle"
],
"levels-skirt__level": [
"levels-skirt__level__Skirt2",
"levels-skirt__level__SkirtCircle",
"levels-skirt__level__AsymmSkirtCircle"
],
"levels-skirt__num_levels": [
"levels-skirt__num_levels__1",
"levels-skirt__num_levels__2",
"levels-skirt__num_levels__3",
"levels-skirt__num_levels__4",
"levels-skirt__num_levels__5"
],
"levels-skirt__level_ruffle": [
"levels-skirt__level_ruffle__none",
"levels-skirt__level_ruffle__moderate",
"levels-skirt__level_ruffle__rich"
],
"levels-skirt__length": [
"levels-skirt__length__micro",
"levels-skirt__length__mini",
"levels-skirt__length__above-knee",
"levels-skirt__length__knee-length",
"levels-skirt__length__midi",
"levels-skirt__length__floor-length"
],
"levels-skirt__rise": [
"levels-skirt__rise__low",
"levels-skirt__rise__mid",
"levels-skirt__rise__high"
],
"levels-skirt__base_length_frac": [
"levels-skirt__base_length_frac__short",
"levels-skirt__base_length_frac__medium",
"levels-skirt__base_length_frac__long"
],
"pants__length": [
"pants__length__micro",
"pants__length__short",
"pants__length__knee-length",
"pants__length__capri",
"pants__length__ankle-length",
"pants__length__full-length"
],
"pants__width": [
"pants__width__fitted",
"pants__width__normal",
"pants__width__loose"
],
"pants__flare": [
"pants__flare__tapering",
"pants__flare__straight",
"pants__flare__slight-flare"
],
"pants__rise": [
"pants__rise__low",
"pants__rise__mid",
"pants__rise__high"
],
"pants__cuff__type": [
"pants__cuff__type__CuffBand",
"pants__cuff__type__CuffSkirt",
"pants__cuff__type__CuffBandSkirt",
"pants__cuff__type__None"
],
"pants__cuff__top_ruffle": [
"pants__cuff__top_ruffle__none",
"pants__cuff__top_ruffle__moderate",
"pants__cuff__top_ruffle__rich"
],
"pants__cuff__cuff_len": [
"pants__cuff__cuff_len__short",
"pants__cuff__cuff_len__medium",
"pants__cuff__cuff_len__long"
],
"pants__cuff__skirt_fraction": [
"pants__cuff__skirt_fraction__small",
"pants__cuff__skirt_fraction__medium",
"pants__cuff__skirt_fraction__large"
],
"pants__cuff__skirt_flare": [
"pants__cuff__skirt_flare__slight",
"pants__cuff__skirt_flare__moderate",
"pants__cuff__skirt_flare__significant"
],
"pants__cuff__skirt_ruffle": [
"pants__cuff__skirt_ruffle__none",
"pants__cuff__skirt_ruffle__some"
]
}
def list_to_prefix_dict(strings):
"""
Converts the list of strings to a dictionary of Prefix -> Original String List (in the order in which it appears).
A prefix refers to the part after the last '__' segment is removed.
"""
result = OrderedDict()
for s in strings:
parts = s.split(CONNECT_TAG)
prefix = CONNECT_TAG.join(parts[:-1]) # Remove the last fragment
if prefix not in result:
result[prefix] = []
result[prefix].append(s) # Put the full string in
return result
def input_caption2random_default_cption(test_gpt_caption=None):
'''all_text_dict: dict is the dict of the text space, and it is ordered,
which can guarantee the order (because the network is trained to ensure the order)
test_gpt_caption:list is the list of gpt_caption, here there is no specific order,
Below, the text in the test_gpt_caption will be retained, and the others will be randomly selected,
and the order will be guaranteed, and a list will be returned'''
test_gpt_caption_list=list_to_prefix_dict(test_gpt_caption)
random_list = []
for key ,value_list in all_text_dict.items():
# if key == 'levels-skirt__base':
# print(value_list)
if key not in test_gpt_caption_list:
if key == 'shirt__length':
random_list.append("shirt__length__super-cropped")
elif key == 'pants__cuff__type':
random_list.append("pants__cuff__type__None")
elif key == "sleeve__cuff__type":
random_list.append("sleeve__cuff__type__None")
elif key == "left__sleeve__cuff__type":
random_list.append("left__sleeve__cuff__type__None")
elif key == 'collar__component__style':
random_list.append("collar__component__style__None")
elif key == "pencil-skirt__low_angle":
random_list.append("pencil-skirt__low_angle__straight")
elif key == "sleeve__connecting_width":
random_list.append("sleeve__connecting_width__medium")
elif key == "sleeve__end_width":
random_list.append("sleeve__end_width__straight")
elif key == "left__sleeve__connecting_width":
random_list.append("left__sleeve__connecting_width__medium")
elif key == "left__sleeve__end_width":
random_list.append("left__sleeve__end_width__straight")
elif 'False' in value_list[0] or 'True' in value_list[0]:
random_list.append(value_list[1])
else:
chice_num=random.randint(0,len(value_list)-1)
random_list.append(value_list[chice_num])
else:
if test_gpt_caption_list[key][0] in value_list:
res= test_gpt_caption_list[key][0]
random_list.append(res)
else:
chice_num = random.randint(0, len(value_list) - 1)
random_list.append(value_list[chice_num])
return random_list
def vec_2_pattern_yaml(yaml_data,param_vec,mask_list):
yaml_file_name = None
pattern_data_dict = {}
pattern_vec = {}
cont = 0
param_vec = np.clip(param_vec, 0, 1)
def extract_new_vec_v2v(data, path=''):
"""
Recursively traverses the data structure and extracts all the 'v' values.
:p aram data: YAML loaded data
:p aram path: The path of the current field, which is used to display the hierarchy
:return: No return value, print directly
"""
nonlocal cont
nonlocal param_vec
nonlocal mask_list
if isinstance(data, dict):
for key, value in data.items():
# If the key is 'v', the path and the corresponding value are printed
if key == 'v':
range = data['range']
if mask_list[cont] == 0 or data['v'] == 0:
cont = cont + 1
continue
elif data['type'] == 'select_null' or data['type'] == 'select':
pass
elif data['type'] == 'bool':
pass
elif data['type'] == 'int':
re_normal_value = param_vec[cont] * (range[1] - range[0]) + range[0]
data['v'] = int(re_normal_value)
elif data['type'] == 'float':
re_normal_value = param_vec[cont] * (range[1] - range[0]) + range[0]
data['v'] = float(re_normal_value*mask_list[cont])
cont = cont + 1
else:
# Recursively moves on to the next layer
extract_new_vec_v2v(value, path + key + '.')
extract_new_vec_v2v(yaml_data, path='')
return yaml_data
class NoAliasDumper(yaml.Dumper):
def ignore_aliases(self, data):
return True
def save_design2yaml(design, new_yaml_path):
garment_param = {'design': design}
new_yaml_path_dir = os.path.dirname(new_yaml_path)
os.makedirs(new_yaml_path_dir, exist_ok=True)
with open(new_yaml_path, 'w') as yaml_file:
yaml.dump(garment_param, yaml_file, default_flow_style=False, allow_unicode=True, sort_keys=False,
Dumper=NoAliasDumper)

160
lmm_utils/sim_utils.py Normal file
View File

@@ -0,0 +1,160 @@
import shutil
from pathlib import Path
import yaml
import pygarment.data_config as data_config
from assets.bodies.body_params import BodyParameters
from assets.garment_programs.meta_garment import MetaGarment
from pygarment.data_config import Properties
from pygarment.meshgen.boxmeshgen import BoxMesh
from pygarment.meshgen.sim_config import PathCofig
# from pygarment.meshgen.simulation import run_sim
#Convert the yaml file directly into an output plate
def garmentyaml_folder2json_folder(input_folder='', output_folder='',
body_to_use='neutral'):
''' Convert the yaml files of the input folder to json files and save them in the output folder There is a folder with a yaml file name under the output folder, which contains JSON files and boilerplate images
Args:
input_folder(string): Enter a folder
output_folder (string): The output folder
body_to_use (string): Select a human body parameter
'''
bodies_measurements = {
# Our model
'neutral': './assets/bodies/mean_all.yaml',
'mean_female': './assets/bodies/mean_female.yaml',
'mean_male': './assets/bodies/mean_male.yaml',
# SMPL
'f_smpl': './assets/bodies/f_smpl_average_A40.yaml',
'm_smpl': './assets/bodies/m_smpl_average_A40.yaml',
#t pose
'mean_all_tpose': './assets/bodies/mean_all_tpose.yaml'
}
body_to_use = body_to_use # CHANGE HERE to use different set of body measurements
# body_to_use = 'mean_all_tpose'
body = BodyParameters(bodies_measurements[body_to_use])
design_files = {
't-shirt': './assets/design_params/t-shirt.yaml',
# Add paths HERE to load other parameters
}
pattern_dir = input_folder # This is the input file, write the yaml folder here
design_files_list = [file for file in Path(pattern_dir).iterdir() if file.suffix == '.yaml']
design_files_stem = [item.stem for item in design_files_list]
design_files_list = [str(item) for item in design_files_list]
print(design_files_list)
design_files = {
k: v for k, v in zip(design_files_stem, design_files_list)
}
designs = {}
for df in design_files:
with open(design_files[df], 'r') as f:
designs[df] = yaml.safe_load(f)['design']
test_garments = []
for df in designs:
try:
garment = MetaGarment(df, body, designs[df])
test_garments.append(garment)
except Exception as e:
print(f"An error occurred with {df}: {e}")
continue
outpath = Path(output_folder) # This is the folder for the output
outpath.mkdir(parents=True, exist_ok=True)
for piece in test_garments:
pattern = piece.assembly()
if piece.is_self_intersecting():
print(f'{piece.name} is Self-intersecting')
folder = pattern.serialize(
outpath,
tag='',
to_subfolder=True,
with_3d=False, with_text=False, view_ids=False,
with_printable=True
)
body.save(folder)
if piece.name in design_files:
shutil.copy(design_files[piece.name], folder)
else:
shutil.copy(design_files['base'], folder)
print(f'Success! {piece.name} saved to {folder}')
def json2modelfolder(input_json):
''' If you simulate a json file, you will save a folder in the directory where the json file is located, removing the specifiction ending as the folder name, and containing all the simulated information
Args
input_json (string): A json file that needs to be mocked
'''
props = data_config.Properties('./assets/Sim_props/default_sim_props.yaml')
props.set_section_stats('sim', fails={}, sim_time={}, spf={}, fin_frame={}, body_collisions={}, self_collisions={})
props.set_section_stats('render', render_time={})
input_path = Path(input_json)
garment_name, _, _ = input_path.stem.rpartition('_') # assuming ending in '_specification'
# garment_name = os.path.splitext(os.path.basename(input_path))[0]
sys_props = data_config.Properties('./system.json')
paths = PathCofig(
in_element_path=input_path.parent,
out_path=input_path.parent,
in_name=garment_name,
body_name='mean_all', # '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)
print(f"Generate box mesh of {garment_name} with resolution {props['sim']['config']['resolution_scale']}...")
print('\nGarment load: ', paths.in_g_spec)
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'])
props.serialize(paths.element_sim_props)
run_sim(
garment_box_mesh.name,
props,
paths,
save_v_norms=False,
store_usd=False, # NOTE: False for fast simulation!
optimize_storage=False, # props['sim']['config']['optimize_storage'],
verbose=False
)
props.serialize(paths.element_sim_props)
def modelandreturn_picture_path(input_json):
''' If you simulate a json file, you will save a folder in the directory where the json file is located, removing the specifiction ending as the folder name.
Contains all the information for the simulation and returns the path to the simulation from the front perspective
Args:
input_json (string): A json file that needs to be mocked
Responds:
image_path2 (string): the path of the simulation map in the front view.
'''
json2modelfolder(input_json)
input_json=Path(input_json)
input_folder=input_json.parent
garment_name, _, _ = input_json.stem.rpartition('_')
image_path2=input_folder / garment_name / f"{str(garment_name)}_render_front.png"
return image_path2

89
lmm_utils/test_picture_batch.py Executable file
View File

@@ -0,0 +1,89 @@
import os
import json
import uuid
import shutil
import argparse
import tqdm
from functools import partial
from concurrent.futures import ThreadPoolExecutor
from helper import category2yaml2json
from lmm_utils.predict_garmentcode_picture import Predictor
def search_picture_files(directory):
"""Search for all image files in the directory"""
picture_files = []
for root, _, files in os.walk(directory):
for file in files:
if file.lower().endswith(('.jpg', '.png', '.jpeg', '.gif')):
picture_files.append(os.path.join(root, file))
return picture_files
def main(input_folder_path, output_folder_path,sim_bool=False):
dsl_ga = Predictor()
all_picture_files = search_picture_files(input_folder_path)
input_output_list = []
uuid_list = []
for input_picture_path in all_picture_files:
output_json_path = input_picture_path.replace(input_folder_path, output_folder_path)
output_json_dir = os.path.dirname(output_json_path)
json_file_name = os.path.splitext(os.path.basename(input_picture_path))[0]
output_json_path = os.path.join(output_json_dir, json_file_name, json_file_name + '.json')
input_output_list.append((input_picture_path, output_json_path))
threadPool = ThreadPoolExecutor(max_workers=5, thread_name_prefix="img_thread")
futures = []
for i, (input_picture_path, output_json_path) in tqdm.tqdm(enumerate(input_output_list)):
if os.path.exists(output_json_path):
continue
item_id = str(uuid.uuid4())
uuid_list.append(item_id)
task = partial(
category2yaml2json,
category='picture',
category_data=input_picture_path,
final_json_path=output_json_path,
id=item_id,
model='Qwen/Qwen2.5-VL-72B-Instruct',
base_url='https://api-inference.modelscope.cn/v1/',
api_key='108a28f0-de01-4c43-b189-6cad25d32990',
dsl_ga=dsl_ga,
sim_bool=sim_bool
)
future = threadPool.submit(task)
futures.append((future, input_picture_path))
print(i, input_picture_path, output_json_path)
fail_picture_list = []
for future, input_picture_path in futures:
try:
result = future.result()
print(f"Task result: {result}")
except Exception as e:
fail_picture_list.append(input_picture_path)
print(f"Task failed: {e}")
# Clean up the temporary folder
for _uuid in uuid_list:
try:
shutil.rmtree(f"user_data/temp_user_folder_for{_uuid}gpt")
except Exception as e:
print(f"Error when deleting temp folder: {e}")
pass
threadPool.shutdown(wait=True)
# Save the list of failures
fail_dir = f"user_data/fail_{os.path.basename(input_folder_path)}"
os.makedirs(fail_dir, exist_ok=True)
with open(os.path.join(fail_dir, "fail_picture_list.json"), "w") as file:
json.dump(fail_picture_list, file, indent=4)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Image-to-GarmentCode generation script")
parser.add_argument('--input', type=str, required=True, help='Input image folder path')
parser.add_argument('--output', type=str, required=True, help='Output folder path')
parser.add_argument('--sim', type=bool, default=False, help='Enable simulation mode (default: False)')
args = parser.parse_args()
main(args.input, args.output,args.sim)

89
lmm_utils/test_text_batch.py Executable file
View File

@@ -0,0 +1,89 @@
import os
import shutil
import traceback
import json
import uuid
import argparse
from functools import partial
from concurrent.futures import ThreadPoolExecutor
from helper import category2yaml2json
from lmm_utils.predict_garmentcode_picture import Predictor
import tqdm
def main(input_text_json_path, output_folder_path,sim_bool=False):
# Load the entered JSON text list
with open(input_text_json_path, 'r') as f:
all_text = json.load(f)
input_output_list = []
json_file_name = os.path.splitext(os.path.basename(input_text_json_path))[0]
for index, input_text in enumerate(all_text):
output_json_path = os.path.join(output_folder_path, json_file_name, str(index), f"{index}.json")
input_output_list.append((input_text, output_json_path))
# Initialize the predictor and thread pool
dsl_ga = Predictor()
threadPool = ThreadPoolExecutor(max_workers=10, thread_name_prefix="test_")
futures = []
uuid_list = []
for i, (input_text, output_json_path) in tqdm.tqdm(enumerate(input_output_list)):
if os.path.exists(output_json_path):
continue
item_id = str(uuid.uuid4())
uuid_list.append(item_id)
task = partial(
category2yaml2json,
category='text',
category_data=input_text,
final_json_path=output_json_path,
id=item_id,
model='Qwen/Qwen2.5-72B-Instruct',
base_url='https://api-inference.modelscope.cn/v1/',
api_key='108a28f0-de01-4c43-b189-6cad25d32990',
dsl_ga=dsl_ga,
sim_bool=sim_bool
)
future = threadPool.submit(task)
futures.append((future, input_text))
print(i, input_text, output_json_path)
# Collect failure information
fail_picture_list = []
for future, input_text in futures:
try:
result = future.result()
print(f"Task result: {result}")
except Exception as e:
traceback.print_exc()
fail_picture_list.append(input_text)
print(f"Task failed: {e}")
# Clean up the temporary folder
for _uuid in uuid_list:
try:
shutil.rmtree(f"user_data/temp_user_folder_for{_uuid}gpt")
except Exception as e:
print(f"Error when deleting temp folder: {e}")
pass
threadPool.shutdown(wait=True)
# Save a record of failures
fail_dir = f"user_data/fail_{os.path.basename(input_text_json_path).split('.')[0]}"
os.makedirs(fail_dir, exist_ok=True)
with open(os.path.join(fail_dir, "fail_picture_list.json"), "w") as file:
json.dump(fail_picture_list, file, indent=4)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Text-to-GarmentCode generation script")
parser.add_argument('--input', type=str, required=True, help='Path to input JSON file with text list')
parser.add_argument('--output', type=str, required=True, help='Path to output folder for results')
parser.add_argument('--sim', type=bool, default=False, help='Enable simulation mode (default: False)')
args = parser.parse_args()
main(args.input, args.output,args.sim)

635
lmm_utils/validation.py Normal file
View File

@@ -0,0 +1,635 @@
_ALL_TEXT =[
"meta__upper__FittedShirt",
"meta__upper__Shirt",
"meta__upper__None",
"meta__wb__StraightWB",
"meta__wb__FittedWB",
"meta__wb__None",
"meta__bottom__SkirtCircle",
"meta__bottom__AsymmSkirtCircle",
"meta__bottom__GodetSkirt",
"meta__bottom__Pants",
"meta__bottom__Skirt2",
"meta__bottom__SkirtManyPanels",
"meta__bottom__PencilSkirt",
"meta__bottom__SkirtLevels",
"meta__bottom__None",
"meta__connected__True",
"meta__connected__False",
"waistband__waist__fitted",
"waistband__waist__slightly-loose",
"waistband__waist__loose",
"waistband__width__narrow",
"waistband__width__medium",
"waistband__width__wide",
"fitted_shirt__strapless__True",
"fitted_shirt__strapless__False",
"shirt__length__super-cropped",
"shirt__length__regular",
"shirt__length__long",
"shirt__width__normal",
"shirt__width__relaxed",
"shirt__flare__tight",
"shirt__flare__straight",
"shirt__flare__flared",
"shirt__flare__very-flared",
"collar__f_collar__CircleNeckHalf",
"collar__f_collar__CurvyNeckHalf",
"collar__f_collar__VNeckHalf",
"collar__f_collar__SquareNeckHalf",
"collar__f_collar__TrapezoidNeckHalf",
"collar__f_collar__CircleArcNeckHalf",
"collar__f_collar__Bezier2NeckHalf",
"collar__b_collar__CircleNeckHalf",
"collar__b_collar__CurvyNeckHalf",
"collar__b_collar__VNeckHalf",
"collar__b_collar__SquareNeckHalf",
"collar__b_collar__TrapezoidNeckHalf",
"collar__b_collar__CircleArcNeckHalf",
"collar__b_collar__Bezier2NeckHalf",
"collar__width__very-narrow",
"collar__width__medium",
"collar__width__wide",
"collar__fc_depth__shallow",
"collar__fc_depth__medium",
"collar__fc_depth__deep",
"collar__bc_depth__shallow",
"collar__bc_depth__medium",
"collar__bc_depth__deep",
"collar__fc_angle__acute",
"collar__fc_angle__standard",
"collar__fc_angle__obtuse",
"collar__bc_angle__acute",
"collar__bc_angle__standard",
"collar__bc_angle__obtuse",
"collar__f_bezier_x__left",
"collar__f_bezier_x__center",
"collar__f_bezier_x__right",
"collar__f_bezier_y__top",
"collar__f_bezier_y__center",
"collar__f_bezier_y__bottom",
"collar__b_bezier_x__left",
"collar__b_bezier_x__center",
"collar__b_bezier_x__right",
"collar__b_bezier_y__top",
"collar__b_bezier_y__center",
"collar__b_bezier_y__bottom",
"collar__f_flip_curve__True",
"collar__f_flip_curve__False",
"collar__b_flip_curve__True",
"collar__b_flip_curve__False",
"collar__component__style__Turtle",
"collar__component__style__SimpleLapel",
"collar__component__style__Hood2Panels",
"collar__component__style__None",
"collar__component__depth__shallow",
"collar__component__depth__medium",
"collar__component__depth__deep",
"collar__component__lapel_standing__True",
"collar__component__lapel_standing__False",
"collar__component__hood_depth__shallow",
"collar__component__hood_depth__medium",
"collar__component__hood_depth__deep",
"collar__component__hood_length__short",
"collar__component__hood_length__medium",
"collar__component__hood_length__long",
"sleeve__sleeveless__True",
"sleeve__sleeveless__False",
"sleeve__armhole_shape__ArmholeSquare",
"sleeve__armhole_shape__ArmholeAngle",
"sleeve__armhole_shape__ArmholeCurve",
"sleeve__length__short",
"sleeve__length__half",
"sleeve__length__three-quarter",
"sleeve__length__long",
"sleeve__length__full",
"sleeve__connecting_width__narrow",
"sleeve__connecting_width__medium",
"sleeve__connecting_width__loose",
"sleeve__connecting_width__very-loose",
"sleeve__end_width__closing",
"sleeve__end_width__straight",
"sleeve__end_width__opening",
"sleeve__sleeve_angle__small",
"sleeve__sleeve_angle__medium",
"sleeve__sleeve_angle__large",
"sleeve__opening_dir_mix__negative-twist",
"sleeve__opening_dir_mix__standard",
"sleeve__opening_dir_mix__positive-twist",
"sleeve__standing_shoulder__True",
"sleeve__standing_shoulder__False",
"sleeve__standing_shoulder_len__short",
"sleeve__standing_shoulder_len__medium",
"sleeve__standing_shoulder_len__long",
"sleeve__connect_ruffle__none",
"sleeve__connect_ruffle__some",
"sleeve__connect_ruffle__obvious",
"sleeve__smoothing_coeff__very-smooth",
"sleeve__smoothing_coeff__moderate",
"sleeve__smoothing_coeff__less-smooth",
"sleeve__cuff__type__CuffBand",
"sleeve__cuff__type__CuffSkirt",
"sleeve__cuff__type__CuffBandSkirt",
"sleeve__cuff__type__None",
"sleeve__cuff__top_ruffle__straight",
"sleeve__cuff__top_ruffle__tapered",
"sleeve__cuff__top_ruffle__very_tapered",
"sleeve__cuff__cuff_len__short",
"sleeve__cuff__cuff_len__medium",
"sleeve__cuff__cuff_len__long",
"sleeve__cuff__skirt_fraction__small",
"sleeve__cuff__skirt_fraction__medium",
"sleeve__cuff__skirt_fraction__large",
"sleeve__cuff__skirt_flare__slight",
"sleeve__cuff__skirt_flare__moderate",
"sleeve__cuff__skirt_flare__significant",
"sleeve__cuff__skirt_ruffle__none",
"sleeve__cuff__skirt_ruffle__some",
"left__enable_asym__True",
"left__enable_asym__False",
"left__fitted_shirt__strapless__True",
"left__fitted_shirt__strapless__False",
"left__shirt__width__normal",
"left__shirt__width__relaxed",
"left__shirt__flare__tight",
"left__shirt__flare__straight",
"left__shirt__flare__flared",
"left__shirt__flare__very-flared",
"left__collar__f_collar__CircleNeckHalf",
"left__collar__f_collar__CurvyNeckHalf",
"left__collar__f_collar__VNeckHalf",
"left__collar__f_collar__SquareNeckHalf",
"left__collar__f_collar__TrapezoidNeckHalf",
"left__collar__f_collar__CircleArcNeckHalf",
"left__collar__f_collar__Bezier2NeckHalf",
"left__collar__b_collar__CircleNeckHalf",
"left__collar__b_collar__CurvyNeckHalf",
"left__collar__b_collar__VNeckHalf",
"left__collar__b_collar__SquareNeckHalf",
"left__collar__b_collar__TrapezoidNeckHalf",
"left__collar__b_collar__CircleArcNeckHalf",
"left__collar__b_collar__Bezier2NeckHalf",
"left__collar__width__narrow",
"left__collar__width__medium",
"left__collar__width__wide",
"left__collar__fc_angle__acute",
"left__collar__fc_angle__standard",
"left__collar__fc_angle__obtuse",
"left__collar__bc_angle__acute",
"left__collar__bc_angle__standard",
"left__collar__bc_angle__obtuse",
"left__collar__f_bezier_x__left",
"left__collar__f_bezier_x__center",
"left__collar__f_bezier_x__right",
"left__collar__f_bezier_y__top",
"left__collar__f_bezier_y__center",
"left__collar__f_bezier_y__bottom",
"left__collar__b_bezier_x__left",
"left__collar__b_bezier_x__center",
"left__collar__b_bezier_x__right",
"left__collar__b_bezier_y__top",
"left__collar__b_bezier_y__center",
"left__collar__b_bezier_y__bottom",
"left__collar__f_flip_curve__True",
"left__collar__f_flip_curve__False",
"left__collar__b_flip_curve__True",
"left__collar__b_flip_curve__False",
"left__sleeve__sleeveless__True",
"left__sleeve__sleeveless__False",
"left__sleeve__armhole_shape__ArmholeSquare",
"left__sleeve__armhole_shape__ArmholeAngle",
"left__sleeve__armhole_shape__ArmholeCurve",
"left__sleeve__length__short",
"left__sleeve__length__half",
"left__sleeve__length__three-quarter",
"left__sleeve__length__long",
"left__sleeve__length__full",
"left__sleeve__connecting_width__narrow",
"left__sleeve__connecting_width__medium",
"left__sleeve__connecting_width__loose",
"left__sleeve__connecting_width__very-loose",
"left__sleeve__end_width__closing",
"left__sleeve__end_width__straight",
"left__sleeve__end_width__opening",
"left__sleeve__sleeve_angle__small",
"left__sleeve__sleeve_angle__medium",
"left__sleeve__sleeve_angle__large",
"left__sleeve__opening_dir_mix__negative-twist",
"left__sleeve__opening_dir_mix__standard",
"left__sleeve__opening_dir_mix__positive-twist",
"left__sleeve__standing_shoulder__True",
"left__sleeve__standing_shoulder__False",
"left__sleeve__standing_shoulder_len__short",
"left__sleeve__standing_shoulder_len__medium",
"left__sleeve__standing_shoulder_len__long",
"left__sleeve__connect_ruffle__none",
"left__sleeve__connect_ruffle__some",
"left__sleeve__connect_ruffle__obvious",
"left__sleeve__smoothing_coeff__very-smooth",
"left__sleeve__smoothing_coeff__moderate",
"left__sleeve__smoothing_coeff__less-smooth",
"left__sleeve__cuff__type__CuffBand",
"left__sleeve__cuff__type__CuffSkirt",
"left__sleeve__cuff__type__CuffBandSkirt",
"left__sleeve__cuff__type__None",
"left__sleeve__cuff__top_ruffle__straight",
"left__sleeve__cuff__top_ruffle__tapered",
"left__sleeve__cuff__top_ruffle__very_tapered",
"left__sleeve__cuff__cuff_len__short",
"left__sleeve__cuff__cuff_len__medium",
"left__sleeve__cuff__cuff_len__long",
"left__sleeve__cuff__skirt_fraction__small",
"left__sleeve__cuff__skirt_fraction__medium",
"left__sleeve__cuff__skirt_fraction__large",
"left__sleeve__cuff__skirt_flare__slight",
"left__sleeve__cuff__skirt_flare__moderate",
"left__sleeve__cuff__skirt_flare__significant",
"left__sleeve__cuff__skirt_ruffle__none",
"left__sleeve__cuff__skirt_ruffle__some",
"skirt__length__micro",
"skirt__length__mini",
"skirt__length__above-knee",
"skirt__length__knee-length",
"skirt__length__midi",
"skirt__length__floor-length",
"skirt__rise__low",
"skirt__rise__mid",
"skirt__rise__high",
"skirt__ruffle__none",
"skirt__ruffle__moderate",
"skirt__ruffle__rich",
"skirt__bottom_cut__none",
"skirt__bottom_cut__shallow",
"skirt__bottom_cut__deep",
"skirt__flare__none",
"skirt__flare__small",
"skirt__flare__medium",
"skirt__flare__large",
"flare-skirt__length__micro",
"flare-skirt__length__mini",
"flare-skirt__length__above-knee",
"flare-skirt__length__knee-length",
"flare-skirt__length__midi",
"flare-skirt__length__floor-length",
"flare-skirt__rise__low",
"flare-skirt__rise__mid",
"flare-skirt__rise__high",
"flare-skirt__suns__slight",
"flare-skirt__suns__moderate",
"flare-skirt__suns__significant",
"flare-skirt__skirt-many-panels__n_panels__few",
"flare-skirt__skirt-many-panels__n_panels__medium",
"flare-skirt__skirt-many-panels__n_panels__many",
"flare-skirt__skirt-many-panels__panel_curve__inward",
"flare-skirt__skirt-many-panels__panel_curve__straight",
"flare-skirt__skirt-many-panels__panel_curve__outward",
"flare-skirt__asymm__front_length__highly-asymmetric",
"flare-skirt__asymm__front_length__strongly-asymmetric",
"flare-skirt__asymm__front_length__moderately-asymmetric",
"flare-skirt__asymm__front_length__slightly-asymmetric",
"flare-skirt__asymm__front_length__symmetric",
"flare-skirt__cut__add__True",
"flare-skirt__cut__add__False",
"flare-skirt__cut__depth__shallow",
"flare-skirt__cut__depth__medium",
"flare-skirt__cut__depth__deep",
"flare-skirt__cut__width__narrow",
"flare-skirt__cut__width__medium",
"flare-skirt__cut__width__wide",
"flare-skirt__cut__place__back_left",
"flare-skirt__cut__place__back_center",
"flare-skirt__cut__place__back_right",
"flare-skirt__cut__place__front_left",
"flare-skirt__cut__place__front_center",
"flare-skirt__cut__place__front_right",
"godet-skirt__base__Skirt2",
"godet-skirt__base__PencilSkirt",
"godet-skirt__insert_w__narrow",
"godet-skirt__insert_w__medium",
"godet-skirt__insert_w__wide",
"godet-skirt__insert_depth__shallow",
"godet-skirt__insert_depth__medium",
"godet-skirt__insert_depth__deep",
"godet-skirt__num_inserts__4",
"godet-skirt__num_inserts__6",
"godet-skirt__num_inserts__8",
"godet-skirt__num_inserts__10",
"godet-skirt__num_inserts__12",
"godet-skirt__cuts_distance__close",
"godet-skirt__cuts_distance__medium",
"godet-skirt__cuts_distance__far",
"pencil-skirt__length__micro",
"pencil-skirt__length__mini",
"pencil-skirt__length__above-knee",
"pencil-skirt__length__knee-length",
"pencil-skirt__length__midi",
"pencil-skirt__length__floor-length",
"pencil-skirt__rise__low",
"pencil-skirt__rise__mid",
"pencil-skirt__rise__high",
"pencil-skirt__flare__tight",
"pencil-skirt__flare__straight",
"pencil-skirt__flare__slight-flare",
"pencil-skirt__low_angle__inward",
"pencil-skirt__low_angle__straight",
"pencil-skirt__low_angle__outward",
"pencil-skirt__front_slit__none",
"pencil-skirt__front_slit__shallow",
"pencil-skirt__front_slit__deep",
"pencil-skirt__back_slit__none",
"pencil-skirt__back_slit__shallow",
"pencil-skirt__back_slit__deep",
"pencil-skirt__left_slit__none",
"pencil-skirt__left_slit__shallow",
"pencil-skirt__left_slit__deep",
"pencil-skirt__right_slit__none",
"pencil-skirt__right_slit__shallow",
"pencil-skirt__right_slit__deep",
"pencil-skirt__style_side_cut__Sun",
"pencil-skirt__style_side_cut__SIGGRAPH_logo",
"pencil-skirt__style_side_cut__None",
"levels-skirt__base__Skirt2",
"levels-skirt__base__PencilSkirt",
"levels-skirt__base__SkirtCircle",
"levels-skirt__base__AsymmSkirtCircle",
"levels-skirt__level__Skirt2",
"levels-skirt__level__SkirtCircle",
"levels-skirt__level__AsymmSkirtCircle",
"levels-skirt__num_levels__1",
"levels-skirt__num_levels__2",
"levels-skirt__num_levels__3",
"levels-skirt__num_levels__4",
"levels-skirt__num_levels__5",
"levels-skirt__level_ruffle__none",
"levels-skirt__level_ruffle__moderate",
"levels-skirt__level_ruffle__rich",
"levels-skirt__length__micro",
"levels-skirt__length__mini",
"levels-skirt__length__above-knee",
"levels-skirt__length__knee-length",
"levels-skirt__length__midi",
"levels-skirt__length__floor-length",
"levels-skirt__rise__low",
"levels-skirt__rise__mid",
"levels-skirt__rise__high",
"levels-skirt__base_length_frac__short",
"levels-skirt__base_length_frac__medium",
"levels-skirt__base_length_frac__long",
"pants__length__micro",
"pants__length__short",
"pants__length__knee-length",
"pants__length__capri",
"pants__length__ankle-length",
"pants__length__full-length",
"pants__width__fitted",
"pants__width__normal",
"pants__width__loose",
"pants__flare__tapering",
"pants__flare__straight",
"pants__flare__slight-flare",
"pants__rise__low",
"pants__rise__mid",
"pants__rise__high",
"pants__cuff__type__CuffBand",
"pants__cuff__type__CuffSkirt",
"pants__cuff__type__CuffBandSkirt",
"pants__cuff__type__None",
"pants__cuff__top_ruffle__straight",
"pants__cuff__top_ruffle__tapered",
"pants__cuff__top_ruffle__very_tapered",
"pants__cuff__cuff_len__short",
"pants__cuff__cuff_len__medium",
"pants__cuff__cuff_len__long",
"pants__cuff__skirt_fraction__small",
"pants__cuff__skirt_fraction__medium",
"pants__cuff__skirt_fraction__large",
"pants__cuff__skirt_flare__slight",
"pants__cuff__skirt_flare__moderate",
"pants__cuff__skirt_flare__significant",
"pants__cuff__skirt_ruffle__none",
"pants__cuff__skirt_ruffle__some"
]
fail_text_list = []
def list_in_text( my_list, start_string, connect_tag='__'):
''' Search the list for all items starting with start-string and only the first two items separated by __,
Then use the text to correspond to the startstring. The first two items are used to determine whether they are in the list of predictions
Args:_ALL_TEXT
my_list (list): The list to be searched
start_string(string): A string at the beginning
connect_tag (string): The symbol that Chinese the book is connected in the my_list
'''
global fail_text_list
temp_flag=True
meta_item_list = [
connect_tag.join(item.split(connect_tag)[:start_string.count(connect_tag) + 2]) # Split and keep the next field starting with the start_string
for item in my_list
if item.startswith(start_string) and item.count(connect_tag) >= start_string.count(connect_tag) + 1
]
meta_item_text = [
connect_tag.join(item.split(connect_tag)[:start_string.count(connect_tag) + 2]) # Split and keep the next field starting with the start_string
for item in _ALL_TEXT
if item.startswith(start_string) and item.count(connect_tag) >= start_string.count(connect_tag) + 1
]
meta_item_list_set=set(meta_item_list)
meta_item_text_set=set(meta_item_text)
start_string_fail_list=list(meta_item_text_set-meta_item_list_set)
if len(start_string_fail_list)>0:
fail_text_list.extend(start_string_fail_list)
temp_flag=False
print(f'mylist{start_string}Words that are missing from the opening word{start_string_fail_list}')
return temp_flag
def bool2condition(my_list,connect_tag='__'):
global fail_text_list
fail_text_list = []
bool_flag = True
meta_list=[item.split(connect_tag)[-1] for item in my_list if item.startswith("meta")]
# Determine whether meta_upper wb bottom is generated
if list_in_text(my_list,'meta')==False:
bool_flag = False
left_bool="False"
left_flag = [s for s in my_list if s.startswith("left__enable_asym")]
if meta_list[0]!="None":
if len(left_flag)==0:
print("left__enable_asym")
fail_text_list.append("left__enable_asym")
bool_flag = False
else:
left_bool=left_flag[0].split(connect_tag)[-1]
left_fittedshirt_strapless_bool=True
left_fittedshirt_strapless_flag=[s for s in my_list if s.startswith("left__fitted_shirt__strapless")]
if meta_list[0] =='FittedShirt' and left_bool=='True' :
if len(left_fittedshirt_strapless_flag)==0 :
print("left__fitted_shirt__strapless")
fail_text_list.append("left__fitted_shirt__strapless")
bool_flag = False
return bool_flag, sorted(list(set(fail_text_list)))
if len(left_fittedshirt_strapless_flag) > 0:
if left_fittedshirt_strapless_flag[0].split(connect_tag)[-1] == 'False':
left_fittedshirt_strapless_bool = False
for meta_item in meta_list:
if meta_item =='FittedShirt':
if list_in_text(my_list,'fitted_shirt')==False:
bool_flag = False
if meta_item =='Shirt':
if list_in_text(my_list,'shirt')==False:
bool_flag=False
if left_bool == 'True':
if list_in_text(my_list, 'left__shirt') == False:
bool_flag = False
if meta_item=="StraightWB" or meta_item=="FittedWB":
if list_in_text(my_list,'waistband')==False:
bool_flag=False
if meta_item =="SkirtCircle" or meta_item =="SkirtManyPanels" or meta_item=='AsymmSkirtCircle':
if list_in_text(my_list,"flare-skirt")==False:
bool_flag=False
if meta_item=='SkirtCircle':
if list_in_text(my_list,'flare-skirt__cut')==False:
bool_flag=False
if meta_item=='AsymmSkirtCircle':
if list_in_text(my_list,"flare-skirt__asymm__front_length")==False:
bool_flag=False
if list_in_text(my_list,'flare-skirt__cut')==False:
bool_flag=False
if meta_item=='SkirtManyPanels':
if list_in_text(my_list,"flare-skirt__skirt-many-panels")==False:
bool_flag=False
if meta_item=='GodetSkirt':
if list_in_text(my_list,"godet-skirt")==False:
bool_flag=False
godet_base_item = [item.split(connect_tag)[-1] for item in my_list if item.startswith("godet-skirt__base")]
if len(godet_base_item) == 0:
bool_flag = False
print("godet-skirt__base")
fail_text_list.append("godet-skirt__base")
return bool_flag, sorted(list(set(fail_text_list)))
else:
godet_base_item = godet_base_item[0]
if godet_base_item == "Skirt2":
if list_in_text(my_list, "skirt") == False:
bool_flag = False
if godet_base_item == "PencilSkirt":
if list_in_text(my_list, "pencil-skirt") == False:
bool_flag = False
if meta_item=='SkirtLevels':
if list_in_text(my_list,"levels-skirt")==False:
bool_flag=False
base_item = [item.split(connect_tag)[-1] for item in my_list if item.startswith("levels-skirt__base")]
if len(base_item) == 0:
bool_flag = False
print("levels-skirt__base")
fail_text_list.append("levels-skirt__base")
return bool_flag, sorted(list(set(fail_text_list)))
else:
base_item = base_item[0]
if base_item == "Skirt2":
if list_in_text(my_list, "skirt") == False:
bool_flag = False
if base_item == "PencilSkirt":
if list_in_text(my_list, "pencil-skirt") == False:
bool_flag = False
if base_item=="SkirtCircle" or base_item=="AsymmSkirtCircle":
if list_in_text(my_list, "flare-skirt") == False:
bool_flag = False
level_item = [item.split(connect_tag)[-1] for item in my_list if item.startswith("levels-skirt__level")]
if len(level_item) == 0:
bool_flag = False
print("levels-skirt__level")
fail_text_list.append("levels-skirt__level")
return bool_flag, sorted(list(set(fail_text_list)))
else:
level_item = level_item[0]
if level_item == "Skirt2":
if list_in_text(my_list, "skirt") == False:
bool_flag = False
if level_item == "SkirtCircle" or level_item == "AsymmSkirtCircle":
if list_in_text(my_list, "flare-skirt") == False:
bool_flag = False
if meta_item=='Pants':
if list_in_text(my_list,"pants")==False:
bool_flag=False
cuff_type = [item.split(connect_tag)[-1] for item in my_list if item.startswith("pants__cuff__type")]
if len(cuff_type) == 0:
bool_flag = False
print("pants__cuff__type")
fail_text_list.append("pants__cuff__type")
return bool_flag, sorted(list(set(fail_text_list)))
else:
if cuff_type[0] != "None":
if list_in_text(my_list, "pants__cuff") == False:
bool_flag = False
if meta_item=='Skirt2':
if list_in_text(my_list,"skirt")==False:
bool_flag=False
if meta_item=='PencilSkirt':
if list_in_text(my_list,"pencil-skirt")==False:
bool_flag=False
meta_upper = [item.split(connect_tag)[-1] for item in my_list if item.startswith("meta__upper")]
if len(meta_upper)==0:
print("meta__upper")
fail_text_list.append("meta__upper")
bool_flag = False
return bool_flag,list(set(fail_text_list))
meta_upper=meta_upper[0]
strapless=None
if meta_upper =='FittedShirt':
strapless=[item.split(connect_tag)[-1] for item in my_list if item.startswith("fitted_shirt__strapless")]
if len(strapless)==0:
bool_flag=False
print("fitted_shirt__strapless")
fail_text_list.append("fitted_shirt__strapless")
return bool_flag,list(set(fail_text_list))
strapless=strapless[0]
if (strapless==str(False) and meta_upper =='FittedShirt')or meta_upper=="Shirt":
if list_in_text(my_list,"collar")==False:
bool_flag=False
if left_bool == 'True' and (left_fittedshirt_strapless_bool ==False or meta_upper=="Shirt") :
if list_in_text(my_list, "left__collar") == False:
bool_flag = False
sleeveless=[item.split(connect_tag)[-1] for item in my_list if item.startswith("sleeve__sleeveless")]
if len(sleeveless)==0:
bool_flag=False
print("sleeve__sleeveless")
fail_text_list.append("sleeve__sleeveless")
return bool_flag,list(set(fail_text_list))
sleeveless=sleeveless[0]
if sleeveless==str(False):
if list_in_text(my_list,"sleeve")==False :
bool_flag=False
cuff_type = [item.split(connect_tag)[-1] for item in my_list if item.startswith("sleeve__cuff__type")]
if len(cuff_type)==0:
bool_flag = False
print("sleeve__cuff__type")
fail_text_list.append("sleeve__cuff__type")
return bool_flag, sorted(list(set(fail_text_list)))
else:
if cuff_type[0]!="None":
if list_in_text(my_list,"sleeve__cuff")==False:
bool_flag = False
if left_bool == 'True' and (left_fittedshirt_strapless_bool == False or meta_upper == "Shirt"):
if list_in_text(my_list,"left__sleeve")==False:
bool_flag=False
#For the processing of the neckline, find the type of neckline and see if it is empty
cuff_type = [item.split(connect_tag)[-1] for item in my_list if item.startswith("left__sleeve__cuff__type")]
if len(cuff_type) == 0:
bool_flag = False
print("left__sleeve__cuff__type")
fail_text_list.append("left__sleeve__cuff__type")
return bool_flag, sorted(list(set(fail_text_list)))
else:
if cuff_type[0] != "None":
if list_in_text(my_list, "left__sleeve__cuff") == False:
bool_flag = False
return bool_flag,sorted(list(set(fail_text_list)))
def bool2text_alltext(text_list):
flag = True
no_in_textspace = list(set(text_list)-set(_ALL_TEXT))
if len(no_in_textspace) > 0:
flag = False
return flag,no_in_textspace

26
pygarment/__init__.py Normal file
View File

@@ -0,0 +1,26 @@
"""
A Python library for building parametric sewing pattern programs
"""
# Building blocks
from pygarment.garmentcode.component import Component
from pygarment.garmentcode.panel import Panel
from pygarment.garmentcode.edge import Edge, CircleEdge, CurveEdge, EdgeSequence
from pygarment.garmentcode.connector import Stitches
from pygarment.garmentcode.interface import Interface
from pygarment.garmentcode.edge_factory import EdgeSeqFactory
from pygarment.garmentcode.edge_factory import CircleEdgeFactory
from pygarment.garmentcode.edge_factory import EdgeFactory
from pygarment.garmentcode.edge_factory import CurveEdgeFactory
# Operations
import pygarment.garmentcode.operators as ops
import pygarment.garmentcode.utils as utils
# Parameter support
from pygarment.garmentcode.params import BodyParametrizationBase, DesignSampler
# Errors
from pygarment.pattern.core import EmptyPatternError

400
pygarment/data_config.py Normal file
View File

@@ -0,0 +1,400 @@
"""
The module contain Porperties class to manage paramters & stats in various parts of the system
"""
from datetime import timedelta
import json
import yaml
from numbers import Number
import traceback
import sys
from pathlib import Path
import numpy as np
# for system info
import platform
import psutil
# --- Nice dumping of floats ---
def float_representer(dumper, data):
if data != data or (data == 0.0 and data == 1.0):
value = '.nan'
elif data == dumper.inf_value:
value = '.inf'
elif data == -dumper.inf_value:
value = '-.inf'
else:
# Custom representation:
# https://stackoverflow.com/a/33944926
value = f'{data:.3g}'
if '.' not in value or 'e' in value: # e only appears for large int numbers with this precision
# An integer hidden as a float
value = f'{int(data):d}.0'
return dumper.represent_scalar('tag:yaml.org,2002:float', value)
yaml.add_representer(float, float_representer)
# --- Main class ----
class Properties():
"""Keeps, loads, and saves cofiguration & statistic information
Supports gets&sets as a dictionary
Provides shortcuts for batch-init configurations
One of the usages -- store system-dependent basic cofiguration
"""
def __init__(self, filename="", clean_stats=False):
self.properties = {}
self.properties_on_load = {}
if filename:
self.properties = self._from_file(filename)
self.properties_on_load = self._from_file(filename)
if clean_stats: # only makes sense when initialized from file =)
self.clean_stats(self.properties)
# ---- Base utils ----
def has(self, key):
"""Used to query if a top-level property/section is already defined"""
return key in self.properties
def serialize(self, filename, backup=None):
"""Log current props to file. If logging failed, at least restore
provided backup or originally loaded props
* backup is expected to be a Properties object
"""
try:
extention = Path(filename).suffix.lower()
if extention == '.json':
with open(filename, 'w') as f_json:
json.dump(self.properties, f_json, indent=2, sort_keys=True)
elif extention == '.yaml':
with open(filename, 'w') as f:
yaml.dump(
self.properties,
f,
default_flow_style=False,
sort_keys=False
)
else:
raise ValueError(f'{self.__class__.__name__}::ERROR::Unsupported file type on serialization: {extention}')
except Exception as e:
print('Exception occured while saving properties:')
traceback.print_exception(*sys.exc_info())
# save backup, s.t. the data is not lost due to interruption of
# the file override
if backup is not None:
backup.serialize(filename)
else:
with open(filename, 'w') as f_json:
json.dump(self.properties_on_load, f_json,
indent=2, sort_keys=True)
raise RuntimeError('Error occured while saving properties. Backup version is saved instead')
def merge(self, filename="", clean_stats=False, re_write=True,
adding_tag='added'):
"""Merge current set of properties with the one from file
* re_write=True sets the default merging of Python dicts, values
from new props overrite
the one from old one if keys are the same
* re_write=False will keep both properties if their values are
different (imported one marked with adding_tag)
"""
new_props = self._from_file(filename)
if clean_stats:
self.clean_stats(new_props)
# merge
self._recursive_dict_update(self.properties, new_props, re_write, adding_tag)
# --- Specialised utils (require domain knowledge) --
def is_fail(self, dataname):
"""
Check if a particular object is listed as fail in any of the sections
Fails may be listed in the stats subsection of any of the section
"""
_, fails_list = self.count_fails()
return dataname in fails_list
def is_fail_section(self, dataname):
"""
Check if a particular object is listed as fail in any of the sections
Fails may be listed in the stats subsection of any of the section
return the section name
"""
for section_key in self.properties:
section = self.properties[section_key]
if isinstance(section, dict) and 'stats' in section and ('fails' in section['stats']):
if isinstance(section['stats']['fails'], dict):
for key in section['stats']['fails']:
if not isinstance(section['stats']['fails'][key], list):
raise NotImplementedError(
'Properties::ERROR:: Fails subsections of the type {} is not supported'.format(
type(section['stats']['fails'][key])))
if dataname in section['stats']['fails'][key]: # expects a list as value
return True, key
elif isinstance(section['stats']['fails'], list):
if dataname in section['stats']['fails'][key]: # expects a list as value
return True, 'fails'
else:
raise NotImplementedError('Properties::ERROR:: Fails subsections of the type {} is not supported'.format(type(section['stats']['fails'])))
return False, None
def count_fails(self, log=False):
"""
Number of (unique) datapoints marked as fail
"""
fails = []
for section_key in self.properties:
section = self.properties[section_key]
section_fails = []
if isinstance(section, dict) and 'stats' in section and ('fails' in section['stats']):
if isinstance(section['stats']['fails'], dict):
for key in section['stats']['fails']:
if not isinstance(section['stats']['fails'][key], list):
raise NotImplementedError(
'Properties::ERROR:: Fails subsections of the type {} is not supported'.format(
type(section['stats']['fails'][key])))
section_fails += section['stats']['fails'][key] # expects a list as value
elif isinstance(section['stats']['fails'], list):
section_fails += section['stats']['fails']
else:
raise NotImplementedError('Properties::Error:: Fails subsections of the type {} is not supported'.format(type(section['stats']['fails'])))
if log:
section['stats']['fails_count'] = len(list(set(section_fails)))
fails += section_fails
fails = list(set(fails))
return len(fails), fails
def add_fail(self, section_name, fail_type, info):
"""Write a failure case to a requested section's stats"""
section = self.properties[section_name]
if 'fails' not in section['stats']:
section['stats']['fails'] = {}
try:
section['stats']['fails'][fail_type].append(info)
except KeyError:
section['stats']['fails'][fail_type] = [info]
# ---------- Properties updates ---------------
def set_basic(self, **kwconfig):
"""Adds/updates info on the top level of properties
Only to be used for basic information!
"""
# section exists
for key, value in kwconfig.items():
self.properties[key] = value
def set_section_config(self, section, **kwconfig):
"""adds or modifies a (top level) section and updates its configuration info
"""
# create new section
if section not in self.properties:
self.properties[section] = {
'config': kwconfig,
'stats': {}
}
return
# section exists
for key, value in kwconfig.items():
self.properties[section]['config'][key] = value
def set_section_stats(self, section, **kwstats):
"""adds or modifies a (top level) section and updates its statistical info
"""
# create new section
if section not in self.properties:
self.properties[section] = {
'config': {},
'stats': kwstats
}
return
# section exists
for key, value in kwstats.items():
self.properties[section]['stats'][key] = value
def clean_stats(self, properties):
""" Remove info from all Stats sub sections """
for _, value in properties.items():
# detect section
if isinstance(value, dict) and 'stats' in value:
value['stats'] = {}
def summarize_stats(self,
key,
log_sum=False, log_avg=False,
log_median=False, log_80=False, log_95=False,
log_min=False, log_max=False,
as_time=False):
"""Make a summary of requested key with requested statistics in current props"""
updated = False
for section in self.properties.values():
# check all stats sections
if isinstance(section, dict) and 'stats' in section:
if key in section['stats']:
stats_values = section['stats'][key]
if isinstance(stats_values, dict):
stats_values = list(stats_values.values())
# summarize all foundable statistics
if isinstance(stats_values, list) and len(stats_values) > 0 and isinstance(stats_values[0], Number):
if log_sum:
section['stats'][key + "_sum"] = str(timedelta(seconds=sum(stats_values))) if as_time else sum(stats_values)
updated = True
if log_avg:
section['stats'][key + "_avg"] = sum(stats_values) / len(stats_values)
if as_time:
section['stats'][key + "_avg"] = str(timedelta(seconds=section['stats'][key + "_avg"]))
updated = True
if log_median:
section['stats'][key + "_med"] = str(timedelta(seconds=np.percentile(stats_values, 50))) if as_time else float(np.percentile(stats_values, 50))
updated = True
if log_80:
section['stats'][key + "_p80"] = str(timedelta(seconds=np.percentile(stats_values, 80))) if as_time else float(np.percentile(stats_values, 80))
updated = True
if log_95:
section['stats'][key + "_p95"] = str(timedelta(seconds=np.percentile(stats_values, 95))) if as_time else float(np.percentile(stats_values, 95))
updated = True
if log_min:
section['stats'][key + "_min"] = str(timedelta(seconds=min(stats_values))) if as_time else min(stats_values)
updated = True
if log_max:
section['stats'][key + "_max"] = str(timedelta(seconds=max(stats_values))) if as_time else max(stats_values)
updated = True
return updated
# -- Specialised updates (require domain knowledge) --
def add_sys_info(self):
"""Add or update system information on the top level of config"""
if sys.version_info.major < 3:
raise NotImplementedError('{}::Requesting system info is not supported for Python 2'.format(self.__class__.__name__))
# https://stackoverflow.com/questions/3103178/how-to-get-the-system-info-with-python
self.properties['system_info'] = {}
self.properties['system_info']['platform'] = platform.system()
self.properties['system_info']['platform-release'] = platform.release()
self.properties['system_info']['platform-version'] = platform.version()
self.properties['system_info']['architecture'] = platform.machine()
self.properties['system_info']['processor'] = platform.processor()
self.properties['system_info']['ram'] = str(round(psutil.virtual_memory().total / (1024.0 ** 3))) + " GB"
try:
import warp # Optional section
if warp.context.runtime is None:
# runtime = warp.context.Runtime()
warp.init()
else:
print(f'{self.__class__.__name__}::INFO::Saving GPU info -- warp already initialized')
curr_device = warp.get_device()
self.properties['system_info']['GPU'] = curr_device.name if curr_device.is_cuda else 'Not used'
except ImportError:
pass # Don't do anything if warp not available
def stats_summary(self):
"""
Compute data simulation processing statistics
"""
updated_render = self.summarize_stats('render_time', log_sum=True, log_avg=True, as_time=True)
updated_frames = self.summarize_stats('fin_frame', log_avg=True)
updated_sim_time = self.summarize_stats('sim_time', log_sum=True, log_avg=True, as_time=True)
updated_spf = self.summarize_stats('spf', log_avg=True, as_time=True)
updated_scan = self.summarize_stats('processing_time', log_sum=True, log_avg=True, as_time=True)
updated_scan_faces = self.summarize_stats('faces_removed', log_avg=True)
updated_self_collisions = self.summarize_stats(
'self_collisions', log_avg=True, log_median=True, log_80=True, log_95=True)
updated_body_collisions = self.summarize_stats(
'body_collisions', log_avg=True, log_median=True, log_80=True, log_95=True)
updated_face_count = self.summarize_stats(
'face_count', log_avg=True, log_median=True, log_min=True, log_max=True)
updated_panel_count = self.summarize_stats(
'panel_count', log_avg=True, log_median=True, log_min=True, log_max=True)
# fails
self.count_fails(log=True)
if not (updated_frames and updated_render and updated_sim_time and updated_spf and updated_self_collisions and updated_body_collisions):
print(f'{self.__class__.__name__}::WARNING::Sim stats summary '
'requested, but not all sections were updated')
# ---- Private utils ----
def _from_file(self, filename):
""" Load properties from previously created file """
extention = Path(filename).suffix.lower()
if extention == '.json':
with open(filename, 'r') as f_json:
return json.load(f_json)
elif extention == '.yaml':
with open(filename, 'r') as f:
return yaml.safe_load(f)
else:
raise ValueError(f'{self.__class__.__name__}::ERROR::Unsupported file type on load: {extention}')
def _recursive_dict_update(self, in_dict, new_dict, re_write=True, adding_tag='added', in_stats=False):
"""
updates input dictionary with the update_dict properly updating all the inner dictionaries
re_write = True replaces the values with the ones from new dictionary if they happen to be different,
re_write = False extends dictionary to include both values if different
"in_stats" shows if we are currently in any of the stats subsections.
In this case, lists are merged instead of being re-written
"""
if not isinstance(new_dict, dict):
in_dict = new_dict # just update with all values
return
for new_key in new_dict:
if new_key in in_dict and isinstance(in_dict[new_key], dict):
# update inner dict properly
self._recursive_dict_update(
in_dict[new_key], new_dict[new_key],
re_write, adding_tag,
(in_stats or new_key == 'stats'))
elif not re_write and new_key in in_dict and in_dict[new_key] != new_dict[new_key]:
if in_stats and isinstance(in_dict[new_key], list):
# merge lists inside stats sections
in_dict[new_key] = in_dict[new_key] + new_dict[new_key]
else:
# Keep both versions (e.g. in configs)
adding_name = new_key + '_' + adding_tag
while adding_name in in_dict: # in case even the added version is already there
adding_name = adding_name + '_added'
in_dict[adding_name] = new_dict[new_key]
in_dict[new_key + '_' + self['name']] = in_dict[new_key]
else: # at sertain depth there will be no more dicts -- recusrion stops
in_dict[new_key] = new_dict[new_key]
# if new_dict is empty -- no update happens
def __getitem__(self, key):
return self.properties[key]
def __setitem__(self, key, value):
self.properties[key] = value
def __contains__(self, key):
return key in self.properties
def __str__(self):
return str(self.properties)

View File

Some files were not shown because too many files have changed in this diff Show More