#!/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
")