Added Cut-Craft extensions (another box makers)

This commit is contained in:
Mario Voigt 2020-07-31 13:46:07 +02:00
parent a45a58f171
commit 6ca5ffdea7
33 changed files with 2234 additions and 0 deletions

View File

@ -0,0 +1,57 @@
# cut-craft
## Python package
Note that this package is written for Python 3, however requires Python 2 compatibility for Inkscape integration.
The `cutcraft` package contains components in the following categories:
| Folder | Description |
| --------- | ---------------------------------------------------- |
| core | Core components (point, line etc). |
| platforms | Platforms used to construct shapes (circular etc). |
| shapes | Fundamental 3D shapes (cylinder, cone, sphere etc). |
| supports | Vertical supports to hold the shape levels apart. |
## Core
| Module | Description |
| --------- | --------------------------------------------------------------------------- |
| point | A 2D point with `x` and `y` coordinates. |
| rectangle | Two `point`s defining topleft and bottom right for a rectangle. |
| trace | An ordered collection of `point`s. |
| part | A collection of one or more `trace`s. |
| line | A type of `trace` with two `point`s defining the start and end of the line. |
| circle | A type of `trace` with `point`s defining a circle. |
| neopixel | A type of `trace` with the `point`s defining a cutout suitable to fit a variety of [NeoPixels](https://www.adafruit.com/category/168). |
## Shapes
| Module | Description |
| -------- | -------------------------------------- |
| shape | The core 3D functionality for a shape. |
| cone | A cone `shape`. |
| cylinder | A cylinder `shape`. |
| sphere | A 3D spherical `shape`. |
> Note that the fundamental `shape`s listed above can be used flexibly considering the number of `circle` segments can be specified. For example a `cone` with 4 segments becomes a **pyramid**, and a `cylinder` with 4 segments becomes a **cube**.
## Supports
| Module | Description |
| -------- | --------------------------------------------------- |
| support | The core support structure functionality. |
| pier | A pier like `support` to hold `shape` levels apart. |
| face | A solid face to `support` `shape` levels. |
## Python 2 vs 3 Compatibility
The initial aim was to develop only for Python 3, however [Inkscape](https://inkscape.org) currently uses Python 2 as the default interpreter for extensions. As a result, the following should be noted while reviewing the code:
1) The calls to `super()` are written in a way that works with both versions of Python.
2) The `math.isclose()` function is not available in Python 2 so a local version has been created in [util.py](util.py).

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .point import Point
from .line import Line
from .rectangle import Rectangle
from .trace import Trace
from .circle import Circle
from .part import Part
from .neopixel import NeoPixel
from .fingerjoint import FingerJoint
__all__ = ["Point", "Line", "Rectangle", "Trace", "Circle", "Part", "NeoPixel", "FingerJoint"]

View File

@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .point import Point
from .line import Line
from .trace import Trace
from ..util import isclose
from math import pi, sin, cos, asin
class Circle(Trace):
def __init__(self, radius, segments, cuts, cutdepth=0.0, start=0.0, end=pi*2.0, rotation=0.0,
origin=Point(0.0, 0.0), thickness=0.0, kerf=0.0):
super(Circle, self).__init__()
self.thickness = thickness
self.kerf = kerf
partial = True if start != 0.0 or end != pi*2.0 else False
if cuts==0:
c = 0.0
else:
if self.thickness <= 0.0:
raise ValueError("cutcraft.circle: parameter 'thickness' not set when 'cuts' greater than zero.")
if cutdepth <= 0.0:
raise ValueError("cutcraft.circle: parameter 'cutdepth' not set when 'cuts' greater than zero.")
c = asin(self.thickness/2/radius)
if partial:
angles = [[rotation+start+(end-start)/segments*seg, 'SEG'] for seg in range(segments+1)] + \
[[rotation+start+(end-start)/(cuts+1)*cut-c, '<CUT'] for cut in range(1, cuts+1)] + \
[[rotation+start+(end-start)/(cuts+1)*cut+c, 'CUT>'] for cut in range(1, cuts+1)]
else:
angles = [[rotation+end/segments*seg, 'SEG'] for seg in range(segments)] + \
[[rotation+end/cuts*cut-c, '<CUT'] for cut in range(cuts)] + \
[[rotation+end/cuts*cut+c, 'CUT>'] for cut in range(cuts)]
angles = sorted(angles)
if angles[0][1] == 'CUT>':
angles = angles[1:] + [angles[0]]
for i, angle in enumerate(angles):
angle.append(self._cnext(angles, i, 'SEG'))
angle.append(self._cprev(angles, i, 'SEG'))
angle.append(self._cnext(angles, i, 'CUT>') if angle[1]=='<CUT' else None)
angle.append(self._cprev(angles, i, '<CUT') if angle[1]=='CUT>' else None)
for i, angle in enumerate(angles):
if angle[1] == 'SEG':
angle.append([self._pos(angle[0], radius)])
for i, angle in enumerate(angles):
if angle[1] != 'SEG':
mult = -1 if angle[1] == '<CUT' else 1
a = angle[0] - mult*c
a2 = a + mult*pi/2
# Line from previous to next segment point.
line1 = Line(angles[angle[2]][6][0], angles[angle[3]][6][0])
# Line from origin offset by thickness.
p1 = self._pos(a2, self.thickness/2)
p2 = p1 + self._pos(a, radius)
line2 = Line(p1, p2)
pintersect = line1.intersection(line2)
pinset = pintersect + self._pos(a, -cutdepth)
if angle[1] == '<CUT':
angle.append([pintersect, pinset])
else:
angle.append([pinset, pintersect])
d1 = pinset.distance(Point(0.0,0.0))
d2 = angles[angle[5]][6][1].distance(Point(0.0,0.0))
if d1<d2:
angles[angle[5]][6][1] = pinset - self._pos(a2, self.thickness)
elif d2<d1:
angle[6][0] = angles[angle[5]][6][1] + self._pos(a2, self.thickness)
pass
incut = False
for i, angle in enumerate(angles):
atype = angle[1]
if atype=='<CUT':
incut = True
elif atype=='CUT>':
incut = False
if atype != 'SEG' or (atype == 'SEG' and not incut):
for pos in angle[6]:
x = origin.x + pos.x
y = origin.y + pos.y
if len(self.x)==0 or not (isclose(x, self.x[-1]) and isclose(y, self.y[-1])):
self.x.append(x)
self.y.append(y)
return
def _cnext(self, angles, i, item):
if i>=len(angles):
i=-1
for j, angle in enumerate(angles[i+1:]):
if angle[1] == item:
return i+1+j
for j, angle in enumerate(angles):
if angle[1] == item:
return j
return None
def _cprev(self, angles, i, item):
if i<=0:
i=len(angles)
for j, angle in enumerate(angles[:i][::-1]):
if angle[1] == item:
return i-j-1
for j, angle in enumerate(angles[::-1]):
if angle[1] == item:
return j
return None
def _pos(self, angle, radius):
return Point(sin(angle)*radius, cos(angle)*radius)

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from math import floor
class FingerJoint(object):
def __init__(self, length, fingerwidth, style, thickness=0.0):
super(FingerJoint, self).__init__()
self.thickness = thickness
if style in ('depth','height'):
self.fingers = [0.0] + \
[pos + fingerwidth*2.0 for pos in self._fingers(length-fingerwidth*4.0, fingerwidth)] + \
[length]
elif style=='width':
self.fingers = [pos + thickness for pos in self._fingers(length-thickness*2.0, fingerwidth)]
else:
raise ValueError("cutcraft.core.fingerjoin: invalid value of '{}' for parameter 'style'.".format(style))
return
def _fingers(self, length, fingerwidth):
count = int(floor(length / fingerwidth))
count = count-1 if count%2==0 else count
return [length/count*c for c in range(count+1)]

