diff options
Diffstat (limited to 'src_py')
| -rw-r--r-- | src_py/opcut/calculate.py (renamed from src_py/opcut/csp.py) | 18 | ||||
| -rw-r--r-- | src_py/opcut/common.py | 4 | ||||
| -rw-r--r-- | src_py/opcut/generate.py (renamed from src_py/opcut/output.py) | 14 | ||||
| -rw-r--r-- | src_py/opcut/main.py | 229 | ||||
| -rw-r--r-- | src_py/opcut/server.py | 44 |
5 files changed, 177 insertions, 132 deletions
diff --git a/src_py/opcut/csp.py b/src_py/opcut/calculate.py index eb08314..7ae14ce 100644 --- a/src_py/opcut/csp.py +++ b/src_py/opcut/calculate.py @@ -3,8 +3,8 @@ import itertools from opcut import common -def calculate(params: common.Params, - method: common.Method +def calculate(method: common.Method, + params: common.Params ) -> common.Result: """Calculate cutting stock problem""" unused = [common.Unused(panel=panel, @@ -23,6 +23,12 @@ def calculate(params: common.Params, elif method == common.Method.FORWARD_GREEDY: return _calculate_forward_greedy(result) + elif method == common.Method.GREEDY_NATIVE: + return _calculate_greedy_native(result) + + elif method == common.Method.FORWARD_GREEDY_NATIVE: + return _calculate_forward_greedy_native(result) + raise ValueError('unsupported method') @@ -62,6 +68,14 @@ 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/common.py b/src_py/opcut/common.py index 9fbb7dd..5f6f26b 100644 --- a/src_py/opcut/common.py +++ b/src_py/opcut/common.py @@ -67,9 +67,11 @@ class OutputSettings(typing.NamedTuple): class Method(enum.Enum): GREEDY = 'greedy' FORWARD_GREEDY = 'forward_greedy' + GREEDY_NATIVE = 'greedy_native' + FORWARD_GREEDY_NATIVE = 'forward_greedy_native' -class OutputType(enum.Enum): +class OutputFormat(enum.Enum): PDF = 'pdf' SVG = 'svg' diff --git a/src_py/opcut/output.py b/src_py/opcut/generate.py index 65f8096..82c8d7b 100644 --- a/src_py/opcut/output.py +++ b/src_py/opcut/generate.py @@ -6,18 +6,18 @@ import cairo from opcut import common -def generate_output(result: common.Result, - output_type: common.OutputType, - panel_id: typing.Optional[str] = None, - settings: common.OutputSettings = common.OutputSettings() - ) -> bytes: +def generate(result: common.Result, + output_format: common.OutputFormat, + panel_id: typing.Optional[str] = None, + settings: common.OutputSettings = common.OutputSettings() + ) -> bytes: """Generate output""" ret = io.BytesIO() - if output_type == common.OutputType.PDF: + if output_format == common.OutputFormat.PDF: surface_cls = cairo.PDFSurface - elif output_type == common.OutputType.SVG: + elif output_format == common.OutputFormat.SVG: surface_cls = cairo.SVGSurface else: diff --git a/src_py/opcut/main.py b/src_py/opcut/main.py index b1d66a8..f09fe71 100644 --- a/src_py/opcut/main.py +++ b/src_py/opcut/main.py @@ -1,4 +1,5 @@ from pathlib import Path +import argparse import asyncio import contextlib import logging.config @@ -7,11 +8,10 @@ import typing from hat import aio from hat import json -import click from opcut import common -import opcut.csp -import opcut.output +import opcut.calculate +import opcut.generate import opcut.server @@ -19,107 +19,153 @@ params_schema_id: str = 'opcut://opcut.yaml#/definitions/params' result_schema_id: str = 'opcut://opcut.yaml#/definitions/result' -def _doc_enum_values(enum_cls): - return ', '.join(str(i.value) for i in enum_cls) +def create_argument_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='action') + + def enum_values(enum_cls): + return ', '.join(str(i.value) for i in enum_cls) + + calculate = subparsers.add_parser('calculate') + calculate.add_argument( + '--method', metavar='METHOD', type=common.Method, + default=common.Method.FORWARD_GREEDY, + help=f"calculate method ({enum_values(common.Method)})") + calculate.add_argument( + '--input-format', metavar='FORMAT', type=json.Format, default=None, + help=f"input params format ({enum_values(json.Format)})") + calculate.add_argument( + '--output-format', metavar='FORMAT', type=json.Format, default=None, + help=f"output result format ({enum_values(json.Format)})") + calculate.add_argument( + '--output', metavar='PATH', type=Path, default=Path('-'), + help=f"output result file path or - for stdout ({result_schema_id})") + calculate.add_argument( + 'params', type=Path, default=Path('-'), + help=f"input params file path or - for stdin ({params_schema_id})") + + generate = subparsers.add_parser('generate') + generate.add_argument( + '--input-format', metavar='FORMAT', type=json.Format, default=None, + help=f"input result format ({enum_values(json.Format)})") + generate.add_argument( + '--output-format', metavar='FORMAT', type=common.OutputFormat, + default=common.OutputFormat.PDF, + help=f"output format ({enum_values(common.OutputFormat)})") + generate.add_argument( + '--panel', metavar='PANEL', default=None, + help="panel identifier") + generate.add_argument( + '--output', metavar='PATH', type=Path, default=Path('-'), + help="output file path or - for stdout") + generate.add_argument( + 'result', type=Path, default=Path('-'), + help=f"input result file path or - for stdin ({result_schema_id})") + + server = subparsers.add_parser('server') + server.add_argument( + '--host', metavar='HOST', default='0.0.0.0', + help="listening host name (default 0.0.0.0)") + server.add_argument( + '--port', metavar='PORT', type=int, default=8080, + help="listening TCP port (default 8080)") + server.add_argument( + '--log-level', metavar='LEVEL', default='info', + choices=['critical', 'error', 'warning', 'info', 'debug', 'notset'], + help="log level (default info)") + + return parser -@click.group() def main(): - """Application main entry point""" - - -@main.command() -@click.option('--method', - default=common.Method.FORWARD_GREEDY, - type=common.Method, - help=f"calculate method ({_doc_enum_values(common.Method)})") -@click.option('--output', - default=None, - metavar='PATH', - type=Path, - help=f"result file path ({result_schema_id})") -@click.argument('params', - required=False, - default=None, - metavar='PATH', - type=Path) + parser = create_argument_parser() + args = parser.parse_args() + + if args.action == 'calculate': + calculate(method=args.method, + input_format=args.input_format, + output_format=args.output_format, + result_path=args.output, + params_path=args.params) + + elif args.action == 'generate': + generate(input_format=args.input_format, + output_format=args.output_format, + panel_id=args.panel, + output_path=args.output, + result_path=args.result) + + elif args.action == 'server': + server(host=args.host, + port=args.port, + log_level=args.log_level) + + else: + raise ValueError('unsupported action') + + def calculate(method: common.Method, - output: typing.Optional[Path], - params: typing.Optional[Path]): - """Calculate result based on parameters JSON""" - params = (json.decode_file(params) if params and params != Path('-') - else json.decode_stream(sys.stdin)) - common.json_schema_repo.validate(params_schema_id, params) - params = common.params_from_json(params) + input_format: typing.Optional[json.Format], + output_format: typing.Optional[json.Format], + result_path: Path, + params_path: Path): + if input_format is None and params_path == Path('-'): + input_format = json.Format.JSON + + if output_format is None and result_path == Path('-'): + output_format = json.Format.JSON + + params_json = (json.decode_stream(sys.stdin, input_format) + if params_path == Path('-') + else json.decode_file(params_path, input_format)) + + common.json_schema_repo.validate(params_schema_id, params_json) + params = common.params_from_json(params_json) try: - res = opcut.csp.calculate(params, method) + result = opcut.calculate.calculate(method=method, + params=params) except common.UnresolvableError: sys.exit(42) - res = common.result_to_json(res) + result_json = common.result_to_json(result) - if output and output != Path('-'): - json.encode_file(res, output) - else: - json.encode_stream(res, sys.stdout) - - -@main.command() -@click.option('--output-type', - default=common.OutputType.PDF, - type=common.OutputType, - help=f"output type ({_doc_enum_values(common.OutputType)})") -@click.option('--panel', - default=None, - help="panel identifier") -@click.option('--output', - default=None, - metavar='PATH', - type=Path, - help="result file path") -@click.argument('result', - required=False, - default=None, - metavar='PATH', - type=Path) -def generate_output(output_type: common.OutputType, - panel: typing.Optional[str], - output: typing.Optional[Path], - result: typing.Optional[Path]): - """Generate output based on result JSON""" - result = (json.decode_file(result) if result and result != Path('-') - else json.decode_stream(sys.stdin)) - common.json_schema_repo.validate(result_schema_id, result) - result = common.result_from_json(result) - - out = opcut.output.generate_output(result, output_type, panel) - - if output and output != Path('-'): - output.write_bytes(out) + if result_path == Path('-'): + json.encode_stream(result_json, sys.stdout, output_format) else: + json.encode_file(result_json, result_path, output_format) + + +def generate(input_format: typing.Optional[json.Format], + output_format: common.OutputFormat, + panel_id: typing.Optional[str], + output_path: Path, + result_path: Path): + if input_format is None and result_path == Path('-'): + input_format = json.Format.JSON + + result_json = (json.decode_stream(sys.stdin, input_format) + if result_path == Path('-') + else json.decode_file(result_path, input_format)) + + common.json_schema_repo.validate(result_schema_id, result_json) + result = common.result_from_json(result_json) + + data = opcut.generate.generate(result=result, + output_format=output_format, + panel_id=panel_id) + + if output_path == Path('-'): stdout, sys.stdout = sys.stdout.detach(), None - stdout.write(out) - - -@main.command() -@click.option('--host', - default='0.0.0.0', - help="listening host name") -@click.option('--port', - default=8080, - type=int, - help="listening TCP port") -@click.option('--log-level', - default='INFO', - type=click.Choice(['CRITICAL', 'ERROR', 'WARNING', 'INFO', - 'DEBUG', 'NOTSET']), - help="log level") + stdout.write(data) + else: + output_path.write_bytes(data) + + def server(host: str, port: int, log_level: str): - """Run server""" logging.config.dictConfig({ 'version': 1, 'formatters': { @@ -129,14 +175,15 @@ def server(host: str, 'console': { 'class': 'logging.StreamHandler', 'formatter': 'console', - 'level': log_level}}, + 'level': log_level.upper()}}, 'root': { - 'level': log_level, + 'level': log_level.upper(), 'handlers': ['console']}, 'disable_existing_loggers': False}) async def run(): - server = await opcut.server.create(host, port) + server = await opcut.server.create(host=host, + port=port) try: await server.wait_closing() diff --git a/src_py/opcut/server.py b/src_py/opcut/server.py index cd7d048..939222c 100644 --- a/src_py/opcut/server.py +++ b/src_py/opcut/server.py @@ -1,6 +1,5 @@ from pathlib import Path import asyncio -import platform import subprocess import sys @@ -24,7 +23,7 @@ async def create(host: str, app.add_routes([ aiohttp.web.get('/', server._root_handler), aiohttp.web.post('/calculate', server._calculate_handler), - aiohttp.web.post('/generate_output', server._generate_output_handler), + aiohttp.web.post('/generate', server._generate_handler), aiohttp.web.static('/', static_dir)]) runner = aiohttp.web.AppRunner(app) @@ -65,19 +64,18 @@ class Server(aio.Resource): return aiohttp.web.Response(status=400, text="Invalid request") - native = request.query.get('native') == 'true' method = common.Method(request.query['method']) params = common.params_from_json(data) try: - result = await _calculate(native, method, params) + result = await _calculate(method, params) return aiohttp.web.json_response(result) except common.UnresolvableError: return aiohttp.web.Response(status=400, text='Result is not solvable') - async def _generate_output_handler(self, request): + async def _generate_handler(self, request): try: data = await request.json() common.json_schema_repo.validate( @@ -87,16 +85,16 @@ class Server(aio.Resource): return aiohttp.web.Response(status=400, text="Invalid request") - output_type = common.OutputType(request.query['output_type']) + output_format = common.OutputFormat(request.query['output_format']) panel = request.query.get('panel') result = common.result_from_json(data) - output = await _generate_output(output_type, panel, result) + output = await _generate(output_format, panel, result) - if output_type == common.OutputType.PDF: + if output_format == common.OutputType.PDF: content_type = 'application/pdf' - elif output_type == common.OutputType.SVG: + elif output_format == common.OutputType.SVG: content_type = 'image/svg+xml' else: @@ -106,8 +104,9 @@ class Server(aio.Resource): content_type=content_type) -async def _calculate(native, method, params): - args = [*_get_calculate_cmd(native), '--method', method.value] +async def _calculate(method, params): + args = [sys.executable, '-m', 'opcut', 'calculate', + '--method', method.value] stdint_data = json.encode(common.params_to_json(params)).encode('utf-8') p = await asyncio.create_subprocess_exec(*args, @@ -125,8 +124,9 @@ async def _calculate(native, method, params): raise Exception(stderr_data.decode('utf-8')) -async def _generate_output(output_type, panel, result): - args = [*_get_generate_output_cmd(), '--output-type', output_type.value, +async def _generate(output_format, panel, result): + args = [sys.executable, '-m', 'opcut', 'generate', + '--output-format', output_format.value, *(['--panel', panel] if panel else [])] stdint_data = json.encode(common.result_to_json(result)).encode('utf-8') @@ -140,21 +140,3 @@ async def _generate_output(output_type, panel, result): return stdout_data raise Exception(stderr_data.decode('utf-8')) - - -def _get_calculate_cmd(native): - if native and sys.platform == 'linux': - if platform.machine() == 'x86_64': - return [str(common.package_path / - 'bin/linux_x86_64-opcut-calculate')] - - elif native and sys.platform == 'win32': - if platform.machine() == 'amd64': - return [str(common.package_path / - 'bin/windows_amd64-opcut-calculate.exe')] - - return [sys.executable, '-m', 'opcut', 'calculate'] - - -def _get_generate_output_cmd(): - return [sys.executable, '-m', 'opcut', 'generate-output'] |
