# 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 소스 사본을 보관하지 **않는다**. 본 문서는 외부 소스에 대한 인터페이스 추출 메모일 뿐. 코드 발췌도 시그니처/주석 수준으로만 인용한다.