Source code for PyR3.factory.MeshFactory

# -*- coding: utf-8 -*-
from __future__ import annotations

from abc import ABCMeta, abstractmethod
from inspect import isclass
from typing import Tuple, Type

from PyR3.factory.fields.Field import Field
from PyR3.shortcut.context import temporary_scene


class _MeshFactoryMeta(ABCMeta):

    required_members = [
        ["__doc__", lambda v: isinstance(v, str)],
        ["__author__", lambda v: isinstance(v, str)],
        ["__version__", lambda v: isinstance(v, str)],
    ]

    def __new__(cls, name, bases, attributes) -> None:
        cls.check_has_members(name, attributes)
        attributes["__factory_fields__"] = cls.get_field_names(
            cls, name, attributes
        )
        attributes["__factory_fields__"] |= cls.get_inherited_fields(bases)
        instance = ABCMeta.__new__(cls, name, bases, attributes)
        cls.wrap_render(instance)
        return instance

    def get_field_names(cls, classname, attributes: dict) -> dict:
        fields = {}
        for name, field in attributes.items():
            if isinstance(field, Field):
                field._set_trace_info(name, classname)
                fields[name] = field
            elif isclass(field) and issubclass(field, Field):
                field = field()
                field._set_trace_info(name, classname)
                fields[name] = field
        return fields

    def get_inherited_fields(bases: Tuple[Type[MeshFactory]]) -> dict:
        inherited_fields = {}
        for base in bases:
            if issubclass(base, MeshFactory):
                inherited_fields.update(getfields(base))
        return inherited_fields

    @classmethod
    def check_has_members(cls, classname, attributes):
        for name, validator in cls.required_members:
            if (value := attributes.get(name, None)) is None:
                raise TypeError(
                    f"Trying to create class {classname} without required member {name}."
                )
            if not validator(value):
                raise TypeError(
                    f"Trying to create class {classname} with {name} of invalid type."
                )

    @classmethod
    def wrap_render(cls, instance: MeshFactory):
        old_render = instance.render

        def render(*args, **kwargs):
            with temporary_scene():
                old_render(*args, **kwargs)

        instance.render = render


[docs]class MeshFactory(metaclass=_MeshFactoryMeta): """Base class for a mesh factory object. Mesh factory requires __doc__, __author__ and __version__ to be defined in mesh factory subclass, otherwise class instantiation will fail. Mesh factory can (and should) make use of Fields (subclasses of Field class) to specify mesh factory customization params. See PyR3.factory.fields modules for first-party fields. To specify field just set class attribute to instance of Field subclass. See :doc:`MeshFactory usage <../usage/factory>`. """ __author__ = "Krzysztof Wiśniewski" __version__ = "2.0.0" def __init__(self, params: dict) -> None: for name, field in getfields(self).items(): param_value = params.get(name, None) cleaned_value = field.digest(param_value) setattr(self, name, cleaned_value)
[docs] @abstractmethod def render(self): """Implements rendering process. You can access cleaned contents of fields via self.field_name. Rendering process shouldn't alter scenes, as it's always happening in isolated scene. Only things that are selected when this function returns will be copied into callers scene. """
[docs]def getfields(mesh_factory: MeshFactory) -> dict: """Returns fields specified for given MeshFactory. :param mesh_factory: Object to fetch fields from. :type mesh_factory: MeshFactory :return: dictionary of factory fields. :rtype: dict """ if isclass(mesh_factory): return mesh_factory.__dict__["__factory_fields__"] else: return mesh_factory.__class__.__dict__["__factory_fields__"]