Source code for ddg.conversion.blender.core

import bmesh
import bpy
import numpy as np

import ddg
from ddg.datastructures.halfedge.surface import Surface
from ddg.datastructures.nets import utils as nutils
from ddg.datastructures.nets.net import (
    DiscreteCurve,
    DiscreteNet,
    EmptyNet,
    Net,
    NetCollection,
    PointNet,
    SmoothNet,
)
from ddg.geometry.quadrics import Quadric
from ddg.geometry.subspaces import Subspace
from ddg.visualization.blender.bmesh import join
from ddg.visualization.blender.curve import create_curve, set_curve_properties
from ddg.visualization.blender.material import set_material

from . import halfedge, net

__all__ = ["to_blender_object_helper", "to_blender_object"]


# -------------------------------------------------------------------------------

# -------------------------------------------------------------------------------
# Constants for conversion type
# -------------------------------------------------------------------------------

# We might want to come up with a better encoding
BLD_CONV = 0x1
BLD_SURF = 0x2
BLD_DCUR = 0x4
BLD_DNET = 0x8
BLD_PNET = 0x10
BLD_ENET = 0x20
BLD_CNET = 0x40
BLD_SURF_2 = 0x80
BLD_DCUR_2 = 0x100
BLD_DNET_2 = 0x200
BLD_PNET_2 = 0x400
BLD_ENET_2 = 0x800
BLD_CNET_2 = 0x1000
BLD_FLAT = BLD_SURF | BLD_DCUR | BLD_DNET | BLD_ENET | BLD_CNET
BLD_DEPTH = BLD_SURF_2 | BLD_DCUR_2 | BLD_DNET_2 | BLD_PNET_2 | BLD_ENET_2 | BLD_CNET_2


# -------------------------------------------------------------------------------


