import json
from random import sample
import bpy
import mathutils
from blenderproc.python.modules.main.Provider import Provider
from blenderproc.python.modules.utility.Config import Config
from blenderproc.python.types.EntityUtility import convert_to_entities
import blenderproc.python.filter.Filter as Filter
import numpy as np
[docs]class Entity(Provider):
"""
Returns a list of objects that comply with defined conditions.
Example 1: Return a list of objects that match a name pattern.
.. code-block:: yaml
{
"provider": "getter.Entity",
"conditions": {
"name": "Suzanne.*"
}
}
Example 2: Returns the first object to: {match the name pattern "Suzanne", AND to be of a MESH type},
OR {match the name pattern, AND have a certain value of a cust. prop}
OR {be inside a bounding box defined by a min and max points}
OR {have a Z position in space greater than -1}
.. code-block:: yaml
{
"provider": "getter.Entity",
"index": 0,
"conditions": [
{
"name": "Suzanne",
"type": "MESH"
},
{
"name": "Cube.*",
"cp_category": "is_cube"
},
{
"cf_inside": {
"min": "[-5, -5, -5]",
"max": "[5, 5, 5]"
}
},
{
"cf_inside": {
"z_min": -1,
}
}
]
}
Example 3: Returns two random objects of MESH type.
.. code-block:: yaml
{
"provider": "getter.Entity",
"random_samples": 2,
"conditions": {
"type": "MESH"
}
}
**Configuration**:
.. list-table::
:widths: 25 100 10
:header-rows: 1
* - Parameter
- Description
- Type
* - conditions
- List of dicts/a dict of entries of format {attribute_name: attribute_value}. Entries in a dict are
conditions connected with AND, if there multiple dicts are defined (i.e. 'conditions' is a list of
dicts, each cell is connected by OR.
- list/dict
* - conditions/attribute_name
- Name of any valid object's attribute, custom property, or custom function. Any given attribute_value of
the type string will be treated as a REGULAR EXPRESSION. Also, any attribute_value for a custom property
can be a string/int/bool/float, while only attribute_value for valid attributes of objects can be a bool
or a list (mathutils.Vector, mathurils.Color and mathutils.Euler are covered by the 'list' type). " In
order to specify, what exactly one wants to look for: For attribute: key of the pair must be a valid
attribute name. For custom property: key of the pair must start with `cp_` prefix. For calling custom
function: key of the pair must start with `cf_` prefix. See table below for supported custom functions.
- string
* - conditions/attribute_value
- Any value to set.
- string, list/Vector, int, bool or float
* - index
- If set, after the conditions are applied only the entity with the specified index is returned.
- int
* - random_samples
- If set, this Provider returns random_samples objects from the pool of selected ones. Define index or
random_samples property, only one is allowed at a time. Default: 0.
- int
* - check_empty
- If this is True, the returned list can not be empty, if it is empty an error will be thrown. Default: False.
- bool
**Custom functions**
.. list-table::
:widths: 25 100 10
:header-rows: 1
* - Parameter
- Description
- Type
* - cf_{inside,outside}
- Returns objects that lies inside/outside of a bounding box or with a specific coordinate component >,<
than a value.
- dict
* - cf_{inside,outside}/(min,max)
- min and max pair defines a bounding box used for a check. cannot be mixed with /[xyz]_{min,max}
configuration.
- list ([x, y, z])
* - cf_{inside,outside}/[xyz]_(min,max)
- Alternative syntax. Defines a hyperplane. Missing arguments extend the bounding box to infinity in that direction.
- float
"""
def __init__(self, config):
Provider.__init__(self, config)
[docs] def _get_conditions_as_string(self):
"""
Returns the used conditions as neatly formatted string
:return: str: containing the conditions
"""
conditions = self.config.get_raw_dict('conditions')
text = json.dumps(conditions, indent=2, sort_keys=True)
def add_indent(t): return "\n".join(" " * len("Exception: ") + e for e in t.split("\n"))
return add_indent(text)
[docs] def run(self):
""" Processes defined conditions and compiles a list of objects.
:return: List of objects that met the conditional requirement. Type: list.
"""
conditions = self.config.get_raw_dict('conditions')
# the list of conditions is treated as or condition
if not isinstance(conditions, list):
conditions = [conditions]
all_objects = convert_to_entities(bpy.context.scene.objects)
filtered_objects = []
# each single condition is treated as and condition
for and_condition in conditions:
new_filtered_objects = self.perform_and_condition_check(and_condition, all_objects)
# Add objects to the total list, if they are not already present there
filtered_objects.extend([obj for obj in new_filtered_objects if obj not in filtered_objects])
random_samples = self.config.get_int("random_samples", 0)
has_index = self.config.has_param("index")
if has_index and random_samples:
raise RuntimeError("Please, define only one of two: `index` or `random_samples`.")
elif has_index:
filtered_objects = [filtered_objects[self.config.get_int("index")]]
elif random_samples:
filtered_objects = sample(filtered_objects, k=min(random_samples, len(filtered_objects)))
check_if_return_is_empty = self.config.get_bool("check_empty", False)
if check_if_return_is_empty and not filtered_objects:
raise Exception(f"There were no objects selected with the following "
f"condition: \n{self._get_conditions_as_string()}")
# Map back to blender objects for now (TODO: Remove in the future)
filtered_objects = [filtered_object.blender_obj for filtered_object in filtered_objects]
return filtered_objects