View scale vs title block scale#
The title block declares a main scale (ECH./SCALE 1:1). A view may
declare its own scale only when it deviates; a view that restates the
title-block scale is redundant template drift. The rule: a view title with no
scale is OK (inherits the title block), a different scale is OK, an equal
scale is a FAIL. Isometric views are skipped (axonometric projections are
unscaled). This demo parses both scales to a float ratio and compares.
Input drawing#
Sheet 1 of support_assembly.json (title-block scale 1:1).
How the check works#
Detect the title block and parse its scale, then compare each non-isometric
view’s fv.title.scale() against it (1e-6 tolerance).
"""Demo: View scale vs title block scale.
Checks that no view restates the title block scale (views with no scale
inherit it; isometric views are skipped), then writes (into the current
working directory):
- ``view_scale_report.md`` — Markdown verification report
- ``view_scale_vs_title_block_check.html`` — annotated drawing
"""
from pathlib import Path
from typing import Optional
from ..shared import write_report
from ..viz_helpers import load_sheet, render_html
from . import build_visualization
from .build_visualization import DRAWING_JSON, OUT_HTML
#: relative → written to the current working directory.
REPORT_MD = Path("view_scale_report.md")
STATUS_BADGE = {
"OK": "🟢 OK",
"FAIL": "🔴 FAIL",
"SKIPPED": "⚪ skipped (iso)",
"NO_TB_SCALE": "🔴 title block scale not found",
}
def build_report_md(rows: list["build_visualization.ViewRow"], title_block_raw: Optional[str]) -> str:
"""Render the Markdown per-view table + summary for the scale report."""
fmt = build_visualization.fmt_scale
tb_display = title_block_raw if title_block_raw is not None else "—"
header = "| View | View scale | Title block scale | Status |\n" "|------|-----------:|------------------:|--------|"
body = "\n".join(
f"| {row.view_name} | {fmt(row.view_scale)} | {tb_display} | {STATUS_BADGE[row.status]} |" for row in rows
)
considered = len(rows)
skipped = sum(row.status == "SKIPPED" for row in rows)
ok = sum(row.status == "OK" for row in rows)
fail = sum(row.status == "FAIL" for row in rows)
no_tb_scale = sum(row.status == "NO_TB_SCALE" for row in rows)
checked = ok + fail
if no_tb_scale:
summary = (
f"**Summary.** Title block scale not found — {no_tb_scale} view(s) could not be checked "
f"against it; {skipped} skipped (isometric)."
)
else:
rate = 100 * ok / checked if checked else 100.0
summary = (
f"**Summary.** {considered} views considered · {skipped} skipped (isometric) · "
f"{ok} OK · {fail} FAIL · pass rate {rate:.0f}% on the checked set."
)
return "\n\n".join([header + "\n" + body, summary])
def main() -> None:
"""Run the view-scale check, write the report and the visualization."""
sheet = load_sheet(DRAWING_JSON, sheet_index=1)
_title_block, title_block_raw, title_block_scale = build_visualization.title_block_scale(sheet)
print(f"Title block scale: {title_block_raw} (= {title_block_scale})\n")
rows = build_visualization.analyze(sheet, title_block_scale)
for row in rows:
print(f" {row.view_name}: scale={build_visualization.fmt_scale(row.view_scale)} -> {row.status}")
ok = sum(row.status == "OK" for row in rows)
fail = sum(row.status == "FAIL" for row in rows)
skipped = sum(row.status == "SKIPPED" for row in rows)
print(f"\n{len(rows)} views considered · {skipped} skipped · {ok} OK · {fail} FAIL")
write_report(REPORT_MD, "View Scale vs Title Block Report", [build_report_md(rows, title_block_raw)])
render_html(sheet, build_visualization.build_extras(sheet), OUT_HTML)
if __name__ == "__main__":
main()
Result#
View |
View scale |
Title block |
Status |
|---|---|---|---|
VUE DE FACE |
— |
1:1 |
🟢 OK |
COUPE A2-A2 |
— |
1:1 |
🟢 OK |
COUPE B2-B2 |
2:1 |
1:1 |
🟢 OK |
VUE ISOMETRIQUE |
— |
1:1 |
⚪ skipped (iso) |
COUPE C2-C2 REDRESSEE |
3:1 |
1:1 |
🟢 OK |
5 views considered · 1 skipped (isometric) · 4 OK · 0 FAIL · pass rate
100%. A view repeating 1:1 would turn red. The visualization labels each
view’s comparison in green and frames the title-block SCALE cell in orange.