Skip to content

Commit 21d5880

Browse files
feat(fc): parts_bootstrap stub and model_3d report field (v4.0.59)
Add pdfcadcore parts_bootstrap sidecar stub, build_model_3d_extra in import_report, and Round 8 Q&A mirror updates. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 93f46c2 commit 21d5880

18 files changed

Lines changed: 1082 additions & 28 deletions

PDFVectorImporter/compare_feature_matrix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Feature:
4343
Feature("lineweight_modes", "Lineweight handling modes", r"\b(lineweight_mode|lineweight)\b", {"SU", "FC", "BL", "LC"}),
4444
Feature("doc_profiling", "Document profiling/classification", r"\b(document_profiler|profile_page|primary_type)\b", {"SU", "FC", "BL", "LC"}),
4545
Feature("scale_reference", "Scale by reference tooling", r"\b(Scale by Reference|scale tool|reference_real_mm|PDFScaleTool)\b", {"SU", "FC", "BL", "LC"}),
46-
Feature("cli_surface", "CLI surface", r"\b(argparse|cli\.py|pdf2dxf\.py)\b", {"SU", "FC", "BL", "LC"}),
46+
Feature("cli_surface", "CLI surface", r"\b(argparse|OptionParser|cli\.(?:py|rb)|pdf2dxf\.py)\b", {"SU", "FC", "BL", "LC"}),
4747
Feature("gui_surface", "GUI surface", r"\b(HtmlDialog|QDialog|bpy\.types\.Operator|--gui|Tkinter)\b", {"SU", "FC", "BL", "LC"}),
4848
Feature("batch_import", "Batch import workflows", r"\b(Batch Import|batch import|BatchImportCommand)\b", {"SU", "FC", "BL", "LC"}),
4949
Feature("qa_automation", "Automated QA harness", r"\b(run_pdf_vector_importer_tests|qa_config|pytest|smoke_test|qa_smoke)\b", {"SU", "FC", "BL", "LC"}),

PDFVectorImporter/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
33
<name>PDFVectorImporter</name>
44
<description>Import PDF vector drawings as editable FreeCAD geometry with arc reconstruction, text, scaling, and steel feature detection.</description>
5-
<version>4.0.58</version>
5+
<version>4.0.59</version>
66
<date>2026-06-26</date>
77
<maintainer email="support@bluecollar-systems.com">BlueCollar Systems</maintainer>
88
<!-- AI Contributors: Claude & Claude Code (Anthropic), ChatGPT & Codex (OpenAI),

PDFVectorImporter/pdfcadcore/import_report.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,29 @@ def _pdf_audit_extras(pdf_path: str) -> Dict[str, Any]:
401401
return merged
402402

403403

404+
def build_model_3d_extra(
405+
host_app: str,
406+
*,
407+
enabled: bool = False,
408+
stats: Optional[Dict[str, Any]] = None,
409+
) -> Dict[str, Any]:
410+
"""Honest model_3d block for import_report.extra (R8-1)."""
411+
412+
if stats:
413+
return dict(stats)
414+
host = str(host_app or "").lower()
415+
if host == "librecad":
416+
return {
417+
"supported": False,
418+
"enabled": False,
419+
"reason": "2D host — PDF import produces planar DXF only",
420+
}
421+
return {
422+
"supported": host in ("freecad", "blender", "sketchup"),
423+
"enabled": bool(enabled),
424+
}
425+
426+
404427
def enrich_import_report_extras(report: "ImportReport") -> None:
405428
"""Attach scale cross-check, performance hint, and refresh human_summary."""
406429

@@ -416,6 +439,9 @@ def enrich_import_report_extras(report: "ImportReport") -> None:
416439
)
417440
if hint:
418441
report.extra["performance_hint"] = hint
442+
if "model_3d" not in report.extra:
443+
host = str((report.host or {}).get("app") or "")
444+
report.extra["model_3d"] = build_model_3d_extra(host)
419445
report.extra["human_summary"] = build_human_summary(report)
420446

421447

