Skip to content

A variety of fixes and features #795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ repos:
hooks:
- id: mypy
args: [ '--ignore-missing-imports' ]
additional_dependencies: [ 'types-PyYAML' ]
files: '^boxes/.*\.py$'
- id: mypy
args: [ '--ignore-missing-imports' ]
Expand All @@ -82,6 +83,10 @@ repos:
args: [ '--ignore-missing-imports' ]
additional_dependencies: [ 'types-Markdown' ]
files: '^scripts/boxesserver$'
- id: mypy
args: [ '--ignore-missing-imports' ]
additional_dependencies: [ 'types-PyYAML' ]
files: '^scripts/boxes_generator$'
- id: mypy
args: [ '--ignore-missing-imports' ]
files: '^setup.py$'
Expand Down
44 changes: 24 additions & 20 deletions boxes/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
import shutil
import subprocess
import tempfile

import io
from boxes.drawing import Context, LBRN2Surface, PSSurface, SVGSurface


class Formats:

pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", "pstoedit.exe"]
pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", r"C:\Program Files\pstoedit\pstoedit.exe", "pstoedit.exe"]
ps2pdf_candidates = ["/usr/bin/ps2pdf", "ps2pdf", "ps2pdf.exe"]

_BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps', 'lbrn2']
Expand Down Expand Up @@ -83,23 +83,27 @@ def convert(self, data, fmt):

if fmt not in self._BASE_FORMATS:
fd, tmpfile = tempfile.mkstemp()
os.write(fd, data.getvalue())
os.close(fd)
fd2, outfile = tempfile.mkstemp()

cmd = self.formats[fmt].format(
pstoedit=self.pstoedit,
ps2pdf=self.ps2pdf,
input=tmpfile,
output=outfile).split()

result = subprocess.run(cmd)
os.unlink(tmpfile)
if result.returncode:
# XXX show stderr output
raise ValueError("Conversion failed. pstoedit returned %i\n\n %s" % (result.returncode, result.stderr))
data = open(outfile, 'rb')
os.unlink(outfile)
os.close(fd2)
try:
os.write(fd, data.getvalue())
os.close(fd)
fd2, outfile = tempfile.mkstemp()
try:
cmd = self.formats[fmt].format(
pstoedit=self.pstoedit,
ps2pdf=self.ps2pdf,
input=tmpfile,
output=outfile).split()
result = subprocess.run(cmd)

if result.returncode:
# XXX show stderr output
raise ValueError("Conversion failed. pstoedit returned %i\n\n %s" % (result.returncode, result.stderr))
with open(outfile, 'rb') as ff:
data = io.BytesIO(ff.read())
finally:
os.close(fd2)
os.unlink(outfile)
finally:
os.unlink(tmpfile)

return data
125 changes: 119 additions & 6 deletions boxes/generators/gridfinitybase.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(self) -> None:
self.argparser.add_argument("--pad_radius", type=float, default=0.8, help="The corner radius for each grid opening. Typical is 0.8,")
self.argparser.add_argument("--panel_x", type=int, default=0, help="the maximum sized panel that can be cut in x direction")
self.argparser.add_argument("--panel_y", type=int, default=0, help="the maximum sized panel that can be cut in y direction")
self.argparser.add_argument("--base_type", type=str, default="standard", choices=["standard", "refined"])


