from blenderproc.python.modules.utility.Config import Config
from blenderproc.python.utility.Utility import Utility, resolve_path
import json
from copy import deepcopy
[docs]class ItemCollection:
""" Manages the reading and creation of multiple items (like light sources or cam poses) from config or file. """
def __init__(self, add_item_func, default_item_parameters):
"""
:param add_item_func: A function which adds a new item. It only should have one parameter which is the configuration item.
:param default_item_parameters: A dict containing the default parameters which should be applied across all items.
"""
self.add_item_func = add_item_func
self.default_item_parameters = default_item_parameters
[docs] def add_items_from_file(self, path, file_format, number_of_arguments_per_parameter):
""" Adds one item per line of the given file.
:param path: The path of the file to read.
:param file_format: Specifies how each line is formatted
:param number_of_arguments_per_parameter: A dict specifying the length of parameters that require more than one argument.
"""
file_format = file_format.split()
# Calc the total number of arguments necessary per line (used for validating the file)
number_of_arguments = sum([self._length_of_parameter(parameter_name, number_of_arguments_per_parameter) for parameter_name in file_format])
# Read in file and split lines up into arguments
for arguments in self._collect_arguments_from_file(path, file_format, number_of_arguments):
# Parse parameters from arguments and add new item
self.add_item(self._parse_arguments_from_file(arguments, file_format, number_of_arguments_per_parameter))
[docs] def add_items_from_dicts(self, dicts):
""" Adds items from a list of dicts.
Every dict specifies the parameters of one new item.
:param dicts: The list of dicts.
"""
for parameters in dicts:
self.add_item(parameters)
[docs] def add_item(self, parameters):
""" Adds a new item based on the given parameters.
:param parameters: A dict specifying the parameters.
"""
# Start with the default parameters
data = deepcopy(self.default_item_parameters)
# Overwrite default parameter values with specific parameters for this item
data = Utility.merge_dicts(parameters, data)
# Create config object
config = Config(data)
# Call function to add new item
self.add_item_func(config)
[docs] def _parse_arguments_from_file(self, arguments, file_format, number_of_arguments_per_parameter):
""" Sets the parameters using the given arguments.
:param arguments: A list of arguments read in from the file.
:param file_format: Specifies how the arguments should be mapped to parameters.
:param number_of_arguments_per_parameter: A dicts which maps parameter names to their required number of arguments.
:return: A dict containing the parameters specified by the given arguments.
"""
data = {}
# Go through all configured parameters, set current one using N next argument
for parameter_name in file_format:
# Check if we should just skip the next parameter
if parameter_name != "_":
# Lookup how many arguments the parameter consumes.
parameter_length = self._length_of_parameter(parameter_name, number_of_arguments_per_parameter)
# Read in the next N arguments
if parameter_length > 1:
parameter_value = arguments[:parameter_length]
else:
parameter_value = arguments[0]
# Set parameter
self._set_parameter_value(data, parameter_name.split("/"), parameter_value)
arguments = arguments[parameter_length:]
else:
arguments = arguments[1:]
return data
[docs] def _set_parameter_value(self, data, parameter_name_parts, value):
""" Sets the parameter inside the given nested dict
({}, ["rotation","value"], 42) will result in:
.. code-block:: yaml
{
"rotation": {
"value": 42
}
}
:param data: The dict into which the parameter value should be written.
:param parameter_name_parts: A list of strings which will be used as keys when creating the nested dicts and setting the value.
:param value: The value to set
"""
if len(parameter_name_parts) == 1:
data[parameter_name_parts[0]] = value
elif len(parameter_name_parts) > 1:
if parameter_name_parts[0] not in data:
data[parameter_name_parts[0]] = {}
self._set_parameter_value(data[parameter_name_parts[0]], parameter_name_parts[1:], value)
[docs] def _length_of_parameter(self, parameter_name, number_of_arguments_per_parameter):
""" Returns how many arguments the given parameter expects.
:param parameter_name: The name of the parameter.
:param number_of_arguments_per_parameter: Dict where {key:value} pairs are {name of the parameter:expected number of arguments} pairs.
:return: The expected number of arguments
"""
# If not specified, 1 is assumed.
if parameter_name in number_of_arguments_per_parameter:
return number_of_arguments_per_parameter[parameter_name]
else:
return 1
[docs] def _collect_arguments_from_file(self, path, file_format, number_of_arguments):
""" Reads in all lines of the given file and returns them as a list of lists of arguments
This method also checks is the lines match the configured file format.
:param path: The path of the file.
:param file_format: Specifies how the arguments should be mapped to parameters.
:param number_of_arguments: The total number of arguments required per line.
:return: A list of lists of arguments
"""
arguments = []
if path != "":
with open(resolve_path(path)) as f:
lines = f.readlines()
# remove all empty lines
lines = [line for line in lines if len(line.strip()) > 3]
for line in lines:
# Split line into separate arguments
line_args = line.strip().split()
# Make sure the arguments match the configured file format
if len(line_args) != number_of_arguments:
raise Exception("A line in the given cam pose file does not match the configured file format:\n" + line.strip() + " (Number of values: " + str(len(line_args)) + ")\n" + str(file_format) + " (Number of values: " + str(number_of_arguments) + ")")
# Parse arguments in line using json. (In this way "test" will be mapped to a string, while 42 will be mapped to an integer)
arguments.append([json.loads(x) for x in line_args])
return arguments