@@ -804,6 +830,7 @@ def build_import_report(
804830
"build_pdf_interactive_note",
805831
"build_performance_hint",
806832
"build_scale_crosscheck",
833+
"build_model_3d_extra",
807834
"enrich_import_report_extras",
808835
"build_import_report",
809836
]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# -*- coding: utf-8 -*-
2+
"""Minimal parts bootstrap sidecar builder (bcs.parts_bootstrap/1.0) — stub emitter."""
3+
4+
from __future__ import annotations
5+
6+
import hashlib
7+
import json
8+
from pathlib import Path
9+
from typing import Any, Dict, List, Optional
10+
11+
SCHEMA = "bcs.parts_bootstrap/1.0"
12+
13+
14+
def _sha256_file(path: str) -> str:
15+
digest = hashlib.sha256()
16+
with open(path, "rb") as handle:
17+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
18+
digest.update(chunk)
19+
return digest.hexdigest()
20+
21+
22+
def build_parts_bootstrap_stub(
23+
pdf_path: str,
24+
*,
25+
page_count: int = 0,
26+
rows: Optional[List[Dict[str, Any]]] = None,
27+
) -> Dict[str, Any]:
28+
"""Return a valid sidecar payload; BOM row extraction is deferred."""
29+
30+
payload: Dict[str, Any] = {
31+
"schema": SCHEMA,
32+
"rows": list(rows or []),
33+
"source_pdf": {
34+
"file": str(Path(pdf_path).name),
35+
"pages": int(page_count or 0),
36+
},
37+
"note": "stub emitter — automated piece-mark extraction deferred",
38+
}
39+
if pdf_path and Path(pdf_path).is_file():
40+
payload["source_pdf"]["sha256"] = _sha256_file(pdf_path)
41+
return payload
42+
43+
44+
def write_parts_bootstrap_sidecar(
45+
output_path: str,
46+
pdf_path: str,
47+
*,
48+
page_count: int = 0,
49+
rows: Optional[List[Dict[str, Any]]] = None,
50+
) -> str:
51+
path = Path(output_path)
52+
path.parent.mkdir(parents=True, exist_ok=True)
53+
manifest = build_parts_bootstrap_stub(
54+
pdf_path,
55+
page_count=page_count,
56+
rows=rows,
57+
)
58+
path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
59+
return str(path)
60+
61+
62+
__all__ = [
63+
"SCHEMA",
64+
"build_parts_bootstrap_stub",
65+
"write_parts_bootstrap_sidecar",
66+
]

PDFVectorImporter/src/PDFImporterCore.py

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ class ImportOptions:
448448
# raster — render full page as image, skip vectors
449449
# hybrid — raster background + vector geometry on top
450450
import_mode: str = "auto"
451+
# Optional 3D model generation. "auto" only runs when text evidence
452+
# indicates an honest third dimension; "extrude" forces closed-shape
453+
# extrusion with page-background guards.
454+
model3d_mode: str = "off" # "off" | "auto" | "extrude"
455+
model3d_depth_mm: float = 3.175 # default 1/8 in plate thickness
451456
max_bezier_segments: int = 128
452457
# Arc reconstruction
453458
detect_arcs: bool = True
@@ -679,6 +684,22 @@ def write_import_report(
679684
"object_count": len(provenance_objects),
680685
}
681686

687+
from pdfcadcore.parts_bootstrap import write_parts_bootstrap_sidecar
688+
689+
bootstrap_path = str(Path(output_path).with_name("parts_bootstrap.json"))
690+
write_parts_bootstrap_sidecar(
691+
bootstrap_path,
692+
pdf_path,
693+
page_count=int(total_pages or pages_imported or 0) or None,
694+
)
695+
extra_ref = report.extra
696+
extra_ref["parts_bootstrap"] = {
697+
"schema": "bcs.parts_bootstrap/1.0",
698+
"sidecar_path": Path(bootstrap_path).name,
699+
"row_count": 0,
700+
"note": "stub emitter — BOM extraction deferred",
701+
}
702+
682703
report.write_json(output_path)
683704
return output_path
684705

@@ -916,6 +937,88 @@ def _make_shape_obj(edges: List, closed: bool, make_face: bool, fc_doc=None):
916937
return None
917938

918939

