#!/usr/bin/env python # (should work in either Python 2 or Python 3) # flatplan 1.3 (c) 2016, 2020 Silas S. Brown. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This is a program to draw an SVG diagram of a flat, using # input that can be collected by a person with low vision and # a centimetre tape measure. Area and wall length are noted # and squared-paper backing is also added. You can also # include the opening arcs of the internal doors. # To measure the flat, start measuring at any wall, and trace # your way around the entire flat until you come back to the # same position. This in effect means every room's length and # width measurements will be taken TWICE, for error checking # (if the line doesn't come back to the same spot, you made a # mistake, which can also be manifest in the two ends of some # door frame not matching up with each other). # For best results, include protrusions of door frames, depth # of windows, etc. (Walls are much less likely to match up # if door-frame protrusion is not taken into account.) # The program assumes the flat is all on one level (hence "flat"), # that it includes only right-angles, and that all walls are # connected to the outside walls (no "islands"). This is # probably valid for nearly all UK flats. # As you go, imagine an ant (or similar) walking around the # wall, and write the measurements as follows: # number = walk that number of centimetres forward # L = turn 90 degrees left # R = turn 90 degrees right # D or E = a door with specified width. # This should be noted when the ant is at the door's hinge # and is facing in the direction of the door's handle; # D = door opens to the right, E = door opens to the lEft. # The position of the ant is not changed. If followed by # more walking in the same direction (e.g. to get through the # doorway), add a separator character (e.g. a colon) between # the two numbers. # If you like, you can ''.join([...]) a list of strings, which # allows you to put comments on some of the measurements. # Place your string into s below: s = ''.join([ "500", "R500", "R500", "R500"]) margin_cm = 100 include_area_summary = True include_doors = True include_grid_lines = True # Where to find history: # on GitHub at https://github.com/ssb22/bits-and-bobs # and on GitLab at https://gitlab.com/ssb22/bits-and-bobs # and on BitBucket https://bitbucket.org/ssb22/bits-and-bobs # and at https://gitlab.developers.cam.ac.uk/ssb22/bits-and-bobs # and in China: https://gitee.com/ssb22/bits-and-bobs # ------------------------------------------------------- xd=0;yd=-1 # start pointing up (SVG origin is top left) curX = curY = oldX = oldY = 0 minX = minY = maxX = maxY = 0 lines = [] ; doors = [] def turnLeft(xd,yd): if yd: return yd,0 else: return 0,-xd def turnRight(xd,yd): if yd: return -yd,0 else: return 0,xd import re for c in re.findall("[lr]|[de]?[0-9.]+",s.lower()): if c=='l': xd,yd = turnLeft(xd,yd) elif c=='r': xd,yd = turnRight(xd,yd) elif c[0] in 'de': radius = float(c[1:]) handleX = curX + radius*xd handleY = curY + radius*yd if c[0]=='d': xd2,yd2 = turnRight(xd,yd) else: xd2,yd2 = turnLeft(xd,yd) openX = curX + radius*xd2 openY = curY + radius*yd2 if c[0]=='d': sweep = 1 else: sweep = 0 doors.append((handleX,handleY,radius,sweep,openX,openY,curX,curY)) elif c: oldX,oldY = curX,curY curX += xd*float(c) curY += yd*float(c) minX = min(minX,curX) minY = min(minY,curY) maxX = max(maxX,curX) maxY = max(maxY,curY) lines.append((oldX,oldY,curX,curY)) minX-=margin_cm ; maxX+=margin_cm ; minY-=margin_cm ; maxY+=margin_cm # help programs that display paper boundaries: xOffset = -minX ; yOffset = -minY minX += xOffset ; maxX += xOffset minY += yOffset ; maxY += yOffset try: xrange except: xrange = range # Python 3 for i in xrange(len(lines)): a,b,c,d = lines[i] a += xOffset ; c += xOffset b += yOffset ; d += yOffset lines[i] = a,b,c,d for i in xrange(len(doors)): a,b,r,s,c,d,e,f = doors[i] a += xOffset ; c += xOffset ; e += xOffset b += yOffset ; d += yOffset ; f += yOffset doors[i] = a,b,r,s,c,d,e,f # area calculation (by flood-filling the outside and counting the rest) : assert minX==minY==0 inside = [] ; totalLineLen = 0 for x in xrange(int(maxX)+1): inside.append([1]*(int(maxY)+1)) # don't say *int(maxX): it creates aliases of the same list errorLine = lines[-1][-2:]+lines[0][:2] # completes the path in case of error for a,b,c,d in lines+[errorLine]: if a>c: c,a = a,c if b>d: b,d = d,b for x in xrange(int(a),int(c)+1): for y in xrange(int(b),int(d)+1): inside[x][y] = 0 totalLineLen += c-a + d-b fillQ = [(0,0)] while fillQ: x,y = fillQ[0] ; del fillQ[0] if inside[x][y]: inside[x][y] = 0 if x>0: fillQ.append((x-1,y)) if x0: fillQ.append((x,y-1)) if y """ % (maxX-minX,maxY-minY,minX,minY,maxX,maxY)) # grid lines: def doGrid(colour,step): print ("""""" % colour) for x in xrange(int(minX),int(maxX),step): print ("""""" % (x,minY,x,maxY)) for y in xrange(int(minY),int(maxY),step): print ("""""" % (minX,y,maxX,y)) print ("") if include_grid_lines: doGrid("cyan",10) ; doGrid("red",100) # the wall itself: print ("""""") for l in lines: print ("""""" % l) print ("") if include_doors: for x1,y1,r,sweep,x2,y2,cx,cy in doors: print ("""""") % (x1,y1,r,r,sweep,x2,y2,cx,cy,x1,y1) if include_area_summary: print ("""Area = %s""" % area) print ("")