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.
draw ui.Path within the coordinate system of a node
-
Hi,
I do not really understand how the frame of an
ui.Path
object works. If have cooked up a small scene that illustrates the problems I have. I am trying to draw anui.Path
that is matching points (coordinates) in the frame of a node. It seems that anui.Path
is always being centered on the center of the presentingShapeNode
.So my question is: How do I draw a path which is not centered on the center of its presenting
ShapeNode
- for example one that only lives in the x+y+ frame ? I would like to be able to actually draw within the coordinates system/frame of the presenting ShapeNode .Cheers,
zipitfrom scene import * class MyScene (Scene): def setup(self): sx, sy = self.size.w * .5, self.size.h * .5 # I would expect the white rect to have its lower left corner # at the center of the screen. But it does not, it is sitting # on the origin of the node, x and y seem to have no effect. self.white = ShapeNode(ui.Path.rect(sx, sy, 200, 200), parent=self, position=(0, 0)) # a reference rect as our white rect is kinda off screen self.red = ShapeNode(ui.Path.rect(0, 0, 150, 150), parent=self, fill_color = 'red', position=(sx, sy)) # Here I would expect a line from the right top corner # of the red rect going to a point (25, 50) in the # top right direction. But again the path is centered # on the node and also the y coordinate is being inverted. path = ui.Path() path.move_to(75, 75) path.line_to(sx + 100, sy + 125) path.line_width = 3 self.cyan = ShapeNode(path, parent=self.red, stroke_color='cyan', position=(0, 0)) if __name__ == '__main__': run(MyScene(), show_fps=False)```
-
@zipit said:
from scene import *
class MyScene (Scene):
def setup(self):
sx, sy = self.size.w * .5, self.size.h * .5
# I would expect the white rect to have its lower left corner
# at the center of the screen. But it does not, it is sitting
# on the origin of the node, x and y seem to have no effect.
self.white = ShapeNode(ui.Path.rect(sx, sy, 200, 200),
parent=self,
position=(0, 0))
# a reference rect as our white rect is kinda off screen
self.red = ShapeNode(ui.Path.rect(0, 0, 150, 150),
parent=self,
fill_color = 'red',
position=(sx, sy))
# Here I would expect a line from the right top corner
# of the red rect going to a point (25, 50) in the
# top right direction. But again the path is centered
# on the node and also the y coordinate is being inverted.
path = ui.Path()
path.move_to(75, 75)
path.line_to(sx + 100, sy + 125)
path.line_width = 3
self.cyan = ShapeNode(path,
parent=self.red,
stroke_color='cyan',
position=(0, 0))if name == 'main':
run(MyScene(), show_fps=False)```anchor_point can help in some cases.
but, the problem seems to be that, no matter what you draw in a shape node, the coordinate system of the path is not honored, instead the bbox of the resulting path is computed, and then you can anchor it to a point relative to the bounding box. That makes it easier to do some things, but harder for others... for instance a diagonal line from (0,0) to (x,y), depending on the sign of x and y, the same anchor point (0,0) refers to either the start, end, or diagonal corner of the line.
I seem to recall a forum post with a workaround that I posted, but my Google-fu is failing me this morning. I think you basically have to keep track of the math yourself on what the "center" and bounds of the path will be, and the use the anchor point to keep things aligned.
-
A
ShapeNode
is basically just aSpriteNode
that automatically creates itsTexture
using a path. The code is pretty simple, and you can actually look at it yourself by opening "Modules & Templates/Standard Library/site-packages/scene.py".If you want full control over the resulting texture's size, you could simply use a vanilla
SpriteNode
and draw the shape yourself.import ui, scene def texture_from_path(path, fill_color, width, height): with ui.ImageContext(width, height) as ctx: ui.set_color(fill_color) path.fill() img = ctx.get_image() return scene.Texture(img)
This is simplified a bit, but it shows the basic approach. You can look at
ShapeNode
's source code if you need its support of shadows, outlines and such. -
Ah, okay, that does make sense now. I do have a follow up question though. Is it possible to access the points of a path after it has been created? edit: and with access I mean read them.
Cheers,
zipit -
@zipit No, that's not possible, you'd have to keep a reference to the points yourself.
-
Okay, thanks. I gave the thread a more meaningful title. I cannot provide a full example as my code relies on other stuff, but here is a snippet how I did solve the problem now (a bit clunky). Maybe it will help someone in the future. I wrote two comments so that the code does make some sense.
def draw_line(self): ''' ''' if self.line is not None: self.line.remove_from_parent() minx, miny = None, None path = ui.Path() path.line_width = 2 # self is a pythonista node. self.anchors are some phythonista node # objects that are children of self. We want to draw a line through all # these anchors within the coord system of self. for i, anchor in enumerate(self.anchors): p = anchor.position # get/update the lower left corner minimum minx, miny = (p.x if minx is None else min(minx, p.x), p.y if miny is None else min(miny, p.y)) if i == 0: path.move_to(p.x, -p.y) else: path.line_to(p.x, -p.y) # the offset(position) of our node has to be the lower left corner # point plus the center vector of our path self.line = ShapeNode(path, stroke_color='green', fill_color='transparent', position = (minx + path.bounds.w * .5, miny + path.bounds.h * .5), parent=self)
-
Hi, looking back to this example, I tried to play a bit and discovered what seems to be an issue:
from scene import * import time class MyScene (Scene): def setup(self): sx, sy = self.size.w, self.size.h self.red = ShapeNode(ui.Path.rect(0,0,sx,sy),parent=self,fill_color='red',position=(0,0),anchor_point=(0,0)) path = ui.Path() path.line_width = 3 path.move_to(0,0) path.line_to(100,0) #path.line_to(0,100) # Uncommenting this line modifies the drawing of both previous lines self.cyan = ShapeNode(path,parent=self.red,stroke_color='white',position=(0,0),anchor_point=(0,0)) if __name__ == '__main__': run(MyScene())
Removing the comment modifies how both previous lines are drawn!
I tried to understand but it is still unclear for me -
@mikeno, without running this, I think what you are seeing is that the commented line changes the bounding box of the whole path and thus the node, and as the node
position
defines the center position, the path seems to move to left. -
Hi Mikael, thx for replying but is there a way to avoid this?
-
@mikeno, do you need to use
scene
, or in other words, what are you trying to do? -
I just want to draw a filled polygone following numerous coordinates, I already managed to do it with ui and canvas but only scene gives me the possibility to use full screen
-
@mikeno try this, and to close, swipe down with two fingers
import ui class my(ui.View): def draw(self): w,h = ui.get_screen_size() path = ui.Path()#.rect(0,0,w,h) path.line_width = 3 ui.set_color('red') path.move_to(100,100) path.line_to(200,100) path.line_to(100,200) path.close() path.fill() #path.stroke() v = my() v.present('fullscreen',hide_title_bar=True)
-
Yes thx it works but it doesn’t with several polygons
-
... and then, I need to redraw in order to see the polygons in a different zoom factor. As I wrote, I already tried with ui but I stoped because I could not redraw, that’s why I tried with scene
-
@mikeno said:
it doesn’t with several polygons
import ui class my(ui.View): def draw(self): w,h = ui.get_screen_size() path1 = ui.Path() path1.line_width = 3 ui.set_color('red') path1.move_to(100,100) path1.line_to(200,100) path1.line_to(100,200) path1.close() path1.fill() path2 = ui.Path()#.rect(0,0,w,h) path2.line_width = 3 ui.set_color('blue') path2.move_to(300,100) path2.line_to(400,100) path2.line_to(300,200) path2.close() path2.fill() v = my() v.present('fullscreen',hide_title_bar=True)
-
-
Thx to both of you, concerning the gestures, it seems to be a little bit complicated but I will try
-
My last issue is that the drawing doesn’t refresh even when I call explicitly drawTest() as you can see in the example below:
import ui class my(ui.View): def __init__(self, *args, **kwargs): self.factor = .5 def draw(self): print('draw()') self.drawTest() def drawTest(self): print('drawTest()') print('%.1f' % (self.factor)) w,h = ui.get_screen_size() path1 = ui.Path() path1.line_width = 3 ui.set_color('red') path1.move_to(100*self.factor,100*self.factor) path1.line_to(200*self.factor,100*self.factor) path1.line_to(100*self.factor,200*self.factor) path1.close() path1.fill() path2 = ui.Path()#.rect(0,0,w,h) path2.line_width = 3 ui.set_color('blue') path2.move_to(300*self.factor,100*self.factor) path2.line_to(400*self.factor,100*self.factor) path2.line_to(300*self.factor,200*self.factor) path2.close() path2.fill() def touch_began(self, touch): print('touch_began()') self.factor += .5 self.drawTest() def will_close(self): print('will_close()') v = my() v.present('fullscreen',hide_title_bar=True)
It redraws only when I turn the iPad, could you please tell me how I can force the refresh with the right zoom factor?
-
@mikeno start from
import ui class my(ui.View): def __init__(self, *args, **kwargs): self.w,self.h = ui.get_screen_size() iv = ui.ImageView(name='iv') iv.frame = (0,0,self.w,self.h) self.add_subview(iv) self.factor = .5 self.update_interval = 1 def update(self): with ui.ImageContext(self.w,self.h) as ctx: path1 = ui.Path() path1.line_width = 3 ui.set_color('red') path1.move_to(100*self.factor,100*self.factor) path1.line_to(200*self.factor,100*self.factor) path1.line_to(100*self.factor,200*self.factor) path1.close() path1.fill() path2 = ui.Path()#.rect(0,0,w,h) path2.line_width = 3 ui.set_color('blue') path2.move_to(300*self.factor,100*self.factor) path2.line_to(400*self.factor,100*self.factor) path2.line_to(300*self.factor,200*self.factor) path2.close() path2.fill() ui_image = ctx.get_image() self['iv'].image = ui_image def touch_began(self, touch): self.factor += .5 def will_close(self): print('will_close()') v = my() v.present('fullscreen',hide_title_bar=True)
-
Whao, I’m impressed, thank you!
But I don’t understand why it doesn’t work in my last example