940+
def _normalize_model3d_mode(raw) -> str:
941+
mode = str(raw or "off").strip().lower().replace("-", "_").replace(" ", "_")
942+
if mode in {"yes", "true", "on", "auto_if_evidence"}:
943+
return "auto"
944+
if mode in {"closed", "closed_shapes", "extrude_closed_shapes", "force"}:
945+
return "extrude"
946+
if mode in {"auto", "extrude"}:
947+
return mode
948+
return "off"
949+
950+
951+
def _model3d_depth_units(opts: ImportOptions) -> float:
952+
try:
953+
depth = float(getattr(opts, "model3d_depth_mm", 3.175) or 3.175)
954+
except (TypeError, ValueError):
955+
depth = 3.175
956+
return max(0.0, depth)
957+
958+
959+
def _model3d_should_extrude(
960+
opts: ImportOptions,
961+
*,
962+
is_closed: bool,
963+
fill,
964+
face_area: float,
965+
page_area: float,
966+
) -> bool:
967+
mode = _normalize_model3d_mode(getattr(opts, "model3d_mode", "off"))
968+
if mode == "off" or not is_closed:
969+
return False
970+
if _model3d_depth_units(opts) <= 0.0:
971+
return False
972+
if face_area <= 1e-6:
973+
return False
974+
# Skip full-page paper/background fills and border frames.
975+
if page_area > 1e-6 and face_area / page_area >= 0.80:
976+
return False
977+
if mode == "auto":
978+
if not bool(getattr(opts, "_model3d_intent_feasible", False)):
979+
return False
980+
# Auto is deliberately conservative: only extrude filled closed
981+
# regions when the drawing text supplies third-dimension evidence.
982+
return fill is not None
983+
return True
984+
985+
986+
def _make_model3d_obj(edges: List, fc_doc=None):
987+
if not edges:
988+
return None
989+
doc = fc_doc or FreeCAD.ActiveDocument
990+
try:
991+
wire = Part.Wire(edges)
992+
if not wire.isClosed() and wire.Vertexes:
993+
p0 = wire.Vertexes[0].Point
994+
pN = wire.Vertexes[-1].Point
995+
if _len2d(_v(p0.x, p0.y), _v(pN.x, pN.y)) > ZERO_TOL:
996+
closer = Part.LineSegment(pN, p0).toShape()
997+
wire = Part.Wire(edges + [closer])
998+
if not wire.isClosed():
999+
return None
1000+
face = Part.Face(wire)
1001+
if face.Area <= 1e-6:
1002+
return None
1003+
obj = doc.addObject("Part::Feature", "PDF_3D_Solid")
1004+
obj.Shape = face
1005+
return obj
1006+
except (RuntimeError, ValueError, TypeError, AttributeError):
1007+
return None
1008+
1009+
1010+
def _extrude_model3d_obj(obj, opts: ImportOptions) -> bool:
1011+
if obj is None:
1012+
return False
1013+
try:
1014+
depth = _model3d_depth_units(opts)
1015+
obj.Shape = obj.Shape.extrude(Vector(0, 0, depth))
1016+
return True
1017+
except (RuntimeError, ValueError, TypeError, AttributeError) as e:
1018+
_warn(f"3D extrusion failed: {e}")
1019+
return False
1020+
1021+
9191022
def _apply_style(obj, stroke_rgb, fill_rgb, width, dashes, opts: ImportOptions):
9201023
"""Set source stroke/fill color, line width, and dash style on a ViewObject."""
9211024
try:
@@ -2510,6 +2613,7 @@ def _import_pdf_page_inner(pdf_doc, pdf_path, page_num, opts, fc_doc):
25102613
_fc_mb_w, _fc_mb_h = float(page.rect.width), float(page.rect.height)
25112614
page_h = _fc_mb_w if _fc_rot in (90, 270) else _fc_mb_h
25122615
scale = (MM_PER_PT if opts.scale_to_mm else 1.0) * opts.user_scale
2616+
page_area_units = max(abs(_fc_mb_w * scale * page_h * scale), 1e-9)
25132617

