feat: Add LOD selection parameter to CityGML/PLATEAU API endpoints#200
feat: Add LOD selection parameter to CityGML/PLATEAU API endpoints#200
Conversation
Thread target_lod parameter through the entire call chain from API endpoints down to the LOD extractor. When specified (LOD1/LOD2/LOD3), only that level is attempted with no fallback. When omitted (None), the existing LOD3→LOD2→LOD1 fallback is preserved for backward compatibility. Refactor extractor.py from hardcoded LOD chain to data-driven loop using the existing LOD_PRIORITY constant from constants.py. Closes #199
Deploying newlppaper-cad with
|
| Latest commit: |
cfcdab4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://be68f437.newlppaper-cad.pages.dev |
| Branch Preview URL: | https://feature-199-lod-selection-ap.newlppaper-cad.pages.dev |
Deploying paper-cad with
|
| Latest commit: |
cfcdab4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f86fca3f.paper-cad.pages.dev |
| Branch Preview URL: | https://feature-199-lod-selection-ap.paper-cad.pages.dev |
There was a problem hiding this comment.
Pull request overview
Adds an optional LOD selection parameter to the CityGML/PLATEAU conversion APIs and threads it through the CityGML→STEP pipeline, while preserving the existing auto-fallback behavior when unspecified.
Changes:
- Introduce
target_lod/lodparameter from FastAPI endpoints down to the LOD extractor and parallel pipeline. - Refactor LOD extraction to iterate using
LOD_PRIORITYand support single-LOD (no fallback) execution when explicitly requested. - Update repository agent guidelines documentation.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/services/citygml/pipeline/parallel.py | Thread target_lod through parallel worker + extractor call. |
| backend/services/citygml/pipeline/orchestrator.py | Add target_lod param to export API and pass through to extraction/parallel path. |
| backend/services/citygml/lod/extractor.py | Add target_lod validation and refactor fallback chain to loop over LOD_PRIORITY. |
| backend/services/citygml/core/types.py | Add target_lod to ConversionContext. |
| backend/models/request_models.py | Add lod field with regex validation to PLATEAU request models. |
| backend/api/routers/plateau.py | Add lod form param to fetch-and-convert and pass through to conversion. |
| backend/api/routers/citygml.py | Add lod form param + validation and pass through to conversion. |
| AGENTS.md | Update repository guidelines formatting/content. |
Comments suppressed due to low confidence (1)
backend/services/citygml/lod/extractor.py:38
extract_building_geometry()docstring still states it always uses the LOD3→LOD2→LOD1 fallback chain, but the function now supportstarget_lodwhere only a single LOD is attempted (no fallback). Updating the opening summary (and any related wording) would avoid misleading callers about behavior whentarget_lodis set.
"""
Extract building geometry using LOD3→LOD2→LOD1 fallback chain.
This is the main entry point for LOD extraction. It orchestrates the progressive
fallback through LOD levels, trying each strategy in order until one succeeds.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) | ||
| lod: Optional[str] = Field( | ||
| default=None, | ||
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", |
There was a problem hiding this comment.
The lod field description doesn’t indicate that providing a value disables automatic fallback to other LODs. Updating the description would prevent confusion for clients relying on the OpenAPI schema.
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", | |
| description=( | |
| "LODレベル指定 / Target LOD level. " | |
| "LOD1/LOD2/LOD3 を指定した場合はその LOD のみを使用し、自動フォールバックは行いません。" | |
| "None(未指定)の場合は LOD3→LOD2→LOD1 の順で自動フォールバックを行います。" | |
| ), |
| # Normalize and validate lod parameter (Issue #199) | ||
| normalized_lod = lod if lod and lod.strip() else None | ||
| if normalized_lod is not None: | ||
| valid_lod_levels = ["LOD1", "LOD2", "LOD3"] | ||
| if normalized_lod not in valid_lod_levels: | ||
| raise HTTPException( |
There was a problem hiding this comment.
normalized_lod = lod if lod and lod.strip() else None doesn’t actually strip whitespace; e.g., lod="LOD2 " will remain "LOD2 " and be rejected. Consider assigning the stripped value (e.g., lod.strip()) so trailing/leading whitespace doesn’t cause an unexpected 400.
| lod: Optional[str] = Form( | ||
| None, | ||
| description="LODレベル指定 / Target LOD (LOD1/LOD2/LOD3, None=自動フォールバック)", | ||
| ), | ||
| ): |
There was a problem hiding this comment.
lod is accepted as a raw Form() value here and passed through to export_step_from_citygml(target_lod=lod) without normalization/validation. Unlike the CityGML router, this path will treat empty strings as invalid and can also allow invalid values to propagate into the pipeline (which may get swallowed in parallel workers and surface as a generic 500). Normalize whitespace-only input to None and validate against {LOD1, LOD2, LOD3}, returning HTTP 400 for invalid values.
| shape_fix_level: Optional[str] = "ultra" | ||
| lod: Optional[str] = Field( | ||
| default=None, | ||
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", |
There was a problem hiding this comment.
The new lod field description mentions auto-fallback when None, but it doesn’t mention the new behavior that when lod is specified the pipeline will not fallback to lower LODs. Since this description feeds OpenAPI docs, consider explicitly stating “指定時はフォールバックなし / no fallback when specified”.
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", | |
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1, 指定時はフォールバックなし / no fallback when specified)", |
| ) | ||
| lod: Optional[str] = Field( | ||
| default=None, | ||
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", |
There was a problem hiding this comment.
The lod field description doesn’t mention that specifying lod disables the LOD3→LOD2→LOD1 fallback. To avoid API consumers assuming fallback still happens, update the description to explicitly say there is no fallback when lod is provided.
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", | |
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1, LOD指定時はフォールバックなし / when LOD is specified, no fallback is applied)", |
| ) | ||
| lod: Optional[str] = Field( | ||
| default=None, | ||
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", |
There was a problem hiding this comment.
The lod field description currently omits that specifying lod results in a single-LOD attempt (no fallback). Since this is part of the public request schema, consider clarifying that behavior in the description.
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)", | |
| description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3). Noneの場合は自動フォールバック LOD3→LOD2→LOD1 を試行しますが、LODを明示指定した場合はそのLODのみを試行しフォールバックは行いません。 / Specify target LOD level (LOD1/LOD2/LOD3). When None, automatic fallback LOD3→LOD2→LOD1 is attempted; when an LOD is explicitly specified, only that LOD is attempted and no fallback occurs.", |
Add a segmented button control (自動/かんたん/標準/詳細) to the Sidebar footer that lets users choose the conversion LOD (assembly difficulty) for papercraft models. Lower LOD = simpler geometry = easier papercraft. New components: - LodSelector.tsx: segmented button with 4 LOD options - LodSelector.module.css: styled to match Paper-CAD design system Modified: - Sidebar.tsx: integrate LodSelector, new props for LOD state - PlateauCesiumPickerReact.tsx: manage selectedLod state, pass to Sidebar and include targetLod in import/unfold callbacks - plateauCesiumPickerDialog.ts: add targetLod to PlateauCesiumPickerResult - citygmlService.ts: add lod parameter to API request bodies - importPlateauBuilding.ts: extract and pass targetLod to service calls Also reverts unnecessary AGENTS.md reformatting from PR #200. Closes #201
Summary
target_lodparameter (LOD1/LOD2/LOD3/None) across the entire CityGML→STEP conversion pipeline, from API endpoints down to the LOD extractorextractor.pyfrom a hardcoded LOD3→LOD2→LOD1 fallback chain to a data-driven loop using the existingLOD_PRIORITYconstanttarget_lod=None(default) retains the existing auto-fallback behaviorChanges (8 files)
backend/services/citygml/core/types.pytarget_lodfield toConversionContextdataclassbackend/services/citygml/lod/extractor.pytarget_lodparameter with validationbackend/services/citygml/pipeline/orchestrator.pytarget_lodthroughexport_step_from_citygml()andextract_single_solid()closurebackend/services/citygml/pipeline/parallel.pytarget_lodthrough parallel worker functionbackend/models/request_models.pylodfield (with regex validation^(LOD1|LOD2|LOD3)$) to 4 Pydantic modelsbackend/api/routers/plateau.pytarget_lodthrough all 4 PLATEAU endpointsbackend/api/routers/citygml.pylodForm parameter with validation toto-stependpointAGENTS.mdAPI Usage
Design Decisions
target_lodis specified: Returns empty result instead of silently downgrading — callers know exactly which LOD they gotextractor.pyfor programmatic callersextract_single_solid()capturestarget_lodfrom outer scope, avoiding changes to themerge_building_partscallback signatureextractor.py;lod1_strategy.py,lod2_strategy.py,lod3_strategy.pyremain unchangedTesting
export_step_from_citygml()call sites verified to passtarget_lodCloses #199
Related: #162, #167