Files
spatial-lab/spatial_lab/cli.py
T

62 lines
4.4 KiB
Python

import argparse
import json
import sys
from pathlib import Path
from . import __version__
from .kernel import build, operator_catalog, load_plugin, validate_bundle
from .project import Project, read_json, write_json
from .examples import demo_project, small_project
def main(argv=None):
p=argparse.ArgumentParser(prog='spatial-lab',description='Mathematical geometry workbench; author through commands, inspect in a browser, transfer exact meshes to Blender.')
p.add_argument('--version',action='version',version=__version__)
p.add_argument('--plugin',action='append',default=[],help='Load a trusted local Python operator module (repeatable)')
s=p.add_subparsers(dest='command',required=True)
q=s.add_parser('init',help='Create an editable project');q.add_argument('project');q.add_argument('--example',choices=['atrium','simple'],default='atrium')
q=s.add_parser('build',help='Evaluate and export the technical bundle');q.add_argument('project');q.add_argument('--out',required=True)
q=s.add_parser('inspect',help='Show evaluated nodes and mesh checks');q.add_argument('project');q.add_argument('--node')
q=s.add_parser('param',help='Change a global parameter transactionally');q.add_argument('project');q.add_argument('name');q.add_argument('value',help='JSON value, e.g. 12 or "2*pi"')
q=s.add_parser('patch',help='Apply an atomic list of graph changes');q.add_argument('project');q.add_argument('changes',help='JSON file containing an array of changes')
q=s.add_parser('node',help='Add or replace a node by stable ID');q.add_argument('project');q.add_argument('spec',help='JSON file containing one node')
q=s.add_parser('remove',help='Remove a node');q.add_argument('project');q.add_argument('node_id');q.add_argument('--cascade',action='store_true')
for name in ['undo','redo']:
q=s.add_parser(name);q.add_argument('project')
s.add_parser('ops',help='List installed operators and their versions')
q=s.add_parser('check',help='Check bundle integrity');q.add_argument('bundle')
q=s.add_parser('serve',help='Run the read-only, live geometry viewer');q.add_argument('project');q.add_argument('--port',type=int,default=8767)
a=p.parse_args(argv)
try:
for plugin in a.plugin:load_plugin(plugin)
if a.command=='ops':result=operator_catalog()
elif a.command=='init':
if Path(a.project).exists():raise ValueError('Project already exists; choose another path')
project=demo_project() if a.example=='atrium' else small_project();b=build(project);write_json(a.project,project);result={'project':str(Path(a.project).resolve()),**b['stats']}
elif a.command=='build':
b=Project(a.project).build();write_json(a.out,b);result={'bundle':str(Path(a.out).resolve()),'hash':b['bundle_hash'],**b['stats'],'warnings':b['validation']['warnings']}
elif a.command=='inspect':
b=Project(a.project).build()
if a.node:
result=next((n for n in b['nodes'] if n['id']==a.node),None)
if result is None:raise ValueError('No such node: '+a.node)
else:result={'name':b['name'],'parameters':b['recipe'].get('parameters',{}),'nodes':b['nodes'],'stats':b['stats'],'bounds':b['bounds'],'validation':b['validation']}
elif a.command=='check':
b=read_json(a.bundle);validate_bundle(b);result={'valid':True,'hash':b['bundle_hash'],'stats':b['stats']}
elif a.command=='serve':
from .server import serve
serve(a.project,a.port);return 0
else:
project=Project(a.project)
if a.command in ['undo','redo']:b=project.travel(a.command)
else:
if a.command=='patch':changes=read_json(a.changes)
elif a.command=='node':changes=[{'action':'upsert_node','node':read_json(a.spec)}]
elif a.command=='remove':changes=[{'action':'remove_node','id':a.node_id,'cascade':a.cascade}]
elif a.command=='param':changes=[{'action':'set_parameter','name':a.name,'value':json.loads(a.value)}]
b=project.apply(changes)
result={'action':a.command,'project_hash':b['project_hash'],**b['stats'],'warnings':b['validation']['warnings']}
print(json.dumps(result,indent=2,allow_nan=False));return 0
except (ValueError,OSError,KeyError,TypeError) as e:
print(json.dumps({'error':str(e)}),file=sys.stderr);return 2
if __name__=='__main__':raise SystemExit(main())