GarmentCode icon indicating copy to clipboard operation
GarmentCode copied to clipboard

Feature/factory refacto

Open quoccuongLE opened this issue 10 months ago • 3 comments

Hello GarmentCode team, @maria-korosteleva

I discovered your awesome project, I tested it for my personal project and experienced some inconvenience about adding new types of clothes, which are mainly implemented in àssets.garment_programs. In fact, adding a new cloth obliges me to directly modify the code source of the GarmentCode project. Then I would like to contribute a little to your project GarmentCode, to make it modular and easily extensible as a python library via factory creational pattern.

Introduction of PR

This Pull Request aims to refactorize the module assets.garment_programs in order to make it extensible and modular. I suggest using a factory creational pattern to create garment modules. The files in assets.garment_programs are split into 5 main modules:

  • bands
  • bodices
  • collars
  • sleeves
  • bottoms

Details

Here is the code structure after modification.

assets/garment_programs/
├── bands  # Module `bands`
│   ├── base.py
│   ├── factory.py
│   └── __init__.py
├── bodice  # Module `bodice`
│   ├── bodice_halves  # Sub-module `bodice_halves`
│   │   ├── bodice_halves.py
│   │   ├── bodice_panels.py
│   │   ├── __init__.py
│   │   └── tee.py
│   ├── factory.py
│   ├── __init__.py
│   └── shirts.py
├── bottoms  # Module `bottoms`
│   ├── circle_skirt.py
│   ├── factory.py
│   ├── godet.py
│   ├── __init__.py
│   ├── pants.py
│   ├── skirt_levels.py
│   └── skirt_paneled.py
├── collars  # Module `collars`
│   ├── collar_halves
│   │   ├── base.py
│   │   ├── factory.py
│   │   └── __init__.py
│   ├── factory.py
│   ├── __init__.py
│   ├── no_panels_collar.py
│   └── panel_collars.py
├── sleeves  # Module `sleeves`
│   ├── armhole_shapes  # Sub-module `armhole_shapes`
│   │   ├── base.py
│   │   ├── factory.py
│   │   └── __init__.py
│   ├── factory.py
│   ├── __init__.py
│   └── sleeves.py
├── meta_garment.py
├── shapes.py
├── base_classes.py
└── stats_utils.py

To make use of the factory creational pattern, I implement 3 main points:

  • A Registry utility in the pygarment/registry.py (a utility registry in a Tensorflow Google project), this file has 2 important functions:

    • register(): Register decorated function or class into registered_collection, in a hierarchical order
    • lookup(): Lookup decorated function or class in registered_collection, in a hierarchical order
  • Restructure the module assets.garment_programs into modules and sub-modules. For example, in the sleeve module, there is an armhole shape as its submodule. For more details, please consult the above folder structure.

  • In each module and submodule, implement factory.py with 2 global variables and 4 utility functions:

    • 2 global variables:
      • _REGISTERED_<CMP>_CLS = {}: Registry (e.g. Python dictionary) to register component classes
      • _REGISTERED_<CMP>_CFG = {} (placeholder for later update on configuration): Registry (e.g. Python dictionary) to register config for component classes
    • 4 utility functions:
      • register_builder() to register classes/ build functions
      • build() to lookup into registry by name and pass input arguments
      • register_config() to register config for classes/ build functions (placeholder only)
      • get_config() to get default config from config registry

In this PR, I also make a change of importing modules in meta_garment.py according to the new module structures, and the approach of calling class from global()["<class_name>"](**args) to factory.build(name=<class_name>, **args).

How to add new classes

It's very simple! Just place @factory.register_builder("<name_of_your_new_class>") ahead a new class. For instance, I just need to add a decorator function register_builder ahead of class ``Sleeve`. Remember for the shake of simplicity (and as the intended class calling way in GarmentCode), I use the identical string name as class.

For instance, in my project, I've just created a new custom class, called MyNewSleeve, put it in assets/garment_programs/sleeves/my_sleeves.py.

import pygarment as pyg
from assets.garment_programs.sleeves import factory

...

@factory.register_builder("MyNewSleeve")
class MyNewSleeve(pyg.Component):
    ...

This will add into _REGISTERED_SLEEVES_CLS an element with "MyNewSleeve" as key and class MyNewSleeve as value, while importing the module sleeves

Registers will be updated via the use of decorator @factory.register_builder(), whenever we import modules.

Explanation of importing python module mechanism: As we import a module (via import details can be declared in __init__.py in the folder of module), python will import also its submodules and sub-submodule recursively. Therefore, within this module, every class headed with @factory.register_builder() will be registered/stored in registries _REGISTERED_<CMP>_CLS as global variables.

After importing modules we need, these registries are accessible indirectly, by convention, through a factory (factory.py) in their corresponding module.

A good practice to import and build class is for example,

from assets.garment_programs.sleeves import *  # registry for sleeves classes will be created/updated

# Now every `sleeves` classes including original and my newly added ones are contained in _REGISTERED_SLEEVES_CLS

# In order to build a class via its name, we need to import factory of sleeves, as follow
from assets.garment_programs.sleeves import factory as sleeve_factory

# We create our MyNewSleeve instance via factory
my_new_sleeve = sleeve_factory.build(name="MyNewSleeve", **configs)

For more examples, see the change in importing in assets/garment_programs/meta_garment.py

from assets.garment_programs.bands import *  # registry for bands classes will be created/updated
from assets.garment_programs.bodice import *  # registry for bodice classes will be created/updated
from assets.garment_programs.bottoms import *  # registry for bottoms classes will be created/updated
from assets.garment_programs.collars import *  # registry for collars classes will be created/updated
from assets.garment_programs.sleeves import *  # registry for sleeves classes will be created/updated

I welcome any suggestion to make it more user-friendly, please directly reply in the comment section. If you would like me to change the PR, please let me know. I would love to contribute to your project.

quoccuongLE avatar Feb 16 '25 15:02 quoccuongLE

Dear @quoccuongLE Thank you so much for your contribution! I did not miss your PR, but I'm currently out of capacity to take a closer look at it. I will try to get it ASAP, but it may take a few weeks

maria-korosteleva avatar Mar 09 '25 20:03 maria-korosteleva

Thank @maria-korosteleva I've just added a bit more explanation about python importing module mechanism with an example to make it easier to understand. Please let me know if you would like to make any change in this PR. Thanks again :)

quoccuongLE avatar Mar 10 '25 10:03 quoccuongLE

@quoccuongLE a few weeks turned into a few months, but I remember about your PR and still plan to take a look. My life is just very short on free time xD

maria-korosteleva avatar Jun 19 '25 21:06 maria-korosteleva