View File

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .point import Point
from math import sqrt
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
# Required as Inkscape does not include math.isclose().
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
class Line(object):
""" Line class defined by start and end Points. """
def __init__(self, point1, point2):
self.pts = (point1, point2)
self.x = [point1.x, point2.x]
self.y = [point1.y, point2.y]
return
def _line(self):
# Convert line segment into a line equation (infinite length).
p1 = self.pts[0]
p2 = self.pts[1]
A = (p1.y - p2.y)
B = (p2.x - p1.x)
C = (p1.x*p2.y - p2.x*p1.y)
return A, B, -C
def intersection(self, other):
# Find the intersection of the lines (infinite length - not segments)
L1 = self._line()
L2 = other._line()
D = L1[0] * L2[1] - L1[1] * L2[0]
Dx = L1[2] * L2[1] - L1[1] * L2[2]
Dy = L1[0] * L2[2] - L1[2] * L2[0]
if D != 0:
x = Dx / D
y = Dy / D
return Point(x, y)
else:
return None
def normal(self):
# Return the unit normal
dx = self.x[1] - self.x[0]
dy = self.y[1] - self.y[0]
d = sqrt(dx*dx + dy*dy)
return dx/d, -dy/d
def addkerf(self, kerf):
nx, ny = self.normal()
offset = Point(ny*kerf, nx*kerf)
self.pts = (self.pts[0] + offset, self.pts[1] + offset)
self.x = [self.pts[0].x, self.pts[1].x]
self.y = [self.pts[0].y, self.pts[1].y]
def __eq__(self, other):
return (self.pts == other.pts)
def __ne__(self, other):
return (self.pts != other.pts)
def __repr__(self):
return "Line(" + repr(self.pts[0]) + ", " + repr(self.pts[1]) + ")"
def __str__(self):
return "(" + str(self.pts[0]) + ", " + str(self.pts[1]) + ")"

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .point import Point
from .trace import Trace
from .part import Part
from math import pi, sin, cos, sqrt
class NeoPixel(Part):
rings = [[1, 0.0], [6, 16.0/2.0], [12, 30.0/2.0]]
size = 5.5
""" Line class defined by start and end Points. """
def __init__(self, style='rings', origin=Point(0.0, 0.0), scale=1.0, rotate=0.0):
super(NeoPixel, self).__init__()
self.scale = scale
if style=='rings':
for ring in self.rings:
pixels = ring[0]
radius = ring[1] * self.scale
for pixel in range(pixels):
a = rotate + pi*2 * pixel / pixels
seg = self._pixel(origin + Point(sin(a) * radius, cos(a) * radius),
pi/4 + a)
self += seg
elif style=='strip':
xo = origin.x
yo = origin.y
xsize = 25.4*2.0*self.scale
size = self.size*self.scale
seg = Trace() + \
Point(xo-xsize/2.0, yo+size/2.0) + \
Point(xo-xsize/2.0, yo-size/2.0) + \
Point(xo+xsize/2.0, yo-size/2.0) + \
Point(xo+xsize/2.0, yo+size/2.0)
seg.close()
self += seg
return
def _pixel(self, position, rotation):
seg = Trace()
xo = position.x
yo = position.y
size = sqrt(2.0*(self.size*self.scale)**2)
for corner in range(4):
# Points added in counterclockwise direction as this is an inner cut.
a = rotation-2.0*pi*corner/4.0
seg += Point(xo + sin(a) * size/2.0, yo + cos(a) * size/2.0)
seg.close()
return seg

View File

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from copy import deepcopy
from .point import Point
from .rectangle import Rectangle
from .trace import Trace
class Part(object):
""" List of traces that make up a part. """
def __init__(self):
self.traces = []
return
def close(self):
""" Close each traces back to their start. """
for trace in self.traces:
trace.close()
return
def applykerf(self, kerf):
""" Apply an offset to allow for the kerf when cutting. """
for trace in self.traces:
trace.applykerf(kerf)
return
def svg(self):
# Generate SVG string for this part.
return " ".join([trace.svg() for trace in self.traces])
def bbox(self):
bboxes = [trace.bbox() for trace in self.traces]
x = [p1.x for p1, p2 in bboxes] + [p2.x for p1, p2 in bboxes]
y = [p1.y for p1, p2 in bboxes] + [p2.y for p1, p2 in bboxes]
return Rectangle(Point(min(x), min(y)), Point(max(x), max(y)))
def area(self):
bbox = self.bbox()
return bbox.area()
def size(self):
bbox = self.bbox()
return bbox.size()
def __add__(self, other):
p = Part()
if isinstance(other, Part):
p.traces = self.traces + deepcopy(other.traces)
elif isinstance(other, Trace):
p.traces = deepcopy(self.traces)
p.traces.append(other)
elif isinstance(other, Point):
p.traces = self.traces
for trace in p.traces:
trace.offset(other)
else:
raise RuntimeError("Can only add a Part, Trace or Point to an existing Part.")
return p
def __iadd__(self, other):
if isinstance(other, Part):
self.traces.extend(other.traces)
elif isinstance(other, Trace):
self.traces.append(deepcopy(other))
elif isinstance(other, Point):
for trace in self.traces:
trace.offset(other)
else:
raise RuntimeError("Can only add a Part, Trace or Point to an existing Part.")
return self
def __repr__(self):
return "Part" + str(self)
def __str__(self):
l = len(self.traces)
return "(" + str(l) + " trace" + ("s" if l>1 else "") + ")"

View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from math import sqrt
class Point(object):
""" Point (x,y) class suppporting addition for offsets. """
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, other):
""" Distance between two points. """
x = self.x - other.x
y = self.y - other.y
return sqrt(x*x+y*y)
def tup(self):
return (self.x, self.y)
def __eq__(self, other):
return (self.x == other.x and self.y == other.y)
def __ne__(self, other):
return (self.x != other.x or self.y != other.y)
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __isub__(self, other):
self.x -= other.x
self.y -= other.y
return self
def __neg__(self):
return Point(-self.x, -self.y)
def __repr__(self):
return "Point(" + str(self.x) + ", " + str(self.y) + ")"
def __str__(self):
return "(" + str(self.x) + ", " + str(self.y) + ")"

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from math import ceil, floor
from .point import Point
class Rectangle(object):
""" Rectangle class defined by top-left and bottom-right Points. """
def __init__(self, point1, point2):
# Correct the input points in case they are not topleft, bottomright as expected.
self.topleft = Point(min(point1.x, point2.x), min(point1.y, point2.y))
self.bottomright = Point(max(point1.x, point2.x), max(point1.y, point2.y))
return
def size(self):
# Calculate the size as: width, height.
return self.bottomright.x-self.topleft.x, self.bottomright.y-self.topleft.y
def area(self):
width, height = self.size()
return width*height
def expanded(self):
# Expand the current Rectangle out to integer boundary.
return Rectangle(Point(floor(self.topleft.x), floor(self.topleft.y)),
Point(ceil(self.bottomright.x), ceil(self.bottomright.y)))
def svg(self):
# Generate SVG string for this rectangle.
ptx = [self.topleft.x, self.bottomright.x, self.bottomright.x, self.topleft.x]
pty = [self.topleft.y, self.topleft.y, self.bottomright.y, self.bottomright.y]
return "M {} {} ".format(ptx[0], pty[0]) + \
" ".join(["L {} {}".format(x, y) for x, y in zip(ptx[1:], pty[1:])]) + \
" L {} {}".format(ptx[0], pty[0])
def __eq__(self, other):
return (self.topleft == other.topleft and self.bottomright == other.bottomright )
def __ne__(self, other):
return (self.topleft != other.topleft or self.bottomright != other.bottomright)
def __repr__(self):
return "Rectangle(" + repr(self.topleft) + ", " + repr(self.bottomright) + ")"
def __str__(self):
return "(" + str(self.topleft) + ", " + str(self.bottomright) + ")"

