diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Table.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Table.py index 20844ec4..d0f1fce7 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Table.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Table.py @@ -223,12 +223,28 @@ def _cross_table_entities( ] -def _table_values_from_cells(entities: list[Any]) -> dict[str, float | None]: - """표 객체의 칸에서 값을 읽는다. 칸의 key가 어느 수량인지 알려 준다.""" +def _table_values_from_cells( + entities: list[Any], drawing_id: str | None = None +) -> dict[str, float | None]: + """표 객체의 칸에서 값을 읽는다. 칸의 key가 어느 수량인지 알려 준다. + + **한 도면에 표가 여럿이면 그 측점 것만 읽는다**(장은 측점 4~6개를 담는다). + 표 엔티티 id 는 `uuid5(f"{도면id}:qtable")` 이라 측점을 되짚을 수 있다. 이 걸름이 + 없던 동안 장 확정이 **모든 측점에 마지막 표 값을 넣었다**(2026-09-03 실측: 4개 측점이 + 전부 지반고 837.21 — 도면에는 840.87·840.33·836.45·837.21 로 제대로 그려져 있었다). + id 로 못 찾으면(옛 도면) 종전처럼 전부 훑는다. + """ + wanted = str(uuid5(_ENTITY_NS, f"{drawing_id}:qtable")) if drawing_id else None + if wanted is not None and not any( + isinstance(entity, dict) and entity.get("id") == wanted for entity in entities + ): + wanted = None table: dict[str, float | None] = {} for entity in entities: if not isinstance(entity, dict) or entity.get("type") != "Table": continue + if wanted is not None and entity.get("id") != wanted: + continue shape = entity.get("shapeData") rows = shape.get("cells") if isinstance(shape, dict) else None if not isinstance(rows, list): @@ -281,7 +297,7 @@ def extract_quantity_table( entities = drawing.get("entities") if not isinstance(entities, list): return None - table = _table_values_from_cells(entities) + table = _table_values_from_cells(entities, drawing_id) if not table: table = _table_values_from_texts(drawing_id, entities) if not table: diff --git a/B07_DesignDetail/B07_DesignDetail_Router.py b/B07_DesignDetail/B07_DesignDetail_Router.py index b47861b8..a46bba9a 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router.py +++ b/B07_DesignDetail/B07_DesignDetail_Router.py @@ -462,7 +462,11 @@ async def invalidate_design_drawing( """확정 도면 편집 시 B07 및 이후 단계를 미확정 상태로 되돌린다.""" try: route_id, project_root, longitudinal_path = await _confirmed_source(project_id) - items = await asyncio.to_thread(_drawing_list, project_root, longitudinal_path) + # 목록은 **설계값을 넣어** 만든다 — 장 나눔이 측점 표시 폭에 따라 달라지므로, + # 설계 없이 만들면 방금 확정한 장 id 가 목록에 없어 [수정]이 404 로 막힌다 + # (2026-09-03 실측: `cross_s00220m` 확정 후 확정 해제 불가). + designs = await _designs_by_chainage(route_id) + items = await asyncio.to_thread(_drawing_list, project_root, longitudinal_path, designs) if drawing_id not in {item.id for item in items}: raise FileNotFoundError("변경된 도면을 찾을 수 없습니다.") await asyncio.to_thread(_invalidate_drawing, project_root, drawing_id) @@ -472,7 +476,6 @@ async def invalidate_design_drawing( if cross_match: stale = [int(cross_match.group(1))] elif CROSS_SHEET_ID.fullmatch(drawing_id): - designs = await _designs_by_chainage(route_id) plan = await asyncio.to_thread( _cross_sheet_plan, project_root, longitudinal_path, designs ) diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support.py b/B07_DesignDetail/B07_DesignDetail_Router_Support.py index 5d0396d8..6d69433e 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support.py @@ -466,12 +466,18 @@ def _cross_sheet_plan( return plan_cross_sheets(blocks) -def _quantity_table(source: dict[str, Any]) -> dict[str, float | None]: +def _quantity_table( + source: dict[str, Any], design: dict[str, Any] | None = None +) -> dict[str, float | None]: """횡단면 원본에서 편집 가능한 수량 산출표 값을 구조화한다 (납품 양식 키). center_z→지반고, design_elevation_m→계획고, 절토고/성토고는 파생 초기값. 나머지 항목은 source["quantities"]에 같은 키가 있으면 읽고 없으면 None으로 두어 CAD 테이블에서 사용자가 채운다. + + **계획고는 횡단 설계(design)에도 있다** — 장 배치 입력의 원본에는 그 값이 없어 + 계획고·절토고·성토고 세 칸이 통째로 비어 나갔다(2026-09-03 실측: 장 확정 시 21개 + 항목 중 지반고 하나만 채워짐). 원본에 없으면 설계에서 읽는다. """ def num(value: Any) -> float | None: @@ -479,6 +485,8 @@ def _quantity_table(source: dict[str, Any]) -> dict[str, float | None]: ground = num(source.get("center_z")) planned = num(source.get("planned_elevation_m", source.get("design_elevation_m"))) + if planned is None and isinstance(design, dict): + planned = num(design.get("design_elevation_m")) cut = max(ground - planned, 0.0) if ground is not None and planned is not None else None fill = max(planned - ground, 0.0) if ground is not None and planned is not None else None quantities = source.get("quantities") if isinstance(source.get("quantities"), dict) else {} @@ -568,7 +576,7 @@ def _cross_section_input( "source": source, "design": design, "design_line": _cross_design_line(longitudinal_path, source, design), - "quantity_table": _quantity_table(source), + "quantity_table": _quantity_table(source, design), "title": station_no_label(float(source.get("chainage_m", chainage)), interval), } @@ -697,7 +705,7 @@ def _read_drawing( source = _read_json(path) label = str(source.get("label") or drawing_id) design_line = _cross_design_line(longitudinal_path, source, stored_design) - quantity_table = _quantity_table(source) + quantity_table = _quantity_table(source, stored_design) # 수량표 제목행 No. 표기: 종단 측점 간격 기준 (납품 도면 양식) longitudinal = _read_json(longitudinal_path) interval = infer_station_interval(longitudinal.get("stations") or [])