Skip to content

feat: make grid ROI teaching coordinate-confident #81

feat: make grid ROI teaching coordinate-confident

feat: make grid ROI teaching coordinate-confident #81

Workflow file for this run

name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
jobs:
build:
name: Build and runner smoke
runs-on: windows-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Verify vendored OpenVisionLab Vision SDK package
shell: pwsh
run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-vision-sdk-package.ps1 -ReportPath artifacts\ci\vision-sdk-package.txt
- name: Verify vendored WPG package
shell: pwsh
run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-wpg-package.ps1 -ReportPath artifacts\ci\wpg-package.txt
- name: Restore
run: dotnet restore OpenVisionLab.ThreeDStudio.slnx
- name: Verify NuGet package-health verifier
run: python scripts\verify-nuget-package-health.py --self-test
- name: Verify NuGet package health
run: python scripts\verify-nuget-package-health.py --solution OpenVisionLab.ThreeDStudio.slnx --report artifacts\ci\nuget_package_health.txt --json-directory artifacts\ci\nuget-package-health
- name: Build
run: dotnet build OpenVisionLab.ThreeDStudio.slnx -c Debug --no-restore
- name: Verify OpenVisionLab Vision SDK 3D package bridge
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-vision-sdk-3d --report artifacts\ci\vision-sdk-3d-package.txt
- name: Verify model key-point artifact and debug overlay
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-model-key-points --report artifacts\ci\model-key-points.txt
- name: Verify current Surface Match artifact contracts
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\current-surface-match'
$selectionDir = Join-Path $artifactDir 'surface-selection'
$multipleDir = Join-Path $artifactDir 'multiple-match'
$equivalenceDir = Join-Path $artifactDir 'pose-equivalence'
New-Item -ItemType Directory -Force -Path $selectionDir, $multipleDir, $equivalenceDir | Out-Null
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-surface-model-surface-selection --report (Join-Path $selectionDir 'report.txt')
if ($LASTEXITCODE -ne 0) { throw 'SurfaceModel surface-selection verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-multiple-surface-match --report (Join-Path $multipleDir 'report.txt')
if ($LASTEXITCODE -ne 0) { throw 'Multiple Surface Match verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-multiple-surface-match-workbench (Join-Path $multipleDir 'known-two-object.surface-model.json') (Join-Path $multipleDir 'known-two-object.prepared-scene.json') (Join-Path $multipleDir 'known-two-object.surface-match-collection.json') (Join-Path $multipleDir 'workbench.txt')
if ($LASTEXITCODE -ne 0) { throw 'Multiple Surface Match Workbench verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-surface-match-pose-equivalence --report (Join-Path $equivalenceDir 'report.txt')
if ($LASTEXITCODE -ne 0) { throw 'Surface Match pose-equivalence verification failed.' }
- name: Verify current Workbench contracts
shell: pwsh
timeout-minutes: 3
run: |
$artifactDir = 'artifacts\ci\current-workbench'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-workbench-docking (Join-Path $artifactDir 'workbench-docking.txt')
if ($LASTEXITCODE -ne 0) { throw 'Workbench docking verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-inspection-workspace-selection (Join-Path $artifactDir 'inspection-workspace.txt')
if ($LASTEXITCODE -ne 0) { throw 'Inspection Workspace verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-validation-set (Join-Path $artifactDir 'validation-set.txt')
if ($LASTEXITCODE -ne 0) { throw 'Validation Set verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-recipe-manager-wpg (Join-Path $artifactDir 'recipe-manager-wpg.txt')
if ($LASTEXITCODE -ne 0) { throw 'Recipe Manager WPG verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-run-log-retention (Join-Path $artifactDir 'run-log-retention.txt')
if ($LASTEXITCODE -ne 0) { throw 'Workbench run-log retention verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-shell-smoke-command-line (Join-Path $artifactDir 'shell-command-line.txt')
if ($LASTEXITCODE -ne 0) { throw 'Shell command-line verification failed.' }
- name: Verify typed C3D Median Filter adapter
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\c3d-filter-adapter'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$golden = Join-Path $artifactDir 'golden.txt'
$output = Join-Path $artifactDir 'filtered.c3d'
$runnerReport = Join-Path $artifactDir 'runner.json'
$teachingReport = Join-Path $artifactDir 'teaching.txt'
$screenshot = Join-Path $artifactDir 'published.png'
$quality = Join-Path $artifactDir 'published-quality.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-c3d-filter --report $golden
if ($LASTEXITCODE -ne 0) { throw 'C3D Median Filter Golden verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --tool-teaching-filter recipes\c3d-xyz-affine-teaching-template.ov3d-teach.json --tool-teaching-step step.filter.01 --output-c3d $output --report $runnerReport
if ($LASTEXITCODE -ne 0) { throw 'C3D Median Filter Runner execution failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-tool-recipe-teaching $teachingReport
if ($LASTEXITCODE -ne 0) { throw 'Tool Recipe teaching verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --tool-teaching-recipe recipes\c3d-xyz-affine-teaching-template.ov3d-teach.json --tool-teaching-step step.filter.01 --smoke-tool-filter-publish --shell-smoke-screenshot $screenshot --shell-screenshot-quality-report $quality
if ($LASTEXITCODE -ne 0) { throw 'C3D Median Filter Workbench Preview/Publish smoke failed.' }
if (-not ((Get-Content $golden -Raw).Contains('C3DMedianFilterGoldenVerification|Pass|cases=13|passed=13|failed=0'))) { throw 'Filter Golden report is incomplete.' }
if (-not ((Get-Content $runnerReport -Raw).Contains('6B929F0FF6874BA7C992453B500DF07B3BAF427817BB5FA1C26F994977FB356D'))) { throw 'Filter Runner output SHA-256 changed.' }
if (-not ((Get-Content $quality -Raw).Contains('ShellScreenshotResult|accepted=True'))) { throw 'Filter Workbench screenshot quality was not accepted.' }
- name: Verify typed C3D Height Difference Edge adapter
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\c3d-height-difference-edge'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$golden = Join-Path $artifactDir 'golden.txt'
$workbench = Join-Path $artifactDir 'workbench.txt'
$screenshot = Join-Path $artifactDir 'preview.png'
$quality = Join-Path $artifactDir 'preview-quality.txt'
$smokeReport = Join-Path $artifactDir 'smoke-only.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-c3d-edge --report $golden
if ($LASTEXITCODE -ne 0) { throw 'C3D Height Difference Edge Golden/Runner verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-tool-edge-workbench $workbench
if ($LASTEXITCODE -ne 0) { throw 'C3D Height Difference Edge Workbench verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --tool-teaching-recipe recipes\c3d-xyz-affine-teaching-template.ov3d-teach.json --tool-teaching-step step.edge.ul.horizontal.01 --smoke-tool-edge-preview --smoke-tool-edge-report $smokeReport --shell-smoke-screenshot $screenshot --shell-screenshot-quality-report $quality
if ($LASTEXITCODE -ne 0) { throw 'C3D Height Difference Edge Workbench Preview smoke failed.' }
if (-not ((Get-Content $golden -Raw).Contains('C3DHeightDifferenceEdgeGoldenVerification|Pass|cases=13|passed=13|failed=0'))) { throw 'Height Difference Edge Golden report is incomplete.' }
if (-not ((Get-Content $workbench -Raw).Contains('Workbench and headless share exact output hash'))) { throw 'Height Difference Edge Workbench/headless parity evidence is missing.' }
if (-not ((Get-Content $smokeReport -Raw).Contains('points=135'))) { throw 'Height Difference Edge smoke-only actual-source point evidence changed.' }
if (-not ((Get-Content $quality -Raw).Contains('ShellScreenshotResult|accepted=True'))) { throw 'Height Difference Edge Workbench screenshot quality was not accepted.' }
- name: Verify typed C3D Line Fit adapter
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\c3d-line-fit'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$golden = Join-Path $artifactDir 'golden.txt'
$workbench = Join-Path $artifactDir 'workbench.txt'
$edgeSmoke = Join-Path $artifactDir 'edge-smoke-only.txt'
$lineSmoke = Join-Path $artifactDir 'line-smoke-only.txt'
$screenshot = Join-Path $artifactDir 'preview.png'
$quality = Join-Path $artifactDir 'preview-quality.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-c3d-line-fit --report $golden
if ($LASTEXITCODE -ne 0) { throw 'C3D Line Fit Golden verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-tool-line-fit-workbench $workbench
if ($LASTEXITCODE -ne 0) { throw 'C3D Line Fit Workbench verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --tool-teaching-recipe recipes\c3d-xyz-affine-teaching-template.ov3d-teach.json --tool-teaching-step step.edge.ul.horizontal.01 --smoke-tool-line-fit-preview --smoke-tool-edge-report $edgeSmoke --smoke-tool-line-fit-report $lineSmoke --shell-smoke-screenshot $screenshot --shell-screenshot-quality-report $quality
if ($LASTEXITCODE -ne 0) { throw 'C3D Line Fit Workbench Preview smoke failed.' }
if (-not ((Get-Content $golden -Raw).Contains('C3DLineFitGoldenVerification|Pass|cases=9|passed=9|failed=0'))) { throw 'Line Fit Golden report is incomplete.' }
if (-not ((Get-Content $workbench -Raw).Contains('Workbench and headless share exact output hash'))) { throw 'Line Fit Workbench/headless parity evidence is missing.' }
if (-not ((Get-Content $lineSmoke -Raw).Contains('points=135|inliers=135|outliers=0'))) { throw 'Line Fit smoke-only actual-source inlier evidence changed.' }
if (-not ((Get-Content $lineSmoke -Raw).Contains('plotPoints=135'))) { throw 'Line Fit residual-plot evidence is missing.' }
if (-not ((Get-Content $quality -Raw).Contains('ShellScreenshotResult|accepted=True'))) { throw 'Line Fit Workbench screenshot quality was not accepted.' }
- name: Verify typed C3D Line Intersection adapter
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\c3d-line-intersection'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$golden = Join-Path $artifactDir 'golden.txt'
$workbench = Join-Path $artifactDir 'workbench.txt'
$toolLab = Join-Path $artifactDir 'tool-lab.png'
$quality = Join-Path $artifactDir 'tool-lab-quality.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-c3d-line-intersection --report $golden
if ($LASTEXITCODE -ne 0) { throw 'C3D Line Intersection Golden verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-tool-line-intersection-workbench $workbench
if ($LASTEXITCODE -ne 0) { throw 'C3D Line Intersection Workbench verification failed.' }
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --tool-teaching-recipe recipes\c3d-xyz-affine-teaching-template.ov3d-teach.json --tool-teaching-step step.corner.ul.01 --line-intersection-tool-lab-screenshot $toolLab --line-intersection-tool-lab-screenshot-quality-report $quality
if ($LASTEXITCODE -ne 0) { throw 'Line Intersection Tool Lab screenshot smoke failed.' }
if (-not ((Get-Content $golden -Raw).Contains('C3DLineIntersectionGoldenVerification|Pass|cases=10|passed=10|failed=0'))) { throw 'Line Intersection Golden report is incomplete.' }
if (-not ((Get-Content $workbench -Raw).Contains('Workbench and headless share exact output hash'))) { throw 'Line Intersection Workbench/headless parity evidence is missing.' }
if (-not ((Get-Content $quality -Raw).Contains('LineIntersectionToolLabScreenshotResult|accepted=True'))) { throw 'Line Intersection Tool Lab screenshot quality was not accepted.' }
- name: Verify binary-only Viewer host
shell: pwsh
timeout-minutes: 5
run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-viewer-dll-host.ps1 -Configuration Release -ArtifactDirectory artifacts\ci\viewer-dll-host
- name: Verify hosted Viewer and Shell screenshot quality
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\shell-screenshot-quality'
$viewerScreenshot = Join-Path $artifactDir 'viewer-c3d.png'
$viewerQualityReport = Join-Path $artifactDir 'viewer-c3d-quality.txt'
$viewerContract = Join-Path $artifactDir 'viewer-c3d-contract.txt'
$shellScreenshot = Join-Path $artifactDir 'shell-c3d.png'
$shellQualityReport = Join-Path $artifactDir 'shell-c3d-quality.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --smoke-screenshot $viewerScreenshot --smoke-screenshot-quality-report $viewerQualityReport --smoke-contracts $viewerContract --shell-smoke-screenshot $shellScreenshot --shell-screenshot-quality-report $shellQualityReport --smoke-c3d thickness --smoke-pick c3d
if ($LASTEXITCODE -ne 0) { throw "Hosted Viewer/Shell screenshot smoke failed with exit code $LASTEXITCODE." }
$viewerQuality = Get-Content $viewerQualityReport
$shellQuality = Get-Content $shellQualityReport
if (-not ($viewerQuality -match 'ViewerScreenshotResult\|accepted=True')) { throw 'Hosted Viewer screenshot quality was not accepted.' }
if (-not ($shellQuality -match 'ShellScreenshotResult\|accepted=True')) { throw 'Shell screenshot quality was not accepted.' }
if (-not ((Get-Content $viewerContract) -match 'C3DMap\|loaded=True')) { throw 'Hosted Viewer contract did not record the C3D map.' }
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## Hosted Viewer and Shell screenshot quality`n"
Add-Content $env:GITHUB_STEP_SUMMARY '```text'
Add-Content $env:GITHUB_STEP_SUMMARY $viewerQuality
Add-Content $env:GITHUB_STEP_SUMMARY $shellQuality
Add-Content $env:GITHUB_STEP_SUMMARY '```'
}
- name: Verify C3D grayscale and thermal color maps
shell: pwsh
timeout-minutes: 3
run: |
$artifactDir = 'artifacts\ci\c3d-color-maps'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$cases = @(
@{ Name = 'grayscale'; Label = 'Grayscale'; Action = 'color-grayscale' },
@{ Name = 'thermal'; Label = 'Thermal'; Action = 'color-thermal' }
)
$summary = New-Object System.Collections.Generic.List[string]
$imageHashes = New-Object System.Collections.Generic.List[string]
foreach ($case in $cases) {
$screenshot = Join-Path $artifactDir "$($case.Name).png"
$qualityReport = Join-Path $artifactDir "$($case.Name)-quality.txt"
$contract = Join-Path $artifactDir "$($case.Name)-contract.txt"
$dotnetArguments = @(
'run', '--project', 'src\OpenVisionLab.ThreeDStudio\OpenVisionLab.ThreeDStudio.csproj',
'-c', 'Debug', '--no-build', '--',
'--smoke-software-rendering',
'--smoke-screenshot', $screenshot,
'--smoke-screenshot-quality-report', $qualityReport,
'--smoke-contracts', $contract,
'--smoke-c3d', 'thickness',
'--smoke-action', $case.Action
)
if ($case.Name -eq 'grayscale') {
$dotnetArguments += @(
'--verify-display-viewmodel',
(Join-Path $artifactDir 'display-viewmodel-verification.txt')
)
}
& dotnet @dotnetArguments
if ($LASTEXITCODE -ne 0) {
throw "$($case.Label) color-map smoke failed with exit code $LASTEXITCODE."
}
$quality = Get-Content $qualityReport -Raw
if ($quality -notmatch 'ViewerScreenshotResult\|accepted=True') {
throw "$($case.Label) screenshot quality was not accepted."
}
$contractText = Get-Content $contract -Raw
$requiredContractText = @(
"ColorMode|mode=$($case.Label)",
"colorMapId=$($case.Label)|colorMap=$($case.Label)",
'C3DHeightDistribution|visible=True|overlayVisible=True',
'availableColorMaps=Solid,Grayscale,Height,Thermal',
'fallbackApplied=False',
'C3DMap|loaded=True',
'renderCacheReady=True'
)
foreach ($required in $requiredContractText) {
if (-not $contractText.Contains($required)) {
throw "$($case.Label) contract is missing: $required"
}
}
$hash = (Get-FileHash -LiteralPath $screenshot -Algorithm SHA256).Hash
$imageHashes.Add($hash)
$summary.Add("ColorMap|name=$($case.Label)|quality=True|contract=True|sha256=$hash")
}
if ($imageHashes[0] -eq $imageHashes[1]) {
throw 'Grayscale and Thermal screenshots are byte-identical.'
}
$verification = Get-Content (Join-Path $artifactDir 'display-viewmodel-verification.txt') -Raw
if (-not $verification.Contains('Display-settings ViewModel verification: Pass (103 checks)')) {
throw 'Display-settings ViewModel verification did not pass 103 checks.'
}
$distributionReport = Join-Path $artifactDir 'height-distribution-verification.txt'
& dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-c3d-height-distribution $distributionReport
if ($LASTEXITCODE -ne 0) {
throw "C3D height-distribution verification failed with exit code $LASTEXITCODE."
}
$distributionVerification = Get-Content $distributionReport -Raw
if (-not $distributionVerification.Contains('C3D height-distribution verification: Pass (26 checks)')) {
throw 'C3D height-distribution verification did not pass 26 checks.'
}
$summary.Add('DisplayViewModel|pass=True|checks=103')
$summary.Add('HeightDistribution|pass=True|checks=26|bins=32|scope=full-source')
$summary.Add('Gate|mandatory=True|scope=C3D Grayscale and Thermal display-only color maps')
$summary | Set-Content (Join-Path $artifactDir 'summary.txt') -Encoding utf8
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## C3D color-map gate`n"
Add-Content $env:GITHUB_STEP_SUMMARY '```text'
Add-Content $env:GITHUB_STEP_SUMMARY $summary
Add-Content $env:GITHUB_STEP_SUMMARY '```'
}
- name: Verify GLB and STL geometry styles
shell: pwsh
timeout-minutes: 5
run: |
$artifactDir = 'artifacts\ci\imported-mesh-geometry-styles'
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-imported-mesh-geometry-styles.ps1 -Configuration Debug -ArtifactDirectory $artifactDir -SkipBuild
if ($LASTEXITCODE -ne 0) {
throw "Imported-mesh Geometry Style verification failed with exit code $LASTEXITCODE."
}
$summaryPath = Join-Path $artifactDir 'summary.txt'
if (-not (Test-Path -LiteralPath $summaryPath -PathType Leaf)) {
throw 'Imported-mesh Geometry Style summary was not created.'
}
$summary = Get-Content -LiteralPath $summaryPath -Raw
if (-not $summary.Contains('Imported-mesh Geometry Style verification: PASS (15/15 checks passed)')) {
throw 'Imported-mesh Geometry Style summary did not record 15/15 passing checks.'
}
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## Imported-mesh Geometry Style gate`n"
Add-Content $env:GITHUB_STEP_SUMMARY '```text'
Add-Content $env:GITHUB_STEP_SUMMARY $summary
Add-Content $env:GITHUB_STEP_SUMMARY '```'
}
- name: Verify WPF pointer input
shell: pwsh
timeout-minutes: 3
run: |
$artifactDir = 'artifacts\ci\pointer-input-observation'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$viewerReport = Join-Path $artifactDir 'viewer-pointer.txt'
$shellReport = Join-Path $artifactDir 'shell-pointer.txt'
dotnet run --project src\OpenVisionLab.ThreeDStudio\OpenVisionLab.ThreeDStudio.csproj -c Debug --no-build -- --smoke-software-rendering --smoke-screenshot (Join-Path $artifactDir 'viewer.png') --smoke-screenshot-quality-report (Join-Path $artifactDir 'viewer-quality.txt') --smoke-contracts (Join-Path $artifactDir 'viewer-contract.txt') --smoke-pointer-input-report $viewerReport
$viewerExitCode = $LASTEXITCODE
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --shell-smoke-screenshot (Join-Path $artifactDir 'shell.png') --shell-screenshot-quality-report (Join-Path $artifactDir 'shell-quality.txt') --smoke-pointer-input-report $shellReport
$shellExitCode = $LASTEXITCODE
$viewerReportExists = Test-Path $viewerReport
$shellReportExists = Test-Path $shellReport
$viewerPassed = $viewerReportExists -and ((Get-Content $viewerReport -Raw) -match 'Result\|pass=True')
$shellPassed = $shellReportExists -and ((Get-Content $shellReport -Raw) -match 'Result\|pass=True')
$summary = @(
'PointerInputObservation'
"Viewer|exitCode=$viewerExitCode|report=$viewerReportExists|pass=$viewerPassed"
"Shell|exitCode=$shellExitCode|report=$shellReportExists|pass=$shellPassed"
'Gate|mandatory=True|reason=Viewer and Shell foreground input passed Windows Actions observation'
)
$summary | Set-Content (Join-Path $artifactDir 'summary.txt') -Encoding utf8
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## WPF pointer input gate`n"
Add-Content $env:GITHUB_STEP_SUMMARY '```text'
Add-Content $env:GITHUB_STEP_SUMMARY $summary
Add-Content $env:GITHUB_STEP_SUMMARY '```'
}
if (-not $viewerPassed -or -not $shellPassed) {
throw "WPF pointer input failed. Viewer exit=$viewerExitCode pass=$viewerPassed; Shell exit=$shellExitCode pass=$shellPassed."
}
- name: Verify Phase 2 difficult-geometry goldens
shell: pwsh
timeout-minutes: 2
run: |
$artifactDir = 'artifacts\ci\phase2-difficult-geometry'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$meshReport = Join-Path $artifactDir 'mesh_deviation_golden.txt'
$nominalActualReport = Join-Path $artifactDir 'nominal_actual_golden.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-mesh-deviation --report $meshReport
if ($LASTEXITCODE -ne 0) {
throw "Phase 2 mesh-deviation golden verification failed with exit code $LASTEXITCODE."
}
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-nominal-actual-comparison --report $nominalActualReport
if ($LASTEXITCODE -ne 0) {
throw "Phase 2 nominal/actual golden verification failed with exit code $LASTEXITCODE."
}
$mesh = Get-Content -LiteralPath $meshReport -Raw
$requiredMeshEvidence = @(
'MeshDeviationGoldenVerification|Pass|cases=23|passed=23|failed=0',
'openSurfaceSign=oriented-local-normal-not-solid-inside-outside',
'Case|stream-reader-nonfinite-normal|Pass|',
'Case|duplicate-vertices-deterministic-tie|Pass|',
'Case|open-surface-local-normal-contract|Pass|',
'Case|edge-sign-is-explicitly-unresolved|Pass|',
'Case|edge-sign-robustly-resolved|Pass|',
'Case|vertex-sign-is-explicitly-unresolved|Pass|',
'Case|vertex-sign-robustly-resolved|Pass|',
'Case|empty-mesh-no-correspondence-rejected|Pass|'
)
foreach ($required in $requiredMeshEvidence) {
if (-not $mesh.Contains($required)) {
throw "Mesh-deviation report is missing required evidence: $required"
}
}
$nominalActual = Get-Content -LiteralPath $nominalActualReport -Raw
$requiredNominalActualEvidence = @(
'NominalActualComparisonVerification|Pass|cases=29|passed=29|failed=0',
'sampling=full-query|displaySampling=independent',
'Case|sparse-dense-full-query-outcomes|Pass|',
'Case|empty-query-no-correspondence-rejected|Pass|'
)
foreach ($required in $requiredNominalActualEvidence) {
if (-not $nominalActual.Contains($required)) {
throw "Nominal/actual report is missing required evidence: $required"
}
}
$summary = @(
'Phase2DifficultGeometry',
'MeshDeviation|pass=True|cases=23|report=mesh_deviation_golden.txt',
'NominalActual|pass=True|cases=29|report=nominal_actual_golden.txt',
'Gate|mandatory=True|scope=synthetic controlled outcomes; registration acceptance excluded'
)
$summary | Set-Content (Join-Path $artifactDir 'summary.txt') -Encoding utf8
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## Phase 2 difficult-geometry gate`n"
Add-Content $env:GITHUB_STEP_SUMMARY '```text'
Add-Content $env:GITHUB_STEP_SUMMARY $summary
Add-Content $env:GITHUB_STEP_SUMMARY '```'
}
- name: Verify registration acceptance contract
shell: pwsh
timeout-minutes: 2
run: |
$artifactDir = 'artifacts\ci\registration-acceptance'
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$reportPath = Join-Path $artifactDir 'registration_acceptance_golden.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-registration-acceptance --report $reportPath
if ($LASTEXITCODE -ne 0) {
throw "Registration acceptance golden verification failed with exit code $LASTEXITCODE."
}
$report = Get-Content -LiteralPath $reportPath -Raw
$requiredEvidence = @(
'RegistrationAcceptanceGoldenVerification|Pass|cases=20|passed=20|failed=0',
'Contract|order=CorrespondenceCount -> Fitness -> InlierRmse -> RigidTransform -> Translation -> Rotation|transform=row-major-float64-4x4|unit=explicit|thresholds=scenario-specific',
'FalseSuccessGuard|zeroCorrespondences=True|rmseZeroAccepted=False|fitnessCheckedBeforeRmse=True',
'Case|zero-correspondence-rmse-zero-rejected|Pass|',
'Case|minimum-correspondence-before-fitness|Pass|',
'Case|fitness-before-rmse|Pass|',
'Case|rmse-after-guards|Pass|',
'Case|nonhomogeneous-transform-rejected|Pass|',
'Case|scaled-transform-rejected|Pass|',
'Case|reflected-transform-rejected|Pass|',
'Case|translation-limit-rejected|Pass|',
'Case|rotation-limit-rejected|Pass|',
'Case|nonfinite-transform-rejected|Pass|',
'Case|unit-mismatch-rejected|Pass|',
'Case|invalid-policy-guards-rejected|Pass|'
)
foreach ($required in $requiredEvidence) {
if (-not $report.Contains($required)) {
throw "Registration acceptance report is missing required evidence: $required"
}
}
$summary = @(
'RegistrationAcceptance',
'Golden|pass=True|cases=20|report=registration_acceptance_golden.txt',
'Order|correspondence-count -> fitness -> RMSE -> rigid-transform -> translation -> rotation',
'FalseSuccessGuard|zero-correspondence-rmse-zero=Rejected',
'Gate|mandatory=True|scope=runtime-neutral policy; product runtime integration excluded'
)
$summary | Set-Content (Join-Path $artifactDir 'summary.txt') -Encoding utf8
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## Registration acceptance gate`n"
Add-Content $env:GITHUB_STEP_SUMMARY '```text'
Add-Content $env:GITHUB_STEP_SUMMARY $summary
Add-Content $env:GITHUB_STEP_SUMMARY '```'
}
- name: Runner smoke
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --recipe recipes\c3d-height-deviation.recipe.json --report artifacts\ci\runner_c3d_height_rule.txt --expect-status Fail
- name: Runner plane flatness smoke
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --recipe recipes\c3d-plane-flatness.recipe.json --report artifacts\ci\runner_c3d_plane_flatness.txt --expect-status Fail
- name: Verify plane flatness golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-plane-flatness --report artifacts\ci\plane_flatness_golden.txt
- name: Verify Thickness Repeatability model golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-thickness-repeatability --report artifacts\ci\thickness_repeatability_golden.txt
- name: Verify aligned point repeatability model golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-aligned-point-repeatability --report artifacts\ci\aligned_point_repeatability_golden.txt
- name: Verify aligned point repeatability study loading and headless execution
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-aligned-point-repeatability-study --report artifacts\ci\aligned_point_repeatability_study_loader.txt
- name: Verify Thickness Repeatability study loading
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-thickness-repeatability-study --report artifacts\ci\thickness_repeatability_study_loader.txt
- name: Verify Calibration Center ViewModel workflow
run: dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-calibration-viewmodel artifacts\ci\calibration_center_viewmodel.txt
- name: Runner point pair dimensions smoke
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --recipe recipes\c3d-point-pair-dimensions.recipe.json --report artifacts\ci\runner_c3d_point_pair_dimensions.txt --expect-status Pass
- name: Verify point pair dimensions golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-point-pair-dimensions --report artifacts\ci\point_pair_dimensions_golden.txt
- name: Runner Gap Flush smoke
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --recipe recipes\c3d-gap-flush.recipe.json --report artifacts\ci\runner_c3d_gap_flush.txt --expect-status Pass
- name: Verify Gap Flush golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-gap-flush --report artifacts\ci\gap_flush_golden.txt
- name: Runner Volume smoke
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --recipe recipes\c3d-volume.recipe.json --report artifacts\ci\runner_c3d_volume.txt --expect-status Pass
- name: Verify Volume golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-volume --report artifacts\ci\volume_golden.txt
- name: Viewer Cross-section Dimensions smoke
shell: pwsh
run: |
$qualityReport = 'artifacts\ci\cross_section_viewer_quality.txt'
dotnet run --project src\OpenVisionLab.ThreeDStudio\OpenVisionLab.ThreeDStudio.csproj -c Debug --no-build -- --smoke-software-rendering --smoke-screenshot artifacts\ci\cross_section_viewer.png --smoke-screenshot-quality-report $qualityReport --smoke-recipe recipes\c3d-cross-section-dimensions.recipe.json --smoke-publish-result --smoke-contracts artifacts\ci\cross_section_viewer.txt
if ($LASTEXITCODE -ne 0) { throw "Viewer Cross-section screenshot smoke failed with exit code $LASTEXITCODE." }
$quality = Get-Content $qualityReport
if (-not ($quality -match 'ViewerScreenshotResult\|accepted=True')) { throw 'Viewer Cross-section screenshot quality was not accepted.' }
- name: Runner Cross-section Dimensions smoke
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --recipe recipes\c3d-cross-section-dimensions.recipe.json --report artifacts\ci\runner_c3d_cross_section_dimensions.txt --expect-status Pass --compare-contract artifacts\ci\cross_section_viewer.txt --viewer-screenshot artifacts\ci\cross_section_viewer.png --run-record artifacts\ci\cross_section_run.json --html-report artifacts\ci\cross_section_report.html --csv-report artifacts\ci\cross_section_metrics.csv
- name: Verify durable run bundle
shell: pwsh
run: |
[xml]$buildProperties = Get-Content Directory.Build.props -Raw
$productVersion = [string]$buildProperties.Project.PropertyGroup.OpenVisionLabProductVersion
$hostApiVersion = [string]$buildProperties.Project.PropertyGroup.OpenVisionLabViewerHostApiVersion
if ([string]$buildProperties.Project.PropertyGroup.Version -ne $productVersion) { throw 'Central product versions do not match.' }
$manifest = Get-Content artifacts\ci\viewer-dll-host\viewer-dll-manifest.json -Raw | ConvertFrom-Json
$record = Get-Content artifacts\ci\cross_section_run.json -Raw | ConvertFrom-Json
$recipePath = 'recipes\c3d-cross-section-dimensions.recipe.json'
$recipeBytes = [System.IO.File]::ReadAllBytes($recipePath)
if ($recipeBytes -contains [byte]13) { throw 'Tracked recipe JSON must use LF line endings for stable raw provenance hashes.' }
$recipeHash = (Get-FileHash $recipePath -Algorithm SHA256).Hash
if ($record.Recipe.Sha256 -ne $recipeHash) { throw 'Run record recipe hash does not match the executed recipe bytes.' }
if ($manifest.schemaVersion -ne '1.0' -or $manifest.applicationVersion -ne $productVersion -or $manifest.viewerHostApiVersion -ne $hostApiVersion) { throw 'Viewer manifest version contract mismatch.' }
if ($record.SchemaVersion -ne '1.2' -or $record.Status -ne 'Pass') { throw 'Run record identity or status mismatch.' }
if ($record.Step.Id -ne 'step.c3d-cross-section-dimensions' -or $record.Step.SourceEntityId -ne 'source.c3d-thickness') { throw 'Run record step identity mismatch.' }
if (@($record.Step.ReferenceIds).Count -ne 1 -or $record.Step.ReferenceIds[0] -ne 'reference.c3d-row-range' -or @($record.Step.MeasurementIds).Count -ne 0) { throw 'Run record step references mismatch.' }
if ($record.ExecutionEnvironment.ApplicationVersion -ne $productVersion -or $record.ExecutionEnvironment.ViewerHostApiVersion -ne $hostApiVersion) { throw 'Run record version contract mismatch.' }
if ($record.Metrics.Count -ne 5 -or $record.Overlays.Count -ne 3) { throw 'Run record result evidence mismatch.' }
if ($record.Recipe.Sha256.Length -ne 64 -or $record.Source.Sha256.Length -ne 64) { throw 'Run record provenance hash mismatch.' }
if ($record.ExecutionEnvironment.ApplicationVersion -eq 'unknown' -or $record.ExecutionEnvironment.ViewerHostApiVersion -eq 'unknown') { throw 'Run record application identity is missing.' }
if ($record.ExecutionEnvironment.GitCommit -ne $env:GITHUB_SHA -or $record.ExecutionEnvironment.GitWorkingTree -ne 'clean') { throw 'Run record Git identity mismatch.' }
if ($record.ExecutionEnvironment.DotNetRuntime -notlike '.NET 10.*' -or $record.ExecutionEnvironment.ProcessArchitecture -eq 'unknown') { throw 'Run record runtime identity is missing.' }
if ($record.ViewerRunnerMatchState -ne 'Matched') { throw 'Run record Viewer/Runner comparison did not match.' }
if (-not (Test-Path $record.Artifacts.ViewerContract) -or -not (Test-Path $record.Artifacts.ViewerScreenshot)) { throw 'Run record Viewer evidence is missing.' }
if (-not (Test-Path artifacts\ci\cross_section_report.html) -or -not (Test-Path artifacts\ci\cross_section_metrics.csv)) { throw 'Run report artifact is missing.' }
if ((Get-Content artifacts\ci\cross_section_report.html -Raw) -notlike '*step.c3d-cross-section-dimensions*') { throw 'HTML report step identity is missing.' }
$csvRows = @(Import-Csv artifacts\ci\cross_section_metrics.csv)
$csvStepIds = @($csvRows.stepId | Select-Object -Unique)
$csvReferenceIds = @($csvRows.stepReferenceIds | Select-Object -Unique)
if ($csvRows.Count -ne 5 -or $csvStepIds.Count -ne 1 -or $csvStepIds[0] -ne 'step.c3d-cross-section-dimensions' -or $csvReferenceIds.Count -ne 1 -or $csvReferenceIds[0] -ne 'reference.c3d-row-range') { throw 'CSV report step identity mismatch.' }
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content $env:GITHUB_STEP_SUMMARY "## Release identity`n"
Add-Content $env:GITHUB_STEP_SUMMARY "- Product: ``$productVersion``"
Add-Content $env:GITHUB_STEP_SUMMARY "- Viewer Host API: ``$hostApiVersion``"
Add-Content $env:GITHUB_STEP_SUMMARY "- Viewer manifest schema: ``$($manifest.schemaVersion)``"
Add-Content $env:GITHUB_STEP_SUMMARY "- Run Record schema: ``$($record.SchemaVersion)``"
Add-Content $env:GITHUB_STEP_SUMMARY "- Viewer/Runner: ``$($record.ViewerRunnerMatchState)``"
}
- name: Verify Shell Run Record 1.2 compatibility
shell: pwsh
run: |
$screenshot = 'artifacts\ci\shell_run_record_1_2.png'
$qualityReport = 'artifacts\ci\shell_run_record_1_2_quality.txt'
dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --smoke-software-rendering --run-record artifacts\ci\cross_section_run.json --shell-smoke-screenshot $screenshot --shell-screenshot-quality-report $qualityReport --shell-evidence-tab run-record --smoke-recipe recipes\c3d-cross-section-dimensions.recipe.json
if ($LASTEXITCODE -ne 0) { throw "Shell Run Record 1.2 smoke failed with exit code $LASTEXITCODE." }
if (-not ((Get-Content $qualityReport) -match 'ShellScreenshotResult\|accepted=True')) { throw 'Shell Run Record 1.2 screenshot quality was not accepted.' }
- name: Verify Cross-section Dimensions golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-cross-section --report artifacts\ci\cross_section_dimensions_golden.txt
- name: Verify C3D map fidelity golden cases
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --verify-c3d-map-fidelity --report artifacts\ci\c3d_map_fidelity_golden.txt
- name: Verify immutable C3D loaded snapshot and height profiles
run: dotnet run --project src\OpenVisionLab.ThreeD.Shell\OpenVisionLab.ThreeD.Shell.csproj -c Debug --no-build -- --verify-c3d-height-profile artifacts\ci\c3d_height_profile.txt
- name: Verify actual C3D viewer-frame roundtrip
run: dotnet run --project src\OpenVisionLab.ThreeD.Runner\OpenVisionLab.ThreeD.Runner.csproj -c Debug --no-build -- --c3d-map-probe 3D\Samples\ThicknessCouponV1\thickness-coupon-v1.C3D --ply artifacts\ci\c3d_map_reference.ply --report artifacts\ci\c3d_map_fidelity_actual.txt --max-sampled-points 140000 --point-only
- name: Verify C3D map with independent Python implementation
run: python scripts\verify-c3d-map-ply.py --source 3D\Samples\ThicknessCouponV1\thickness-coupon-v1.C3D --ply artifacts\ci\c3d_map_reference.ply --report artifacts\ci\c3d_map_fidelity_python.txt --max-sampled-points 140000 --first-cell 198,198 --second-cell 198,468
- name: Generate PLY coordinate signature
run: python scripts\ply-coordinate-signature.py --ply artifacts\ci\c3d_map_reference.ply --report artifacts\ci\c3d_map_reference_signature.txt
- name: Upload CI artifacts
if: always()
uses: actions/upload-artifact@v6
with:
name: openvisionlab-3d-ci-artifacts
path: artifacts/ci/**
if-no-files-found: ignore