From babe3d394a600494c1db4e7daf40e39afd76da75 Mon Sep 17 00:00:00 2001 From: "bozo.kopic" Date: Mon, 27 Jun 2022 22:07:56 +0200 Subject: WIP native implementation --- src_py/opcut/calculate.py | 43 ++++----- src_py/opcut/libopcut.py | 228 ++++++++++++++++++++++++++++++++++++++++++++++ src_py/opcut/main.py | 2 +- 3 files changed, 247 insertions(+), 26 deletions(-) create mode 100644 src_py/opcut/libopcut.py (limited to 'src_py') diff --git a/src_py/opcut/calculate.py b/src_py/opcut/calculate.py index 7ae14ce..e0a6752 100644 --- a/src_py/opcut/calculate.py +++ b/src_py/opcut/calculate.py @@ -1,33 +1,23 @@ import itertools from opcut import common +from opcut import libopcut def calculate(method: common.Method, params: common.Params ) -> common.Result: """Calculate cutting stock problem""" - unused = [common.Unused(panel=panel, - width=panel.width, - height=panel.height, - x=0, - y=0) - for panel in params.panels] - result = common.Result(params=params, - used=[], - unused=unused) if method == common.Method.GREEDY: - return _calculate_greedy(result) + return _calculate_greedy(_create_initial_result(params)) - elif method == common.Method.FORWARD_GREEDY: - return _calculate_forward_greedy(result) + if method == common.Method.FORWARD_GREEDY: + return _calculate_forward_greedy(_create_initial_result(params)) - elif method == common.Method.GREEDY_NATIVE: - return _calculate_greedy_native(result) - - elif method == common.Method.FORWARD_GREEDY_NATIVE: - return _calculate_forward_greedy_native(result) + if method in (common.Method.GREEDY_NATIVE, + common.Method.FORWARD_GREEDY_NATIVE): + return libopcut.calculate(method, params) raise ValueError('unsupported method') @@ -35,6 +25,17 @@ def calculate(method: common.Method, _fitness_K = 0.03 +def _create_initial_result(params): + return common.Result(params=params, + used=[], + unused=[common.Unused(panel=panel, + width=panel.width, + height=panel.height, + x=0, + y=0) + for panel in params.panels]) + + def _calculate_greedy(result): while not _is_done(result): new_result = None @@ -68,14 +69,6 @@ def _calculate_forward_greedy(result): return result -def _calculate_greedy_native(result): - raise NotImplementedError() - - -def _calculate_forward_greedy_native(result): - raise NotImplementedError() - - def _get_next_results(result): selected_item = None used_items = {used.item.id for used in result.used} diff --git a/src_py/opcut/libopcut.py b/src_py/opcut/libopcut.py new file mode 100644 index 0000000..1d31fd7 --- /dev/null +++ b/src_py/opcut/libopcut.py @@ -0,0 +1,228 @@ +from pathlib import Path +import ctypes +import sys + +from opcut import common + + +def calculate(method: common.Method, + params: common.Params + ) -> common.Result: + if not _lib: + raise Exception("native implementation not available") + + native_method = _encode_method(method) + + id_panels = {panel_id: panel + for panel_id, panel in enumerate(params.panels)} + id_items = {item_id: item + for item_id, item in enumerate(params.items)} + + a = _lib.opcut_allocator_create(ctypes.pythonapi.PyMem_Malloc, + ctypes.pythonapi.PyMem_Free) + if a is None: + raise Exception("allocation error") + + try: + native_panels = _encode_panels(a, id_panels) + native_items = _encode_items(a, id_items) + native_params = _encode_params(a, params, native_panels, native_items) + + native_result = ctypes.POINTER(_lib.opcut_result_t)() + ret = _lib.opcut_calculate(a, native_method, native_params, + ctypes.byref(native_result)) + + if ret == _lib.OPCUT_UNSOLVABLE: + raise common.UnresolvableError() + + if ret != _lib.OPCUT_SUCCESS: + raise Exception() + + used = list(_decode_used(native_result.contents.used, id_panels, + id_items)) + unused = list(_decode_unused(native_result.contents.unused, + id_panels)) + return common.Result(params=params, + used=used, + unused=unused) + + finally: + _lib.opcut_allocator_destroy(a) + + +def _encode_method(method): + if method == common.Method.GREEDY_NATIVE: + return _lib.OPCUT_METHOD_GREEDY + + if method == common.Method.FORWARD_GREEDY_NATIVE: + return _lib.OPCUT_METHOD_FORWARD_GREEDY + + raise ValueError('unsupported method') + + +def _encode_panels(a, id_panels): + panels = None + for panel_id, panel in id_panels.items(): + panels = _lib.opcut_panel_create(a, panel_id, panel.width, + panel.height, panels) + if panels is None: + raise Exception("allocation error") + return panels + + +def _encode_items(a, id_items): + items = None + for item_id, item in id_items.items(): + items = _lib.opcut_item_create(a, item_id, item.width, item.height, + item.can_rotate, items) + if items is None: + raise Exception("allocation error") + return items + + +def _encode_params(a, params, panels, items): + params = _lib.opcut_params_create(a, params.cut_width, + params.min_initial_usage, panels, items) + if params is None: + raise Exception("allocation error") + return params + + +def _decode_used(used, id_panels, id_items): + while used: + yield common.Used(panel=id_panels[used.contents.panel.contents.id], + item=id_items[used.contents.item.contents.id], + x=used.contents.x, + y=used.contents.y, + rotate=used.contents.rotate) + used = used.contents.next + + +def _decode_unused(unused, id_panels): + while unused: + yield common.Unused(panel=id_panels[unused.contents.panel.contents.id], + width=unused.contents.width, + height=unused.contents.height, + x=unused.contents.x, + y=unused.contents.y) + unused = unused.contents.next + + +class _Lib: + + def __init__(self, path: Path): + lib = ctypes.cdll.LoadLibrary(str(path)) + + self.OPCUT_SUCCESS = 0 + self.OPCUT_ERROR = 1 + self.OPCUT_UNSOLVABLE = 42 + + self.OPCUT_METHOD_GREEDY = 0 + self.OPCUT_METHOD_FORWARD_GREEDY = 1 + + self.opcut_malloc_t = ctypes.c_void_p + self.opcut_free_t = ctypes.c_void_p + self.opcut_allocator_t_p = ctypes.c_void_p + + self.opcut_panel_t = type('opcut_panel_t', (ctypes.Structure, ), {}) + self.opcut_panel_t._fields_ = [ + ('id', ctypes.c_int), + ('width', ctypes.c_double), + ('height', ctypes.c_double), + ('next', ctypes.POINTER(self.opcut_panel_t)), + ('area', ctypes.c_double)] + + self.opcut_item_t = type('opcut_item_t', (ctypes.Structure, ), {}) + self.opcut_item_t._fields_ = [ + ('id', ctypes.c_int), + ('width', ctypes.c_double), + ('height', ctypes.c_double), + ('can_rotate', ctypes.c_bool), + ('next', ctypes.POINTER(self.opcut_item_t)), + ('area', ctypes.c_double)] + + self.opcut_params_t = type('opcut_params_t', (ctypes.Structure, ), {}) + self.opcut_params_t._fields_ = [ + ('cut_width', ctypes.c_double), + ('min_initial_usage', ctypes.c_bool), + ('panels', ctypes.POINTER(self.opcut_panel_t)), + ('items', ctypes.POINTER(self.opcut_item_t)), + ('panels_area', ctypes.c_double)] + + self.opcut_used_t = type('opcut_used_t', (ctypes.Structure, ), {}) + self.opcut_used_t._fields_ = [ + ('panel', ctypes.POINTER(self.opcut_panel_t)), + ('item', ctypes.POINTER(self.opcut_item_t)), + ('x', ctypes.c_double), + ('y', ctypes.c_double), + ('rotate', ctypes.c_bool), + ('next', ctypes.POINTER(self.opcut_used_t))] + + self.opcut_unused_t = type('opcut_unused_t', (ctypes.Structure, ), {}) + self.opcut_unused_t._fields_ = [ + ('panel', ctypes.POINTER(self.opcut_panel_t)), + ('width', ctypes.c_double), + ('height', ctypes.c_double), + ('x', ctypes.c_double), + ('y', ctypes.c_double), + ('next', ctypes.POINTER(self.opcut_unused_t)), + ('area', ctypes.c_double), + ('initial', ctypes.c_bool)] + + self.opcut_result_t = type('opcut_result_t', (ctypes.Structure, ), {}) + self.opcut_result_t._fields_ = [ + ('params', ctypes.POINTER(self.opcut_params_t)), + ('used', ctypes.POINTER(self.opcut_used_t)), + ('unused', ctypes.POINTER(self.opcut_unused_t))] + + functions = [ + (self.opcut_allocator_t_p, + 'opcut_allocator_create', + [self.opcut_malloc_t, self.opcut_free_t]), + + (None, + 'opcut_allocator_destroy', + [self.opcut_allocator_t_p]), + + (ctypes.POINTER(self.opcut_panel_t), + 'opcut_panel_create', + [self.opcut_allocator_t_p, ctypes.c_int, ctypes.c_double, + ctypes.c_double, ctypes.POINTER(self.opcut_panel_t)]), + + (ctypes.POINTER(self.opcut_item_t), + 'opcut_item_create', + [self.opcut_allocator_t_p, ctypes.c_int, ctypes.c_double, + ctypes.c_double, ctypes.c_bool, + ctypes.POINTER(self.opcut_item_t)]), + + (ctypes.POINTER(self.opcut_params_t), + 'opcut_params_create', + [self.opcut_allocator_t_p, ctypes.c_double, ctypes.c_bool, + ctypes.POINTER(self.opcut_panel_t), + ctypes.POINTER(self.opcut_item_t)]), + + (ctypes.c_int, + 'opcut_calculate', + [self.opcut_allocator_t_p, ctypes.c_int, + ctypes.POINTER(self.opcut_params_t), + ctypes.POINTER(ctypes.POINTER(self.opcut_result_t))]) + ] + + for restype, name, argtypes in functions: + function = getattr(lib, name) + function.argtypes = argtypes + function.restype = restype + setattr(self, name, function) + + +if sys.platform == 'win32': + _lib_suffix = '.dll' +elif sys.platform == 'darwin': + _lib_suffix = '.dylib' +else: + _lib_suffix = '.so' + +try: + _lib = _Lib(Path(__file__).parent / f'_libopcut{_lib_suffix}') +except Exception: + _lib = None diff --git a/src_py/opcut/main.py b/src_py/opcut/main.py index f09fe71..958e135 100644 --- a/src_py/opcut/main.py +++ b/src_py/opcut/main.py @@ -59,7 +59,7 @@ def create_argument_parser() -> argparse.ArgumentParser: '--output', metavar='PATH', type=Path, default=Path('-'), help="output file path or - for stdout") generate.add_argument( - 'result', type=Path, default=Path('-'), + 'result', type=Path, default=Path('-'), nargs='?', help=f"input result file path or - for stdin ({result_schema_id})") server = subparsers.add_parser('server') -- cgit v1.2.3-70-g09d2