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.
Request for an App
-
I'm using an iPad 3. After deleting old versions of files that may have been causing the problem, your current git is working again. For some reason it seems easier to move points when it is not zoomed but that is a minor issue. Now I just need a way to enter a known distance that may not be part of the area calculation ( e.g. an internal room dimension). What I do now, which is somewhat clumsy, is pick 2 points with a known distance and change the scaling factor from the default of .08 until the distance shown matches the known. Then I have to restart the program (since the points used to determine the scale may not be part of the area), set the scale to the previously determined value and measure the area.
-
@JonB I added the (primitive) file picker snippet to your script to enable selecting an image from the photo gallery. The modified version is in my git as floorplanarea-2.py. I wish I could say I understood your code well enough to make more than trivial changes (e.g. scale setting mode with feet/inches, a way to save the image with the area calculation, etc.) but I sincerely appreciate the work you have already done.
-
Been busy, but I may try to pick this up again soon.
-
@JonB That would be great. In the meantime, I shall try to gain a better understanding of the script.
-
@JonB After not using the script for awhile I recently tried it and it is failing. When I run floorplanarea-6.py https://github.com/ifuchs/RoomAreaFinder/blob/master/floorplanarea-6.py, the file picker comes up, I select an image, the script just ends. If I try to run it again, I get a Value Error, "View is already being presented or animation is in progress".
Also is there a better way to use the selected image than the kluge I added which picks the image and saves to a temp file before calling the main routine (Room AreaView)? -
Strange behavior. I went back to try the app again and it worked ONCE. It has reverted to the behavior described a couple of days ago. It returns immediately after selecting a photo and then if I run it again, it gives a Value error (as if the animation is running already)
-
@JonB Any chance you could take a look at this? Thanks.
-
Is this failing due to a Pythonista bug? It was working on previous releases.
# coding: utf-8 #!python2 # coding: utf-8 import ui import photos class RoomAreaView(ui.View): ''' top level container, consisting of an imageview and an overlay. ideally this might also contain a menu bar, for changing the mode - for instance you might have a button for enabling room drawing, another one for editing points, another for zoom/pan''' def __init__(self, file): # create image view that fills this top level view. # since this RoomAreaView is the one that gets presented, it will be # resized automatically, so we want the imageview to stay tied to the # full view size, so we use flex. Also, the content mode is important # to prevent the image from being squished iv = ui.ImageView(frame=self.bounds) iv.flex = 'wh' iv.content_mode = ui.CONTENT_SCALE_ASPECT_FIT self.add_subview(iv) # right now, read in file from constructor. a future improvement would # be to have a file menu to select an image, which sets the iv.image iv.image = ui.Image.named(file) iv.touch_enabled = False # add the overlay. this is the touch sensitive 'drawing' layer, and # store a refernce to it. this is set to the same size as the # imageview rv = RoomAreaOverlay(frame=self.bounds, flex='wh') self.add_subview(rv) self.rv = rv def will_close(self): '''upon close, dump out the current area. do this by first getting the set of points. The zip notation lets us convert from a tuple of the form ((x0,y0),(x1,y1),...) to x=(x0,x1,...) and y=(y0,y1,...)''' x, y = zip(*self.rv.pts) print polygonArea(x, y, float(self.rv.scale.value)), self.rv.scale.value / 10 class RoomAreaOverlay(ui.View): '''A touch sensitive overlay. Touches add to a list of points, which are then used to compute area. Lengths are shown for each line segment, and a scaling parameter is used to adjust the length of drawn lines to known dimensions''' def __init__(self, *args, **kwargs): # call View.__init__ so we can handle any standard view constructor # arguments, such as frame, bg_color, etc ui.View.__init__(self, *args, **kwargs) self.touch_enabled = True # store list of points, and also current point for use in dragging self.pts = [] self.curridx = -1 # create a textfield to set scale. could be a slider instead # scale is in units of feet/pixel, or really measurementunits/pixel # it might be easier to have user enter this as 1/scale (pixel/measunit), so that it will be a number >1. # If using a Slider instead, use self.scale.value instead of # float(self.scale.text) elsewhere self.scale = ui.Slider(frame=(400, 0, 500, 30)) self.scale.action = self.set_scale self.add_subview(self.scale) # self.scale.text=self.scale.value # create a label showing current computer room area self.lbl = ui.Label(frame=(200, 0, 130, 30)) self.lbl.text = 'Area' self.add_subview(self.lbl) self.lbl.bg_color = 'white' def set_scale(self, sender): '''action for self.scale. called whenever scale is changed. when that happens we update the computation of room area, and also call set_needs_display, which forces a redraw, thus redrawing distance units''' self.update_area() self.set_needs_display() def update_area(self): '''update the area label by computing the polygon area with current scale value''' x, y = zip(*self.pts) area = polygonArea(x, y, self.scale.value / 10) self.lbl.text = 'Area: {} squnits'.format(area) def touch_began(self, touch): '''when starting a touch, fiest check if there are any points very near by. if so, set currpt to that to allow dragging previous points, if not, add a new point ''' self.curridx = -1 currpt = (touch.location.x, touch.location.y) # search existing points for a near match for i, p in enumerate(self.pts): if abs(ui.Point(*p) - ui.Point(*currpt)) < 20: self.curridx = i # if not match found, add a new point if self.curridx == -1: self.pts.append(currpt) self.set_needs_display() def touch_moved(self, touch): ''' update the current point, and redraw''' self.pts[self.curridx] = (touch.location.x, touch.location.y) self.set_needs_display() def touch_ended(self, touch): ''' called when lifting finger. append the final point to the permanent list of pts, then clear the current point, and redraw''' self.curridx = -1 self.set_needs_display() self.update_area() def draw(self): ''' called by iOS whenever set_needs_display is called, or whenever os decides it is needed, for instance view rotates''' # set color to red ui.set_color((1, 0, 0)) # create a copy of self.pts, and append the current point drawpts = self.pts if drawpts: # move path to first point: pth = ui.Path() pth.move_to(*drawpts[0]) p0 = drawpts[0] for p in drawpts[1:]: # for each point after the first, draw a line from the previous point, compute length vector L # draw the line segment pth.line_to(*p) # compute point which is halfway along line between the # start/end point. L is the vector pointing from start to # finish. H is the Point at the halfway, referenced back to # global origin L = (ui.Point(*p) - ui.Point(*p0)) P0 = ui.Point(*p0) H = P0 + L / 2 # halfway point # put text at the halfway point, containing length ofmsegment * # scale ui.draw_string('%3.2f' % abs( L * self.scale.value / 10), (H.x, H.y, 0, 0), color='red') # set starting point for next line segment p0 = p pth.stroke() # draw the path if len(drawpts) > 2: # 'close' the path to show area computed with ui.GState(): pth = ui.Path() pth.move_to(*drawpts[-1]) pth.line_to(*drawpts[0]) pth.set_line_dash([2, 3]) ui.set_color((1, .5, .5, .5)) pth.stroke() # create a 10 pixel circle at last entered point ui.Path.oval(drawpts[-1][0] - 5, drawpts[-1][1] - 5, 10, 10).fill() # show circles for previously entered points. smaller and lighter ui.set_color((1, .5, .5)) for p in drawpts[0:-1]: ui.Path.oval(p[0] - 3, p[1] - 3, 6, 6).fill() def polygonArea(X, Y, scale): '''compute scaled area of polygon,assuming it is closed ''' area = 0 j = len(X) - 1 for i in range(len(X)): area = area + (X[j] + X[i]) * (Y[j] - Y[i]) * scale**2 j = i return abs(area / 2) photos.pick_image().save("temp.jpg") v = RoomAreaView('temp.jpg') v.present('fullscreen')```
-
What is the error you are getting? PAste the full traceback.
I am getting a IOError: cannot write mode P as JPEG, which im looking at... -
Try this:
p=photos.pick_image() p.save("temp.jpg") #we need to allow some time for animation to completely complete import time time.sleep(1) v = RoomAreaView('temp.jpg') v.present()
I cant really explain why, but it seems that trying to present a view immediately after pick_image() doesnt quite allow the animation to complete, at least sometimes. Adding a little time seems to work. 1 second is probably overkill,myou can experiment with it.
-
The first time I execute this script after Pythonista is started, the file picker comes up, I select an image, the script ends ( i.e. The run triangle returns) and no window is opened. If I run it again, I get:
Traceback (most recent call last):
File "/private/var/mobile/Containers/Shared/AppGroup/B86CEE4E-E4A9-4BB4-9CA7-6E13BDA2C2A4/Pythonista3/Documents/floorplanarea-6.py", line 160, in <module>
v.present('fullscreen')
ValueError: View is already being presented or animation is in progressAt this point Pythonista needs to be quit or any app that tries to present a view will fail similarly.
-
Did you try my delay approach? You will need to force quit pythonista, then make the above mod.
-
Just tried it and it worked! Is there a better way to do the image selecting that does not involve saving the temp file at all?
Unrelated question: this script is extremely useful and would be much easier to use if I could select 2 points that define one known dimension and set the scale by entering it as feet and inches rather than using a slider. What I do now is select 2 points and move the slider until it is as close as I can get to the printed dimension but I can never get it quite right as the slider always changes as I take my finger off and the delta for a small movement is enough to make the positioning tricky.
Thanks so much for your expert help. -
I am (slowly) working on an update that will have a much nicer user interface, allow pinch zooming for precision point placement, and have support for multiple rooms, saving and reloading results, and presenting summaries etc.
But things go slowly...
-
That's great! I understand and I shall be patient. Would it help in designing the UI if I posted links to 1 or 2 typical floor plans to give a better idea of what I'm trying to do?
-
@jonb I would be happy to discuss use cases here or, if you prefer, outside the forum. One thing you did not mention is the ability to set scale, start entering points and then clear points/lines without reentering scale. Also,, while it does not affect the area calculation very much, it would be nice to have the option to force a line segment to be vertical or horizontal as most floorplans are oriented in this way and positioning the line is a bit tricky. Having the ability to store the plan with the scale and area would be VERY helpful.I am convinced that with the additions you mention, this could be successfully sold on the App Store.
-
I pushed a change which includes the ability to two finger pinch zoom, and two finger pan.
I made this change a long time ago, but i now added in your file picker. Points can be moved by dragging on an existing point, thus you can set your scale, and then move points, or if you dont get it quite exact, you can zoom in and tweak.I updated the scale textbox to allow simple math. For instance, if scal is currently 0.08, you draw a line which says 23.2, but the real distance is 25 you could enter 0.08*25/23.2, and the scale will automatically be set so your length is exact.
I also added a clear button to clear the path.
https://github.com/jsbain/RoomAreaFinder.git
anyway, just sort of temporary changes using the old ui.
-
@jonb Here is a "real world" image of a floor plan that I am trying to measure. If I zoom in, the red circle is sometimes too large to position accurately. Also, would be nice to be able to measure the total area as well as the area of internal rooms or spaces. Right now I can do either but not both on one plan. Also, might be better if the window were full screen?
-
The quickest method would probably be to import the jpg and trace it using the pen tool. Simply add a small amount of code to the traced object to compute the area dynamically. Although your situation is highly particular, I believe it will serve your needs. I'm currently working on that screenshot.
-
@Lesternixon Thanks. @jonb wrote a fantastic script that does exactly what I wanted. Unfortunately with the new release of Pythonista it fails due to a problem with the image module. Hopefully this will get fixed at some point.