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).

src/drawing_tools/demo_scenarios/view_scale_vs_title_block/minimal_script.py#
"""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.