View File

@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .point import Point
from .line import Line
from ..util import isclose, iscloselist
class Trace(object):
""" List of coordinates that make a boundary. """
def __init__(self, x=None, y=None):
self.x = [] if x is None else x
self.y = [] if y is None else y
self.closed = False
return
def close(self):
""" Close the trace back to the start. """
if not self.closed:
if isclose(self.x[0], self.x[-1]) and isclose(self.y[0], self.y[-1]):
# Start and end should be the same.
self.x[-1] = self.x[0]
self.y[-1] = self.y[0]
else:
# Add new end point to close the loop.
self.x.append(self.x[0])
self.y.append(self.y[0])
self.closed = True
return
def applykerf(self, kerf):
""" Apply an offset to allow for the kerf when cutting. """
self.close()
# Convert the points to lines.
lines = [Line(Point(x1, y1), Point(x2, y2)) for x1, y1, x2, y2 in
zip(self.x[:-1], self.y[:-1], self.x[1:], self.y[1:])]
# Add the kerf to the lines.
for line in lines:
line.addkerf(kerf)
# Extract the line intersections as the new points.
pts = [line1.intersection(line2) for line1, line2 in zip(lines, lines[-1:] + lines[:-1])]
self.clear()
self.x += [pt.x for pt in pts]
self.y += [pt.y for pt in pts]
self.x += self.x[:1]
self.y += self.y[:1]
return
def offset(self, pt):
""" Move a trace by an x/y offset. """
self.x = [x + pt.x for x in self.x]
self.y = [y + pt.y for y in self.y]
def clear(self):
self.x = []
self.y = []
def svg(self):
# Generate SVG string for this trace.
if len(self.x)<2:
return ""
return "M {} {} ".format(self.x[0], self.y[0]) + \
" ".join(["L {} {}".format(x, y) for x, y in zip(self.x[1:], self.y[1:])])
def bbox(self):
return Point(min(self.x), min(self.y)), Point(max(self.x), max(self.y))
def __len__(self):
return len(self.x)
def __eq__(self, other):
return (iscloselist(self.x,other.x) and iscloselist(self.y, other.y))
def __ne__(self, other):
return (not iscloselist(self.x, other.x) or not iscloselist(self.y, other.y))
def __add__(self, other):
new = Trace()
if isinstance(other, Point):
new.x = self.x + [other.x]
new.y = self.y + [other.y]
elif isinstance(other, Trace):
new.x = self.x + other.x
new.y = self.y + other.y
else:
raise RuntimeError("Can only add a Trace or Point to an existing Trace.")
return new
def __iadd__(self, other):
if isinstance(other, Point):
self.x.append(other.x)
self.y.append(other.y)
elif isinstance(other, Trace):
self.x += other.x
self.y += other.y
else:
raise RuntimeError("Can only add a Trace or Point to an existing Trace.")
return self
def __repr__(self):
return "Trace(" + str(self.x) + ", " + str(self.y) + ")"
def __str__(self):
return "(" + str(self.x) + ", " + str(self.y) + ")"
def __getitem__(self, key):
""" Used to override the slice functionality (eg: reversing). """
new = Trace()
new.x = self.x[key]
new.y = self.y[key]
return new
def __setitem__(self, key, value):
""" Used to override the slice functionality. """
if isinstance(value, Point):
self.x[key] = value.x
self.y[key] = value.y
else:
raise RuntimeError("Can only update a single item in an existing Trace.")
return self
def __delitem__(self, key):
""" Used to override the slice functionality (eg: reversing). """
del self.x[key]
del self.y[key]
return self
def __reversed__(self):
""" Used to override the slice functionality (eg: reversing). """
new = Trace()
new.x = list(reversed(self.x))
new.y = list(reversed(self.y))
return new

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .platform import Platform
from .circular import Circular
from .rollerframe import RollerFrame
__all__ = ["Platform", "Circular", "RollerFrame"]

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.point import Point
from ..core.part import Part
from ..core.circle import Circle
from .platform import Platform
from math import pi
class Circular(Platform):
""" Circular Platform. """
def __init__(self, radius, inradius, segments, cuts, cutdepth, start=0.0, end=pi*2, rotation=0.0,
origin=Point(0.0, 0.0), thickness=0.0):
super(Circular, self).__init__(thickness)
self.radius = radius
self.inradius = inradius
self.segments = segments
outer = Circle(self.radius, segments, cuts, cutdepth=cutdepth, start=start, end=end,
rotation=rotation, origin=origin, thickness=thickness)
outer.close()
inner = Circle(self.inradius, segments, 0, start=start, end=end,
rotation=rotation, origin=origin, thickness=thickness)
inner.close()
self.traces.append(outer)
self.traces.append(reversed(inner))

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
class Platform(Part):
def __init__(self, thickness):
super(Platform, self).__init__()
self.thickness = thickness
return

View File

