Created
January 17, 2017 13:49
-
-
Save GrantTrebbin/7cb290b91f2192a2551339e8b053c2f7 to your computer and use it in GitHub Desktop.
Create and Arrange Rectangles with relationships.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# rectangleBuilder | |
# Grant Trebbin - 2017_01_17 | |
import svgwrite | |
class Rectangle: | |
def __init__(self, rectangle_width, rectangle_height): | |
# Calculate coordinates for corners and middle of sides for rectangle | |
# These are offset from the bottom left | |
# B = bottom | |
# T = top | |
# L = left | |
# R = right | |
# M = middle | |
self.coordinates = {'BL': [0, 0], | |
'ML': [0, rectangle_height / 2], | |
'TL': [0, rectangle_height], | |
'TM': [rectangle_width / 2, rectangle_height], | |
'TR': [rectangle_width, rectangle_height], | |
'MR': [rectangle_width, rectangle_height / 2], | |
'BR': [rectangle_width, 0], | |
'BM': [rectangle_width / 2, 0]} | |
# Position of the bottom left corner | |
self.offset = [0, 0] | |
# information about the rectangle | |
self.dimensions = [rectangle_width, | |
rectangle_height, | |
rectangle_width * rectangle_height] | |
def __call__(self, marker): | |
# When called, the Rectangle object will return the coordinates of the | |
# specified marker. For Example | |
# | |
# test_rectangle = Rectangle(100, 50) | |
# test_rectangle('TM') | |
# | |
# will return a list containing the xy coordinates for the top middle | |
# [50, 50] | |
marker_relative = self.coordinates[marker] | |
marker_absolute = [marker_relative[0] + self.offset[0], | |
marker_relative[1] + self.offset[1]] | |
return marker_absolute | |
def __str__(self): | |
# When printed, the coordinates of all four corners are displayed | |
return str([self("BL"), self("TL"), self("TR"), self("BR")]) | |
def position(self, marker, location, move_relative): | |
# Place the rectangle at certain xy position in the world | |
# Marker is the xy coordinate on the rectangle to use as a reference | |
# point. The rectangle will be shifted so that this point is located | |
# at the location variable. Move_relative allows the rectangle to be | |
# shifted from the final position. For Example | |
# | |
# A = rectangle(10, 50) | |
# B = rectangle(20, 100) | |
# A.position('BM', [0,0], [0,0]) | |
# B.position('BM', A('TM'), [1 , 1]) | |
# | |
# Rectangle A is positioned so that its bottom middle point is at the | |
# origin. Rectangle B is positioned so that its bottom middle is | |
# shifted 1 up and 1 to the right of the top middle of rectangle A | |
marker_coordinates = self.coordinates[marker] | |
self.offset = [move_relative[0] + location[0] - marker_coordinates[0], | |
move_relative[1] + location[1] - marker_coordinates[1]] | |
def make_svg_coord_transform(total_width, total_height, minimum_x, minimum_y): | |
# The test box was designed using the standard xy coordinate system for | |
# mathematics. SVG is different the y axis is flipped. | |
# This changes coordinates so that the SVG output is right. | |
return lambda v: [v[0] - minimum_x, total_height - v[1] + minimum_y] | |
# Define box parameters | |
#d = 230 + 20 | |
#w = 166 + 20 | |
#h = 54 + 14 + 14 + 10 + 10 | |
w = 125.4 | |
d = 77.5 | |
h = 16.9 | |
t = 4.2 | |
alpha = 0.55 | |
b = alpha * t | |
gt = 1 | |
gi = 1 | |
gs = 1 | |
# Create rectangles | |
rA = Rectangle(w + 4*t, (d + 2*b - gt) / 2) | |
rB = Rectangle(w + 2*b, h + t + 2*b) | |
rC = Rectangle(w + 2*t + 2*b, d + 2*b) | |
rD = Rectangle(w + 2*b, h + t + 2*b) | |
rE = Rectangle(w + 4 * t, (d + 2 * b - gt) / 2) | |
rF = Rectangle(h + 2*b, d + 2*t) | |
rG = Rectangle((w + 2*t + 2*b - gi) / 2, d) | |
rH = Rectangle(h + 2*b, d + 2*t) | |
rI = Rectangle((w + 2*t + 2*b - gi) / 2, d) | |
rJ = Rectangle((d + 2*b - gs) / 2, h) | |
rK = Rectangle((d + 2*b - gs) / 2, h) | |
rL = Rectangle((d + 2*b - gs) / 2, h) | |
rM = Rectangle((d + 2*b - gs) / 2, h) | |
# Position the rectangles by defining relationships | |
rA.position("BM", [0, 0], [0, 0]) | |
rB.position("BM", rA("TM"), [0, 0]) | |
rC.position("BM", rB("TM"), [0, 0]) | |
rD.position("BM", rC("TM"), [0, 0]) | |
rE.position("BM", rD("TM"), [0, 0]) | |
rF.position("MR", rC("ML"), [0, 0]) | |
rG.position("MR", rF("ML"), [0, 0]) | |
rH.position("ML", rC("MR"), [0, 0]) | |
rI.position("ML", rH("MR"), [0, 0]) | |
rJ.position("TR", rD("TL"), [0, -t - b]) | |
rK.position("TL", rD("TR"), [0, -t - b]) | |
rL.position("BR", rB("BL"), [0, t + b]) | |
rM.position("BL", rB("BR"), [0, t + b]) | |
# Create a list of the outline coordinates | |
outline = [rA('BL'), rA('TL'), rB('BL'), rL('BR'), rL('BL'), | |
rL('TL'), rL('TR'), rB('TL'), rC('BL'), rF('BR'), | |
rF('BL'), rG('BR'), rG('BL'), rG('TL'), rG('TR'), | |
rF('TL'), rF('TR'), rC('TL'), rD('BL'), rJ('BR'), | |
rJ('BL'), rJ('TL'), rJ('TR'), rD('TL'), rE('BL'), | |
rE('TL'), rE('TR'), rE('BR'), rD('TR'), rK('TL'), | |
rK('TR'), rK('BR'), rK('BL'), rD('BR'), rC('TR'), | |
rH('TL'), rH('TR'), rI('TL'), rI('TR'), rI('BR'), | |
rI('BL'), rH('BR'), rH('BL'), rC('BR'), rB('TR'), | |
rM('TL'), rM('TR'), rM('BR'), rM('BL'), rB('BR'), | |
rA('TR'), rA('BR'), rA('BL')] | |
# Create a list of the start and end coordinates of each bend line | |
bends = [[rB('BL'), rB('BR')], | |
[rB('TL'), rB('TR')], | |
[rD('BL'), rD('BR')], | |
[rD('TL'), rD('TR')], | |
[rC('TL'), rC('BL')], | |
[rC('TR'), rC('BR')], | |
[rG('TR'), rG('BR')], | |
[rI('TL'), rI('BL')], | |
[rJ('TR'), rJ('BR')], | |
[rK('TL'), rK('BL')], | |
[rL('TR'), rL('BR')], | |
[rM('TL'), rM('BL')]] | |
# Calculate the extents of the rectangles | |
rectangles = [rA, rB, rC, rD, rE, rF, rG, rH, rI, rJ, rK, rL, rM] | |
min_x = 0 | |
max_x = 0 | |
min_y = 0 | |
max_y = 0 | |
for rectangle in rectangles: | |
corners = ['TL', 'TR', 'BL', 'BR'] | |
for corner in corners: | |
test_coord = rectangle(corner) | |
if test_coord[0] > max_x: | |
max_x = test_coord[0] | |
if test_coord[0] < min_x: | |
min_x = test_coord[0] | |
if test_coord[1] > max_y: | |
max_y = test_coord[1] | |
if test_coord[1] < min_y: | |
min_y = test_coord[1] | |
# Generate the transform to make coordinates appear correct in an SVG file | |
height = max_y - min_y | |
width = max_x - min_x | |
SVG_coord_transform = make_svg_coord_transform(width, height, min_x, min_y) | |
# Transform the outline and bend lines for the SVG | |
SVG_points = [] | |
for point in outline: | |
SVG_points.append(SVG_coord_transform(point)) | |
SVG_lines = [] | |
for line in bends: | |
SVG_lines.append([SVG_coord_transform(line[0]), | |
SVG_coord_transform(line[1])]) | |
# Draw the SVG | |
dwg = svgwrite.Drawing('test.svg', | |
size=(str(width)+'mm', str(height)+'mm'), | |
viewBox=('0 0 ' + str(width) + ' ' + str(height))) | |
for line in SVG_lines: | |
dwg.add(dwg.line(line[0], line[1], | |
stroke='red', | |
fill='none', | |
stroke_width=1)) | |
dwg.add(dwg.polyline(SVG_points, stroke='black', fill='none', stroke_width=1)) | |
dwg.save() | |
# Find all the horizontal edges. Add a list of the x coord and the start and | |
# end y coordinates to a list | |
horizontal_lines = [] | |
vertical_lines = [] | |
for rect in rectangles: | |
horizontal_lines.append([rect('TL')[1], rect('TL')[0], rect('TR')[0]]) | |
horizontal_lines.append([rect('BL')[1], rect('BL')[0], rect('BR')[0]]) | |
vertical_lines.append([rect('TL')[0], rect('BL')[1], rect('TL')[1]]) | |
vertical_lines.append([rect('TR')[0], rect('BR')[1], rect('TR')[1]]) | |
# When drawing the box an offset from the bottom or left edge may be needed | |
x_plot_offset = 30 | |
y_plot_offset = 30 | |
# Generate the plot instructions rounded to the nearest 0.5 | |
offset_horizontal_lines = [] | |
for line in horizontal_lines: | |
offset_horizontal_lines.append([0.5 * round((line[0] - min_y + y_plot_offset)/0.5), | |
0.5 * round((line[1] - min_x + x_plot_offset)/0.5), | |
0.5 * round((line[2] - min_x + x_plot_offset)/0.5)]) | |
offset_vertical_lines = [] | |
for line in vertical_lines: | |
offset_vertical_lines.append([0.5 * round((line[0] - min_x + x_plot_offset)/0.5), | |
0.5 * round((line[1] - min_y + y_plot_offset)/0.5), | |
0.5 * round((line[2] - min_y + y_plot_offset)/0.5)]) | |
# Sort the horizontal and vertical line sections to make plotting easier | |
offset_horizontal_lines.sort(key=lambda v: (v[0], v[1])) | |
offset_vertical_lines.sort(key=lambda v: (v[0], v[1])) | |
# Calculate the total area | |
total_area = rA.dimensions[2] + rB.dimensions[2] + rC.dimensions[2] +\ | |
rD.dimensions[2] + rE.dimensions[2] + rF.dimensions[2] +\ | |
rG.dimensions[2] + rH.dimensions[2] + rI.dimensions[2] +\ | |
rJ.dimensions[2] + rK.dimensions[2] + rL.dimensions[2] +\ | |
rM.dimensions[2] | |
# Print out all the stats and the cut drawing instructions. | |
print('Item width =', w, '\nItem depth =', d, '\nItem height =', h) | |
print('Template width =', width, '\nTemplate height =', height) | |
print('Total area =', round(total_area)) | |
print("Cut and bend list") | |
print("Horizontal Lines") | |
for line in offset_horizontal_lines: | |
print(line[0], ', (', line[1], '-', line[2], ') length =', line[2] - line[1]) | |
print("\nVertical Lines") | |
for line in offset_vertical_lines: | |
print(line[0], ', (', line[1], '-', line[2], ') length =', line[2] - line[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment