Added Cut-Craft extensions (another box makers)
This commit is contained in:
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
|
Reference in New Issue
Block a user