@ -0,0 +1,379 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.point import Point
from ..core.part import Part
from ..core.trace import Trace
from ..core.circle import Circle
from ..core.fingerjoint import FingerJoint
from ..core.neopixel import NeoPixel
from .platform import Platform
from ..util import intersection
from math import pi, sqrt, asin, atan
#import inkex
class RollerFrame(Platform):
""" RollerBot Platform. """
def __init__(self, supwidth, wheelradius, upperradius, lowerradius,
facesize, barsize, primarygapwidth, secondarygapwidth,
scale, part_id, thickness=0.0):
super(RollerFrame, self).__init__(thickness)
self.supwidth = supwidth
self.barsize = barsize
cutdepth = supwidth / 3.0
barradius = sqrt(2.0*(barsize/2.0)**2)
facewidth = primarygapwidth*2.0 + thickness*3.0
faceheight = facesize + thickness
fjoint = FingerJoint(faceheight, thickness*2.0, 'height', thickness=thickness) # Face
bjoint = FingerJoint(faceheight, thickness*2.0, 'depth', thickness=thickness) # Base
wjoint = FingerJoint(facewidth, thickness*2.0, 'width', thickness=thickness) # Length
if part_id<5:
# The circular segments for the main body structure.
# Outer section.
x = barsize/2.0
y = sqrt(lowerradius**2 - x**2)
a = atan(x/y)
outer = Circle(upperradius, 72, 5, cutdepth=cutdepth, start=0.0, end=pi, thickness=thickness) + \
Point(0.0, -upperradius + cutdepth) + \
Point(-thickness, -upperradius + cutdepth) + \
Point(-thickness, -upperradius) + \
Point(-barsize/2.0, -upperradius) + \
Circle(lowerradius, 72, 4, cutdepth=cutdepth, start=pi+a, end=pi*2-a, thickness=thickness) + \
Point(-barsize/2.0, upperradius) + \
Point(-thickness, upperradius) + \
Point(-thickness, upperradius - cutdepth) + \
Point(0.0, upperradius - cutdepth)
outer.close()
self.traces.append(outer)
if part_id in (0,4):
# Central Motor Position.
inner = Trace() + \
Point(-barsize/2.0, -barsize/2.0) + \
Point(-barsize/2.0, barsize/2.0) + \
Point(-barsize/6.0, barsize/2.0) + \
Point(-barsize/6.0, barsize/2.0-thickness/2.0) + \
Point(barsize/6.0, barsize/2.0-thickness/2.0) + \
Point(barsize/6.0, barsize/2.0) + \
Point(barsize/2.0, barsize/2.0) + \
Point(barsize/2.0, -barsize/2.0) + \
Point(barsize/6.0, -barsize/2.0) + \
Point(barsize/6.0, -barsize/2.0+thickness/2.0) + \
Point(-barsize/6.0, -barsize/2.0+thickness/2.0) + \
Point(-barsize/6.0, -barsize/2.0)
inner.close()
self.traces.append(reversed(inner))
# Outer parts are complete, inner parts have cutouts.
if part_id in (1,2,3):
# Central Motor Position and Bar.
inner = Trace() + \
Point(-barsize/2.0*1.3, -barsize/2.0) + \
Point(-barsize/2.0*1.3, -barsize/2.0*0.55) + \
Point(-barsize/2.0, -barsize/2.0*0.55) + \
Point(-barsize/2.0, barsize/2.0*0.55) + \
Point(-barsize/2.0*1.3, barsize/2.0*0.55) + \
Point(-barsize/2.0*1.3, barsize/2.0) + \
Point(barsize/2.0, barsize/2.0) + \
Point(barsize/2.0, barsize/10.0) + \
Point(barsize/2.0*1.2, barsize/20.0) + \
Point(barsize/2.0*1.2, -barsize/20.0) + \
Point(barsize/2.0, -barsize/10.0) + \
Point(barsize/2.0, -barsize/2.0)
inner.close()
self.traces.append(reversed(inner))
# Upper segment cut-outs.
x = supwidth/2.0
y = sqrt((upperradius-supwidth)**2 - x**2)
a_outer = atan(x/y)
y = sqrt((barradius+supwidth)**2 - x**2)
a_inner = atan(x/y)
inner = self._segment(upperradius-supwidth, barradius+supwidth,
0, 1, cutdepth, 0.0, a_outer, a_inner)
self.traces.append(reversed(inner))
fa = (pi/2.0 - self._faceangle(facesize, upperradius)) / 2.0
(fx, fy) = intersection(upperradius, angle=fa)
if 0:
inner = Trace() + \
Point(fx, -fy) + \
Point(fy, -fy) + \
Point(fy, -fx)
inner.close()
self.traces.append(reversed(inner))
oy = fy-thickness*2.0
(ox, oa) = intersection(upperradius, y=oy)
if 0:
inner = Trace() + \
Point(ox, -oy) + \
Point(oy, -oy) + \
Point(oy, -ox)
inner.close()
self.traces.append(reversed(inner))
iy = oy
(ix, ia) = intersection(upperradius-supwidth, y=iy)
if part_id==2:
inner = Circle(upperradius-supwidth, 18, 0, cutdepth=cutdepth,
start=pi/3+a_outer, end=pi-a_outer,
thickness=self.thickness) + \
reversed(Circle(barradius+supwidth, 18, 0, cutdepth=cutdepth,
start=pi/3+a_inner, end=pi-a_inner,
thickness=self.thickness))
inner.close()
self.traces.append(reversed(inner))
# Temporary cut to remove where the face will be installed.
oy = fy-thickness
(ox, oa) = intersection(upperradius, y=oy)
inner = Trace() + \
Point(ox, -ox) + \
Point(oy, -ox) + \
Point(oy, -oy) + \
Point(ox, -oy)
inner.close()
self.traces.append(reversed(inner))
else:
inner = Circle(upperradius-supwidth, 18, 0, cutdepth=cutdepth,
start=pi/3*1+a_outer, end=pi/2+ia,
thickness=self.thickness) + \
reversed(Circle(barradius+supwidth, 18, 0, cutdepth=cutdepth,
start=pi/3*1+a_inner, end=pi/3*2-a_inner,
thickness=self.thickness))
inner.close()
self.traces.append(reversed(inner))
ia = pi/2 - ia
(ix, iy) = intersection(upperradius-supwidth, angle=ia)
inner = Circle(upperradius-supwidth, 18, 0, cutdepth=cutdepth,
start=pi/2+ia, end=pi/3*3-a_outer,
thickness=self.thickness) + \
reversed(Circle(barradius+supwidth, 18, 0, cutdepth=cutdepth,
start=pi/3*2+a_inner, end=pi/3*3-a_inner,
thickness=self.thickness)) + \
Point(ix, -ix)
inner.close()
self.traces.append(reversed(inner))
# Face and base cutout slots.
cy = fy-thickness
for (x1, x2) in zip(fjoint.fingers[1::2],fjoint.fingers[2::2]):
inner = Trace() + \
Point(cy+x1, -cy) + \
Point(cy+x2, -cy) + \
Point(cy+x2, -cy-thickness) + \
Point(cy+x1, -cy-thickness)
inner.close()
self.traces.append(reversed(inner))
for (y1, y2) in zip(bjoint.fingers[1::2],bjoint.fingers[2::2]):
inner = Trace() + \
Point(cy, -cy-y2) + \
Point(cy, -cy-y1) + \
Point(cy+thickness, -cy-y1) + \
Point(cy+thickness, -cy-y2)
inner.close()
self.traces.append(reversed(inner))
if 0:
if part_id==2:
for seg in range(2):
segnext = seg*2+1
inner = self._segment(upperradius-supwidth, barradius+supwidth,
seg, segnext, cutdepth, 0.0, a_outer, a_inner)
self.traces.append(reversed(inner))
else:
for seg in range(3):
segnext = seg+1
inner = self._segment(upperradius-supwidth, barradius+supwidth,
seg, segnext, cutdepth, 0.0, a_outer, a_inner)
self.traces.append(reversed(inner))
# Lower segment cut-outs.
x = supwidth/2.0
y = sqrt((lowerradius-supwidth)**2 - x**2)
a_outer = atan(x/y)
y = sqrt((barradius+supwidth)**2 - x**2)
a_inner = atan(x/y)
for seg in range(3):
segnext = seg+1
inner = self._segment(lowerradius-supwidth, barradius+supwidth,
seg, segnext, cutdepth, pi, a_outer, a_inner)
self.traces.append(reversed(inner))
if part_id in (1,2,3):
r_mid = barradius+supwidth + ((upperradius-supwidth) - (barradius+supwidth))/2.0
self._slot(barsize/2.0 + supwidth/2.0, cutdepth*1.5, cutdepth)
self._slot(barsize/2.0 + supwidth/2.0, -cutdepth*1.5, cutdepth)
self._slot(barsize/2.0 + supwidth/2.0, r_mid+cutdepth*1.5, cutdepth)
self._slot(barsize/2.0 + supwidth/2.0, r_mid-cutdepth*1.5, cutdepth)
elif part_id in (5,6):
# The board supports.
x = primarygapwidth
y = ((upperradius-supwidth) + (barradius+supwidth))/2.0 + supwidth*2.0
t = Trace() + \
Point(0.0, 0.0) + \
Point(0.0, supwidth-cutdepth*2.0) + \
Point(-cutdepth, supwidth-cutdepth*2.0) + \
Point(-cutdepth, supwidth-cutdepth*1.0) + \
Point(0.0, supwidth-cutdepth*1.0) + \
Point(0.0, supwidth*2.0-cutdepth*2.0) + \
Point(-cutdepth, supwidth*2.0-cutdepth*2.0) + \
Point(-cutdepth, supwidth*2.0-cutdepth*1.0) + \
Point(0.0, supwidth*2.0-cutdepth*1.0) + \
Point(0.0, y-supwidth*2.0+cutdepth*1.0) + \
Point(-cutdepth, y-supwidth*2.0+cutdepth*1.0) + \
Point(-cutdepth, y-supwidth*2.0+cutdepth*2.0) + \
Point(0.0, y-supwidth*2.0+cutdepth*2.0) + \
Point(0.0, y-supwidth+cutdepth*1.0) + \
Point(-cutdepth, y-supwidth+cutdepth*1.0) + \
Point(-cutdepth, y-supwidth+cutdepth*2.0) + \
Point(0.0, y-supwidth+cutdepth*2.0) + \
Point(0.0, y) + \
Point(x, y) + \
Point(x, y-supwidth-cutdepth*1.0) + \
Point(x+cutdepth, y-supwidth-cutdepth*1.0) + \
Point(x+cutdepth, y-supwidth-cutdepth*2.0) + \
Point(x, y-supwidth-cutdepth*2.0) + \
Point(x, supwidth-cutdepth*1.0) + \
Point(x+cutdepth, supwidth-cutdepth*1.0) + \
Point(x+cutdepth, supwidth-cutdepth*2.0) + \
Point(x, supwidth-cutdepth*2.0) + \
Point(x, 0.0)
t.close()
self.traces.append(t)
elif part_id==7:
# The face components.
t = Trace(x=[thickness], y=[thickness])
for i, pos in enumerate(fjoint.fingers[1:-1]):
if i%2==0:
t += Point(pos, thickness)
t += Point(pos, 0.0)
else:
t += Point(pos, 0.0)
t += Point(pos, thickness)
t += Point(faceheight, thickness)
t += Point(faceheight, facewidth-thickness)
for i, pos in enumerate(reversed(fjoint.fingers[1:-1])):
if i%2==0:
t += Point(pos, facewidth-thickness)
t += Point(pos, facewidth)
else:
t += Point(pos, facewidth)
t += Point(pos, facewidth-thickness)
t += Point(thickness, facewidth-thickness)
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
if i%2==0:
t += Point(thickness, pos)
t += Point(0.0, pos)
else:
t += Point(0.0, pos)
t += Point(thickness, pos)
t.close()
self.traces.append(t)
elif part_id==8:
t = Trace(x=[thickness], y=[0.0])
t += Point(facewidth-thickness, 0.0)
for i, pos in enumerate(bjoint.fingers[1:-1]):
if i%2==0:
t += Point(facewidth-thickness, pos)
t += Point(facewidth, pos)
else:
t += Point(facewidth, pos)
t += Point(facewidth-thickness, pos)
t += Point(facewidth-thickness, faceheight)
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
if i%2==0:
t += Point(pos, faceheight)
t += Point(pos, faceheight-thickness)
else:
t += Point(pos, faceheight-thickness)
t += Point(pos, faceheight)
t += Point(thickness, faceheight)
for i, pos in enumerate(reversed(bjoint.fingers[1:-1])):
if i%2==0:
t += Point(thickness, pos)
t += Point(0.0, pos)
else:
t += Point(0.0, pos)
t += Point(thickness, pos)
t.close()
self.traces.append(t)
for eye in range(2):
np = NeoPixel(style='rings', origin=Point(facewidth/4.0*(1+eye*2), faceheight/2.5), scale=scale, rotate=pi/2)
self.traces.extend(np.traces)
np = NeoPixel(style='strip', origin=Point(facewidth/2.0, faceheight*0.80), scale=scale)
self.traces.extend(np.traces)
# Camera
csize = 8.5*scale
t = Trace() + \
Point(facewidth/2.0 - csize/2.0, faceheight/2.5 - csize/2.0) + \
Point(facewidth/2.0 + csize/2.0, faceheight/2.5 - csize/2.0) + \
Point(facewidth/2.0 + csize/2.0, faceheight/2.5 + csize/2.0) + \
Point(facewidth/2.0 - csize/2.0, faceheight/2.5 + csize/2.0)
t.close()
self.traces.append(t)
def _faceangle(self, size, radius):
# Returns total angle required for a face.
o = sqrt(2.0*(size**2))*0.5
h = radius
return 2.0*asin(o/h)
def _segment(self, r_outer, r_inner, seg, segnext, cutdepth, a_offset, a_outer, a_inner):
# Create an inner segment cutout.
t = Circle(r_outer, 18, 0, cutdepth=cutdepth,
start=a_offset+pi/3*seg+a_outer, end=a_offset+pi/3*segnext-a_outer,
thickness=self.thickness) + \
reversed(Circle(r_inner, 18, 0, cutdepth=cutdepth,
start=a_offset+pi/3*seg+a_inner, end=a_offset+pi/3*segnext-a_inner,
thickness=self.thickness))
if a_offset == 0.0 and seg == 0:
r_mid = r_inner + (r_outer - r_inner)/2.0
t += Trace() + \
Point(self.supwidth / 2.0, r_mid - self.supwidth) + \
Point(self.barsize/2.0 + self.supwidth, r_mid - self.supwidth) + \
Point(self.barsize/2.0 + self.supwidth, r_mid + self.supwidth) + \
Point(self.supwidth / 2.0, r_mid + self.supwidth)
t.close()
return t
def _slot(self, x, y, cutdepth):
slot = Trace() + \
Point(x-self.thickness/2.0, y+cutdepth/2.0) + \
Point(x+self.thickness/2.0, y+cutdepth/2.0) + \
Point(x+self.thickness/2.0, y-cutdepth/2.0) + \
Point(x-self.thickness/2.0, y-cutdepth/2.0)
slot.close()
self.traces.append(reversed(slot))

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .shape import Shape
from .box import Box
from .cone import Cone
from .cylinder import Cylinder
from .sphere import Sphere
from .rollerbot import RollerBot
__all__ = ["Shape", "Box", "Cone", "Cylinder", "Sphere", "RollerBot"]

