Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
GKPath question
-
No, you just have to specify the argtypes and restype manually.
We probably ought to go back through old forum posts to find the various cases when the encoding parser fails, and monkey patch that function.
-
@JonB, after asking the question above, I have been trying to go the manual encoding route, with no real success.
What I have is an identity SKWarpGeometryGrid created with
gridWithColumns_rows_
. It reports the correct number of rows, cols and vertices, and self-identifies as an identity grid where the source and destination grids point to the same grid.Now I try to change the destination grid with
gridByReplacingDestPositions_
. This requiresvector_float2
and returns a new grid with the destination positions replaced.These are the destination points I would like to try:
dest = [ (0.0, -0.5), (0.5, 0.0), (1.0, 0.0), (0.0, 0.5), (0.5, 0.5), (1.0, 0.5), (0.0, 1.0), (0.5, 1.0), (1.0, 1.0), ]
(Only the second float of the first tuple is changed from the identity grid.)
I have this manual definition:
f = self.geometry.gridByReplacingDestPositions_ f.argtypes = [c_void_p] f.restype = c_void_p f.encoding = b'@@:@'
And these for creating the destination argument from the array above:
class float2(Structure): _fields_ = [('x',c_float),('y',c_float)] @classmethod def _get_float2(cls, positions): floats = [] for pos in positions: f = float2() f.x, f.y = pos floats.append(f) floats_array = (float2 * len(floats))(*floats) return floats_array
The method gets called and returns an updated SKWarpGeometryGrid, which unfortunately is still the same identity warp. This leads me to believe that my float2 is probably wrong and gets implicitly rejected without error.
I have also tried
gridWithColumns_rows_sourcePositions_destPositions_
, with same identity grid result.Any next steps to try would be greatly appreciated.
-
How many points does the source grid have? If you are just replacing the dest, I think you have to make sure the number is the same.
I'll play around with it -- do you have code that initializes the grid in the first place?
-
Okay, playing with this some more, I can’t event get valid points out of the destPositionsAt, etc. doing the same in playgrounds does provide valid values. The issue seems to be that libffi doesn’t seem to support SIMD, or at least it didn’t for the version used by Pythonista.
There might be some workarounds using MTLKit, but not sure
-
@JonB, I get expected values out with the following:
addr = ctypes.addressof(warp_geometry.destPositions().contents) for _ in range(9): p = float2.from_address(addr) print(p.x, p.y) addr += ctypes.sizeof(float2)
... but still cannot get the destGeometry set, even if I modify this same memory area and set it back.
-
-
@cvp, thanks, no I did not. But now that I did, seems to match the docs, except for the disheartening ”Warning: Unrecognized filer type: '1' using 'void*'” everywhere that the grids are being referred to.
-
Ahh, the extra pointer was throwing me off.
This is a pointer to a float2 array.Ok, so this does seem to work, both modifying and creating from scratch.
Note the somewhat simpler cast of the c_void_p directly to the POINTER type we want (you could also have set restype to POINTER(float2*n)), allowing list-like access to the contents rather than mucking about with addresses. Likewise, passing in can just use byref.I think there is a caveat that you probably have to be careful if you create from scratch, because float2 might have some memory alignment requirements that this doesn’t enforce.
from objc_util import * from ctypes import * NSBundle.bundleWithPath_('/System/Library/Frameworks/SpriteKit.framework').load() NSBundle.bundleWithPath_('/System/Library/Frameworks/Metal.framework').load() SKWarpGeometryGrid=ObjCClass('SKWarpGeometryGrid') class float2(Structure): _fields_ = [('x',c_float),('y',c_float)] def __repr__(self): 'convienence repr' return ' float2({},{})'.format(self.x,self.y) @classmethod def _get_float2(cls, positions): floats = [] for pos in positions: f = float2() f.x, f.y = pos floats.append(f) floats_array = (float2 * len(floats))(*floats) return floats_array def dump_float2array(ptr,n): data = cast(ptr, POINTER(float2*n)).contents for d in data: print(d) return data g=SKWarpGeometryGrid.gridWithColumns_rows_(3,3) '''note, destPositins returns a pointer to an arrayof float2. we can cast then access contents, which gves us easy array like access to data''' print('initial dest') dst0=dump_float2array(g.destPositions(), 9) '''it seems modifying the returned value directly modifies the original object. so, make a copy first. could have also done this in the dump fcn so we don't forget''' new_dst=(float2*9).from_buffer_copy(dst0) ''' just change one element to check that this works''' new_dst[2].x=.999 new_dst[2].y=.998 '''it appears that changing the returned memory changes the actual grid, maybe not what is intended. might be better to do some sort of memcopy before modifying''' print('initial dest after modifying memory') dump_float2array(g.destPositions(), 9) g2=g.gridByReplacingDestPositions_(byref(new_dst), restype=c_void_p, argtypes=[c_void_p]) print('new grid dest') dump_float2array(g2.destPositions(), 9) '''create from scratch''' src=float2._get_float2([[0,0],[0,.5],[0.5,0],[.5,.5]]) dst=float2._get_float2([[0,0],[0,.75],[0.75,0],[.75,.75]]) g3=SKWarpGeometryGrid.gridWithColumns_rows_sourcePositions_destPositions_(2,2,byref(src),byref(dst), restype=c_void_p, argtypes=[c_int,c_int, c_void_p,c_void_p]) print('from scratch') dump_float2array(g3.destPositions(), 4)
-
By the way, ctypes is smart enough to figure out
dest = [ (0.0, -0.5), (0.5, 0.0), (1.0, 0.0), (0.0, 0.5), (0.5, 0.5), (1.0, 0.5), (0.0, 1.0), (0.5, 1.0), (1.0, 1.0), ] new_dst=(float2*9)(*dest)
So, the _get_float2 method is not needed, or at least doesn’t need the loop.
This also works for nested struts,like
(CGRect*2)(((0,0),(50,75)),((50,0),(50,75)))
Where essentially the tuple is interpreted outside-> in, each nested tuple becomes the constructor arguments for the next lower level field, etc.
-
@JonB, color me impressed, once again.
Here is all that is needed, then:
warp = SKWarpGeometryGrid.gridWithColumns_rows_(2, 2) dest = [ (0.0, -0.5), (0.5, 0.0), (1.0, 0.0), (0.0, 0.5), (0.5, 0.5), (1.0, 0.5), (0.0, 1.0), (0.5, 1.0), (1.0, 1.0), ] new_dest = (float2*9)(*dest) g2 = warp.gridByReplacingDestPositions_( byref(new_dest), restype=c_void_p, argtypes=[c_void_p])
(Note that for some Apple-ish reason, the col & row numbers are one less than what you would think given the number of vertices.)
-