[PYTHON] Try using design patterns (exporter edition)

Overview

This is for beginners who want to try using design patterns in Maya Python. I will leave the detailed explanation about the design pattern to other sites, How does the code improve when you actually use it? Focus on. As an application example, let's take something like a data exporter as a sample.

motivation

I want to improve the design power and code quality by actually using the design pattern. I want to find a pattern that can be used especially in tool implementation.

Design pattern

The following pattern is used this time. For details of the pattern, please check the reference site at the bottom of the page.

pattern Overview (my perception) merit
TemplateMethod Abstract the method called in the parent class and leave the implementation inside the method to the subclass. Process flowOf each process while bindingBehaviorIs left to the subclass.
FactoryMethod If you want to use another object in the class, abstract the generation process and use the (abstracted) generation object. The generation process and method implementation are left to each subclass. It inherits the goodness of Template Method. When creating another object and calling a methodProcess flowGenerateBehaviorCan be separated.
Factory Prepare and use a class that only creates objects according to the specified conditions. From the side that uses the object, the object according to the conditionsGenerateCan be separated.

** Try using it for exporter scripts **

I thought that exporting Maya scene information was a relatively large process, so I chose it. The image is like outputting appropriate data using FBX export. There is a recognition that the specifications are likely to be changed later, such as selecting output data or performing special processing depending on the conditions. It describes what the code will look like before and after applying the design pattern.

Base code

If you arrange the processes without thinking about anything in particular, it will look like the following.

scene_exporter.py


def scene_export(option):
    """Output models and animations in the scene"""
    #Pre-processing

    if option.mode == ExportMode.head:
        #Find Head mesh data in Hierarchy
        #Do something
        #Export
    if option.mode == ExportMode.body:
        #Find Body mesh data in Hierarchy
        #Do something
        #Export
    if option.mode == ExportMode.animation:
        #Find Skeleton data in the Hierarchy
        #Do something
        #Export

    #Post-processing

Template Method adaptation

For the time being, apply the Template Method pattern to standardize the export process.

exporter.py


from abc import ABCMeta, abstractmethod


class BaseExporter(object):
    """Exporter basic class"""
    __metaclass__ = ABCMeta

    def export(self):
        select_export_targets()
        export_selection()

    @abstractmethod
    def select_export_targets(self):
        """Select the export target"""
        pass

    @abstractmethod
    def export_selection(self):
        """Export selected objects"""
        pass


class HeadExporter(BaseExporter):
    """Exporter class for HEAD"""
    def select_export_targets(self):
        cmds.select("|char|mesh|head")

    def export_selection(self):
        #Implemented output processing for HEAD

class BodyExporter(BaseExporter):
    """Exporter class for BODY"""
    def select_export_targets(self):
        cmds.select("|char|mesh|body")

    def export_selection(self):
        #Implemented output processing for BODY

class AnimExporter(BaseExporter):
    """Exporter class for ANIM"""
    def select_export_targets(self):
        cmds.select("|char|skel|root")

    def export_selection(self):
        #Implemented output processing for ANIM

scene_exporter_v2.py


def scene_export(option):
    """Output models and animations in the scene"""
    #Pre-processing

    if option.mode == ExportMode.head:
        HeadExporter().export()
    if option.mode == ExportMode.body:
        BodyExporter().export()
    if option.mode == ExportMode.animation:
        AnimExporter().export()

    #Post-processing

Further apply the Factory pattern

Even if TemplateMethod is applied, the maintainability when adding a mode has not been improved yet. Create a new Factory class and let it branch and generate. The main process just uses the BaseExporter subclass where export () exists.

exporter_factory.py


class BaseExporterFactory(object):
    """Factory class of BaseExporter class"""
    def create(option):
        if option.mode == ExportMode.head:
            return HeadExporter()
        if option.mode == ExportMode.body:
            return BodyExporter()
        if option.mode == ExportMode.animation:
            return AnimExporter()

scene_exporter_v3.py


def scene_export(option):
    """Output models and animations in the scene"""
    #Pre-processing

    BaseExporterFactory().create(option).export()

    #Post-processing

Further apply the Factory Method pattern

When the specification is added, we will "create and use a log object inside the BaseExporter class". Apply the FactoryMethod because you will be creating and using objects in the BaseExporter class.

