engine-bridge v3 live: /scene /camera /selection all real (#10)
Live end-to-end verification against EG-BIM Modeler succeeded on the
second attempt:
/health -> {"status":"ok","port":38080}
/scene -> {"object_count":4,"document_path":"NewSpace0"}
/camera -> {"eye":[192.97,-328.52,170.72],
"target":[33.03,-72.61,10.78],
"up":[0,0,1],"fov":45}
/selection -> {"selected_ids":["ac0380a2-...","d9a287ee-..."]}
1st attempt returned default-zero camera. Root cause: the viewport lambda
used EditorPlugin.View, which is only populated when the plugin is
actually Run() by a user trigger; our bridge plugin just boots an HTTP
server from its constructor and never runs a command, so View stayed
null. Space access worked because RootSpace goes through AppManager,
which is populated for the whole app.
Fix (HmEgBridgePlugin.BuildProvider):
Before: viewportProvider = () => View;
After: viewportProvider = () => {
var vm = AppManager?.ViewportManager;
if (vm is null) return null;
return vm.FocusedViewport ?? vm.Viewports.FirstOrDefault();
};
Confirmed against read-only view of
HmEGApplicationManagementLibrary/SubManager/ViewportManager.cs
which exposes FocusedViewport and Viewports. EGViewport : HmEGViewport
so the lambda matches the Func<HmEGViewport?> contract directly.
Plus: scripts/deploy-egbim-plugin.bat for one-click deploy. Checks for
a running SUT, builds Debug, purges the legacy Recordingtest.EgPlugin
folder, cleans the destination, copies 3 DLLs (+ PDBs) into
EG-BIM Modeler/Plugins/Recordingtest.Sut.EgBim.PluginHost/
and prints the curl commands for verification. HmEG.dll and the
Editor*.dll assemblies are deliberately NOT copied — the SUT already
supplies them.
PROGRESS.md: engine-bridge v3 row finalized; the long-running "라이브
검증 대기" item is done. PLAN.md P1 advances to the Runner <-> sidecar
integration (snapshot /scene /camera /selection at scenario end and
include in the golden baseline).
Follow-up (noted in history): document_path returned "NewSpace0" for
an unsaved scratch document — need to retest with a saved .hmeg file
to confirm the real FileManager.CurrentFile round-trip.
Ref: #10 follow-up, engine-bridge-v3 contract DoD D7 satisfied.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
PLAN.md
4
PLAN.md
@@ -8,9 +8,9 @@
|
|||||||
1. **훅 동작 검증** — SessionStart/Stop/Guard 3개 shell 스크립트를 실제로 트리거시켜 확인
|
1. **훅 동작 검증** — SessionStart/Stop/Guard 3개 shell 스크립트를 실제로 트리거시켜 확인
|
||||||
- 의존: jq 설치 여부 확인
|
- 의존: jq 설치 여부 확인
|
||||||
|
|
||||||
## P1 — 라이브 검증 (사용자 환경 필요)
|
## P1 — 다음 통합 단계
|
||||||
|
|
||||||
4. **engine-bridge v3 라이브 검증** — 코드 쪽 완료 (3-tier 분리 2단계 + EgBim 람다 실 매핑). SUT 환경에서 plugin 배치 후 `curl http://localhost:38080/scene /camera /selection`로 실값 확인. PerspectiveCamera cast로 Fov 추출 여부 검증.
|
4. **Runner + engine-bridge sidecar 연결** — 시나리오 실행 종료 시점에 `/scene` `/camera` `/selection` 을 한 번 더 스냅샷해 sidecar JSON으로 베이스라인에 포함. golden file의 의미적(semantic) 차원 확보.
|
||||||
5. ~~recorder Gap I-1~~ — **deferred**. UIA poller PoC 결과 본질적 한계 확인 (AutomationPeer 부재 컨트롤은 못 봄). generic WPF DLL injection 또는 AutomationPeer AI 부착 PoC가 선결.
|
5. ~~recorder Gap I-1~~ — **deferred**. UIA poller PoC 결과 본질적 한계 확인 (AutomationPeer 부재 컨트롤은 못 봄). generic WPF DLL injection 또는 AutomationPeer AI 부착 PoC가 선결.
|
||||||
|
|
||||||
## Follow-ups (non-blocking)
|
## Follow-ups (non-blocking)
|
||||||
|
|||||||
@@ -48,11 +48,12 @@
|
|||||||
| 2026-04-09 | HmEG 소스 survey + `docs/hmeg-api-survey.md` | Q1~Q7 식별, `HmegDirectStateProvider` 설계 근거 |
|
| 2026-04-09 | HmEG 소스 survey + `docs/hmeg-api-survey.md` | Q1~Q7 식별, `HmegDirectStateProvider` 설계 근거 |
|
||||||
| 2026-04-09 | **3-tier 분리 1단계 (incremental)** — `Recordingtest.Bridge.Abstractions` (Generic) + `Recordingtest.Hmeg.Bridge` (HmEG-aware) 신설, `HmegDirectStateProvider` + `ChainedEngineStateProvider` wire-up, 115 tests | commit `f6b6e74` |
|
| 2026-04-09 | **3-tier 분리 1단계 (incremental)** — `Recordingtest.Bridge.Abstractions` (Generic) + `Recordingtest.Hmeg.Bridge` (HmEG-aware) 신설, `HmegDirectStateProvider` + `ChainedEngineStateProvider` wire-up, 115 tests | commit `f6b6e74` |
|
||||||
| 2026-04-09 | **3-tier 분리 2단계** — `EgPlugin` → `Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost`, `EngineBridge` → `Hmeg/Recordingtest.Hmeg.Catalog`, `EngineBridge.Client` → `Hmeg/Recordingtest.Hmeg.Bridge.Client`, `EngineBridge.Probe` → `Hmeg/Recordingtest.Hmeg.Catalog.Probe`, 테스트 동일. `Recordingtest.Architecture.Tests` 11건 추가 (의존 그래프 강제). 126 tests | commit pending |
|
| 2026-04-09 | **3-tier 분리 2단계** — `EgPlugin` → `Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost`, `EngineBridge` → `Hmeg/Recordingtest.Hmeg.Catalog`, `EngineBridge.Client` → `Hmeg/Recordingtest.Hmeg.Bridge.Client`, `EngineBridge.Probe` → `Hmeg/Recordingtest.Hmeg.Catalog.Probe`, 테스트 동일. `Recordingtest.Architecture.Tests` 11건 추가 (의존 그래프 강제). 126 tests | commit pending |
|
||||||
| 2026-04-09 | **engine-bridge v3 EgBim 람다 wire-up** — `EditorPlugin` base 직접 사용: `RootSpace`, `View(EGViewport:HmEGViewport)`, `AppManager.FileManager.CurrentFile`. `HmegDirectStateProvider` 이제 실값 가능. `Editor02.HmEGAppManager.dll` 참조 추가. 라이브 검증 대기 | commit pending |
|
| 2026-04-09 | **engine-bridge v3 EgBim 람다 wire-up** — `EditorPlugin` base 직접 사용: `RootSpace`, `View(EGViewport:HmEGViewport)`, `AppManager.FileManager.CurrentFile`. `HmegDirectStateProvider` 이제 실값 가능. `Editor02.HmEGAppManager.dll` 참조 추가 | commit `9fe0536` 계열 |
|
||||||
|
| 2026-04-09 | **engine-bridge v3 라이브 검증 🎉** — `/scene` `/camera` `/selection` 모두 실값 반환. viewport 람다를 `AppManager.ViewportManager.FocusedViewport` 로 교체 (`View`는 `Run()` 안 타는 bridge plugin에서 항상 null). deploy-egbim-plugin.bat 추가 | commit pending |
|
||||||
|
|
||||||
## In progress
|
## In progress
|
||||||
|
|
||||||
- **engine-bridge v3 라이브 검증 대기** — 코드 쪽은 완료 (`EditorPlugin.RootSpace`/`View`/`AppManager.FileManager.CurrentFile` 실 매핑). 사용자 환경에서 `curl /scene /camera /selection`로 실값 확인 필요.
|
_(없음)_
|
||||||
|
|
||||||
## Follow-ups
|
## Follow-ups
|
||||||
|
|
||||||
|
|||||||
110
docs/history/2026-04-09_engine-bridge-v3-live-success.md
Normal file
110
docs/history/2026-04-09_engine-bridge-v3-live-success.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 2026-04-09 — engine-bridge v3 라이브 검증 성공
|
||||||
|
|
||||||
|
**이슈**: #10 follow-up (engine-bridge v3)
|
||||||
|
**소요 시간**: ~25분
|
||||||
|
**Context 사용량**: input ~40k / output ~8k tokens (Opus 4.6)
|
||||||
|
|
||||||
|
## 결과
|
||||||
|
|
||||||
|
🎉 **engine-bridge v3 라이브 end-to-end 성공**. EG-BIM Modeler 실 환경에서 4개 HTTP 엔드포인트 모두 실값 반환 확인.
|
||||||
|
|
||||||
|
```
|
||||||
|
PS> curl http://localhost:38080/health
|
||||||
|
{"status":"ok","port":38080}
|
||||||
|
|
||||||
|
PS> curl http://localhost:38080/scene
|
||||||
|
{"object_count":4,"document_path":"NewSpace0"}
|
||||||
|
|
||||||
|
PS> curl http://localhost:38080/camera
|
||||||
|
{"eye":[192.97503478492047,-328.523410937345,170.7271607015133],
|
||||||
|
"target":[33.03212842830112,-72.61476076675402,10.784254344893952],
|
||||||
|
"up":[0,0,1],
|
||||||
|
"fov":45}
|
||||||
|
|
||||||
|
PS> curl http://localhost:38080/selection
|
||||||
|
{"selected_ids":["ac0380a2-b493-4218-97b7-8017841151c5",
|
||||||
|
"d9a287ee-8d1c-4e32-b879-92575a346ddf"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
이것이 곧 golden-file 회귀 테스트의 결정성 신호 축이 된다.
|
||||||
|
|
||||||
|
## 과정
|
||||||
|
|
||||||
|
### 1. 배포 스크립트 `scripts/deploy-egbim-plugin.bat`
|
||||||
|
|
||||||
|
사용자가 더블클릭 한 번에 빌드→복사 가능하도록:
|
||||||
|
1. `EG-BIM Modeler.exe` 실행 여부 체크 (DLL 잠금 방지)
|
||||||
|
2. `dotnet build` Debug
|
||||||
|
3. 기존 `Plugins/Recordingtest.EgPlugin/` (legacy) + 기존 `Plugins/Recordingtest.Sut.EgBim.PluginHost/` 삭제
|
||||||
|
4. 3개 DLL(+PDB) 복사:
|
||||||
|
- `Recordingtest.Sut.EgBim.PluginHost.dll` (App-specific)
|
||||||
|
- `Recordingtest.Hmeg.Bridge.dll` (HmEG-aware)
|
||||||
|
- `Recordingtest.Bridge.Abstractions.dll` (Generic)
|
||||||
|
5. 배포 목록 + curl 명령 안내
|
||||||
|
|
||||||
|
`HmEG.dll` / `Editor*.dll`은 SUT에 이미 있으므로 복사 안 함.
|
||||||
|
|
||||||
|
### 2. 1차 라이브 결과 (camera 실패)
|
||||||
|
|
||||||
|
```
|
||||||
|
/scene → object_count=4 ✅
|
||||||
|
/camera → eye/target/up 전부 (0,0,0) ❌
|
||||||
|
/selection → 2 GUID ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
`/scene` 이 작동했으므로 `AppManager.ViewportManager.RootSpace` 경로는 OK. 즉 `spaceProvider` 정상.
|
||||||
|
|
||||||
|
`/camera` 는 default fallback. 원인: `HmEgBridgePlugin`의 `viewportProvider = () => this.View`에서 `View`가 항상 null.
|
||||||
|
|
||||||
|
### 3. 원인 분석
|
||||||
|
|
||||||
|
`EditorPlugin.View` 는 **플러그인이 `Run()` 될 때만 주입**되는 값. 우리 bridge plugin은 constructor에서 HTTP server만 돌리고 MEF trigger로 실행되지 않으므로 `View` 세터가 한 번도 호출되지 않음.
|
||||||
|
|
||||||
|
반면 `AppManager` 는 `TriggerStateService.AppManager` 를 통해 얻어지며 SUT 전역 상태라 즉시 접근 가능. 그래서 `RootSpace` 는 동작하지만 `View` 만 null이었던 것.
|
||||||
|
|
||||||
|
### 4. Fix — ViewportManager.FocusedViewport 경유
|
||||||
|
|
||||||
|
`D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\SubManager\ViewportManager.cs` (read-only) 확인:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public HashSet<EGViewport> Viewports { get; set; }
|
||||||
|
public EGViewport FocusedViewport { get; ... }
|
||||||
|
public ObservableCollection<HmModel> SelectedModels { get; ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
`FocusedViewport` 는 활성 뷰포트를 직접 노출. `viewportProvider` 를 다음으로 교체:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var vm = AppManager?.ViewportManager;
|
||||||
|
if (vm is null) return null;
|
||||||
|
var focused = vm.FocusedViewport;
|
||||||
|
if (focused is not null) return focused;
|
||||||
|
foreach (var v in vm.Viewports) if (v is not null) return v;
|
||||||
|
return null;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 2차 라이브 결과 — 전부 성공
|
||||||
|
|
||||||
|
재빌드 → 배포 → SUT 재실행 → curl:
|
||||||
|
- `/camera` eye/target/up 실 좌표
|
||||||
|
- `fov=45` (PerspectiveCameraCore 기본값이 45라 실값인지 fallback인지 구분 안 되지만 reflection이 Position/LookDirection/UpDirection에서 성공한 이상 FieldOfView 도 동일 경로로 성공했다고 판단)
|
||||||
|
- `/selection` 2 GUID 유지
|
||||||
|
- `/scene` object_count=4 유지
|
||||||
|
|
||||||
|
### 6. 부수 관찰
|
||||||
|
|
||||||
|
- `document_path="NewSpace0"` — `FileManager.CurrentFile` 이 빈 파일에 대해 Space 이름을 반환하는 듯. 저장된 `.hmeg` 파일일 때 진짜 경로 확인 필요 (follow-up).
|
||||||
|
- `HmegDirectStateProvider` 의 Selection walk (Space.Children 재귀 + `ISelectable.IsSelected`) 가 잘 동작. 대안 `ViewportManager.SelectedModels` 로 단순화 가능하나 현재 walk 방식이 결정적이고 테스트 친화적이라 그대로 유지.
|
||||||
|
|
||||||
|
## 남은 것 (다음 단계 P1)
|
||||||
|
|
||||||
|
- **Runner ↔ engine-bridge sidecar 연결** — 시나리오 재생 종료 시점에 `/scene` `/camera` `/selection` 한 번 더 호출해 sidecar JSON으로 베이스라인에 포함. `.received.json` 정규화 규칙 추가. 이게 진짜 golden-file 회귀 파이프라인의 마지막 조각.
|
||||||
|
- `document_path` 의 unsaved vs saved 문서 케이스 확인
|
||||||
|
- (선택) `SelectionManager.SelectedModels` 직접 사용 옵션 비교
|
||||||
|
|
||||||
|
## 관련
|
||||||
|
|
||||||
|
- `src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/HmEgBridgePlugin.cs` (viewportProvider 교체)
|
||||||
|
- `scripts/deploy-egbim-plugin.bat` (신규 — 배포 자동화)
|
||||||
|
- `src/Hmeg/Recordingtest.Hmeg.Bridge/HmegDirectStateProvider.cs` (reflection 그대로 재사용)
|
||||||
|
- `docs/hmeg-api-survey.md` (Q1~Q7 완성)
|
||||||
85
scripts/deploy-egbim-plugin.bat
Normal file
85
scripts/deploy-egbim-plugin.bat
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
@echo off
|
||||||
|
REM Deploy Recordingtest.Sut.EgBim.PluginHost into the local EG-BIM Modeler
|
||||||
|
REM Plugins folder. Build first, then copy the plugin + HmEG-aware + generic
|
||||||
|
REM bridge DLLs side-by-side so MEF discovery picks them up as one plugin.
|
||||||
|
REM
|
||||||
|
REM Usage: double-click, or: scripts\deploy-egbim-plugin.bat
|
||||||
|
|
||||||
|
setlocal enableextensions
|
||||||
|
cd /d "%~dp0.."
|
||||||
|
|
||||||
|
set "CSPROJ=src\Sut\EgBim\Recordingtest.Sut.EgBim.PluginHost\Recordingtest.Sut.EgBim.PluginHost.csproj"
|
||||||
|
set "OUTDIR=src\Sut\EgBim\Recordingtest.Sut.EgBim.PluginHost\bin\Debug\net8.0-windows"
|
||||||
|
set "DEST=EG-BIM Modeler\Plugins\Recordingtest.Sut.EgBim.PluginHost"
|
||||||
|
|
||||||
|
echo ============================================================
|
||||||
|
echo [1/4] Safety check — is EG-BIM Modeler running?
|
||||||
|
echo ============================================================
|
||||||
|
tasklist /FI "IMAGENAME eq EG-BIM Modeler.exe" 2>nul | find /I "EG-BIM Modeler.exe" >nul
|
||||||
|
if not errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [WARN] EG-BIM Modeler is running. Close it before deploying
|
||||||
|
echo to avoid locked plugin DLLs.
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ============================================================
|
||||||
|
echo [2/4] Build Recordingtest.Sut.EgBim.PluginHost (Debug)
|
||||||
|
echo ============================================================
|
||||||
|
dotnet build "%CSPROJ%" -c Debug -nologo -v q
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [ERROR] Build failed. Aborting deploy.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ============================================================
|
||||||
|
echo [3/4] Purge legacy and fresh-copy plugin folder
|
||||||
|
echo ============================================================
|
||||||
|
if exist "EG-BIM Modeler\Plugins\Recordingtest.EgPlugin" (
|
||||||
|
echo Removing legacy Recordingtest.EgPlugin folder...
|
||||||
|
rmdir /s /q "EG-BIM Modeler\Plugins\Recordingtest.EgPlugin"
|
||||||
|
)
|
||||||
|
if exist "%DEST%" (
|
||||||
|
echo Cleaning existing %DEST%...
|
||||||
|
rmdir /s /q "%DEST%"
|
||||||
|
)
|
||||||
|
mkdir "%DEST%"
|
||||||
|
|
||||||
|
REM Copy plugin entry + HmEG-aware provider + generic contract.
|
||||||
|
REM Intentionally *not* copying HmEG.dll / Editor*.dll — those are already
|
||||||
|
REM provided by the SUT at runtime.
|
||||||
|
copy /y "%OUTDIR%\Recordingtest.Sut.EgBim.PluginHost.dll" "%DEST%\" >nul || goto :copy_fail
|
||||||
|
copy /y "%OUTDIR%\Recordingtest.Hmeg.Bridge.dll" "%DEST%\" >nul || goto :copy_fail
|
||||||
|
copy /y "%OUTDIR%\Recordingtest.Bridge.Abstractions.dll" "%DEST%\" >nul || goto :copy_fail
|
||||||
|
|
||||||
|
REM Optional PDBs for in-SUT debugging (ignore if missing)
|
||||||
|
copy /y "%OUTDIR%\Recordingtest.Sut.EgBim.PluginHost.pdb" "%DEST%\" >nul 2>&1
|
||||||
|
copy /y "%OUTDIR%\Recordingtest.Hmeg.Bridge.pdb" "%DEST%\" >nul 2>&1
|
||||||
|
copy /y "%OUTDIR%\Recordingtest.Bridge.Abstractions.pdb" "%DEST%\" >nul 2>&1
|
||||||
|
|
||||||
|
echo ============================================================
|
||||||
|
echo [4/4] Deployed files:
|
||||||
|
echo ============================================================
|
||||||
|
dir /b "%DEST%"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo OK — Recordingtest.Sut.EgBim.PluginHost deployed.
|
||||||
|
echo Next: launch EG-BIM Modeler, then in a shell:
|
||||||
|
echo curl http://localhost:38080/health
|
||||||
|
echo curl http://localhost:38080/scene
|
||||||
|
echo curl http://localhost:38080/camera
|
||||||
|
echo curl http://localhost:38080/selection
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:copy_fail
|
||||||
|
echo.
|
||||||
|
echo [ERROR] Copy failed. Check that the build output exists:
|
||||||
|
echo %OUTDIR%
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
@@ -73,14 +73,25 @@ public sealed class HmEgBridgePlugin : EditorPlugin, IDisposable
|
|||||||
|
|
||||||
Func<HmEGViewport?> viewportProvider = () =>
|
Func<HmEGViewport?> viewportProvider = () =>
|
||||||
{
|
{
|
||||||
|
// EditorPlugin.View is only populated when the plugin is actually
|
||||||
|
// Run() by a user command; our bridge plugin lives as a long-
|
||||||
|
// running HTTP server and never runs a trigger, so View stays
|
||||||
|
// null. Instead pull the active viewport from the global
|
||||||
|
// ViewportManager, preferring FocusedViewport, then falling back
|
||||||
|
// to any registered viewport. EGViewport implements HmEGViewport.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// EGViewport implements HmEGViewport; the base class exposes it
|
var vm = AppManager?.ViewportManager;
|
||||||
// as EGViewport on the Obsolete View property. We catch and
|
if (vm is null) return null;
|
||||||
// return null to survive the obsolete warning at runtime.
|
var focused = vm.FocusedViewport;
|
||||||
#pragma warning disable CS0618 // Obsolete API on EditorPlugin.View
|
if (focused is not null) return focused;
|
||||||
return View;
|
var any = vm.Viewports;
|
||||||
#pragma warning restore CS0618
|
if (any is null) return null;
|
||||||
|
foreach (var v in any)
|
||||||
|
{
|
||||||
|
if (v is not null) return v;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
catch { return null; }
|
catch { return null; }
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user