View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
from ..core.point import Point
from ..core.trace import Trace
from ..core.fingerjoint import FingerJoint
from .shape import Shape
class Box(Shape):
""" List of segments that make up a part. """
def __init__(self, width, depth, height, thickness, kerf, top=True, bottom=True,
left=True, right=True, front=True, back=True):
super(Box, self).__init__(thickness, kerf)
self.width = width
self.depth = depth
self.height = height
self.faces = []
self.parts = []
for face in range(6):
p = self._face(face, width, depth, height, thickness, top, bottom, left, right, front, back)
self.faces.append((p, face))
self.parts.append((p, face))
if kerf:
for part, _ in self.parts:
part.applykerf(kerf)
def _face(self, face, width, depth, height, thickness, top, bottom, left, right, front, back):
faces = (top, bottom, left, right, front, back)
# Check if the requested face is active for this box.
if faces[face] == False:
return None
wjoint = FingerJoint(width, thickness*2.0, 'width', thickness=thickness)
djoint = FingerJoint(depth, thickness*2.0, 'depth', thickness=thickness)
hjoint = FingerJoint(height, thickness*2.0, 'height', thickness=thickness)
if face in (0, 1):
t = Trace(x=[thickness], y=[thickness])
for i, pos in enumerate(djoint.fingers[1:-1]):
if i%2==0:
t += Point(pos, thickness)
t += Point(pos, 0.0)
else:
t += Point(pos, 0.0)
t += Point(pos, thickness)
t += Point(depth-thickness, thickness)
for i, pos in enumerate(wjoint.fingers[1:-1]):
if i%2==0:
t += Point(depth-thickness, pos)
t += Point(depth, pos)
else:
t += Point(depth, pos)
t += Point(depth-thickness, pos)
t += Point(depth-thickness, width-thickness)
for i, pos in enumerate(reversed(djoint.fingers[1:-1])):
if i%2==0:
t += Point(pos, width-thickness)
t += Point(pos, width)
else:
t += Point(pos, width)
t += Point(pos, width-thickness)
t += Point(thickness, width-thickness)
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
if i%2==0:
t += Point(thickness, pos)
t += Point(0.0, pos)
else:
t += Point(0.0, pos)
t += Point(thickness, pos)
elif face in (2, 3):
t = Trace(x=[0.0], y=[0.0])
for i, pos in enumerate(djoint.fingers[1:-1]):
if i%2==0:
t += Point(pos, 0.0)
t += Point(pos, thickness)
else:
t += Point(pos, thickness)
t += Point(pos, 0.0)
t += Point(depth, 0.0)
for i, pos in enumerate(hjoint.fingers[1:-1]):
if i%2==0:
t += Point(depth, pos)
t += Point(depth-thickness, pos)
else:
t += Point(depth-thickness, pos)
t += Point(depth, pos)
t += Point(depth, height)
for i, pos in enumerate(reversed(djoint.fingers[1:-1])):
if i%2==0:
t += Point(pos, height)
t += Point(pos, height-thickness)
else:
t += Point(pos, height-thickness)
t += Point(pos, height)
t += Point(0.0, height)
for i, pos in enumerate(reversed(hjoint.fingers[1:-1])):
if i%2==0:
t += Point(0.0, pos)
t += Point(thickness, pos)
else:
t += Point(thickness, pos)
t += Point(0.0, pos)
pass
elif face in (4, 5):
t = Trace(x=[thickness], y=[0.0])
for i, pos in enumerate(wjoint.fingers[1:-1]):
if i%2==0:
t += Point(pos, 0.0)
t += Point(pos, thickness)
else:
t += Point(pos, thickness)
t += Point(pos, 0.0)
t += Point(width-thickness, 0.0)
for i, pos in enumerate(hjoint.fingers[1:-1]):
if i%2==0:
t += Point(width-thickness, pos)
t += Point(width, pos)
else:
t += Point(width, pos)
t += Point(width-thickness, pos)
t += Point(width-thickness, height)
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
if i%2==0:
t += Point(pos, height)
t += Point(pos, height-thickness)
else:
t += Point(pos, height-thickness)
t += Point(pos, height)
t += Point(thickness, height)
for i, pos in enumerate(reversed(hjoint.fingers[1:-1])):
if i%2==0:
t += Point(thickness, pos)
t += Point(0.0, pos)
else:
t += Point(0.0, pos)
t += Point(thickness, pos)
pass
t.close()
return Part() + t

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
from ..platforms.circular import Circular
from .shape import Shape
class Cone(Shape):
""" List of segments that make up a part. """
def __init__(self, height, radius1, radius2, segments, cuts, cutdepth, platforms,
thickness, kerf):
super(Cone, self).__init__(thickness, kerf)
# levels = [p/(platforms-1)*height for p in range(platforms)]
# radii = [radius1 + (radius2-radius1)*p/(platforms-1) for p in range(platforms)]
# for level, radius in zip(levels, radii):
# p = cc.Part()
# p += cp.Circular(radius, segments, cuts, cutdepth, thickness=thickness, kerf=kerf).part
# self.parts.append((p, level))

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
from ..platforms.circular import Circular
from ..supports.pier import Pier
from .shape import Shape
class Cylinder(Shape):
""" List of segments that make up a part. """
def __init__(self, height, radius, inradius, segments, cuts, cutdepth, supwidth, platforms,
thickness, kerf):
super(Cylinder, self).__init__(thickness, kerf)
self.platforms = []
self.piers = []
# List of vertical positions for the platforms
levels = [float(p)/float(platforms-1)*(height-thickness) for p in range(platforms)]
for level in levels:
p = Circular(radius, inradius, segments, cuts, cutdepth, thickness=thickness)
self.platforms.append((p, level))
self.parts.append((p, level))
for _ in range(cuts):
p = Pier(height, supwidth, supwidth-cutdepth, [(level, 0.0) for level in levels], thickness=thickness)
self.piers.append((p, None))
self.parts.append((p, None))
if kerf:
for part, _ in self.parts:
part.applykerf(kerf)

