55from bpy .types import Object
66import numpy as np
77
8- COMPATIBLE_TYPES = [bpy .types .Mesh , bpy .types .Curve , bpy .types .PointCloud ]
8+ COMPATIBLE_TYPES = [bpy .types .Mesh , bpy .types .Curves , bpy .types .PointCloud ]
99
1010
1111class NamedAttributeError (AttributeError ):
@@ -76,29 +76,29 @@ class AttributeDomains(Enum):
7676
7777 Attributes
7878 ----------
79- POINT : AttributeDomain
79+ POINT : str
8080 The point domain of geometry data which includes vertices, point cloud and control points of curves.
81- EDGE : AttributeDomain
81+ EDGE : str
8282 The edges of meshes, defined as pairs of vertices.
83- FACE : AttributeDomain
83+ FACE : str
8484 The face domain of meshes, defined as groups of edges.
85- CORNER : AttributeDomain
85+ CORNER : str
8686 The face domain of meshes, defined as pairs of edges that share a vertex.
87- CURVE : AttributeDomain
87+ CURVE : str
8888 The Spline domain, which includes the individual splines that each contain at least one control point.
89- INSTANCE : AttributeDomain
89+ INSTANCE : str
9090 The Instance domain, which can include sets of other geometry to be treated as a single group.
91- LAYER : AttributeDomain
91+ LAYER : str
9292 The domain of single Grease Pencil layers.
9393 """
9494
95- POINT = AttributeDomain ( name = "POINT" )
96- EDGE = AttributeDomain ( name = "EDGE" )
97- FACE = AttributeDomain ( name = "FACE" )
98- CORNER = AttributeDomain ( name = "CORNER" )
99- CURVE = AttributeDomain ( name = "CURVE" )
100- INSTANCE = AttributeDomain ( name = "INSTNANCE" )
101- LAYER = AttributeDomain ( name = "LAYER" )
95+ POINT = "POINT"
96+ EDGE = "EDGE"
97+ FACE = "FACE"
98+ CORNER = "CORNER"
99+ CURVE = "CURVE"
100+ INSTANCE = "INSTANCE"
101+ LAYER = "LAYER"
102102
103103
104104@dataclass
@@ -331,21 +331,6 @@ def dtype(self) -> Type:
331331 def n_values (self ) -> int :
332332 return np .prod (self .shape , dtype = int )
333333
334- @classmethod
335- def from_object (
336- cls ,
337- obj : bpy .types .Object ,
338- name : str ,
339- atype : AttributeType ,
340- domain : AttributeDomain ,
341- ):
342- att = obj .data .get (name )
343- if att is None :
344- att = obj .data .attributes .new (
345- name = name , type = atype .value .type_name , domain = domain .value .name
346- )
347- return Attribute (att )
348-
349334 def from_array (self , array : np .ndarray ) -> None :
350335 """
351336 Set the attribute data from a numpy array.
@@ -394,12 +379,45 @@ def __str__(self):
394379 )
395380
396381
382+ def _match_atype (
383+ atype : str | AttributeTypes | None , data : np .ndarray
384+ ) -> AttributeTypes :
385+ if isinstance (atype , str ):
386+ try :
387+ atype = AttributeTypes [atype ]
388+ except KeyError :
389+ raise ValueError (
390+ f"Given data type { atype = } does not match any of the possible attribute types: { list (AttributeTypes )= } "
391+ )
392+ if atype is None :
393+ atype = guess_atype_from_array (data )
394+ return atype
395+
396+
397+ def _match_domain (
398+ domain : str | AttributeDomains | None ,
399+ ) -> str :
400+ if isinstance (domain , str ):
401+ try :
402+ AttributeDomains [domain ] # Validate the string is a valid domain
403+ return domain
404+ except KeyError :
405+ raise ValueError (
406+ f"Given domain { domain = } does not match any of the possible attribute domains: { list (AttributeDomains )= } "
407+ )
408+ if domain is None :
409+ return AttributeDomains .POINT .value
410+ if isinstance (domain , AttributeDomains ):
411+ return domain .value
412+ return domain
413+
414+
397415def store_named_attribute (
398416 obj : bpy .types .Object ,
399417 data : np .ndarray ,
400418 name : str ,
401419 atype : str | AttributeTypes | None = None ,
402- domain : str | AttributeDomain | AttributeDomains = AttributeDomains .POINT ,
420+ domain : str | AttributeDomains = AttributeDomains .POINT ,
403421 overwrite : bool = True ,
404422) -> bpy .types .Attribute :
405423 """
@@ -415,7 +433,7 @@ def store_named_attribute(
415433 The name of the attribute.
416434 atype : str or AttributeTypes or None, optional
417435 The attribute type to store the data as. If None, type is inferred from data.
418- domain : str or AttributeDomain , optional
436+ domain : str or AttributeDomains , optional
419437 The domain of the attribute, by default 'POINT'.
420438 overwrite : bool, optional
421439 Whether to overwrite existing attribute, by default True.
@@ -446,41 +464,37 @@ def store_named_attribute(
446464 ```
447465 """
448466
449- if isinstance (atype , str ):
450- try :
451- atype = AttributeTypes [atype ]
452- except KeyError :
453- raise ValueError (
454- f"Given data type { atype = } does not match any of the possible attribute types: { list (AttributeTypes )= } "
455- )
467+ atype = _match_atype (atype , data )
468+ domain = _match_domain (domain )
456469
457- if isinstance (domain , str ):
458- try :
459- domain = AttributeDomains [domain ].value
460- except KeyError :
461- raise ValueError (
462- f"Given domain { domain = } does not match any of the possible attribute domains: { list (AttributeDomains )= } "
463- )
470+ if isinstance (obj , bpy .types .Object ):
471+ obj_data = obj .data
472+ else :
473+ obj_data = obj .data
464474
465- if atype is None :
466- atype = guess_atype_from_array (data )
475+ if not isinstance (
476+ obj_data , (bpy .types .Mesh , bpy .types .Curves , bpy .types .PointCloud )
477+ ):
478+ raise NamedAttributeError (
479+ f"Object must be a mesh, curve or point cloud to store attributes, not { type (obj_data )} "
480+ )
467481
468482 if name == "" :
469483 raise NamedAttributeError ("Attribute name cannot be an empty string." )
470484
471- attribute = obj . data .attributes .get (name ) # type: ignore
485+ attribute = obj_data .attributes .get (name ) # type: ignore
472486 if not attribute or not overwrite :
473- current_names = obj . data .attributes .keys ()
474- attribute = obj . data . attributes .new (name , atype .value .type_name , domain . name )
487+ current_names = obj_data .attributes .keys ()
488+ attribute = obj_data . attributes .new (name , atype .value .type_name , domain )
475489
476490 if attribute is None :
477491 [
478- obj . data . attributes .remove (obj . data .attributes [name ])
479- for name in obj . data .attributes .keys ()
492+ obj_data . attributes .remove (obj_data .attributes [name ])
493+ for name in obj_data .attributes .keys ()
480494 if name not in current_names
481495 ] # type: ignore
482496 raise NamedAttributeError (
483- f"Could not create attribute `{ name } ` of type `{ atype .value .type_name } ` on domain `{ domain . name } `. "
497+ f"Could not create attribute `{ name } ` of type `{ atype .value .type_name } ` on domain `{ domain } `. "
484498 "Potentially the attribute name is too long or there is no geometry on the object for the given domain."
485499 )
486500
0 commit comments