25592fa6c2
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
142 lines
5.8 KiB
Python
142 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Build a Panel Analysis page-spec JSON with NO Revit running.
|
|
|
|
python tools/pa_pagespec.py
|
|
python tools/pa_pagespec.py --reads reads.json --out spec.json
|
|
python tools/pa_pagespec.py --reads reads.json --job-root JOBDIR
|
|
|
|
This is the CPython half of the owner's scope note on the HTML-page
|
|
rewrite: the front end (前端) must open and run before Revit ever supplies a
|
|
value, Revit only supplies PREFILL. `vibe.pa_pagespec.build_pagespec()` is
|
|
already Revit-free (see that module's own docstring); this script is what
|
|
gets `values` / `know` in front of it the same way `tools/deckcheck.py` gets
|
|
knowledge in front of its own checks -- off a live `doc`, on host CPython.
|
|
|
|
`--reads` is a JSON file shaped like whatever `PanelAnalysis.pushbutton`'s
|
|
`_reads_for()` hands `panelanalysisform.prefill()` -- the exact file a
|
|
bridge dry-run should save (see `_scratch/pa_pagespec_dump.py`, which writes
|
|
`_scratch/pa_reads_wpa655.json` for this purpose) or a hand-written stand-in
|
|
for testing the page with no model at all. Omitted, or pointed at a file
|
|
that is not there, this runs with `reads = {}` -- an EMPTY, but still
|
|
complete and defaulted, form (see `pa_pagespec.build_pagespec`'s "THE TWO
|
|
CALLING SHAPES" note): every field present, every `pa_defaults` rule
|
|
applied, every table at its minimum row count, every drawing's geometry
|
|
degraded to a placeholder rather than missing. That is the shape the HTML
|
|
page needs to be developable and testable in a browser with no Revit and no
|
|
model at all.
|
|
|
|
`--job-root` names the folder holding a job's OWN `knowledge.yaml` (see
|
|
`tools/deckcheck.py`'s `build_knowledge()` docstring for exactly what that
|
|
resolves) -- omitted, this runs with just the shipped defaults + catalogue
|
|
layers, same as `deckcheck.build_knowledge(None)`.
|
|
|
|
`--out` writes the spec there (UTF-8); omitted, it prints to stdout.
|
|
|
|
CONSTRAINTS THIS RESPECTS (same as `tools/deckcheck.py`, for the same
|
|
reason): host CPython, no Revit, standalone; prints no plant or client name
|
|
of its own -- design numbers and marks flowing through a `--reads` file are
|
|
data, not this file's business, per CLAUDE.md's "No client or job
|
|
information in the repo".
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import io
|
|
import json
|
|
import os
|
|
import sys
|
|
import types
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
REPO = os.path.abspath(os.path.join(HERE, os.pardir))
|
|
LIB = os.path.join(REPO, 'WillTools.extension', 'lib')
|
|
if LIB not in sys.path:
|
|
sys.path.insert(0, LIB)
|
|
|
|
# Same trick `tools/deckcheck.py` / `tools/ticket_delta.py` /
|
|
# `WillTools.extension/tests/conftest.py` all use: `vibe/__init__.py`
|
|
# imports the Revit API at module scope, which is fatal off Revit. Register
|
|
# a bare `vibe` package pointed at the directory so the Revit-free
|
|
# submodules (`panelanalysisform`, `pa_pagespec`, the five page modules, the
|
|
# four `pa_sketch_*` modules, `pa_defaults`) import normally and
|
|
# `__init__.py` never runs.
|
|
if 'vibe' not in sys.modules:
|
|
_stub = types.ModuleType('vibe')
|
|
_stub.__path__ = [os.path.join(LIB, 'vibe')]
|
|
sys.modules['vibe'] = _stub
|
|
|
|
if HERE not in sys.path:
|
|
sys.path.insert(0, HERE)
|
|
|
|
import deckcheck # noqa: E402 -- reuses build_knowledge(), not re-derived
|
|
from vibe import pa_pagespec # noqa: E402
|
|
from vibe import panelanalysisform as F # noqa: E402
|
|
|
|
|
|
def _load_reads(path):
|
|
"""`(reads_dict, error_or_None)`. A path that is not given, or not
|
|
there, is the ordinary "no Revit at all" case -- `{}`, no error. A path
|
|
that IS there and will not parse as a JSON object is reported, never
|
|
silently treated as empty (the same "a layer that fails to load must
|
|
never look like a layer that agrees" discipline `knowledge._merge_file`
|
|
uses)."""
|
|
if not path:
|
|
return {}, None
|
|
if not os.path.isfile(path):
|
|
return {}, "'{0}' does not exist; running with reads = {{}}.".format(
|
|
path)
|
|
try:
|
|
with io.open(path, 'r', encoding='utf-8') as fh:
|
|
data = json.load(fh)
|
|
except Exception as exc:
|
|
return {}, "'{0}' could not be read as JSON ({1!r}); running with " \
|
|
'reads = {{}}.'.format(path, exc)
|
|
if not isinstance(data, dict):
|
|
return {}, "'{0}' is not a JSON object; running with reads = {{}}." \
|
|
.format(path)
|
|
return data, None
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument('--reads', default=None,
|
|
help='JSON file of the model reads dict (omit for '
|
|
'an empty, still-complete form)')
|
|
parser.add_argument('--job-root', default=None,
|
|
help="folder holding the job's own knowledge.yaml "
|
|
'(omit for shipped defaults + catalogue only)')
|
|
parser.add_argument('--out', default=None,
|
|
help='write the spec here (UTF-8); omit to print '
|
|
'to stdout')
|
|
args = parser.parse_args(argv)
|
|
|
|
reads, reads_err = _load_reads(args.reads)
|
|
if reads_err:
|
|
sys.stderr.write(reads_err + '\n')
|
|
|
|
try:
|
|
know = deckcheck.build_knowledge(args.job_root)
|
|
except Exception as exc:
|
|
sys.stderr.write(
|
|
'knowledge could not be resolved off Revit ({0!r}); running '
|
|
'with the bare shipped defaults.\n'.format(exc))
|
|
know = {}
|
|
|
|
values = F.prefill(reads, know=know)
|
|
spec = pa_pagespec.build_pagespec(values, know=know, reads=reads)
|
|
blob = pa_pagespec.dumps(spec)
|
|
|
|
if args.out:
|
|
with io.open(args.out, 'w', encoding='utf-8') as fh:
|
|
fh.write(blob if isinstance(blob, str) else blob.decode('utf-8'))
|
|
sys.stderr.write('wrote {0} ({1} bytes)\n'.format(
|
|
args.out, len(blob)))
|
|
else:
|
|
print(blob)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|