View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
from ..platforms.rollerframe import RollerFrame
from ..supports.pier import Pier
from .shape import Shape
class RollerBot(Shape):
""" List of segments that make up a part. """
def __init__(self, width, supwidth, wheelradius, upperradius, lowerradius,
facesize, barsize, primarygapwidth, secondarygapwidth, scale,
thickness, kerf):
super(RollerBot, self).__init__(thickness, kerf)
self.platforms = []
self.piers = []
cutdepth = supwidth / 3.0
for level in range(9):
# for level in range(7):
p = RollerFrame(supwidth, wheelradius, upperradius, lowerradius,
facesize, barsize, primarygapwidth, secondarygapwidth,
scale, level, thickness=thickness)
self.platforms.append((p, 0.0))
self.parts.append((p, 0.0))
levels = [0.0, secondarygapwidth+thickness,
secondarygapwidth+primarygapwidth+thickness*2.0,
secondarygapwidth+primarygapwidth*2.0+thickness*3.0,
secondarygapwidth*2.0+primarygapwidth*2.0+thickness*4.0 ]
height = secondarygapwidth*2.0+primarygapwidth*2.0+thickness*5.0
for _ in range(9):
p = Pier(height, supwidth, supwidth-cutdepth, [(level, 0.0) for level in levels], thickness=thickness)
self.piers.append((p, None))
self.parts.append((p, None))
levels = [0.0, secondarygapwidth+thickness ]
height = secondarygapwidth+thickness*2.0
for _ in range(4):
p = Pier(height, supwidth, supwidth-cutdepth, [(level, 0.0) for level in levels], thickness=thickness)
self.piers.append((p, None))
self.parts.append((p, None))
if kerf:
for part, _ in self.parts:
part.applykerf(kerf)

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
class Shape(object):
def __init__(self, thickness, kerf):
self.thickness = thickness
self.kerf = kerf
self.parts = []
return
def close(self):
for part in self.parts:
part[0].close()

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
from ..platforms.circular import Circular
from .shape import Shape
class Sphere(Shape):
""" List of segments that make up a part. """
def __init__(self):
super(Sphere, self).__init__()

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from .support import Support
from .pier import Pier
__all__ = ["Support", "Pier"]

