aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--playground/csp/.gitignore1
-rw-r--r--playground/csp/main.py50
-rw-r--r--src_py/opcut/common.py49
-rw-r--r--src_py/opcut/csp.py111
-rw-r--r--src_py/opcut/output.py108
5 files changed, 217 insertions, 102 deletions
diff --git a/playground/csp/.gitignore b/playground/csp/.gitignore
new file mode 100644
index 0000000..61d48f9
--- /dev/null
+++ b/playground/csp/.gitignore
@@ -0,0 +1 @@
+/output.pdf
diff --git a/playground/csp/main.py b/playground/csp/main.py
index d08f748..8aed531 100644
--- a/playground/csp/main.py
+++ b/playground/csp/main.py
@@ -1,38 +1,42 @@
import sys
sys.path += ['../../src_py']
+from opcut import common
from opcut import csp
+from opcut import output
def main():
panels = [
- csp.Panel(id='p1', width=100, height=100)
+ common.Panel(id='p1', width=100, height=65)
]
items = [
- csp.Item(id='i1', width=10, height=10, rotate=True),
- csp.Item(id='i2', width=10, height=9, rotate=True),
- csp.Item(id='i3', width=20, height=8, rotate=True),
- csp.Item(id='i4', width=10, height=20, rotate=True),
- csp.Item(id='i5', width=30, height=19, rotate=True),
- csp.Item(id='i6', width=10, height=18, rotate=True),
- csp.Item(id='i7', width=10, height=17, rotate=True),
- csp.Item(id='i8', width=20, height=16, rotate=True),
- csp.Item(id='i9', width=10, height=15, rotate=True),
- csp.Item(id='i10', width=30, height=14, rotate=True),
- csp.Item(id='i11', width=10, height=20, rotate=True),
- csp.Item(id='i12', width=30, height=19, rotate=True),
- csp.Item(id='i13', width=10, height=18, rotate=True),
- csp.Item(id='i14', width=10, height=17, rotate=True),
- csp.Item(id='i15', width=20, height=16, rotate=True),
- csp.Item(id='i16', width=10, height=15, rotate=True),
- csp.Item(id='i17', width=30, height=14, rotate=True),
- csp.Item(id='i18', width=10, height=20, rotate=True),
- csp.Item(id='i19', width=30, height=19, rotate=True),
+ common.Item(id='i1', width=10, height=10, can_rotate=True),
+ common.Item(id='i2', width=10, height=9, can_rotate=True),
+ common.Item(id='i3', width=20, height=8, can_rotate=True),
+ common.Item(id='i4', width=10, height=20, can_rotate=True),
+ common.Item(id='i5', width=30, height=19, can_rotate=True),
+ common.Item(id='i6', width=10, height=18, can_rotate=False),
+ common.Item(id='i7', width=10, height=17, can_rotate=True),
+ common.Item(id='i8', width=20, height=16, can_rotate=True),
+ common.Item(id='i9', width=10, height=15, can_rotate=True),
+ common.Item(id='i10', width=30, height=14, can_rotate=True),
+ common.Item(id='i11', width=10, height=20, can_rotate=True),
+ common.Item(id='i12', width=19, height=30, can_rotate=False),
+ common.Item(id='i13', width=10, height=18, can_rotate=True),
+ common.Item(id='i14', width=10, height=17, can_rotate=True),
+ common.Item(id='i15', width=20, height=16, can_rotate=True),
+ common.Item(id='i16', width=10, height=15, can_rotate=True),
+ common.Item(id='i17', width=30, height=14, can_rotate=True),
+ common.Item(id='i18', width=10, height=20, can_rotate=True),
+ common.Item(id='i19', width=30, height=19, can_rotate=True),
]
- cut_width = 1
- method = csp.Method.FORWARD_GREEDY
+ cut_width = 0.4
+ method = common.Method.FORWARD_GREEDY
result = csp.calculate(panels, items, cut_width, method)
- print(result)
+ pdf_bytes = output.generate_pdf(result)
+ with open('output.pdf', 'wb') as f:
+ f.write(pdf_bytes)
if __name__ == '__main__':
diff --git a/src_py/opcut/common.py b/src_py/opcut/common.py
new file mode 100644
index 0000000..c7884e8
--- /dev/null
+++ b/src_py/opcut/common.py
@@ -0,0 +1,49 @@
+import enum
+
+from opcut import util
+
+
+State = util.namedtuple(
+ 'State',
+ ['cut_width', 'float'],
+ ['panels', 'List[Panel]'],
+ ['items', 'List[Item]'],
+ ['used', 'List[Used]'],
+ ['unused', 'List[Unused]'])
+
+Panel = util.namedtuple(
+ 'Panel',
+ ['id', 'Any'],
+ ['width', 'float'],
+ ['height', 'float'])
+
+Item = util.namedtuple(
+ 'Item',
+ ['id', 'Any'],
+ ['width', 'float'],
+ ['height', 'float'],
+ ['can_rotate', 'bool'])
+
+Used = util.namedtuple(
+ 'Used',
+ ['panel', 'Panel'],
+ ['item', 'Item'],
+ ['x', 'float'],
+ ['y', 'float'],
+ ['rotate', 'bool'])
+
+Unused = util.namedtuple(
+ 'Unused',
+ ['panel', 'Panel'],
+ ['width', 'float'],
+ ['height', 'float'],
+ ['x', 'float'],
+ ['y', 'float'])
+
+Method = enum.Enum('Method', [
+ 'GREEDY',
+ 'FORWARD_GREEDY'])
+
+
+class UnresolvableError(Exception):
+ """Exception raised when State is not solvable"""
diff --git a/src_py/opcut/csp.py b/src_py/opcut/csp.py
index 209acec..4d53d6e 100644
--- a/src_py/opcut/csp.py
+++ b/src_py/opcut/csp.py
@@ -1,82 +1,35 @@
-import enum
import itertools
-from opcut import util
-
-
-State = util.namedtuple(
- '_State',
- ['cut_width', 'float'],
- ['panels', 'List[Panel]'],
- ['items', 'List[Item]'],
- ['used', 'List[Used]'],
- ['unused', 'List[Unused]'])
-
-Panel = util.namedtuple(
- 'Panel',
- ['id', 'Any'],
- ['width', 'float'],
- ['height', 'float'])
-
-Item = util.namedtuple(
- 'Item',
- ['id', 'Any'],
- ['width', 'float'],
- ['height', 'float'],
- ['rotate', 'bool'])
-
-Used = util.namedtuple(
- 'Used',
- ['panel', 'Panel'],
- ['item', 'Item'],
- ['x', 'float'],
- ['y', 'float'],
- ['rotate', 'bool'])
-
-Unused = util.namedtuple(
- 'Unused',
- ['panel', 'Panel'],
- ['width', 'float'],
- ['height', 'float'],
- ['x', 'float'],
- ['y', 'float'])
-
-Method = enum.Enum('Method', [
- 'GREEDY',
- 'FORWARD_GREEDY'])
-
-
-class UnresolvableError(Exception):
- pass
+from opcut import common
def calculate(panels, items, cut_width, method):
"""Calculate cutting stock problem
Args:
- panels (List[Panel]): input panels
- items (List[Item]): input items
+ panels (List[common.Panel]): input panels
+ items (List[common.Item]): input items
cut_width (float): cut width
- method (Method): calculation method
+ method (common.Method): calculation method
Returns:
State
"""
- state = State(
+ state = common.State(
cut_width=cut_width,
panels=panels,
items=items,
used=[],
- unused=[Unused(panel=panel,
- width=panel.width,
- height=panel.height,
- x=0,
- y=0)
+ unused=[common.Unused(panel=panel,
+ width=panel.width,
+ height=panel.height,
+ x=0,
+ y=0)
for panel in panels])
return {
- Method.GREEDY: _calculate_greedy,
- Method.FORWARD_GREEDY: _calculate_forward_greedy
+ common.Method.GREEDY: _calculate_greedy,
+ common.Method.FORWARD_GREEDY: _calculate_forward_greedy
}[method](state)
@@ -93,7 +46,7 @@ def _calculate_greedy(state):
new_state = next_state
new_fitness = next_state_fitness
if not new_state:
- raise UnresolvableError()
+ raise common.UnresolvableError()
state = new_state
return state
@@ -105,13 +58,13 @@ def _calculate_forward_greedy(state):
for next_state in _get_next_states(state):
try:
next_state_fitness = _fitness(_calculate_greedy(next_state))
- except UnresolvableError:
+ except common.UnresolvableError:
continue
if new_fitness is None or next_state_fitness < new_fitness:
new_state = next_state
new_fitness = next_state_fitness
if not new_state:
- raise UnresolvableError()
+ raise common.UnresolvableError()
state = new_state
return state
@@ -134,7 +87,7 @@ def _get_next_states(state):
def _get_next_states_for_item(state, item):
ret = []
loop_iter = ((False, i, unused) for i, unused in enumerate(state.unused))
- if item.rotate:
+ if item.can_rotate:
loop_iter = itertools.chain(
loop_iter,
((True, i, unused) for i, unused in enumerate(state.unused)))
@@ -155,28 +108,28 @@ def _cut_item_from_unused(unused, item, rotate, cut_width, vertical):
item_height = item.height if not rotate else item.width
if unused.height < item_height or unused.width < item_width:
return None, []
- used = Used(panel=unused.panel,
- item=item,
- x=unused.x,
- y=unused.y,
- rotate=rotate)
+ used = common.Used(panel=unused.panel,
+ item=item,
+ x=unused.x,
+ y=unused.y,
+ rotate=rotate)
new_unused = []
width = unused.width - item_width - cut_width
- height = unused.height if vertical else item.height
+ height = unused.height if vertical else item_height
if width > 0:
- new_unused.append(Unused(panel=unused.panel,
- width=width,
- height=height,
- x=unused.x + item_width + cut_width,
- y=unused.y))
+ new_unused.append(common.Unused(panel=unused.panel,
+ width=width,
+ height=height,
+ x=unused.x + item_width + cut_width,
+ y=unused.y))
width = item_width if vertical else unused.width
height = unused.height - item_height - cut_width
if height > 0:
- new_unused.append(Unused(panel=unused.panel,
- width=width,
- height=height,
- x=unused.x,
- y=unused.y + item_height + cut_width))
+ new_unused.append(common.Unused(panel=unused.panel,
+ width=width,
+ height=height,
+ x=unused.x,
+ y=unused.y + item_height + cut_width))
return used, new_unused
diff --git a/src_py/opcut/output.py b/src_py/opcut/output.py
new file mode 100644
index 0000000..bd1def1
--- /dev/null
+++ b/src_py/opcut/output.py
@@ -0,0 +1,108 @@
+import io
+
+from reportlab.pdfgen import canvas
+from reportlab.lib.units import mm
+
+from opcut import util
+
+
+def generate_pdf(state, pagesize_mm=(210, 297),
+ margin_top_mm=10, margin_bottom_mm=20,
+ margin_left_mm=10, margin_right_mm=10):
+ """Generate PDF output
+
+ Args:
+ state (opcut.common.State): state
+ pagesize (Tuple[float,float]): page size as (with, height) in mm
+ margin_top_mm (float): margin top in mm
+ margin_bottom_mm (float): margin bottom in mm
+ margin_left_mm (float): margin left in mm
+ margin_right_mm (float): margin right in mm
+
+ Returns:
+ bytes
+
+ """
+ pagesize = tuple(map(lambda x: x * mm, pagesize_mm))
+ margin = _Margin(top=margin_top_mm * mm,
+ right=margin_right_mm * mm,
+ bottom=margin_bottom_mm * mm,
+ left=margin_left_mm * mm)
+ ret = io.BytesIO()
+ c = canvas.Canvas(ret, pagesize=pagesize)
+ c.setFillColorRGB(0.9, 0.9, 0.9)
+ for panel in state.panels:
+ _pdf_write_panel(c, pagesize, margin, state, panel)
+ c.save()
+ return ret.getvalue()
+
+
+def generate_csv(state):
+ """Generate CSV output
+
+ Args:
+ state (opcut.common.State): state
+
+ Returns:
+ bytes
+
+ """
+ return b''
+
+
+_Margin = util.namedtuple('_Margin', 'top', 'right', 'bottom', 'left')
+
+
+def _pdf_write_panel(c, pagesize, margin, state, panel):
+ if (panel.width / panel.height >
+ (pagesize[0] - margin.left - margin.right) /
+ (pagesize[1] - margin.top - margin.bottom)):
+ scale = (
+ (pagesize[0] - (margin.left + margin.right) * mm) /
+ panel.width)
+ else:
+ scale = (
+ (pagesize[1] - (margin.top + margin.bottom) * mm) /
+ panel.height)
+ width = panel.width * scale
+ height = panel.height * scale
+ x0 = ((pagesize[0] - width) * margin.left /
+ (margin.left + margin.right))
+ y0 = ((pagesize[1] - height) * margin.bottom /
+ (margin.top + margin.bottom))
+ c.setFillColorRGB(0.5, 0.5, 0.5)
+ c.rect(x0, y0, width, height, stroke=1, fill=1)
+ for used in state.used:
+ if used.panel != panel:
+ continue
+ _pdf_write_used(c, x0, y0, scale, state, used)
+ for unused in state.unused:
+ if unused.panel != panel:
+ continue
+ _pdf_write_unused(c, x0, y0, scale, state, unused)
+ c.setFillColorRGB(0, 0, 0)
+ c.drawCentredString(pagesize[0] / 2, margin.bottom / 2,
+ panel.id)
+ c.showPage()
+
+
+def _pdf_write_used(c, x0, y0, scale, state, used):
+ width = used.item.width * scale
+ height = used.item.height * scale
+ if used.rotate:
+ width, height = height, width
+ x = used.x * scale + x0
+ y = (used.panel.height - used.y) * scale + y0 - height
+ c.setFillColorRGB(1, 1, 1)
+ c.rect(x, y, width, height, stroke=0, fill=1)
+ c.setFillColorRGB(0, 0, 0)
+ c.drawCentredString(x + width / 2, y + height / 2 - 6, used.item.id)
+
+
+def _pdf_write_unused(c, x0, y0, scale, state, unused):
+ width = unused.width * scale
+ height = unused.height * scale
+ x = unused.x * scale + x0
+ y = (unused.panel.height - unused.y) * scale + y0 - height
+ c.setFillColorRGB(0.9, 0.9, 0.9)
+ c.rect(x, y, width, height, stroke=0, fill=1)