25142618
# Top-level group
25152619
top_group = None
@@ -3183,6 +3287,23 @@ def flush_sub(close_flag: bool, _wires=wires_edges):
31833287
_apply_style(obj, stroke_rgb, fill_rgb, width, dashes, opts)
31843288
parent.addObject(obj)
31853289
obj_count += 1
3290+
try:
3291+
face_area = float(getattr(obj.Shape, "Area", 0.0) or 0.0)
3292+
except (AttributeError, TypeError, ValueError):
3293+
face_area = 0.0
3294+
if _model3d_should_extrude(
3295+
opts,
3296+
is_closed=is_closed,
3297+
fill=fill,
3298+
face_area=abs(face_area),
3299+
page_area=page_area_units,
3300+
):
3301+
solid = _make_model3d_obj(edges, fc_doc=fc_doc)
3302+
if solid is not None and _extrude_model3d_obj(solid, opts):
3303+
_apply_style(solid, stroke_rgb, fill_rgb, width, dashes, opts)
3304+
parent.addObject(solid)
3305+
obj_count += 1
3306+
opts._model3d_solids = int(getattr(opts, "_model3d_solids", 0) or 0) + 1
31863307

31873308
# ── Flush remaining batched shapes ──
31883309
if _batch_size:
@@ -3836,6 +3957,7 @@ def import_pdf(pdf_path: str, opts: Optional[ImportOptions] = None):
38363957
_unit_scale = (MM_PER_PT if opts.scale_to_mm else 1.0) * opts.user_scale
38373958
page_height_scaled = 792 * _unit_scale # default: US Letter height in points
38383959
page_heights_scaled: Dict[int, float] = {}
3960+
model3d_text_evidence: List[str] = []
38393961
t_phase = time.perf_counter()
38403962
try:
38413963
from pdfcadcore.fitz_loader import PdfOpenError, safe_open
@@ -3849,10 +3971,29 @@ def import_pdf(pdf_path: str, opts: Optional[ImportOptions] = None):
38493971
for p in pages:
38503972
if 1 <= p <= total_pages:
38513973
try:
3852-
page_heights_scaled[p] = pdoc.load_page(p - 1).rect.height * _unit_scale
3974+
_page_for_meta = pdoc.load_page(p - 1)
3975+
page_heights_scaled[p] = _page_for_meta.rect.height * _unit_scale
3976+
try:
3977+
model3d_text_evidence.append(_page_for_meta.get_text("text") or "")
3978+
except (RuntimeError, ValueError, AttributeError):
3979+
pass
38533980
except (ValueError, RuntimeError):
38543981
pass
38553982
opts.phase_timings_ms["open_pdf_ms"] = (time.perf_counter() - t_phase) * 1000.0
3983+
try:
3984+
from pdfcadcore.model3d_intent import analyze_model3d_intent
3985+
3986+
intent = analyze_model3d_intent(model3d_text_evidence, host_supports_3d=True)
3987+
opts._model3d_intent = intent.to_dict()
3988+
opts._model3d_intent_feasible = bool(intent.feasible)
3989+
except (ImportError, RuntimeError, TypeError, ValueError, AttributeError):
3990+
opts._model3d_intent = {
3991+
"feasible": False,
3992+
"plates": [],
3993+
"members": [],
3994+
"skipped_reason": "3D intent analysis unavailable",
3995+
}
3996+
opts._model3d_intent_feasible = False
38563997
except PdfOpenError as e:
38573998
_err(str(e))
38583999
return

_LLM_CONTROL_PACK/QA/Q&A_INDEX.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
# Q&A Index
22

