Skip to content

feat: Add LOD selection parameter to CityGML/PLATEAU API endpoints#200

Open
Soynyuu wants to merge 1 commit intomainfrom
feature/199-lod-selection-api
Open

feat: Add LOD selection parameter to CityGML/PLATEAU API endpoints#200
Soynyuu wants to merge 1 commit intomainfrom
feature/199-lod-selection-api

Conversation

@Soynyuu
Copy link
Owner

@Soynyuu Soynyuu commented Mar 5, 2026

Summary

  • Add target_lod parameter (LOD1/LOD2/LOD3/None) across the entire CityGML→STEP conversion pipeline, from API endpoints down to the LOD extractor
  • Refactor extractor.py from a hardcoded LOD3→LOD2→LOD1 fallback chain to a data-driven loop using the existing LOD_PRIORITY constant
  • Preserve full backward compatibility: target_lod=None (default) retains the existing auto-fallback behavior

Changes (8 files)

File Change
backend/services/citygml/core/types.py Add target_lod field to ConversionContext dataclass
backend/services/citygml/lod/extractor.py Major refactor: Replace hardcoded LOD chain with data-driven loop, add target_lod parameter with validation
backend/services/citygml/pipeline/orchestrator.py Thread target_lod through export_step_from_citygml() and extract_single_solid() closure
backend/services/citygml/pipeline/parallel.py Thread target_lod through parallel worker function
backend/models/request_models.py Add lod field (with regex validation ^(LOD1|LOD2|LOD3)$) to 4 Pydantic models
backend/api/routers/plateau.py Pass target_lod through all 4 PLATEAU endpoints
backend/api/routers/citygml.py Add lod Form parameter with validation to to-step endpoint
AGENTS.md Update repository guidelines with improved formatting and details

API Usage

# Auto-fallback (default, backward-compatible)
curl -X POST /api/citygml/to-step -F "file=@building.gml"

# LOD2 only (no fallback)
curl -X POST /api/citygml/to-step -F "file=@building.gml" -F "lod=LOD2"

# PLATEAU endpoint with LOD selection
curl -X POST /api/plateau/fetch-and-convert -F "address=東京都..." -F "lod=LOD3"

Design Decisions

  • No fallback when target_lod is specified: Returns empty result instead of silently downgrading — callers know exactly which LOD they got
  • Validation at two layers: Pydantic regex pattern for request models + explicit check in extractor.py for programmatic callers
  • Closure capture pattern: extract_single_solid() captures target_lod from outer scope, avoiding changes to the merge_building_parts callback signature
  • LOD strategy files untouched: All gating logic lives in extractor.py; lod1_strategy.py, lod2_strategy.py, lod3_strategy.py remain unchanged

Testing

  • All 8 files pass Python AST syntax checking
  • All 5 export_step_from_citygml() call sites verified to pass target_lod
  • pytest not available in current environment; manual/CI testing recommended

Closes #199
Related: #162, #167

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
Copilot AI review requested due to automatic review settings March 5, 2026 13:43
@cloudflare-workers-and-pages
Copy link

Deploying newlppaper-cad with  Cloudflare Pages  Cloudflare Pages

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

View logs

@cloudflare-workers-and-pages
Copy link

Deploying paper-cad with  Cloudflare Pages  Cloudflare Pages

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

View logs

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / lod parameter from FastAPI endpoints down to the LOD extractor and parallel pipeline.
  • Refactor LOD extraction to iterate using LOD_PRIORITY and 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 supports target_lod where only a single LOD is attempted (no fallback). Updating the opening summary (and any related wording) would avoid misleading callers about behavior when target_lod is 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)",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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 の順で自動フォールバックを行います。"
),

Copilot uses AI. Check for mistakes.
Comment on lines +231 to +236
# 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(
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +264 to 268
lod: Optional[str] = Form(
None,
description="LODレベル指定 / Target LOD (LOD1/LOD2/LOD3, None=自動フォールバック)",
),
):
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
shape_fix_level: Optional[str] = "ultra"
lod: Optional[str] = Field(
default=None,
description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
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)",

Copilot uses AI. Check for mistakes.
)
lod: Optional[str] = Field(
default=None,
description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)",

Copilot uses AI. Check for mistakes.
)
lod: Optional[str] = Field(
default=None,
description="LODレベル指定 / Target LOD level (LOD1/LOD2/LOD3, None=自動フォールバック LOD3→LOD2→LOD1)",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.",

Copilot uses AI. Check for mistakes.
Soynyuu added a commit that referenced this pull request Mar 5, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: LOD選択パラメータのバックエンドAPI対応

2 participants