View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.trace import Trace
from .support import Support
from ..util import isclose
class Pier(Support):
""" List of segments that make up a part. """
def __init__(self, height, depth, cutdepth, levels, thickness):
super(Pier, self).__init__(height, thickness)
self.depth = depth
self.cutdepth = cutdepth
# The 'levels' list is defined as follows:
# [(height1, x-offset1), (height2, x-offset2), ...]
# where the values are:
# height<n>: The height of the bottom of the platform (0.0=at base level, height-thickness=at top level).
# x-offset<n>: The distance from the core axis for this level. Used to create slopes and curves.
ys, xs = zip(*sorted(levels))
if any([(h2) - (h1) < 3*thickness for h1, h2
in zip(ys[:-1], ys[1:])]):
raise RuntimeError("Pier levels are too close. Try decreasing the number of levels.")
self.topcut = isclose(ys[-1], height-thickness)
self.bottomcut = isclose(ys[0], 0.0)
self.vertical = all([isclose(x1, x2) for x1, x2 in zip(xs[:-1], xs[1:])])
# Starting at the bottom inner point, trace up the uncut side.
if self.vertical:
tx = [0.0, 0.0]
ty = [0.0, height]
else:
tx = list(xs)
ty = list(ys[:-1]) + [height]
# Add the top points.
xtop = xs[0]
xbottom = xs[-1]
if self.topcut:
tx.extend([xtop+depth-cutdepth, xtop+depth-cutdepth, xtop+depth])
ty.extend([height, height-thickness, height-thickness])
else:
tx.extend([xtop+depth])
ty.extend([height])
if self.topcut and self.bottomcut:
xs = xs[1:-1]
ys = ys[1:-1]
elif self.topcut:
xs = xs[:-1]
ys = ys[:-1]
elif self.bottomcut:
xs = xs[1:]
ys = ys[1:]
for y, x in zip(reversed(ys), reversed(xs)):
tx.extend([x+depth, x+depth-cutdepth, x+depth-cutdepth, x+depth])
ty.extend([y+thickness, y+thickness, y, y])
if self.bottomcut:
tx.extend([xbottom+depth, xbottom+depth-cutdepth, xbottom+depth-cutdepth, xbottom])
ty.extend([thickness, thickness, 0.0, 0.0])
else:
tx.extend([xbottom+depth, xbottom])
ty.extend([0.0, 0.0])
self.traces.append(Trace(x=tx, y=ty))

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from ..core.part import Part
class Support(Part):
""" General support. """
def __init__(self, height, thickness):
super(Support, self).__init__()
self.height = height
self.thickness = thickness

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Michael Matthews
#
# This file is part of CutCraft.
#
# CutCraft is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CutCraft is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CutCraft. If not, see <http://www.gnu.org/licenses/>.
from math import pi, atan2, cos, sin, sqrt
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
# Required as Inkscape uses old version of Python that does not include math.isclose().
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
def iscloselist(a, b):
# Functionality of isclose() for lists of values.
return all([isclose(aval, bval) for aval, bval in zip(a, b)])
def intersection(radius, angle=None, x=None, y=None):
# With a circle of a given radius determine the intercepts for an angle, x or y coordinate.
if angle:
# Returns (x,y) tuple of intercept.
return (cos(angle)*radius, sin(angle)*radius)
elif x:
y = sqrt((radius)**2 - x**2)
return (y, atan2(y, x))
elif y:
x = sqrt((radius)**2 - y**2)
return (x, atan2(y, x))
else:
raise ValueError("Invalid values passed to intersection().")

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<_name>Cut-Craft Box</_name>
<id>fablabchemnitz.de.cutcraft.box</id>
<param name="active-tab" type="notebook">
<page name="title" _gui-text="Box Properties">
<param name="unit" _gui-text="Measurement Units" type="optiongroup" appearance="minimal">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
</param>
<_param name="help1" type="description" xml:space="preserve">------------------------------</_param>
<param name="width" type="float" min="10.0" max="1000.0" precision="3" _gui-text="Box Width">60.0</param>
<param name="depth" type="float" min="10.0" max="1000.0" precision="3" _gui-text="Box Depth">30.0</param>
<param name="height" type="float" min="10.0" max="1000.0" precision="3" _gui-text="Box Height">30.0</param>
<_param name="help2" type="description" xml:space="preserve">------------------------------</_param>
<param name="thickness" type="float" min="0.1" max="1000.0" precision="3" _gui-text="Material Thickness">5.0</param>
<param name="kerf" type="float" min="0.0" max="1000.0" precision="3" _gui-text="Laser Cutter Kerf">0.01</param>
<param name="linethickness" _gui-text="Line Thickness" type="optiongroup" appearance="minimal">
<option value="1px">1 pixel</option>
<option value="0.002in">hairline</option>
</param>
</page>
<page name="Usage1" _gui-text="Help">
<_param name="use1" type="description" xml:space="preserve">Cut Craft Box: Help
Measurement Units: Unit of measurement for all subsequent values entered in this dialog.
Width: Cylinder Width.
Depth: Cylinder Depth.
Height: Cylinder Height.
Thickness: Thickness of the material.
Kerf: Laser Cutter Kerf (tolerance). Varies based on cutter and material thickness.
Line Thickness: Thickness of the cutting line on the display.
</_param>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="FabLab Chemnitz">
<submenu _name="Finger-jointed/Tabbed Boxes" />
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">fablabchemnitz_cutcraftbox.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
import inkex
from fablabchemnitz_cutcraftshape import CutCraftShape
import cutcraft.platforms as cp
from cutcraft.shapes import Box
class CutCraftBox(CutCraftShape):
def __init__(self):
CutCraftShape.__init__(self)
self.arg_parser.add_argument("--width", type=float, default=6.0, help="Box Width")
self.arg_parser.add_argument("--depth", type=float, default=6.0, help="Box Depth")
self.arg_parser.add_argument("--height", type=float, default=60.0, help="Box height")
def effect(self):
CutCraftShape.effect(self)
width = self.svg.unittouu( str(self.options.width) + self.unit )
depth = self.svg.unittouu( str(self.options.depth) + self.unit )
height = self.svg.unittouu( str(self.options.height) + self.unit )
shape = Box(width, depth, height, self.thickness, self.kerf)
self.pack(shape)
if __name__ == '__main__':
CutCraftBox().run()

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<_name>Cut-Craft Cylinder</_name>
<id>fablabchemnitz.de.cutcraft.cylinder</id>
<param name="active-tab" type="notebook">
<page name="title" _gui-text="Cylinder Properties">
<param name="unit" _gui-text="Measurement Units" type="optiongroup" appearance="minimal">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
</param>
<_param name="help1" type="description" xml:space="preserve">------------------------------</_param>
<param name="height" type="float" min="10.0" max="1000.0" precision="3" _gui-text="Height">60.0</param>
<param name="outer" type="float" min="0.1" max="1000.0" precision="3" _gui-text="Outer diameter">60.0</param>
<param name="inner" type="float" min="0.1" max="1000.0" precision="3" _gui-text="Inner diameter">30.0</param>
<param name="vertices" type="int" min="3" max="180" _gui-text="Number of Vertices (3..180)">3</param>
<param name="levels" type="int" min="2" max="100" _gui-text="Number of Levels (2..100)">2</param>
<param name="supports" type="int" min="3" max="18" _gui-text="Number of Supports (3..18)">3</param>
<param name="supwidth" type="float" min="0.1" max="1000.0" _gui-text="Support Width">6.0</param>
<_param name="help2" type="description" xml:space="preserve">------------------------------</_param>
<param name="thickness" type="float" min="0.1" max="1000.0" precision="3" _gui-text="Material Thickness">5.0</param>
<param name="kerf" type="float" min="0.0" max="1000.0" precision="3" _gui-text="Laser Cutter Kerf">0.01</param>
<param name="linethickness" _gui-text="Line Thickness" type="optiongroup" appearance="minimal">
<option value="1px">1 pixel</option>
<option value="0.002in">hairline</option>
</param>
</page>
<page name="Usage1" _gui-text="Help">
<_param name="use1" type="description" xml:space="preserve">Cut Craft Cylinder: Help
Measurement Units: Unit of measurement for all subsequent values entered in this dialog.
Height: Cylinder Height.
Outer Diameter: Outside diameter of the Cylinder.
Inner Diameter: Inside diameter of the Cylinder.
Number of Vertices: Number of vertices for the Cylinder (3 = Triangle, 4 = Square, ... 90 = Circular).
Number of Levels: Number of horizontal circular platforms.
Number of Supports: Number of vertical supports holding the cylinder together.
Support Width: Width of the vertical supports holding the cylinder together.
Thickness: Thickness of the material.
Kerf: Laser Cutter Kerf (tolerance). Varies based on cutter and material thickness.
Line Thickness: Thickness of the cutting line on the display.
</_param>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="FabLab Chemnitz">
<submenu _name="Finger-jointed/Tabbed Boxes" />
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">fablabchemnitz_cutcraftcylinder.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
import inkex
from fablabchemnitz_cutcraftshape import CutCraftShape
import cutcraft.platforms as cp
from cutcraft.shapes import Cylinder
class CutCraftCylinder(CutCraftShape):
def __init__(self):
CutCraftShape.__init__(self)
self.arg_parser.add_argument("--vertices", type=int, default=3, help="Number of vertices")
self.arg_parser.add_argument("--levels", type=int, default=3, help="Number of levels")
self.arg_parser.add_argument("--supports", type=int, default=3, help="Number of supports")
self.arg_parser.add_argument("--supwidth", type=float, default=6.0, help="Support Width")
self.arg_parser.add_argument("--height", type=float, default=60.0, help="Cylinder height")
self.arg_parser.add_argument("--outer", type=float, default=60.0, help="Diameter of cylinder")
self.arg_parser.add_argument("--inner", type=float, default=30.0, help="Diameter of central hole - 0.0 for no hole")
def effect(self):
CutCraftShape.effect(self)
vertices = self.options.vertices
levels = self.options.levels
supports = self.options.supports
supwidth = self.svg.unittouu( str(self.options.supwidth) + self.unit )
height = self.svg.unittouu( str(self.options.height) + self.unit )
outer = self.svg.unittouu( str(self.options.outer) + self.unit )
inner = self.svg.unittouu( str(self.options.inner) + self.unit )
if outer<=inner:
self._error("ERROR: Outer diameter must be greater than inner diameter.")
exit()
shape = Cylinder(height, outer/2.0, inner/2.0, vertices, supports, supwidth/2.0, supwidth, levels,
self.thickness, self.kerf)
self.pack(shape)
if __name__ == '__main__':
CutCraftCylinder().run()

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<_name>Cut-Craft RollerBot</_name>
<id>fablabchemnitz.de.cutcraft.rollerbot</id>
<param name="active-tab" type="notebook">
<page name="title" _gui-text="RollerBot Properties">
<param name="unit" _gui-text="Measurement Units" type="optiongroup" appearance="minimal">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
</param>
<_param name="help1" type="description" xml:space="preserve">------------------------------</_param>
<param name="supwidth" type="float" min="0.1" max="1000.0" _gui-text="Support Width">12.0</param>
<_param name="help2" type="description" xml:space="preserve">------------------------------</_param>
<param name="thickness" type="float" min="0.1" max="1000.0" precision="3" _gui-text="Material Thickness">5.0</param>
<param name="kerf" type="float" min="0.0" max="1000.0" precision="3" _gui-text="Laser Cutter Kerf">0.01</param>
<param name="linethickness" _gui-text="Line Thickness" type="optiongroup" appearance="minimal">
<option value="1px">1 pixel</option>
<option value="0.002in">hairline</option>
</param>
</page>
<page name="Usage1" _gui-text="Help">
<_param name="use1" type="description" xml:space="preserve">Cut Craft RollerBot: Help
Measurement Units: Unit of measurement for all subsequent values entered in this dialog.
Support Width: Width of the supports holding the robot together.
Thickness: Thickness of the material.
Kerf: Laser Cutter Kerf (tolerance). Varies based on cutter and material thickness.
Line Thickness: Thickness of the cutting line on the display.
</_param>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="FabLab Chemnitz">
<submenu _name="Finger-jointed/Tabbed Boxes" />
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">fablabchemnitz_cutcraftrollerbot.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
import inkex
from fablabchemnitz_cutcraftshape import CutCraftShape
import cutcraft.platforms as cp
from cutcraft.shapes import RollerBot
class CutCraftRollerBot(CutCraftShape):
def __init__(self):
CutCraftShape.__init__(self)
self.arg_parser.add_argument("--supwidth", type=float, default=6.0, help="Support Width")
def effect(self):
CutCraftShape.effect(self)
supwidth = self.svg.unittouu( str(self.options.supwidth) + self.unit )
# Constants in the current RollerBot design.
wheelradius = self.svg.unittouu( str(100.0) + "mm" )
upperradius = self.svg.unittouu( str(92.0) + "mm" )
lowerradius = self.svg.unittouu( str(82.0) + "mm" )
facesize = self.svg.unittouu( str(50.0) + "mm" )
barsize = self.svg.unittouu( str(25.4) + "mm" )
scale = self.svg.unittouu( str(1.0) + "mm" )
primarygapwidth = self.svg.unittouu( str(70.0) + "mm" ) # Must be greater than width of Raspberry PI / Arduino.
secondarygapwidth = self.svg.unittouu( str(25.0) + "mm" )
width = primarygapwidth*2.0 + secondarygapwidth*2.0 + self.thickness*5.0
shape = RollerBot(width, supwidth, wheelradius, upperradius, lowerradius,
facesize, barsize, primarygapwidth, secondarygapwidth, scale,
self.thickness, self.kerf)
self.pack(shape)
if __name__ == '__main__':
CutCraftRollerBot().run()