3-
Updated: 2026-07-04 (Round 7 - SU v3.7.79 + Steel Logic v1.0.10; Reviewer O/P/Q)
3+
Updated: 2026-07-04 (Round 8 — PDF→3D optional extrusion + Q&A audit closure)
4+
5+
---
6+
7+
## Active session — Round 8 PDF→3D (2026-07-04)
8+
9+
| File | Role |
10+
|------|------|
11+
| **`QA-2026-07-04_round8-pdf-to-3d-questions.md`** | Round 8 — Reviewer W: host scope, 3D semantics, eligibility, UI, report contract, Z-order research |
12+
| **`QA-2026-07-04_round8-pdf-to-3d-answers.md`** | Round 8 — Reviewer X answers W1–W7 + prior OPEN cross-answers |
13+
| **`QA-2026-07-04_round8-resolution.md`** | Round 8 — Agreements R8-1…R8-9 + phased implementation plan |
14+
| **`QA-2026-07-04_round8-sketchup-cli-images-app-pass/QA-2026-07-04_round8-sketchup-cli-images-app-pass.md`** | Round 8 earlier pass — CLI/images/app QA closure |
15+
| **`QA-2026-07-04_feature-parity-matrix.md`** | Updated parity audit incl. `model_3d`, `parts_bootstrap` stub |
16+
17+
> **Round 8 PDF→3D status:** SU v3.7.80 ships optional extrude + `extra.model_3d`; LC honest 2D note; FC `parts_bootstrap` stub v4.0.59. FC/BL solid extrusion UI deferred Phase 2/3.
18+
19+
---
20+
21+
## Active session — Round 8 CLI/App pass (2026-07-04)
22+
23+
| File | Role |
24+
|------|------|
25+
| **QA-2026-07-04_round8-sketchup-cli-images-app-pass/QA-2026-07-04_round8-sketchup-cli-images-app-pass.md** | Round 8 - implementation/QA closure: SketchUp direct CLI launch fix, embedded-image extraction verification, app import-report ingestion bridge, localization audit cleanup, website/corpus smoke QA |
26+
| **QA-2026-07-04_round8-sketchup-cli-images-app-pass/feature_matrix.md** | Cross-repo feature matrix regenerated after SketchUp CLI detection fix |
27+
| **QA-2026-07-04_round8-sketchup-cli-images-app-pass/feature_matrix.json** | Machine-readable feature matrix |
28+
29+
> **Round 8 status:** tracked importer/app matrix coverage is 100%; automated tests are green. Remaining gates are host-app visual signoff and future Report Doctor UI integration.
430
531
---
632

@@ -12,9 +38,10 @@ Updated: 2026-07-04 (Round 7 - SU v3.7.79 + Steel Logic v1.0.10; Reviewer O/P/Q)
1238
| **QA-2026-07-04_round7-reviewer-p-answers.md** | Round 7 - Reviewer P answers to O-1..O-7 + prior OPEN cross-table |
1339
| **QA-2026-07-04_round7-reviewer-q-answers.md** | Round 7 - Reviewer Q cross-answers to O-1..O-7 (corpus T1-12, field test, Report Doctor) |
1440
| **QA-2026-07-04_round7-reviewer-n-questions.md** | Round 7 - Reviewer N questions (parallel lane) |
41+
| **QA-2026-07-04_round7-reviewer-r-answers.md** | Round 7 - Reviewer R cross-answers to N16-N19 + prior OPEN |
1542
| **QA-2026-07-04_round7-resolution.md** | Round 7 - Agreements R7-1..R7-11 + cross-answer matrix |
1643

17-
> **Round 7 cross-answer matrix complete (O questions; P + Q answers).** R7-10 embedded-image corpus anchor **T1-12 SHIPPED**.
44+
> **Round 7 cross-answer matrix complete (O questions; P + Q + R answers).** R7-10 embedded-image corpus anchor **T1-12 SHIPPED**.
1845
1946
---
2047