class BlenderBridge:
    """Object creating a link between a DDG object and a Blender object.

    WARNING:
        This class should not be used on its own. Use ddg.to_blender_object or
        ddg.to_blender_object_helper instead.

    Parameters
    ----------
    obj : DDG object
        Object to link.
    conv_type : int
        Conversion type of the object.
    options : dict (optional, default={})
        Options to use in the conversion.
    attributes : dict (optional, default={})
        Attributes set on blender object.
    depth_bounding : int (optional, default=None)
        Bounding for the first layer of nets.
    material : string (optional, default=None)
        Material of the created Blender object.
    collection : bpy.types.Collection (optional, default=None)
        Collection to link the blender object to
    mesh_transformations: function or list of functions (optional, default=[])
        Functions which should be applied to the mesh of the object.
        The transformation functions need to be defined such that
        they only take one (required) parameter, namely the mesh.
    bmesh_transformations: function or list of functions (optional, default=[])
        Functions which should be applied to the bmesh of the object.
        The transformation functions need to be defined such that
        they only take one (required) parameter, namely the bmesh.
    obj_transformations: function or list of functions (optional, default=[])
        Functions which should be applied to the object.
        The transformation functions need to be defined such that
        they only take one (required) parameter, namely the object.

    Attributes
    ----------
    obj : DDG object
        Object to link.
    object : Blender object
        Blender object associated with the DDG object.
    options : dict
        Options to use in the conversion.
    conv_type : int
        Conversion type of the object.
    depth_bounding : int
        Bounding for the first layer of nets.
    depth : bool
        True if obj is net of DDG objects.
    bpy_data : bmesh or Blender curve
        Blender datastructure used in conversion.

    Methods
    -------
    update
        Update Blender data.
    animation
        Used for animations.
    """

    def __init__(
        self,
        obj,
        conv_type,
        attributes={},
        options={},
        depth_bounding=None,
        material=None,
        collection=None,
        mesh_transformations=[],
        bmesh_transformations=[],
        obj_transformations=[],
        link=True,
        free=True,
    ):

        if not conv_type & BLD_CONV:
            raise TypeError(str(type(obj)) + " is not convertible.")

        self.options = options
        self.attributes = attributes
        self.depth_bounding = depth_bounding
        self.obj = obj
        self.conv_type = conv_type

        self.bmesh_trafos = (
            bmesh_transformations
            if type(bmesh_transformations) is list
            else [bmesh_transformations]
        )
        self.mesh_trafos = (
            mesh_transformations
            if type(mesh_transformations) is list
            else [mesh_transformations]
        )
        self.obj_trafos = (
            obj_transformations
            if type(obj_transformations) is list
            else [obj_transformations]
        )

        self.free = free

        curve = False
        he = False
        if conv_type & BLD_DEPTH:
            self.depth = True

            if conv_type & BLD_SURF_2:
                self.update_bpy_data = halfedge.hes_to_bmesh
                he = True

            elif conv_type & BLD_DCUR_2:
                curve = True
                self.update_bpy_data = net.curve_to_bpy_curve

            elif conv_type & BLD_DNET_2:
                self.update_bpy_data = net.net_to_bmesh

            elif conv_type & BLD_PNET_2:
                self.update_bpy_data = net.point_to_bmesh

            elif conv_type & BLD_ENET_2:
                self.update_bpy_data = net.empty_to_bmesh

        else:
            self.depth = False

            if conv_type & BLD_SURF:
                self.update_bpy_data = halfedge.hes_to_bmesh
                he = True

            elif conv_type & BLD_DCUR:
                curve = True
                self.update_bpy_data = net.curve_to_bpy_curve

            elif conv_type & BLD_DNET:
                self.update_bpy_data = net.net_to_bmesh

            elif conv_type & BLD_PNET:
                self.update_bpy_data = net.point_to_bmesh

            elif conv_type & BLD_ENET:
                self.update_bpy_data = net.empty_to_bmesh

        attributes = {
            "name": None,
            "matrix_world": None,
            "location": (0, 0, 0),
            **attributes,
        }

        if attributes["name"] is None:
            if hasattr(obj, "name"):
                attributes["name"] = obj.name
            else:
                attributes["name"] = "DDG Object"

        if attributes["matrix_world"] is None:
            attributes["matrix_world"] = np.eye(4)
            attributes["matrix_world"][:-1, -1] = np.array(attributes["location"])

        if curve:
            self.bpy_data = bpy.data.curves.new(attributes["name"], type="CURVE")
            self.bpy_data.dimensions = "3D"
            self.object = bpy.data.objects.new(attributes["name"], self.bpy_data)
            c_props = self.options["curve_properties"]
            del self.options["curve_properties"]
            set_curve_properties(self.bpy_data, **c_props)
        else:
            self.mesh = bpy.data.meshes.new(attributes["name"])
            self.bpy_data = bmesh.new()
            self.object = bpy.data.objects.new(attributes["name"], self.mesh)

        # Every Blender object/curve/mesh must have a distinct .name attribute.
        # If one tries to create multiple Blender objects/curves/meshes with
        # the same name attributes['name'], then Blender will automatically
        # append ".001" to the second object, ".002" to the third object and so
        # forth.
        # We already created Blender objects/curves/meshes above. This means
        # that it's possible that attributes['name'] != self.object.name. We
        # should not overwrite self.object.name with the deduplicated name.
        for key in attributes:
            if key != "name":
                setattr(self.object, key, attributes[key])

        if collection is None:
            collection = bpy.context.scene.collection
        if link:
            collection.objects.link(self.object)

        if material is not None:
            set_material(self.object, material)

        self.animationflag = False
        self.animationframe = bpy.context.scene.frame_current

        bpy.app.handlers.frame_change_post.append(self.animation)
        self.update()

    def animation(self, context, depsgraph=None):
        if self.animationflag:
            self._update()
        self.animationflag = False
        self.animationframe = bpy.context.scene.frame_current

    def _update(self):
        error = False
        try:
            upfct = self.update_bpy_data
            if not self.depth:
                upfct(self.obj, bpy_data=self.bpy_data, **self.options)
            else:
                if isinstance(self.bpy_data, bmesh.types.BMesh):
                    if isinstance(self.obj, NetCollection):
                        bmeshes = []
                        for net_ in self.obj:  # underscore avoids shadowing net module
                            subdomain = nutils.bound_domain(
                                net_.domain, self.depth_bounding
                            )
                            for obj in net_[subdomain]:
                                bmeshes.append(upfct(obj, **self.options))
                        join(*bmeshes, free=True, bm=self.bpy_data)
                    else:
                        subdomain = nutils.bound_domain(
                            self.obj.domain, self.depth_bounding
                        )
                        join(
                            *[
                                upfct(obj, **self.options)
                                for obj in self.obj[subdomain]
                            ],
                            free=True,
                            bm=self.bpy_data
                        )
                else:
                    curve_coords = []
                    cyclic = []

                    if isinstance(self.obj, NetCollection):
                        for n in self.obj:
                            subdomain = nutils.bound_domain(
                                n.domain, self.depth_bounding
                            )
                            for c in n[subdomain]:
                                if isinstance(c, NetCollection):
                                    for d in c:
                                        sbdomain = nutils.bound_domain(
                                            d.domain, self.options["bounding"]
                                        )
                                        curve_coords.append(list(d[sbdomain]))
                                        cyclic.append(sbdomain.periodic)
                                else:
                                    sbdomain = nutils.bound_domain(
                                        c.domain, self.options["bounding"]
                                    )
                                    curve_coords.append(list(c[sbdomain]))
                                    cyclic.append(sbdomain.periodic)

                    else:
                        subdomain = nutils.bound_domain(
                            self.obj.domain, self.depth_bounding
                        )

                        for c in self.obj[subdomain]:
                            if isinstance(c, NetCollection):
                                for d in c:
                                    sbdomain = nutils.bound_domain(
                                        d.domain, self.options["bounding"]
                                    )
                                    curve_coords.append(list(d[sbdomain]))
                                    cyclic.append(sbdomain.periodic)
                            else:
                                sbdomain = nutils.bound_domain(
                                    c.domain, self.options["bounding"]
                                )
                                curve_coords.append(list(c[sbdomain]))
                                cyclic.append(sbdomain.periodic)

                    create_curve(curve_coords, cyclic=cyclic, curve=self.bpy_data)

            if isinstance(self.bpy_data, bmesh.types.BMesh):
                for trafo in self.bmesh_trafos:
                    trafo(self.bpy_data)
                self.bpy_data.to_mesh(self.mesh)
                if self.free:
                    self.bpy_data.free()
                for trafo in self.mesh_trafos:
                    trafo(self.mesh)
            for trafo in self.obj_trafos:
                trafo(self.object)

        except Exception as e:
            temp = conversion_type(self.obj)
            if temp == self.conv_type:
                raise e
            else:
                error = True

        if error:
            raise TypeError(
                "Object type has changed. Object was "
                + readable_conversion_type(self.conv_type)
                + " and now is "
                + readable_conversion_type(temp)
                + "."
            )

    def update(self):
        frame = bpy.context.scene.frame_current
        if self.animationframe == frame:
            self._update()
        else:
            self.animationflag = True


