aboutsummaryrefslogtreecommitdiff
path: root/src_py
diff options
context:
space:
mode:
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.py4
-rw-r--r--src_py/opcut/generate.py (renamed from src_py/opcut/output.py)14
-rw-r--r--src_py/opcut/main.py229
-rw-r--r--src_py/opcut/server.py44
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']