_LLM_CONTROL_PACK/QA/QA-2026-06-24_worker-status-log.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
2026-07-04 22:30 UTC | WS-R8-3D | Round 8 PDF→3D + Q&A audit | SHIPPED | SU v3.7.80 optional Extrude to 3D + extra.model_3d; LC v1.0.53 honest 2D model_3d; FC v4.0.59 parts_bootstrap stub + build_model_3d_extra pdfcadcore; Round 8 W/X/R8 resolution; Reviewer R cross-answers to N16–N19; gates green. OPEN: T-01 human, R7-9 CLI merge, FC/BL extrude UI Phase 2/3.
12
2026-07-04 18:00 UTC | WS-R6-P1b | Round 6 follow-up (ffb2f575) | SHIPPED | R6-8 source_provenance wired all hosts: LC v1.0.52 + BL v1.0.56 sidecar + extra.summary from text spans; SU v3.7.78 extra.source_provenance summary only (native label/3D text). OPEN T-01 only.
23
2026-07-03 18:35 UTC | WS-T01-N | Anonymous Reviewer N | FIELD EVIDENCE | Owner T-01 screenshots (SU Make 2017, 1017 - Rev 0): BOM QUAN column digits render 90deg rotated; MARK/DESCRIPTION correct. PyMuPDF ground truth (extract probe, BOM page): ALL QUAN digits dir=(1.0,0.0) HORIZONTAL at x=1947-1955, rows y=135-334 (265pt span below QUAN header at y=69). Root cause: geometry_builder.rb prepare_bom_table_context 160pt window < 265pt table -> lower rows lose bom_table_row? -> label_angle_pdf line ~1196 tall-bbox fallback returns 90. NOTE to the session mid-fix (320pt window + failing test "BOM quantity 3 should stay horizontal (got 90.0)"): 320pt covers this sheet; also genuinely-rotated items on same page that MUST stay rotated: digits/marks at dir=(0,-1) e.g. w1023 at (1153.7,998.4), digit 2 at (1103.0,578.6), and two diagonal digits dir~(0.756,-0.655)=40.9deg at (1123.9,761.5)/(859.6,1155.2) - good negative-test fixtures. extract_page on this page: 1.1s/431 items (core extraction is not the big-PDF bottleneck; host entity creation is).
34
2026-07-03 19:15 UTC | WS-T01-N | Anonymous Reviewer N | FIELD EVIDENCE | Owner T-01 round 2 (BL 5.1.2 + FC 1.1.1, 1017 + USGS Alvord topo): 4 defects filed in QA-2026-07-03_T01-field-report-BL-FC.md. BL-1 fat lineweights ROOT CAUSE: importer.py:161 scales paper-space line_width by geometry factor then bl_geometry_builder.py:177 bevel_depth - fix: paper-space mode default (cli --lineweight-mode exists, GUI ignores). BL-2 slow large files: per-batch curve objects flood depsgraph - merge per page+color+width. FC-1 dense dimension cluster spacing (313 16 class) - needs corpus vector first. FC-2 fill color lost ROOT CAUSE: only LineColor ever set (PDFImporterCore.py:878,3435), ShapeColor never assigned - green fills render default grey. Suggested claim order FC-2, BL-1, FC-1, BL-2. All need in-host before/after per rule 6.
@@ -6,3 +7,4 @@
67
2026-07-04 15:00 UTC | WS-R6-S | Reviewer S + T | SHIPPED | Round 6 importer contract slice: actual_text_entity_types SU Ruby + Import Health; report_meta.build_stamp pdfcadcore+SU; cli_error_copy LC/BL; BL GUI redraw fix; FC CI corpus schema step. Versions SU 3.7.77, FC 4.0.57, LC 1.0.50, BL 1.0.54. Q&A: round6 S/T/resolution + feature-parity-matrix. OPEN: R4-1 vectors, T-01 visual retest, source_provenance, KettleTag app slice.
78
2026-07-04 16:00 UTC | WS-R7-CLOSE | Round 7 gap sweep | SHIPPED | SU v3.7.79 + Steel Logic v1.0.10 on origin; Reviewer Q cross-answers (O-1..O-7); R7-10 T1-12 embedded_images_regression.pdf; SU CI fix ruby22 endless range in image_extractor. OPEN: T-01 visual, CLI merge P1, KettleTag loop.
89

10+
2026-07-04 18:20 UTC | WS-R8-N | Anonymous Reviewer N | VERIFIED+SHIPPED | QA sweep: Round 7 matrix complete + R7-1..R7-7 claims spot-verified REAL (T1-12 exists; SU embedded_image_extractor_test 3/12 green; app 237/237 incl scanner/deep-links; all 4 importer CIs green). OWNER FEATURE Round 8 opened (3D model generation): slice 1 SHIPPED - pdfcadcore/model3d_intent.py detects plate callouts (PL thickness) + rolled shapes (W/L/C/HSS/PIPE/WT + BOM lengths) w/ honest skipped_reason + 2D-host message; 7 tests vs real 1017 rows; FC 93f46c2 / LC 3614134 / BL 34dd1b6 pushed, suites 117/60/58 green ALL IN SYNC. Design doc QA-2026-07-04_round8-3d-model-generation.md: R8-A..R8-F host contracts (FC members first), AISC profile source decision R8-E blocks R8-A (propose corpus aisc_profiles.json from Steel Logic v16 CSV), corpus anchor R8-F spec, DONE criteria incl owner T-01 sign-off. Claim rows in answer docs.

0 commit comments

Comments
 (0)