def conversion_type(obj):
    """Returns the conversion type of the given object.

    Parameters
    ----------
    obj : Object

    Returns
    -------
    conversion_type : int
    """

    conv_type = 0b0

    if isinstance(obj, NetCollection):
        conv_type |= BLD_CNET
        obj = obj._nets[0]

    if isinstance(obj, Surface):
        conv_type |= BLD_CONV | BLD_SURF

    elif isinstance(obj, Net):
        if isinstance(obj, SmoothNet):
            return conv_type
        elif isinstance(obj, DiscreteCurve):
            conv_type |= BLD_DCUR
        elif isinstance(obj, DiscreteNet):
            conv_type |= BLD_DNET
        elif isinstance(obj, PointNet):
            conv_type |= BLD_PNET
        elif isinstance(obj, EmptyNet):
            conv_type |= BLD_ENET

        if not isinstance(obj, EmptyNet):
            # get a single point in the domain
            bounded_domain = nutils.bound_domain(obj.domain, 1)
            sample = [bounded_domain[i][0] for i in range(obj.dimension)]
            temp = obj(*sample)
            # This is better than before, but still not perfect. There are nets
            # like some from confocal_quadrics or subspaces with homogeneous
            # parametrization for which some values (0,...,0 in both cases)
            # are invalid inputs. Maybe domains should have a way of handling
            # this.
            # Perhaps something like this? But other parts of the library have
            # a similar problem, for example DiscreteNet.__getitem__

            # bounded_domain = nutils.bound_domain(obj.domain, 2)
            # for sample in bounded_domain.traverser:
            #     try:
            #         temp = obj(*sample)
            #     except Exception as e:
            #         error = e
            #     # executes when no exception occured
            #     else:
            #         break
            # # executes when for loop ran without encountering break
            # else:
            #     raise Exception(
            #         f"Sampling of {obj} object to determine its ambient "
            #         f"dimension failed with (last) error '{error}'."
            #     )
        else:
            temp = obj()

        # After making them subscriptable, NetCollections with three elements
        # have shape (3,), so we have to exclude them here.
        if not isinstance(temp, NetCollection) and np.shape(temp) == (3,):
            conv_type |= BLD_CONV
        elif conv_type & BLD_PNET:
            return 0
        else:
            temp = conversion_type(temp)

            if temp & BLD_DEPTH == 0:
                conv_type |= (BLD_CONV & temp) | ((temp >> 1) << 7)

    return conv_type


