Added Cut-Craft extensions (another box makers)
This commit is contained in:
parent
a45a58f171
commit
6ca5ffdea7
57
extensions/cutcraft/README.md
Normal file
57
extensions/cutcraft/README.md
Normal 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).
|
18
extensions/cutcraft/__init__.py
Normal file
18
extensions/cutcraft/__init__.py
Normal 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/>.
|
||||
|
28
extensions/cutcraft/core/__init__.py
Normal file
28
extensions/cutcraft/core/__init__.py
Normal 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"]
|
132
extensions/cutcraft/core/circle.py
Normal file
132
extensions/cutcraft/core/circle.py
Normal 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)
|
41
extensions/cutcraft/core/fingerjoint.py
Normal file
41
extensions/cutcraft/core/fingerjoint.py
Normal 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)]
|
83
extensions/cutcraft/core/line.py
Normal file
83
extensions/cutcraft/core/line.py
Normal 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]) + ")"
|
66
extensions/cutcraft/core/neopixel.py
Normal file
66
extensions/cutcraft/core/neopixel.py
Normal 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
|
92
extensions/cutcraft/core/part.py
Normal file
92
extensions/cutcraft/core/part.py
Normal 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 "") + ")"
|
65
extensions/cutcraft/core/point.py
Normal file
65
extensions/cutcraft/core/point.py
Normal 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) + ")"
|
61
extensions/cutcraft/core/rectangle.py
Normal file
61
extensions/cutcraft/core/rectangle.py
Normal 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) + ")"
|
150
extensions/cutcraft/core/trace.py
Normal file
150
extensions/cutcraft/core/trace.py
Normal 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
|
23
extensions/cutcraft/platforms/__init__.py
Normal file
23
extensions/cutcraft/platforms/__init__.py
Normal 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"]
|
40
extensions/cutcraft/platforms/circular.py
Normal file
40
extensions/cutcraft/platforms/circular.py
Normal 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))
|
25
extensions/cutcraft/platforms/platform.py
Normal file
25
extensions/cutcraft/platforms/platform.py
Normal 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
|
379
extensions/cutcraft/platforms/rollerframe.py
Normal file
379
extensions/cutcraft/platforms/rollerframe.py
Normal 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))
|
26
extensions/cutcraft/shapes/__init__.py
Normal file
26
extensions/cutcraft/shapes/__init__.py
Normal 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"]
|
163
extensions/cutcraft/shapes/box.py
Normal file
163
extensions/cutcraft/shapes/box.py
Normal 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
|
35
extensions/cutcraft/shapes/cone.py
Normal file
35
extensions/cutcraft/shapes/cone.py
Normal 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))
|
48
extensions/cutcraft/shapes/cylinder.py
Normal file
48
extensions/cutcraft/shapes/cylinder.py
Normal 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)
|
65
extensions/cutcraft/shapes/rollerbot.py
Normal file
65
extensions/cutcraft/shapes/rollerbot.py
Normal 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)
|
28
extensions/cutcraft/shapes/shape.py
Normal file
28
extensions/cutcraft/shapes/shape.py
Normal 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()
|
26
extensions/cutcraft/shapes/sphere.py
Normal file
26
extensions/cutcraft/shapes/sphere.py
Normal 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__()
|
22
extensions/cutcraft/supports/__init__.py
Normal file
22
extensions/cutcraft/supports/__init__.py
Normal 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"]
|
85
extensions/cutcraft/supports/pier.py
Normal file
85
extensions/cutcraft/supports/pier.py
Normal 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))
|
26
extensions/cutcraft/supports/support.py
Normal file
26
extensions/cutcraft/supports/support.py
Normal 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
|
41
extensions/cutcraft/util.py
Normal file
41
extensions/cutcraft/util.py
Normal 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().")
|
60
extensions/fablabchemnitz_cutcraftbox.inx
Normal file
60
extensions/fablabchemnitz_cutcraftbox.inx
Normal 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>
|
27
extensions/fablabchemnitz_cutcraftbox.py
Normal file
27
extensions/fablabchemnitz_cutcraftbox.py
Normal 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()
|
73
extensions/fablabchemnitz_cutcraftcylinder.inx
Normal file
73
extensions/fablabchemnitz_cutcraftcylinder.inx
Normal 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>
|
40
extensions/fablabchemnitz_cutcraftcylinder.py
Normal file
40
extensions/fablabchemnitz_cutcraftcylinder.py
Normal 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()
|
53
extensions/fablabchemnitz_cutcraftrollerbot.inx
Normal file
53
extensions/fablabchemnitz_cutcraftrollerbot.inx
Normal 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>
|
37
extensions/fablabchemnitz_cutcraftrollerbot.py
Normal file
37
extensions/fablabchemnitz_cutcraftrollerbot.py
Normal 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()
|
119
extensions/fablabchemnitz_cutcraftshape.py
Normal file
119
extensions/fablabchemnitz_cutcraftshape.py
Normal 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)
|
Reference in New Issue
Block a user