def generate_grid(self, nx, ny, shift_x=0, shift_y=0):
Expand All @@ -83,6 +84,94 @@ def generate_grid(self, nx, ny, shift_x=0, shift_y=0):
y = ly+((pitch // 2)-ofs)*yoff
self.hole(x, y, d=dia)

def generate_refined_grid(self, nx, ny, shift_x=0, shift_y=0, dovetails=True):
radius, pad_radius = self.radius, self.pad_radius
pitch = self.pitch
opening = self.opening

for col in range(nx):
for row in range(ny):
lx = col*pitch+pitch/2 + shift_x
ly = row*pitch+pitch/2 + shift_y

self.rectangularHole(lx, ly, opening, opening, r=radius, color=Color.ETCHING)
self.hole(lx, ly, d=17)
# 0,0 is bottom-left grid
if dovetails:
if col == 0:
self.plate_to_plate_hole(lx, ly, "<")
if row == 0:
self.plate_to_plate_hole(lx, ly, "v")
if row == (ny - 1):
self.plate_to_plate_hole(lx, ly, "^")
if col == (nx - 1):
self.plate_to_plate_hole(lx, ly, ">")
if self.cut_pads_mag_diameter > 0:
ofs = self.cut_pads_mag_offset
# make the pads slightly smaller for press fit
dia = self.cut_pads_mag_diameter - 0.5
for xoff, yoff in ((1,1), (-1,1), (1,-1), (-1,-1)):
x = lx+((pitch // 2)-ofs)*xoff
y = ly+((pitch // 2)-ofs)*yoff
self.hole(x, y, d=dia)

@restore
def plate_to_plate_hole(self, ctr_x, ctr_y, pos):
if pos == "<":
self.moveTo(ctr_x - self.pitch/2, ctr_y - 3 + self.burn, 0)
elif pos == "v":
self.moveTo(ctr_x + 3 - self.burn, ctr_y - self.pitch/2, 90)
elif pos == "^":
self.moveTo(ctr_x - 3 + self.burn, ctr_y + self.pitch/2, -90)
elif pos == ">":
self.moveTo(ctr_x + self.pitch/2, ctr_y + 3 - self.burn, 180)

self.edge(3)
self.corner(-53, 0)
self.edge(5)
self.corner(53, 0)
self.edge(3)
self.corner(90, 0)
self.edge(13.5 - (self.burn*2))
self.corner(90, 0)
self.edge(3)
self.corner(53, 0)
self.edge(5)
self.corner(-53, 0)
self.edge(3)

@restore
def plate_to_plate_tab(self, x, y):
""" 3mm
5.325mm/-----
|-------/ 5mm
| 6 mm 14mm
|
"""
self.edge(3)
self.corner(53, 0)
self.edge(5)
self.corner(-53, 0)
self.edge(6)
self.corner(-53, 0)
self.edge(5)
self.corner(53, 0)
self.edge(3)
self.corner(90, 0)
self.edge(13.5)
self.corner(90, 0)
self.edge(3)
self.corner(53, 0)
self.edge(5)
self.corner(-53, 0)
self.edge(6)
self.corner(-53, 0)
self.edge(5)
self.corner(53, 0)
self.edge(3)
self.corner(90, 0)
self.edge(13.5)

def subdivide_grid(self, X, Y, A, B):
# Calculate the number of subdivisions needed in each dimension
num_x = math.ceil(X / A)
Expand Down Expand Up @@ -141,6 +230,21 @@ def render(self):
# if both size_y and y were provided, y takes precedence
self.size_y = max(self.size_y, self.y*self.pitch)


self.exact_size = ((self.size_x == self.x*self.pitch) and (self.size_y == self.y*self.pitch))

# make tabs for refined bases if:
# - height is 0 (no need for dovetails if there are walls), and
# - the box is exact sized (no need for dovetails if the box is not exactly on pitch)
if self.h == 0 and self.base_type == "refined" and self.exact_size:
num_tabs = 8
for ii in range(num_tabs):
dontdraw = self.move(17, 14, "right", before=True)
if not dontdraw:
self.plate_to_plate_tab(0,0)
self.move(17, 14, "right")
self.moveTo(-(17+self.spacing)*8, 15)

if self.panel_x != 0 and self.panel_y != 0:
self.render_split(self.size_x, self.size_y, self.h, self.x, self.y, self.pitch, self.m)
else:
Expand Down Expand Up @@ -372,11 +476,13 @@ def render_unsplit(self, x, y, h, nx, ny, pitch, margin):
callback=[partial(self.generate_grid, nx, ny, shift_x, shift_y)]
)

# add margin for walls and lid
x += 2 * margin
y += 2 * margin

if h > 0:
# add margin for walls and lid
x += 2 * margin
y += 2 * margin
shift_x += margin
shift_y += margin


self.rectangularWall(x, h, [b, sideedge, t1, sideedge],
ignore_widths=[1, 6], move="right")
Expand All @@ -388,6 +494,13 @@ def render_unsplit(self, x, y, h, nx, ny, pitch, margin):
ignore_widths=[1, 6], move="left up")

if self.bottom_edge != "e":
self.rectangularWall(x, y, "ffff", move="up")
if self.base_type != "refined":
self.rectangularWall(x, y, "ffff", move="right")
else:
self.rectangularWall(x, y, "ffff", move="right", callback=[partial(self.generate_refined_grid, nx, ny, shift_x, shift_y, False)])

self.lid(x, y)
self.lid(x, y)
else:
if self.base_type == "refined":
# Generate a refined based and include dovetails if exact sized
self.rectangularWall(x, y, "eeee", move="right", callback=[partial(self.generate_refined_grid, nx, ny, shift_x, shift_y, self.exact_size)])
20 changes: 8 additions & 12 deletions boxes/generators/gridfinitytraylayout.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import argparse

import boxes
from boxes import Boxes, lids, restore, boolarg
from boxes.Color import Color
Expand Down Expand Up @@ -45,14 +43,9 @@ def __init__(self) -> None:
self.argparser.add_argument("--cut_pads_mag_diameter", type=float, default=6.5, help="if pads are cut add holes for magnets. Typical is 6.5, zero to disable,")
self.argparser.add_argument("--cut_pads_mag_offset", type=float, default=7.75, help="if magnet hole offset from pitch corners. Typical is 7.75.")
self.argparser.add_argument("--base_thickness", type=float, default=0.0, help="the thickness of base the box will sit upon. 0 to use the material thickness, 4.65 for a standard Gridfinity 3D printed base")
if self.UI == "web":
self.argparser.add_argument("--layout", type=str, help="You can hand edit this before generating", default="\n");
else:
self.argparser.add_argument(
"--input", action="store", type=argparse.FileType('r'),
default="traylayout.txt",
help="layout file")
self.layout = None
self.argparser.add_argument("--layout", type=str, help="You can hand edit this before generating", default="\n")
if self.UI != "web":
self.argparser.add_argument("--input", action="store", type=str, default="traylayout.txt", help="layout file")

def generate_layout(self):
layout = ''
Expand All @@ -63,8 +56,11 @@ def generate_layout(self):
if county == 0:
county = self.ny

stepx = self.x / countx
stepy = self.y / county
x = self.pitch * self.nx - self.margin
y = self.pitch * self.ny - self.margin

stepx = x / countx
stepy = y / county
for i in range(countx):
line = ' |' * i + f" ,> {stepx}mm\n"
layout += line
Expand Down
3 changes: 0 additions & 3 deletions boxes/generators/photoframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
from boxes import BoolArg, Boxes, Color, edges

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())


@dataclass
class Dimensions:
Expand Down
35 changes: 20 additions & 15 deletions boxes/generators/traylayout.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from __future__ import annotations

import io

import os
import boxes
from boxes import *
from boxes import lids
Expand Down Expand Up @@ -102,23 +102,21 @@ def __init__(self) -> None:
self.addSettingsArgs(boxes.edges.FingerJointSettings)
self.addSettingsArgs(lids.LidSettings)
self.buildArgParser("h", "hi", "outside", "sx", "sy")
if self.UI == "web":
self.argparser.add_argument(
"--layout", action="store", type=str, default="\n",
help="""* Set **sx** and **sy** before editing this!
self.argparser.add_argument(
"--layout", action="store", type=str, default="\n",
help="""* Set **sx** and **sy** before editing this!
* You can still change measurements afterwards
* You can replace the hyphens and vertical bars representing the walls
with a space character to remove the walls.
* You can replace the space characters representing the floor by a "X"
to remove the floor for this compartment.
* Resize text area if necessary.""")
self.description = ""
else:
self.description = ""
if self.UI != "web":
self.argparser.add_argument(
"--input", action="store", type=argparse.FileType('r'),
"--input", action="store", type=str,
default="traylayout.txt",
help="layout file")
self.layout = None

def vWalls(self, x: int, y: int) -> int:
"""Number of vertical walls at a crossing."""
Expand Down Expand Up @@ -152,16 +150,23 @@ def hFloor(self, x: int, y: int) -> bool:
return (y > 0 and self.floors[y - 1][x]) or (y < len(self.y) and self.floors[y][x])

@restore
def edgeAt(self, edge, x, y, length, angle=0):
def edgeAt(self, edge, x, y, length, angle=0, corner=None):
self.moveTo(x, y, angle)
edge = self.edges.get(edge, edge)
edge(length)
if corner:
self.corner(90)

def prepare(self):
if self.layout:
self.parse(self.layout.split('\n'))
elif os.path.exists(self.input):
with open(self.input) as input_file:
self.parse(input_file)
elif callable(getattr(self, "generate_layout", None)):
self.parse(self.generate_layout().split('\n'))
else:
self.parse(self.input)
raise RuntimeError("traylayout requires --layout, --input, or implementation of generate_layout")

if self.outside:
self.x = self.adjustSize(self.x)
Expand Down Expand Up @@ -333,7 +338,7 @@ def base_plate(self, callback=None, move=None):
posy + w + b, self.x[x],
-180)
if x == 0 or not self.floors[y][x - 1]:
self.edgeAt("e", posx - w, posy + w + b, w, 0)
self.edgeAt("e", posx, posy + w + b, w, -180, corner=True)
elif y == 0 or not self.floors[y - 1][x - 1]:
self.edgeAt("e", posx - t, posy + w + b, t, 0)
if x == lx - 1 or not self.floors[y][x + 1]:
Expand All @@ -346,7 +351,7 @@ def base_plate(self, callback=None, move=None):
elif x == 0 or y == ly or not self.floors[y][x - 1]:
self.edgeAt("e", posx - t, posy + t - w - b, t)
if x == lx - 1 or y == 0 or not self.floors[y-1][x + 1]:
self.edgeAt("e", posx + self.x[x], posy + t -w - b, w)
self.edgeAt("e", posx + self.x[x], posy + t -w - b, w, corner=True)
posx += self.x[x] + self.thickness
posy += self.y[y - 1] + self.thickness

Expand All @@ -367,7 +372,7 @@ def base_plate(self, callback=None, move=None):
# Right edge
self.edgeAt(e, posx + w + b, posy, self.y[y], 90)
if y == 0 or not self.floors[y-1][x-1]:
self.edgeAt("e", posx + w + b, posy + self.y[y], w, 90)
self.edgeAt("e", posx + w + b, posy + self.y[y], w, 90, corner=True)
elif x == lx or y == 0 or not self.floors[y - 1][x]:
self.edgeAt("e", posx + w + b, posy + self.y[y], t, 90)
if y == ly - 1 or not self.floors[y+1][x-1]:
Expand All @@ -382,7 +387,7 @@ def base_plate(self, callback=None, move=None):
self.edgeAt("e", posx + t - w - b,
posy + self.y[y] + t, t, -90)
if y == ly - 1 or not self.floors[y + 1][x]:
self.edgeAt("e", posx + t - w - b, posy, w, -90)
self.edgeAt("e", posx + t - w - b, posy, w, -90, corner=True)
posy += self.y[y] + self.thickness
if x < lx:
posx += self.x[x] + self.thickness
Expand Down
Loading