View File

@ -0,0 +1,119 @@
#!/usr/bin/env python3
import gettext
import inkex
from math import floor
from cutcraft.core import Point, Rectangle
from lxml import etree
#TODOS
'''
since InkScape 1.0 / Python 3 adjustments are required to fix "TypeError: '<' not supported between instances of 'Pier' and 'Pier'". A __lt__ method has to be implemented
"for this reasion items = sorted([(p[0].area(),p[0]) for p in shape.parts], reverse=True)" was commented out
'''
class CutCraftNode(object):
def __init__(self, rect):
self.children = []
self.rectangle = rect
self.part = None
def insert(self, part, shape):
if len(self.children)>0:
node = self.children[0].insert(part, shape)
if node is not None:
return node
else:
return self.children[1].insert(part, shape)
if self.part is not None:
return None
pwidth, pheight = part.bbox().expanded().size()
nwidth, nheight = self.rectangle.expanded().size()
if pwidth>nwidth or pheight>nheight:
# Too small.
return None
if pwidth==nwidth and pheight==nheight:
# This node fits.
self.part = part
return self
nleft, ntop = self.rectangle.expanded().topleft.tup()
nright, nbottom = self.rectangle.expanded().bottomright.tup()
if nwidth - pwidth > nheight - pheight:
r1 = Rectangle(Point(nleft, ntop),
Point(nleft+pwidth, nbottom))
r2 = Rectangle(Point(nleft+pwidth+1.0, ntop),
Point(nright, nbottom))
else:
r1 = Rectangle(Point(nleft, ntop),
Point(nright, ntop+pheight))
r2 = Rectangle(Point(nleft, ntop+pheight+1.0),
Point(nright, nbottom))
self.children = [CutCraftNode(r1), CutCraftNode(r2)]
return self.children[0].insert(part, shape)
class CutCraftShape(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.arg_parser.add_argument("--active-tab", default="Options", help="The tab selected when OK was pressed")
self.arg_parser.add_argument("--unit", default="mm", help="unit of measure for circular pitch and center diameter")
self.arg_parser.add_argument("--thickness", type=float, default=20.0, help="Material Thickness")
self.arg_parser.add_argument("--kerf", type=float, default=20.0, help="Laser Cutter Kerf")
self.arg_parser.add_argument("--linethickness", default="1px", help="Line Thickness")
def effect(self):
self.unit = self.options.unit
self.thickness = self.svg.unittouu( str(self.options.thickness) + self.unit)
self.kerf = self.svg.unittouu( str(self.options.kerf) + self.unit)
self.linethickness = self.svg.unittouu(self.options.linethickness)
svg = self.document.getroot()
self.docwidth = self.svg.unittouu(svg.get('width'))
self.docheight = self.svg.unittouu(svg.get('height'))
self.parent=self.svg.get_current_layer()
layer = etree.SubElement(svg, 'g')
layer.set(inkex.addNS('label', 'inkscape'), 'newlayer')
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
def _debug(self, string):
inkex.debug( gettext.gettext(str(string)) )
def _error(self, string):
inkex.errormsg( gettext.gettext(str(string)) )
def pack(self, shape):
# Pack the individual parts onto the current canvas.
line_style = { 'stroke': '#000000',
'stroke-width': str(self.linethickness),
'fill': 'none' }
#items = sorted([(p[0].area(),p[0]) for p in shape.parts], reverse=True)
items = [(p[0].area(),p[0]) for p in shape.parts]
#for p in shape.parts:
# inkex.utils.debug(p[0])
rootnode = CutCraftNode(Rectangle(Point(0.0, 0.0), Point(floor(self.docwidth), floor(self.docheight))))
for i, (_, part) in enumerate(items):
node = rootnode.insert(part, self)
if node is None:
self._error("ERROR: Cannot fit parts onto canvas.\n" +
"Try a larger canvas and then manually arrange if required.")
exit()
bbox = part.bbox().expanded()
part += -bbox.topleft
part += node.rectangle.topleft
line_attribs = { 'style' : str(inkex.Style(line_style)),
inkex.addNS('label','inkscape') : 'Test ' + str(i),
'd' : part.svg() }
_ = etree.SubElement(self.parent, inkex.addNS('path','svg'), line_attribs)