Hi!
Here's a script that uses the IOHelpers API to create and edit the point properties instead of using directly the ResourceProperty API.
The IOHelpers API is mainly dedicated to edit ParticleContainer objects, so I hope this is your case.
Please note that currently the Property Editor isn't notified when a new property is added via IOHelpers. Close it and open a new one to show all properties.
For the sake of testing, I'm creating matrices with random values, but you just have to replace that with your real matrices.
I have attached the script and a project that uses it.
I hope this answers your needs. Let me know if you have questions.
We will also investigate the set_double issue.
python code
import random
INVALID_INDEX = -1 + 2**32
ENABLE_DEBUG_LOGS = False
def log_debug(msg):
if ENABLE_DEBUG_LOGS:
ix.log_info('[DEBUG] %r' % msg)
# -------------------------------------------------------------------
# Helpers for ResourceProperty.
value_types = {
0 : 'int8',
1 : 'uint8',
2 : 'int16',
3 : 'uint16',
4 : 'int32',
5 : 'uint32',
6 : 'int64',
7 : 'uint64',
8 : 'float16',
9 : 'float32',
10 : 'float64',
11 : 'char',
12 : 'wchar',
}
def get_type_size(value_type):
"""
Get the property type size in bytes. Example: float32 -> 4 bytes.
"""
return ix.api.ResourceProperty.get_type_size(value_type)
def get_type_name(value_type, with_size = False):
"""
Get the property type as name string.
"""
if value_type in value_types:
if with_size:
return '%s (%d bytes)' % (value_types[value_type], get_type_size(value_type))
else:
return '%s' % value_types[value_type]
else:
return'unknown'
def get_values_as(resource, item_index, value_type):
"""
Get the resources values of the item at the given index.
If the requested type doesn't match with the actual property type an empty value list is returned,
"""
if value_type == ix.api.ResourceProperty.TYPE_CHAR:
return resource.get_string(item_index)
if value_type == ix.api.ResourceProperty.TYPE_INT_8 or value_type == ix.api.ResourceProperty.TYPE_UINT_8:
return resource.get_byte(item_index)
if value_type == ix.api.ResourceProperty.TYPE_INT_16 or value_type == ix.api.ResourceProperty.TYPE_UINT_16:
return resource.get_short(item_index)
if value_type == ix.api.ResourceProperty.TYPE_INT_32 or value_type == ix.api.ResourceProperty.TYPE_UINT_32:
return resource.get_int(item_index)
if value_type == ix.api.ResourceProperty.TYPE_INT_64 or value_type == ix.api.ResourceProperty.TYPE_UINT_64:
return resource.get_long(item_index)
elif value_type == ix.api.ResourceProperty.TYPE_FLOAT_32:
return resource.get_float(item_index)
elif value_type == ix.api.ResourceProperty.TYPE_FLOAT_64:
return resource.get_double(item_index)
ix.log_warning('get_values_as: unsupported type "%s"' % get_type_name(value_type))
return []
# -------------------------------------------------------------------
# Functions to create dummy matrix data and convert it to string.
def create_dummy_m44d(random_values = False, transpose = True):
"""
Create a GMathMatrix4x4d with values for the sake of testing.
"""
if random_values:
# matrix with random values
m44d = ix.api.GMathMatrix4x4d()
for i in range(4):
for j in range(4):
m44d.set_item(i, j, random.random())
else:
# identity matrix
m44d = ix.api.GMathMatrix4x4d(True)
# to be in Alembic format the matrix must be transposed
if transpose:
m44d.transpose()
return m44d
def m44d_to_string(m44d, separator = ' '):
"""
Get the GMathMatrix4x4d flattened values as a string separated with the separator.
"""
s = ''
for i in range(4):
for j in range(4):
s += '%f' % m44d.get_item(i, j)
if j < 3: s += separator
if i < 3: s += separator
return s
# -------------------------------------------------------------------
def get_point_property(particle_object, prop_name):
"""
Get a GeometryPointProperty from a GeometryParticle object.
"""
prop_collection = particle_object.get_module().get_properties()
if not prop_collection:
ix.log_warning('There are no properties.')
return None
prop_index = prop_collection.get_property_index(prop_name)
if prop_index == INVALID_INDEX:
ix.log_warning('Property "' + prop_name + '" not found.')
return None
prop = prop_collection.get_property(prop_index)
if not prop:
ix.log_warning('Failed to retrieve property "' + prop_name + '".')
return None
return prop
# -------------------------------------------------------------------
def get_point_property_indices(particle_object, prop_name):
"""
Get the GeometryPointProperty indices.
Returns None if the property isn't found or isn't indexed.
"""
prop = get_point_property(particle_object, prop_name)
if not prop:
return None
if not prop.has_indices():
return None
return prop.get_indices()
# -------------------------------------------------------------------
def print_point_property_indices(particle_object, prop_name):
"""
Get the value indices of the GeometryPointProperty.
"""
indices = get_point_property_indices(particle_object, prop_name)
if indices:
s = []
for i in indices:
s.append(i)
print 'Value indices for %s: index count = %d, indices = %r' % (prop_name, indices.get_count(), s)
# -------------------------------------------------------------------
def create_point_property(particle_container_object, prop_name, value_type, dimension):
"""
Create a GeometryPointProperty in a GeometryParticleContainer with no values.
If the property already exists it is overwritten.
Known bug in 3.6 SP8: the Property Editor doesn't refresh after a property has been created by IOHelpers.
Parameters
----------
particle_container_object:
The GeometryParticleContainer object.
prop_name:
Property name.
value_type:
Property value type (see ResourceProperty.Type).
dimension:
Property dimension: number of data items per value.
Examples:
- 1 vector3 value has 3 data items -> dimension = 3
- 1 matrix44 value has 16 data items -> dimension = 16
Returns
-------
Boolean:
True if succeeded, False otherwise.
"""
print 'Creating property "%s.%s" ...' % (particle_container_object.get_name(), prop_name)
return ix.api.IOHelpers.create_particles_property(particle_container_object, prop_name, value_type, dimension, '')
# -------------------------------------------------------------------
def set_point_property_values(particle_container_object, prop_name, values):
"""
Set the values of a GeometryPointProperty in a GeometryParticleContainer.
This implementation might be limited if the values array is too big.
It could be possible to rework to add a parameter containing a list of point
indices along with the associated values, so that the values can be set by chunks.
Parameters
----------
particle_container_object:
The GeometryParticleContainer object.
prop_name:
Property name.
values:
Python string array containg 1 string value per point.
Each string must contain as many values (separated by spaces) as the dimension of the property.
If there are too many values the excess values are ignored.
If there are not enough values, some points won't have values.
Example:
- to set the values of 2 x vec2f to (1.0, 2.0) and (3.0 4.0), the python string array is ["1.0 2.0", "3.0 4.0"].
Returns
-------
Boolean:
True if all values were set, False if at least one value failed to be set.
"""
print 'Setting values in property "%s.%s" ...' % (particle_container_object.get_name(), prop_name)
point_count = int(particle_container_object.get_module().get_point_count())
values_used = min(point_count, len(values))
if len(values) != point_count:
ix.log_warning('Value count (%d) and point count (%d) differ: only %d values will be used.' % (point_count, len(values), values_used))
return
# NOTE: this function would require some reworking to use indexed values in order to reduce the number of stored values, to share
# values between points. For example you can have N values, and assign those N values to M points, where M = point_count and N <= M.
# - point 0 -> assign value at index 0
# - point 1 -> assign value at index 0
# - point 2 -> assign value at index 2
# ...
# - point M-1 -> assign value at index 1
# assign each value to each point
all_ok = False
for i in range(values_used):
# create a new value index for the new value
new_indices = ix.api.ULongSet()
new_indices.add(i)
ok = ix.api.IOHelpers.edit_particles_property(particle_container_object, prop_name, new_indices, ix.api.IOHelpers.EDIT_PROPERTY_MODE_SET, values[i])
if not ok:
ix.log_warning('Failed to set value for point %d.' % i)
all_ok = all_ok and ok
return all_ok
# -------------------------------------------------------------------
def print_property_values(particle_container_object, prop_name, value_type):
"""
Print the values of a GeometryPointProperty in a GeometryParticleContainer.
"""
print 'Printing property "%s.%s" ...' % (particle_container_object.get_name(), prop_name)
module = particle_container_object.get_module()
point_count = int(module.get_point_count())
log_debug('point count = %d' % point_count)
# get the property collection
prop_collection = module.get_properties()
if not prop_collection:
ix.log_warning('No properties.')
return
# get the property index
prop_index = prop_collection.get_property_index(prop_name)
if prop_index == INVALID_INDEX:
ix.log_warning('Property "' + prop_name + '" not found.')
return
# get the property
prop = prop_collection.get_property(prop_index)
if not prop:
ix.log_warning('Failed to get property "%s" at index %d.' % (prop_name, prop_index))
return
value_type = prop.get_value_type()
# debug stuff
value_count = prop.get_value_count() # number of property values
value_extent = prop.get_value_extent() # number of value item per per property value (e.g. vec2 has extent = 2)
index_count = prop.get_index_count()
time_sampling = prop.get_time_sampling()
sample_count = time_sampling.get_sample_count()
log_debug('value_type = %d (%s)' % (value_type, get_type_name(value_type)))
log_debug('value_count = %d' % value_count) # number of values: 2 x vec3f -> 2
log_debug('value_extent = %d' % value_extent) # number of value "items" per value: vec3f -> 3
log_debug('index_count = %d' % index_count) # number of indices: usually same as number of points
log_debug('sample_count = %d' % sample_count) # number of samples per value
log_debug('has_indices = %r' % prop.has_indices())
# get the ResourceProperty for the 1st sample (0)
resource = prop.get_values_property(0)
# debug stuff
log_debug('resource.value_size = %r' % resource.get_value_size()) # size of 1 value item: vec3f -> float32 = 4 bytes
log_debug('resource.item_count = %r' % resource.get_item_count()) # number of values (== prop.value_count)
log_debug('resource.value_count = %r' % resource.get_value_count()) # total number of value items: 2 x vec3f -> 2 x 3 = 6
log_debug('resource.item_value_count = %r' % resource.get_item_value_count(0)) # number of items per value: vec3f -> 3
# iterate over the points
for item_index in range(resource.get_item_count()):
cur_values = get_values_as(resource, item_index, value_type)
log_debug('len(cur_values) = %r' % len(cur_values))
log_debug('cur_values = %r' % cur_values)
if len(cur_values) == 0:
continue
cur_extent = resource.get_item_value_count(item_index)
log_debug('cur_extent = %d' % cur_extent)
log = 'value %d = ' % item_index
if value_type == ix.api.ResourceProperty.TYPE_CHAR:
log += cur_values[:cur_extent]
else:
for i in range(cur_extent):
log += '%r ' % cur_values[i]
print log
# -------------------------------------------------------------------
# Run it !
# get the particle container and its point count
container = ix.get_item("project://scene/context/container")
point_count = int(container.get_module().get_point_count())
# ----- create a matrix44d property (dim 16) -----
# create as many matrices as points in the particle container, and put the values in a string array
m44d_values = []
for i in range(point_count):
m44d = create_dummy_m44d(True)
m44d_values.append(m44d_to_string(m44d))
prop_name = 'm44_float64'
create_point_property(container, prop_name, ix.api.ResourceProperty.TYPE_FLOAT_64, 16)
set_point_property_values(container, prop_name, m44d_values)
print_property_values(container, prop_name, ix.api.ResourceProperty.TYPE_FLOAT_64)
# ----- create an int32 property (dim 1) -----
int32_values = []
for i in range(point_count):
int32_values.append(str(i))
prop_name = 'int32'
create_point_property(container, prop_name, ix.api.ResourceProperty.TYPE_INT_32, 1)
set_point_property_values(container, prop_name, int32_values)
print_property_values(container, prop_name, ix.api.ResourceProperty.TYPE_INT_32)
# ----- create a string property (dim 1) -----
text_values = []
for i in range(point_count):
text_values.append('text_%d' % i)
prop_name = 'text'
create_point_property(container, prop_name, ix.api.ResourceProperty.TYPE_CHAR, 1)
set_point_property_values(container, prop_name, text_values)
print_property_values(container, prop_name, ix.api.ResourceProperty.TYPE_CHAR)