33import bpy
44
55
6- class ColumnAccessor :
7- """
8- Helper class to handle column operations on AttributeArray.
9-
10- This provides a clean way to handle operations like pos[:, 2] += 1.0
11- without the complexity of numpy array views.
12- """
13-
14- def __init__ (self , parent , column_idx ):
15- self .parent = parent
16- self .column_idx = column_idx
17- self .parent_array = np .asarray (parent ).view (np .ndarray )
18-
19- def _apply_operation (self , operation , value ):
20- """Common method for all in-place operations."""
21- operation (self .parent_array [:, self .column_idx ], value )
22- self .parent ._sync_to_blender ()
23- return self
24-
25- def __iadd__ (self , value ):
26- return self ._apply_operation (lambda arr , val : arr .__iadd__ (val ), value )
27-
28- def __isub__ (self , value ):
29- return self ._apply_operation (lambda arr , val : arr .__isub__ (val ), value )
30-
31- def __imul__ (self , value ):
32- return self ._apply_operation (lambda arr , val : arr .__imul__ (val ), value )
33-
34- def __itruediv__ (self , value ):
35- return self ._apply_operation (lambda arr , val : arr .__itruediv__ (val ), value )
36-
37- def __array__ (self , dtype = None ):
38- """Convert to array, handling optional dtype argument."""
39- column_data = self .parent_array [:, self .column_idx ]
40- return column_data .astype (dtype ) if dtype is not None else column_data
41-
42- def __eq__ (self , other ):
43- """Handle equality comparison."""
44- column_data = self .parent_array [:, self .column_idx ]
45-
46- if hasattr (other , "__array__" ):
47- return np .array_equal (column_data , np .asarray (other ))
48- return column_data == other
49-
50- def __array_wrap__ (self , out_arr , context = None ):
51- """Handle the output of NumPy ufuncs and other functions."""
52- self .parent_array [:, self .column_idx ] = out_arr
53- self .parent ._sync_to_blender ()
54- return self
55-
56- @property
57- def column_data (self ):
58- """Get the column data."""
59- return self .parent_array [:, self .column_idx ]
60-
61- def __getattr__ (self , name ):
62- """Delegate attribute access to the column data."""
63- column_data = self .parent_array [:, self .column_idx ]
64- if hasattr (column_data , name ):
65- return getattr (column_data , name )
66- raise AttributeError (
67- f"'{ self .__class__ .__name__ } ' object has no attribute '{ name } '"
68- )
69-
70-
716class AttributeArray (np .ndarray ):
727 """
738 A numpy array subclass that automatically syncs changes back to the Blender object.
@@ -109,6 +44,8 @@ def __new__(cls, obj: bpy.types.Object, name: str) -> "AttributeArray":
10944 arr ._blender_object = obj
11045 arr ._attribute = attr
11146 arr ._attr_name = name
47+ # Track the root array so that views can sync the full data
48+ arr ._root = arr
11249 return arr
11350
11451 def __array_finalize__ (self , obj ):
@@ -119,72 +56,11 @@ def __array_finalize__(self, obj):
11956 self ._blender_object = getattr (obj , "_blender_object" , None )
12057 self ._attribute = getattr (obj , "_attribute" , None )
12158 self ._attr_name = getattr (obj , "_attr_name" , None )
122-
123- def __eq__ (self , other ):
124- """Handle equality comparison for array objects."""
125- self_arr = np .asarray (self ).view (np .ndarray )
126-
127- if isinstance (other , AttributeArray ):
128- other_arr = np .asarray (other ).view (np .ndarray )
129- return np .array_equal (self_arr , other_arr )
130-
131- if isinstance (other , ColumnAccessor ):
132- return np .array_equal (self_arr , other .parent_array )
133-
134- if hasattr (other , "__array__" ):
135- other_arr = np .asarray (other )
136-
137- # Handle shape differences for column comparisons
138- if (
139- self_arr .shape != other_arr .shape
140- and other_arr .ndim == 1
141- and self_arr .ndim == 2
142- and other_arr .shape [0 ] == self_arr .shape [0 ]
143- ):
144- return any (
145- np .array_equal (self_arr [:, i ], other_arr )
146- for i in range (self_arr .shape [1 ])
147- )
148-
149- return np .array_equal (self_arr , other_arr )
150-
151- return self_arr == other
152-
153- def __getitem__ (self , key ):
154- """Get item with special handling for column operations."""
155- # Handle column operations: pos[:, 2]
156- if (
157- isinstance (key , tuple )
158- and len (key ) == 2
159- and isinstance (key [0 ], slice )
160- and key [0 ] == slice (None )
161- and isinstance (key [1 ], int )
162- ):
163- return ColumnAccessor (self , key [1 ])
164-
165- return super ().__getitem__ (key )
59+ # Preserve reference to the root array for syncing
60+ self ._root = getattr (obj , "_root" , self )
16661
16762 def __setitem__ (self , key , value ):
16863 """Set item and sync changes back to Blender."""
169- # Handle column operations: pos[:, 2] = value
170- if (
171- isinstance (key , tuple )
172- and len (key ) == 2
173- and isinstance (key [0 ], slice )
174- and key [0 ] == slice (None )
175- and isinstance (key [1 ], int )
176- ):
177- arr_view = np .asarray (self ).view (np .ndarray )
178- col_idx = key [1 ]
179-
180- if isinstance (value , ColumnAccessor ):
181- arr_view [:, col_idx ] = value .column_data
182- else :
183- arr_view [:, col_idx ] = value
184-
185- self ._sync_to_blender ()
186- return
187-
18864 super ().__setitem__ (key , value )
18965 self ._sync_to_blender ()
19066
@@ -224,7 +100,9 @@ def _sync_to_blender(self):
224100 if self ._blender_object is None :
225101 return
226102
227- data_to_sync = np .asarray (self ).view (np .ndarray )
103+ # Always sync using the root array to ensure full shape
104+ root = getattr (self , "_root" , self )
105+ data_to_sync = np .asarray (root ).view (np .ndarray )
228106 data_to_sync = self ._ensure_correct_shape (data_to_sync )
229107
230108 # Ensure float32 dtype
0 commit comments