Implement the parent class and subclass of ExporterLog, which is the Product of FactoryMethod.

exporter_log.py


from abc import ABCMeta, abstractmethod


class BaseExporterLog(object):
    """Exporter log base class"""
    __metaclass__ = ABCMeta

    @abstractmethod
    def open(self):
        """Start logging"""
        pass

    @abstractmethod
    def close(self):
        """End logging"""
        pass


class MyExporterLog(BaseExporterLog):
    """Exporter log subclass"""
    def open(self):
        print "export logging start"

    def close(self):
        print "export logging close"

Define the generation and use of logs in BaseExporter, which is the Creator of FactoryMethod, and leave the implementation to the subclass.

exporter.py


from abc import ABCMeta, abstractmethod


class BaseExporter(object):
    """Exporter basic class"""
    __metaclass__ = ABCMeta

    def export(self):
        self.log = create_exporter_log()
        self.log.open()

        select_export_targets()
        export_selection()

        self.log.close()

    @abstractmethod
    def create_exporter_log(self):
        """Generate an export log"""
        pass

    @abstractmethod
    def select_export_targets(self):
        """Select the export target"""
        pass

    @abstractmethod
    def export_selection(self):
        """Export selected objects"""
        pass


class HeadExporter(BaseExporter):
    """Exporter class for HEAD"""
    def create_exporter_log(self):
        return MyExporterLog()

    def select_export_targets(self):
        cmds.select("|char|mesh|head")

    def export_selection(self):
        #Implemented output processing for HEAD

After that, I think that it may or may not be like using logs on the main processing side.

scene_exporter_v4.py


def scene_export(option):
    """Output models and animations in the scene"""
    #Pre-processing

    exporter = BaseExporterFactory().create(option)
    exporter.export()

    #Whether or not to process the log

    #Post-processing

** Impression **

Regardless of whether the recognition and examples are appropriate, looking at the code after pattern adaptation is somewhat convincing. If it's just the amount of code, the base code is the smallest, but considering that it will be changed later, I used a pattern It's useful to spend some time refactoring.

With the pattern used this time, I find it difficult to understand the Factory Method pattern. It feels like a Derivation of the Factory pattern from the name, but I recognized that it is a derivation of the Template Method. Because I thought so, the flow of pattern adaptation also brought the Template Method first.

I would like to continue studying while waiting for advice and advice from various fields.

** Reference site **

-1. Ruby Design Pattern [Template Method] --Shred IT !!!! -4. Ruby Design Pattern [Factory Method] --Shred IT !!!! -3. TemplateMethod pattern | TECHSCORE -4. FactoryMethod pattern | TECHSCORE

Recommended Posts

Try using design patterns (exporter edition)
Try using Tkinter
Try using docker-py
Try using cookiecutter
Try using PDFMiner
Try using geopandas
Try using Selenium
Try using scipy
Try using pandas.DataFrame
Try using django-swiftbrowser
Try using matplotlib
Try using tf.metrics
Try using PyODE
Try using virtualenv (virtualenvwrapper)
[Azure] Try using Azure Functions
Try using virtualenv now
Try using W & B
Try using Django templates.html
[Kaggle] Try using LGBM
Try using Python's feedparser.
Try using Python's Tkinter
Try using Tweepy [Python2.7]
Try using Pytorch's collate_fn
Dezapata_0 Why learn design patterns?
Try using PythonTex with Texpad.
[Python] Try using Tkinter's canvas
Try using Jupyter's Docker image
Try using scikit-learn (1) --K-means clustering
Try function optimization using Hyperopt
Try using matplotlib with PyCharm
Try using Azure Logic Apps
Try using Kubernetes Client -Python-
[Kaggle] Try using xg boost
Try using the Twitter API
Try using OpenCV on Windows
Try using Jupyter Notebook dynamically
Try using AWS SageMaker Studio
Try tweeting automatically using Selenium.
Try using SQLAlchemy + MySQL (Part 1)
Try using the Twitter API
Design Patterns in Python: Introduction
Try using SQLAlchemy + MySQL (Part 2)
Try using Django's template feature
Try using the PeeringDB 2.0 API
Try using Pelican's draft feature
Try using pytest-Overview and Samples-
Try using folium with anaconda