def readable_conversion_type(conv_type):
    """Converts the given conversion type into a human-readable string.

    Parameters
    ----------
    conv_type : int
        Conversion type to create a string for.

    Returns
    -------
    string
        human-readable string for the conversion type
    """
    out = ""
    if conv_type & BLD_SURF:
        out += "Halfedge Surface"
    elif conv_type & BLD_ENET:
        out += "EmptyNet"
    elif conv_type & BLD_PNET:
        out += "PointNet"
    elif conv_type & BLD_DCUR:
        out += "DiscreteCurve"
    elif conv_type & BLD_DNET:
        out += "DiscreteNet"
    if conv_type & BLD_CNET:
        out = "Collection of " + out + "s"

    layer = ""
    if conv_type & BLD_SURF_2:
        layer += " Halfedge Surfaces"
    elif conv_type & BLD_ENET_2:
        layer += " EmptyNets"
    elif conv_type & BLD_PNET_2:
        layer += " PointNets"
    elif conv_type & BLD_DCUR_2:
        layer += " DiscreteCurves"
    elif conv_type & BLD_DNET_2:
        layer += " DiscreteNets"
    if conv_type & BLD_CNET_2:
        layer = " Collections of" + layer

    if layer != "":
        out += " of" + layer

    if conv_type & BLD_CONV:
        return "Convertible " + out
    else:
        return "Not convertible " + out


def is_convertible(obj):
    """Returns True, if the given object is convertible into a
    Blender object.

    Parameters
    ----------
    obj : Object

    Returns
    -------
    bool : True, when object is convertible, else False
    """
    return BLD_CONV & conversion_type(obj)


