# HmEG Public API Survey (engine-bridge v3 source) > 본 문서는 `D:\GiteaAll\HmEngine\HmEG\HmEG` 소스를 read-only로 훑어 정리한 결과다. > 목적: `HmEgDirectStateProvider`와 SUT-side bridge 추가 엔드포인트(`/focus`, `/hit-test`, `/command`) 설계 근거. > **수정 금지** — 본 폴더는 우리 저장소 바깥의 SUT 소스다. 참조만. ## 식별 마커 — `` HmEG는 `` XML doc 태그로 **공식 공개 surface**를 표시한다. v3에서 우리가 의존할 모든 멤버는 이 태그가 붙은 것만 사용한다. (난독화/리네이밍 시 해당 마커가 안전 목록 역할을 할 가능성이 큼.) 검색 (read-only): ``` grep -rn 'public-hmeg-api' D:\GiteaAll\HmEngine\HmEG\HmEG ``` ## 핵심 타입 (확인 완료) ### 1. `ModelBase` — 모든 모델 엔티티의 추상 base 경로: `Model\Scene\ModelData\ModelBase.cs` | 멤버 | 타입 | 비고 | |---|---|---| | `Uid` | `Guid` | **결정성 핵심** — 영구 고유 ID. golden file에 그대로 쓸 수 있음 | | `Name` | `string` | 사용자 보이는 이름 | | `ModelType` | `ModelType` (enum) | Mesh / Line / 빌보드 등 | | `GeoType` | `HmGEntityType` | HmGeometry 공통 | | `Tag` | `object` | 사용자 커스텀 | | `Label` | `string` | dwg export XData용 | | `ModelMatrix` | `HmMatrix3D` | 변환 행렬 | | `ItemsChanged` | `event EventHandler` | 자식 변경 알림 — wait_for 후보 | ### 2. `Space : ModelBase` — 문서 컨테이너 (= "Space" 트리의 루트) 경로: `Model\Scene\ModelData\Space.cs`, `Space.Functions.cs` | 멤버 | 타입 | 비고 | |---|---|---| | `Children` | `EgObservableFastList` | **scene 트리 자식 노드** (= 객체 리스트) | | `ItemsCount` | `int` | **`/scene` 의 ObjectCount 가 직접 매핑** | | `Viewports` | `List` | 이 Space에 연결된 뷰포트들 | | `SplitRow` / `SplitCol` | `int` | 뷰포트 분할 | | `EnableGroupSelection` | `bool` | | | `Add(ModelBase)` / `DeleteModel(...)` / `AddSpace(...)` / `Clear()` | mutator | **read-only로만 사용** | | `ImportFileModels` / `ImportInstancingModels` | event | 파일 import 알림 — wait_for 후보 | ### 3. `HmModel : ModelBase` — 실제 형상 객체 경로: `Model\Scene\ModelData\HmModel.cs` | 멤버 | 타입 | 비고 | |---|---|---| | `GEntity` | `HmGEntity` | HmGeometry 공통 | | `EGgEntity` | `EgObject` | EG geometry 객체 | | `BlockName` | `string` | | | `LayerName` | `string` | | | `LineTypeName` | `string` | | | `LineTypeScale` | `double` | | | `AttributeReferences` | `HmAttributeReferenceCollection` | | | `MouseEnter` / `MouseLeave` / `MouseDown` | event | **마우스 hit-test 결과 통지** — recorder에서 element 식별에 사용 가능 | ### 4. `HmEGViewport` (interface) — 뷰포트 공개 인터페이스 경로: `Interface\IHmEGViewport.cs` (namespace `HmEG`) | 멤버 | 타입 | 비고 | |---|---|---| | `CameraCore` | `CameraCore` (`Model\Camera\CameraCore.cs`) | **카메라 진입점** | | `Renderables` | `IEnumerable` | 3D 씬 그래프 루트 노드 enumerable | | `D2DRenderables` | `IEnumerable` | 2D 노드 | | `RenderHost` | `IRenderHost` | | | `EffectsManager` | `IEffectsManager` | | | `ViewportRectangle` | `EGRectangleI` | 픽셀 단위 | | `Attach(IRenderHost)` / `Detach()` | mutator | | | `Update(TimeSpan)` / `InvalidateRender(...)` / `InvalidateSceneGraph(...)` | mutator | | ### 5. `IHmCamera` — 카메라 표준 인터페이스 경로: `Interface\IHmCamera.cs` | 멤버 | 타입 | 매핑 | |---|---|---| | `Position` | `HmVector3D` | `CameraSnapshot.Eye` | | `LookDirection` | `HmVector3D` | `Eye + LookDir = Target` | | `UpDirection` | `HmVector3D` | `CameraSnapshot.Up` | | `CreateLeftHandSystem` | `bool` | 좌표계 정규화 시 필요 | | `CreateViewMatrix(HmMatrix3D)` | method | | | `CreateProjectionMatrix(double aspectRatio)` | method | fov는 별도 추출 (PerspectiveCamera에) | > **주의**: `IHmCamera`에는 `Fov`/`FieldOfView`가 없다. `PerspectiveCamera : ProjectionCamera : CameraCore` 쪽에 있을 것으로 추정. v3 작성 시 `CameraCore` → `PerspectiveCamera` cast로 fov를 꺼낸다. ### 6. `ISelectable` — 노드 선택 상태 경로: `Interface\Interfaces.cs:235` ```csharp public interface ISelectable { bool IsSelected { get; set; } } ``` **중앙 집중 selection 리스트는 HmEG core에 없음.** SelectedIds를 얻으려면 Space 트리를 walk하면서 `ISelectable.IsSelected == true`인 노드를 모아야 한다. 또는 SUT 측 AppManager가 selection 리스트를 따로 들고 있을 수 있음 — **확인 필요**. ### 7. `HmSceneNode : MaterialGeometryNode, IDynamicReflectable, ISelectable` 경로: `Model\Scene\HmSceneNode.cs` 씬 그래프의 실제 렌더링 노드. `IsSelected`를 가짐. `Renderables`에서 흘러나옴. ### 8. `HmEG.IPlugin` (interface) 경로: `PlugIns\IPlugin.cs` ```csharp public interface IPlugin { string Name { get; } EGViewport View { get; set; } // ← 플러그인에 직접 주입되는 뷰포트 bool RethrowException { get; set; } object Run(params object[] args); } ``` **중요**: HmEG가 플러그인을 로드할 때 `View`를 직접 set 해준다. 즉 플러그인은 따로 Space/AppManager를 찾아갈 필요 없이 **`this.View`로 즉시 뷰포트에 도달**한다. 거기서 `CameraCore`, `Renderables` (씬 노드 enumerable) 모두 접근 가능. `Renderables`를 walk하면서 `ISelectable.IsSelected`로 선택된 노드 추출. > 본 저장소의 `HmEgBridgePlugin`은 `EditorPlugin` (SUT-side `Editor03.PluginInterface`) 베이스를 쓴다. `EditorPlugin`이 내부적으로 `HmEG.IPlugin.View`를 set 해주는지, 아니면 다른 경로(예: `AppManager`)를 통해 Space에 접근하는지는 **`Editor03.PluginInterface.dll` 디컴파일 또는 SUT-side 소스가 있어야 확정 가능**. ## 아직 모르는 것 (확인 대기) | | 항목 | 어디서 찾아야 하는가 | |---|---|---| | Q1 | "활성 Space" 진입점 — `EGViewport.Space`? `AppManager.Instance.ActiveSpace`? | `Editor03.PluginInterface` / SUT-side AppManager | | Q2 | "활성 Viewport" 가 여러 개일 때 어느 것이 active 인가 | 동일 | | Q3 | 중앙 selection 리스트 (예: `AppManager.Selection`) 또는 selection-changed 이벤트 | 동일 | | Q4 | 명령 (Command) 생명주기 이벤트 — `CommandStarted` / `CommandFinished` 같은 것 | 동일 (`Editor.AppManager.AppModeManager` 후보) | | Q5 | `PerspectiveCamera`에서 `FieldOfView` 정확한 프로퍼티 이름 | `Model\Camera\PerspectiveCamera.cs` (read-only로 한 번만 더 확인하면 됨) | | Q6 | 문서 파일 경로 — 저장 후의 `*.hmeg` 경로 보유처 | `Editor03.PluginInterface` 또는 `AppManager.ActiveDocument` | | Q7 | `EGViewport` 와 `HmEGViewport` 관계 — `EGViewport`가 후자를 구현? 아니면 별개 SUT-side 클래스? | `Editor03.PluginInterface` | ## v3 구현 방향 (Direct Provider) ```csharp // src/Recordingtest.EgPlugin/HmEgDirectStateProvider.cs public sealed class HmEgDirectStateProvider : IEngineStateProvider { private readonly Func _getViewport; // plugin이 주입 private readonly Func _getSpace; // 동일 public IReadOnlyList GetSelectedIds() { var sp = _getSpace(); if (sp is null) return Array.Empty(); var ids = new List(); Walk(sp, ids); return ids; static void Walk(ModelBase node, List ids) { if (node is ISelectable s && s.IsSelected) ids.Add(node.Uid.ToString()); if (node is Space space) foreach (var child in space.Children) Walk(child, ids); } } public CameraSnapshot GetCamera() { var vp = _getViewport(); if (vp is null) return Default; var cam = vp.CameraCore; // CameraCore → IHmCamera 캐스트, 또는 Position/Look/Up 직접 접근 // PerspectiveCamera 캐스트로 FOV ... } public SceneSnapshot GetScene() { var sp = _getSpace(); return new SceneSnapshot( sp?.ItemsCount ?? 0, DocumentPathFromSomewhere()); // Q6 } public bool GetRenderComplete() => true; // 후속 작업 } ``` `_getViewport` / `_getSpace` 람다는 `HmEgBridgePlugin`이 자기 환경(`EditorPlugin`이 노출하는 진입점)에서 캡처해 넘긴다. 람다 형태로 두면 다음과 같은 이점이 있다: - 플러그인 base 클래스가 진입점을 어떻게 노출하는지가 바뀌어도 v3 provider는 영향 없음 - 단위 테스트는 fake 람다를 넘겨서 검증 ## SUT-side bridge 추가 엔드포인트 (Gap I 우회) `BridgeHttpServer` / `StateRouter`에 추가: | 엔드포인트 | 응답 | 사용처 | |---|---|---| | `GET /focus` | `{"path": "...", "type": "...", "name": "..."}` | recorder가 key_down 시 polling. `Keyboard.FocusedElement` (WPF)를 Dispatcher 위에서 호출 | | `GET /hit-test?x=&y=` | `{"hit": "HmModel#guid", "type": "...", "name": "..."}` | recorder가 click 시 호출. Space/Renderables walk + `VisualTreeHelper.HitTest` + (있으면) HmEG의 pick API | | `GET /command` | `{"running": "BOX", "phase": "awaiting_first_corner"}` | player의 wait_for. Q4 이벤트 구독 결과 캐시 | 이건 engine-bridge v3 본 contract와 별도 contract 권장 (`/contract sut-side-bridge`). ## 다음 액션 1. **engine-bridge-v3.md 계약 갱신** — reflection 항목 제거, 위 타입/멤버 이름으로 D2/D3/D4 고정 2. **Q1~Q7 확인** — 사용자가 SUT-side(Editor03.PluginInterface 또는 AppManager) 소스/경로 알려주면 read-only로 1~2회 더 확인 3. Q1/Q3 채워지면 `HmEgDirectStateProvider` 구현 + 플러그인 wire-up 4. CI fallback — 기존 `ReflectionEngineStateProvider` + `IAppManagerAccessor`는 fake-friendly 형태로 유지 (HmEG 어셈블리 없는 단위 테스트 환경에서 빌드/테스트 가능해야 함) 5. 라이브 검증 → curl 로 `/state` 확인 → 보정 → 커밋 ## 라이선스 / 위생 본 저장소는 HmEG 소스 사본을 보관하지 **않는다**. 본 문서는 외부 소스에 대한 인터페이스 추출 메모일 뿐. 코드 발췌도 시그니처/주석 수준으로만 인용한다.