[docs]def to_blender_object_helper( obj, name=None, location=None, hide_viewport=None, hide_render=None, scale=None, shade_smooth=False, bounding_box=None, accept_all=False, material=None, collection=None, **kwargs ): """Fast conversion function for ddg objects to be converted to blender. It combines the following two utilities: * converting Subspaces and Quadrics to SmoothNets, SmoothNets to DiscreteNets and DiscreteNets and half-edge objects to blender objects * simplifies the input of commonly used arguments for the Blender object creation. Notes for the conversion: * each conversion that uses a DiscreteNet at some point requires a sampling keyword, see Options for SmoothNet, respectively ddg.nets.conversion.sample_smooth_domain. * each ddg object may have specific conversion arguments, see below. Note that e.g. for a Quadric one can use specific arguments for the conversion from a Quadric to a SmoothNet, from a SmoothNet to a DiscreteNet and from a DiscreteNet to a Blender object. * conversion from 1d Subspaces or Conics use options for DiscreteCurves Notes for the commonly used arguments: * All the keyword arguments of this function will be converted to a form that is readable for :py:func:`~ddg.conversion.blender.core.to_blender_object`. * in \\*\\*kwargs further keyword arguments can be given in a form required by :py:func:`~ddg.conversion.blender.core.to_blender_object`. Parameters ---------- obj : ddg object Any object of the ddg library except some types of nets with depth (see warning). name : str (default=None) Name of the resulting Blender object location : iterable of shape (3,) (default=(0,0,0)) Location of the resulting Blender object. hide_viewport : bool (default=False) Boolean to set to blender_object.hide_viewport. hide_render : bool (default=False) Boolean to set to blender_object.hide_render. scale: iterable of shape (3,) (default=(1,1,1)) Scaling of the resulting Blender object set as blender_object.scale. shade_smooth : bool (default=False) Boolean to decide whether to apply :py:func:`ddg.visualization.blender.mesh.shade_smooth` to the mesh of the resulting Blender object. bounding_box : iterable of shape (3,) (default=(1,1,1)) Boolean to decide weather to apply :py:func:`ddg.visualization.blender.bmesh.cut_bounding_box` (respectively :py:func:`ddg.datastructures.nets.utils.cut_bounding_box` if blender_object.data will be of type bpy.Curves). accept_all : bool (optional default=False) When True all options are accepted, but no warning will be given when the conversion does not support one of them. material : string or bpy.types.Material (optional, default=None) Material of the created Blender object collection : bpy.types.Collection (default=None) Collection to link the blender object to. If set to None the object will be linked to bpy.context.scene.collection. **kwargs Keyword arguments to use in the conversion. Available options depend on the type of the given object (see below). All other keyword arguments (that are not type specific) will be handed over to :py:func:`~ddg.conversion.blender.core.to_blender_object`. For example this can be further (lists of) transformations of the mesh/bmesh or Blender object or attributes of the resulting objects. .. rubric:: Options for Subspaces: convex : bool (default=True) Whether to use convex parametrization (see :py:func:`~.subspace_to_smooth_net`). affine : bool (default=True) Whether the resulting smooth net should return homogeneous or affine coordinates (see :py:func:`~.subspace_to_smooth_net`). domain : list or SmoothDomain (default=None) Optionally a domain to assign to the SmoothNet (see :py:func:`ddg.datastructures.nets.utils.create_subdomain`, :py:func:`~ddg.conversion.nets.geometry.core.to_smooth_net`). As well as all options for SmoothNet and DiscreteNet. .. rubric:: Options for Quadrics: affine : bool (default=False) Whether the resulting SmoothNet should return affine or homogeneous coordinates (see :py:func:`~.quadric_to_smooth_net`). domain : list or SmoothDomain (default=None) Optionally a domain to assign to the SmoothNet (see :py:func:`ddg.datastructures.nets.utils.create_subdomain`, :py:func:`~ddg.conversion.nets.geometry.core.to_smooth_net`). As well as all options for SmoothNet and DiscreteNet. .. rubric:: Options for Spheres: No options. .. rubric:: Options for SmoothNet: sampling : list, int or float See :py:func:`ddg.datastructures.nets.conversion.sample_smooth_domain`. anchor : list or None Anchor point for sampling process. atol : list, int or float Tolerance(s) for sampling process. .. rubric:: Options for DiscreteNet: bounding : int (default=10) Used to bound unbounded domains of nets. only_wire : bool (default=False) When True, only the wireframe of the net will be created. .. rubric:: Options for DiscreteCurve: bounding : int (default=10) Used to bound unbounded domains of nets. curve_type : string (default='POLY') Blender curve type. See the Blender docs for all available types. curve_properties : dictionary (default={'bevel_depth': 0.015}) Dictionary containing Blender curve properties. See the Blender docs for all available properties. .. rubric:: Options for PointNet: sphere_radius : float (default=0) Radius of the sphere representing a point. sphere_subdivision : int (default=3) How many subdivisions will be applied to the sphere. .. rubric:: Options for Half-Edge: co_attr : str (default='co') Name of the vertex attribute that stores the coordinates. See Also -------- to_blender_object Returns ------- Blender object Blender object associated with the ddg object. Warnings -------- This function can only handle DiscreteNets and DiscreteCurves as nets with depth. Not supported for SmoothNets and SmoothCurves. Also in the case of DiscreteNets and DiscreteCurves the net must consist of either * EmptyNets * PointNets * DiscreteNets * Half-edge Objects * Collections of either """ def move_to_other_dict(first_dict, second_dict, key, val=None): """ Helper to remove a key from first_dict and write it to second_dict. If it's not in fist_dict and a val is given this will be written to second_dict. """ if key in first_dict: second_dict[key] = first_dict[key] del first_dict[key] else: # This is not ideal. What if we want the default to be None? if val is not None: second_dict[key] = val # find type of obj or type of obj in NetCollection obj_by_type = obj._nets[0] if isinstance(obj, ddg.NetCollection) else obj # conversion of all non-directly convertible objects to DiscreteNets # namely Subspaces, Quadrics and Spheres if not ( is_convertible(obj_by_type) or isinstance(obj_by_type, ddg.datastructures.nets.net.Net) ): to_smooth_net_kwargs = {} if isinstance(obj_by_type, Subspace): move_to_other_dict(kwargs, to_smooth_net_kwargs, "convex", True) move_to_other_dict(kwargs, to_smooth_net_kwargs, "affine", True) if isinstance(obj_by_type, Quadric): move_to_other_dict(kwargs, to_smooth_net_kwargs, "affine", True) move_to_other_dict(kwargs, to_smooth_net_kwargs, "domain") obj = ddg.to_smooth_net(obj, **to_smooth_net_kwargs) # find new type of obj or type of obj in NetCollection obj_by_type = obj._nets[0] if isinstance(obj, ddg.NetCollection) else obj # conversion of all SmoothNets or Subspaces, Quadrics and Spheres that # previously have been converted to a SmoothNet if isinstance(obj_by_type, ddg.SmoothNet): if isinstance(obj, EmptyNet) and not isinstance(obj, PointNet): pass else: sample_smooth_net_kwargs = {} move_to_other_dict(kwargs, sample_smooth_net_kwargs, "sampling") move_to_other_dict(kwargs, sample_smooth_net_kwargs, "anchor") move_to_other_dict(kwargs, sample_smooth_net_kwargs, "atol") if not sample_smooth_net_kwargs.get("sampling", None): raise ValueError( "The given object is (or has been converted to) a " "SmoothNet. The further conversion to a DiscreteNet " "requires a sampling that was not given." ) else: obj = ddg.sample_smooth_net(obj, **sample_smooth_net_kwargs) # treatment of objects resulting in bpy.Curves conv_type = conversion_type(obj) if conv_type & BLD_DCUR_2 or (conv_type & BLD_DCUR and not conv_type & BLD_DEPTH): if bounding_box is not None: nutils.bound_domain(obj, bounding=kwargs.pop("bounding", 100)) obj = nutils.cut_bounding_box(obj, bounding_box) bounding_box = None # Reformatting keyword arguments bmesh_transformations = kwargs.get("bmesh_transformations", []) if type(bmesh_transformations) is not list: bmesh_transformations = [bmesh_transformations] mesh_transformations = kwargs.get("mesh_transformations", []) if type(mesh_transformations) is not list: mesh_transformations = [mesh_transformations] attributes = kwargs.get("attributes", {}) if bounding_box is not None: bmesh_transformations.append( lambda bmesh: ddg.visualization.blender.bmesh.cut_bounding_box( bmesh, bounding_box, np.zeros(3) ) ) if shade_smooth: mesh_transformations.append(ddg.visualization.blender.mesh.shade_smooth) attributes.update( { k: v for k, v in ( ("name", name), ("hide_viewport", hide_viewport), ("scale", scale), ("hide_render", hide_render), ("location", location), ) if v is not None } ) kwargs["bmesh_transformations"] = bmesh_transformations kwargs["mesh_transformations"] = mesh_transformations kwargs["attributes"] = attributes return to_blender_object( obj, accept_all=accept_all, material=material, collection=collection, **kwargs )
[docs]def to_blender_object( obj, accept_all=False, depth_bounding=10, attributes={}, material=None, collection=None, mesh_transformations=[], bmesh_transformations=[], obj_transformations=[], link=True, **options ): """Create and link a Blender object from a given DDG object. Parameters ---------- obj : ddg.datastructure DDG datastructure to convert into a Blender object. accept_all : bool (optional default=False) When True all options are accepted, but no warning will be given when the conversion does not support one of them. depth_bounding : int (optional, default=10) Bounding used for the highest layer of Nets with depth attributes : dict (optional, default={}) Attributes that will be assigned to the resulting Blender object such as name, parent, matrix_world, location, hide_viewport etc. in a dictionary as {'attribute': value} material : string or bpy.types.Material (optional, default=None) Material of the created Blender object collection : bpy.types.Collection (optional, default=None) Collection to link the blender object to. If set to None the object will be linked to bpy.context.scene.collection. mesh_transformations : function or list of functions (optional, default=[]) Functions which should be applied to the mesh of the object. The transformation functions need to be defined such that they only take one (required) parameter, namely the mesh. bmesh_transformations : function or list of functions (optional, default=[]) Functions which should be applied to the bmesh of the object. The transformation functions need to be defined such that they only take one (required) parameter, namely the bmesh. obj_transformations : function or list of functions (optional, default=[]) Functions which should be applied to the object. The transformation functions need to be defined such that they only take one (required) parameter, namely the object. link: bool (optional, default=True) If set to True, the object will be linked to the given collection. If set to False, the object will be created but not linked to any collection. **options Options to use in the conversion. Available options depend on the type of the given object. .. rubric:: Options for DiscreteNet: bounding : int (default=10) Used to bound unbounded domains of nets. only_wire : bool (default=False) When True, only the wireframe of the net will be created. .. rubric:: Options for DiscreteCurve: bounding : int (default=10) Used to bound unbounded domains of nets. curve_type : string (default='POLY') Blender curve type. See the Blender docs for all available types. curve_properties : dictionary (default={'bevel_depth': 0.015}) Dictionary containing Blender curve properties. See the Blender docs for all available properties. .. rubric:: options for PointNet: sphere_radius : float (default=0) Radius of the sphere representing a point. sphere_subdivision : int (default=3) How many subdivisions will be applied to the sphere. .. rubric:: Options for HalfEdge surfaces: co_attr : str (default='co') The name of the vertex attribute that contains the coordinates. Returns ------- Blender object Blender object associated with the DDG object. Raises ------ TypeError if object is not convertible. """ conv_type = conversion_type(obj) if not conv_type & BLD_CONV: raise TypeError("Object " + str(obj) + " is not convertible.") if conv_type & BLD_DEPTH: if conv_type & BLD_SURF_2: options = _hes_options(accept_all, **options) elif conv_type & BLD_DCUR_2: options = _curve_options(accept_all, **options) elif conv_type & BLD_DNET_2: options = _net_options(accept_all, **options) elif conv_type & BLD_ENET_2: options = _empty_options(accept_all, **options) elif conv_type & BLD_PNET_2: options = _point_options(accept_all, **options) else: if conv_type & BLD_SURF: options = _hes_options(accept_all, **options) elif conv_type & BLD_DCUR: options = _curve_options(accept_all, **options) elif conv_type & BLD_DNET: options = _net_options(accept_all, **options) elif conv_type & BLD_ENET: options = _empty_options(accept_all, **options) elif conv_type & BLD_PNET: options = _point_options(accept_all, **options) bobj = BlenderBridge( obj, conv_type, attributes=attributes, depth_bounding=depth_bounding, options=options, material=material, collection=collection, mesh_transformations=mesh_transformations, bmesh_transformations=bmesh_transformations, obj_transformations=obj_transformations, link=link, ) return bobj.object
def _curve_options(accept_all=False, **options): defaults = { "bounding": 10, "curve_type": None, "curve_properties": {"bevel_depth": 0.015}, } valid_options = set(defaults) given_options = set(options) if not given_options.issubset(valid_options) and not accept_all: unknown = str(given_options.difference(valid_options)) unknown = unknown.strip("{").strip("}") raise TypeError("Received unkown option(s) for object creation: " + unknown) for i in valid_options.intersection(given_options): defaults[i] = options[i] return defaults def _empty_options(accept_all=False, **options): given = set(options) if not given.issubset(set()) and not accept_all: unknown = str(given) unknown = unknown.strip("{").strip("}") raise TypeError("Received unkown option(s) for object creation: " + unknown) return {} def _net_options(accept_all=False, **options): defaults = {"bounding": 10, "only_wire": False} valid_options = set(defaults) given_options = set(options) if not given_options.issubset(valid_options) and not accept_all: unknown = str(given_options.difference(valid_options)) unknown = unknown.strip("{").strip("}") raise TypeError("Received unkown option(s) for object creation: " + unknown) for i in valid_options.intersection(given_options): defaults[i] = options[i] return defaults def _point_options(accept_all=False, **options): defaults = {"sphere_radius": 0, "sphere_subdivision": 3} valid_options = set(defaults) given_options = set(options) if not given_options.issubset(valid_options) and not accept_all: unknown = str(given_options.difference(valid_options)) unknown = unknown.strip("{").strip("}") raise TypeError("Received unkown option(s) for object creation: " + unknown) for i in valid_options.intersection(given_options): defaults[i] = options[i] return defaults def _hes_options(accept_all=False, **options): defaults = {"co_attr": "co"} valid_options = set(defaults) given_options = set(options) if not given_options.issubset(valid_options) and not accept_all: unknown = str(given_options.difference(valid_options)) unknown = unknown.strip("{").strip("}") raise TypeError("Received unkown option(s) for object creation: " + unknown) for i in valid_options.intersection(given_options): defaults[i] = options[i] return defaults