feat(ui): Always-on-top gizmo overlay; remove customer STEP files (v0.3.1)
- Gizmo now renders in a separate transparent Viewport3D overlaid on the main view, so the manipulator is never occluded inside solid parts and is always grabbable from any angle - Overlay camera follows the main camera (position/dir/FOV/near-far) on every change; raw Viewport3D lets empty-area clicks pass through so orbit / zoom / measurement still work; IsHitTestVisible bound to GizmoEnabled. Mouse-up handled on the overlay (handledEventsToo), with _gizmoBakePending guarding against double-bake - Window title shows version (STPViewer V0.3.1) - Remove the two test STEP models from the repo (customer part numbers) -> moved to For_AI/ (gitignored); add *.stp/*.step to .gitignore so CAD files never get committed again - Docs updated (ARCHITECTURE Phase 11, CLAUDE.md, README) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,3 +13,7 @@ secret/*
|
|||||||
|
|
||||||
# AI 協作素材不入 git
|
# AI 協作素材不入 git
|
||||||
For_AI/
|
For_AI/
|
||||||
|
|
||||||
|
# 客戶 CAD 料號檔不入 git(避免外流);測試模型放 For_AI/
|
||||||
|
*.stp
|
||||||
|
*.step
|
||||||
|
|||||||
+9
-2
@@ -89,10 +89,9 @@ STPViewer/
|
|||||||
│ ├── run-STPViewer.ps1.example
|
│ ├── run-STPViewer.ps1.example
|
||||||
│ └── run-STPViewer.sh.example
|
│ └── run-STPViewer.sh.example
|
||||||
│
|
│
|
||||||
├── For_AI/ # 🚫 gitignored — AI 協作素材
|
├── For_AI/ # 🚫 gitignored — AI 協作素材 + 測試模型 *.stp(客戶料號不入 git)
|
||||||
│
|
│
|
||||||
├── STPViewer.sln
|
├── STPViewer.sln
|
||||||
├── *.stp # 根目錄現有測試模型(Amphenol connector)
|
|
||||||
│
|
│
|
||||||
└── src/
|
└── src/
|
||||||
└── STPViewer/
|
└── STPViewer/
|
||||||
@@ -335,6 +334,14 @@ NuGet 相依(自動還原):`CADability`、`HelixToolkit.Wpf`、`CommunityT
|
|||||||
|
|
||||||
**驗收:** 拖 X 箭頭零件僅沿世界 X 移動(與視角無關);拖環任意角度旋轉;放開後量測/干涉與顯示位置一致
|
**驗收:** 拖 X 箭頭零件僅沿世界 X 移動(與視角無關);拖環任意角度旋轉;放開後量測/干涉與顯示位置一致
|
||||||
|
|
||||||
|
### Phase 11 — Gizmo always-on-top 疊圖層(工作量:M)
|
||||||
|
- [x] 操作器移到獨立透明 `Viewport3D`(`gizmoOverlay`)疊在主視窗上 → 不在主場景,**永不被實體遮擋、永遠可抓**
|
||||||
|
- [x] `_overlayCamera` 在主相機 `Changed` 時同步(Position/方向/FOV/near-far);同尺寸 → 投影一致、操作器精準貼合
|
||||||
|
- [x] raw Viewport3D 空白處不吃滑鼠 → 穿透回主視窗(orbit/縮放/量測不受影響);`IsHitTestVisible` 綁 `GizmoEnabled`
|
||||||
|
- [x] 放開事件改掛 overlay(`handledEventsToo`);`_gizmoBakePending` 防同次重複烘焙
|
||||||
|
|
||||||
|
**驗收:** 操作器從任何視角都浮在零件上可見可抓;開啟操作器時右鍵 orbit / 量測仍正常(穿透)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Future Extensions(下一輪)
|
## Future Extensions(下一輪)
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ dotnet run --project src/STPViewer
|
|||||||
dotnet publish src/STPViewer -c Release -o publish/STPViewer
|
dotnet publish src/STPViewer -c Release -o publish/STPViewer
|
||||||
```
|
```
|
||||||
|
|
||||||
測試模型:根目錄 `test.stp`(Amphenol RA PHD 座端,小檔)與 `Amphenol PHD to PHD Cable 10201248 1.stp`(39MB 大組件,效能測試用)
|
測試模型放 `For_AI/`(gitignored,**客戶料號不入 git**):`For_AI/test.stp`(Amphenol RA PHD 座端,小檔)與
|
||||||
|
`For_AI/Amphenol PHD to PHD Cable 10201248 1.stp`(39MB 大組件,效能測試用)。`*.stp`/`*.step` 已全域 gitignore
|
||||||
|
|
||||||
## 開發慣例
|
## 開發慣例
|
||||||
|
|
||||||
@@ -50,6 +51,10 @@ dotnet publish src/STPViewer -c Release -o publish/STPViewer
|
|||||||
代理 Transform 變更(`DependencyPropertyDescriptor.AddValueChanged`)即時套到目標 BodyVisual(暫時);
|
代理 Transform 變更(`DependencyPropertyDescriptor.AddValueChanged`)即時套到目標 BodyVisual(暫時);
|
||||||
**放開滑鼠才 `TransformRoot` 烘焙** — MouseUp 被 manipulator 標 handled,MainWindow 用 `AddHandler(..., handledEventsToo: true)` 才收得到。
|
**放開滑鼠才 `TransformRoot` 烘焙** — MouseUp 被 manipulator 標 handled,MainWindow 用 `AddHandler(..., handledEventsToo: true)` 才收得到。
|
||||||
烘焙用 `Dispatcher.BeginInvoke` 延後到 manipulator 自身事件處理完,避免 reentrancy;`_gizmoBaking` 旗標防 Transform 歸零的回呼重入
|
烘焙用 `Dispatcher.BeginInvoke` 延後到 manipulator 自身事件處理完,避免 reentrancy;`_gizmoBaking` 旗標防 Transform 歸零的回呼重入
|
||||||
|
- Gizmo always-on-top(v0.3.1):操作器放在獨立透明 `Viewport3D`(`gizmoOverlay`)疊在主視窗上,**不在主場景所以永不被實體遮擋**。
|
||||||
|
`_overlayCamera` 在主 `Camera.Changed` 時同步主相機(Position/方向/FOV/near-far);raw Viewport3D 空白處不吃滑鼠 → 穿透回主視窗(orbit/量測正常);
|
||||||
|
`IsHitTestVisible` 綁 `GizmoEnabled`。Manipulator 是 `UIElement3D`、用 `GetViewport3D()` 抓所在層相機,故在疊圖層用同步相機運作。
|
||||||
|
放開事件靠 overlay 的 `AddHandler(MouseLeftButtonUp, handledEventsToo:true)`(manipulator 會標 handled),`_gizmoBakePending` 防同次重複烘焙
|
||||||
- 干涉/面距/對齊等運算在背景執行緒;`Freeze()` 幾何後才跨執行緒
|
- 干涉/面距/對齊等運算在背景執行緒;`Freeze()` 幾何後才跨執行緒
|
||||||
- 匯入在背景執行緒;`Freeze()` 幾何後才跨執行緒
|
- 匯入在背景執行緒;`Freeze()` 幾何後才跨執行緒
|
||||||
- Commit 格式:Conventional Commits(`feat:` / `fix:` / `docs:` …)
|
- Commit 格式:Conventional Commits(`feat:` / `fix:` / `docs:` …)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ STP/STEP 3D 檢視器(Windows 桌面程式,C# .NET 8 WPF)— 多檔匯入
|
|||||||
| 🎯 三點 | 來源檔 3 特徵點 + 目標檔 3 對應點 → 旋轉+平移一次貼合(方向不同也能對) |
|
| 🎯 三點 | 來源檔 3 特徵點 + 目標檔 3 對應點 → 旋轉+平移一次貼合(方向不同也能對) |
|
||||||
- **旋轉**:樹面板選檔案 + 工具列 ↻X/↻Y/↻Z 繞中心 +90°(擺正方向用,連按累加)
|
- **旋轉**:樹面板選檔案 + 工具列 ↻X/↻Y/↻Z 繞中心 +90°(擺正方向用,連按累加)
|
||||||
- **拖曳** 🖐:手形游標模式,左鍵按住零件沿螢幕平面拖動、放開定位(粗定位用;精確貼合用對齊);右鍵轉視角不受影響
|
- **拖曳** 🖐:手形游標模式,左鍵按住零件沿螢幕平面拖動、放開定位(粗定位用;精確貼合用對齊);右鍵轉視角不受影響
|
||||||
- **操作器** ⊹:樹面板選檔案 → 顯示 XYZ 三色箭頭 + 旋轉環(Fusion 360 風格);拖箭頭沿該軸移動(與視角無關,適合沿插合軸推進)、拖環繞軸轉任意角度,放開即定位
|
- **操作器** ⊹:樹面板選檔案 → 顯示 XYZ 三色箭頭 + 旋轉環(Fusion 360 風格);拖箭頭沿該軸移動(與視角無關,適合沿插合軸推進)、拖環繞軸轉任意角度,放開即定位。操作器永遠浮在最上層、不被實體遮擋
|
||||||
- **干涉檢查** 🧩:勾選剛好 2 個可見檔案 → 相交時顯示紅色干涉交線 + 相交三角形對數;無相交時回報最小間隙 gap(gap≈0 即為配合 match,共面貼合不算干涉)
|
- **干涉檢查** 🧩:勾選剛好 2 個可見檔案 → 相交時顯示紅色干涉交線 + 相交三角形對數;無相交時回報最小間隙 gap(gap≈0 即為配合 match,共面貼合不算干涉)
|
||||||
- **剖面**:✂ 開關 + X/Y/Z 軸 + 位置滑桿 + 反向;CPU 網格裁切,原始幾何保留(量測不受影響)
|
- **剖面**:✂ 開關 + X/Y/Z 軸 + 位置滑桿 + 反向;CPU 網格裁切,原始幾何保留(量測不受影響)
|
||||||
- **單位**:mm ⇄ inch 一鍵切換,既有量測(清單與 3D 標籤)即時換算
|
- **單位**:mm ⇄ inch 一鍵切換,既有量測(清單與 3D 標籤)即時換算
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xmlns:h="http://helix-toolkit.org/wpf"
|
xmlns:h="http://helix-toolkit.org/wpf"
|
||||||
xmlns:conv="clr-namespace:STPViewer.Converters"
|
xmlns:conv="clr-namespace:STPViewer.Converters"
|
||||||
xmlns:vm="clr-namespace:STPViewer.ViewModels"
|
xmlns:vm="clr-namespace:STPViewer.ViewModels"
|
||||||
Title="STPViewer — STP/STEP 3D 檢視器"
|
Title="STPViewer V0.3.1 — STP/STEP 3D 檢視器"
|
||||||
Height="820" Width="1320"
|
Height="820" Width="1320"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver">
|
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver">
|
||||||
@@ -206,6 +206,11 @@
|
|||||||
<h:DefaultLights />
|
<h:DefaultLights />
|
||||||
</h:HelixViewport3D>
|
</h:HelixViewport3D>
|
||||||
|
|
||||||
|
<!-- 操作器疊圖層:透明 Viewport3D 疊在主視窗上、永遠最上層。
|
||||||
|
只在操作器開啟時 IsHitTestVisible(否則空白處滑鼠才會穿透回主視窗) -->
|
||||||
|
<Viewport3D x:Name="gizmoOverlay" Grid.Column="1"
|
||||||
|
IsHitTestVisible="{Binding GizmoEnabled}" />
|
||||||
|
|
||||||
<!-- ─── 量測結果面板 ─── -->
|
<!-- ─── 量測結果面板 ─── -->
|
||||||
<GroupBox Grid.Column="2" Header="量測結果" Margin="4">
|
<GroupBox Grid.Column="2" Header="量測結果" Margin="4">
|
||||||
<ListBox ItemsSource="{Binding Measurements}" HorizontalContentAlignment="Stretch"
|
<ListBox ItemsSource="{Binding Measurements}" HorizontalContentAlignment="Stretch"
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = _vm;
|
DataContext = _vm;
|
||||||
_vm.Attach(viewport);
|
_vm.Attach(viewport);
|
||||||
|
_vm.AttachOverlay(gizmoOverlay);
|
||||||
Loaded += MainWindow_Loaded;
|
Loaded += MainWindow_Loaded;
|
||||||
|
|
||||||
// Gizmo 操作器的放開事件:manipulator 會把 MouseUp 標成 handled,需 handledEventsToo 才收得到
|
// Gizmo 操作器放在疊圖層;manipulator 會把 MouseUp 標 handled,需 handledEventsToo 才收得到
|
||||||
viewport.AddHandler(MouseLeftButtonUpEvent,
|
gizmoOverlay.AddHandler(MouseLeftButtonUpEvent,
|
||||||
new MouseButtonEventHandler((_, _) => _vm.OnGizmoMouseUp()), handledEventsToo: true);
|
new MouseButtonEventHandler((_, _) => _vm.OnGizmoMouseUp()), handledEventsToo: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,8 +112,33 @@ public partial class MainViewModel : ObservableObject
|
|||||||
viewport.Camera.Changed += (_, _) => OnCameraMoved();
|
viewport.Camera.Changed += (_, _) => OnCameraMoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>注入操作器疊圖層(透明 Viewport3D,疊在主視窗上、永遠最上層)</summary>
|
||||||
|
public void AttachOverlay(System.Windows.Controls.Viewport3D overlay)
|
||||||
|
{
|
||||||
|
_overlayViewport = overlay;
|
||||||
|
_overlayCamera = new PerspectiveCamera();
|
||||||
|
overlay.Camera = _overlayCamera;
|
||||||
|
// 操作器材質需打光(manipulator 用 DiffuseMaterial)
|
||||||
|
overlay.Children.Add(new ModelVisual3D { Content = new AmbientLight(Color.FromRgb(0x80, 0x80, 0x80)) });
|
||||||
|
overlay.Children.Add(new ModelVisual3D { Content = new DirectionalLight(Colors.White, new Vector3D(-1, -1, -3)) });
|
||||||
|
SyncOverlayCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>疊圖層相機跟隨主相機(每次主相機變更時呼叫,讓操作器疊在正確螢幕位置)</summary>
|
||||||
|
private void SyncOverlayCamera()
|
||||||
|
{
|
||||||
|
if (_overlayCamera is null || _viewport?.Camera is not ProjectionCamera src) return;
|
||||||
|
_overlayCamera.Position = src.Position;
|
||||||
|
_overlayCamera.LookDirection = src.LookDirection;
|
||||||
|
_overlayCamera.UpDirection = src.UpDirection;
|
||||||
|
_overlayCamera.NearPlaneDistance = src.NearPlaneDistance;
|
||||||
|
_overlayCamera.FarPlaneDistance = src.FarPlaneDistance;
|
||||||
|
if (src is PerspectiveCamera p) _overlayCamera.FieldOfView = p.FieldOfView;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnCameraMoved()
|
private void OnCameraMoved()
|
||||||
{
|
{
|
||||||
|
SyncOverlayCamera(); // 操作器疊圖層跟著主相機
|
||||||
if (!_edgesSuspended) { _edgesSuspended = true; SetEdgesActive(false); }
|
if (!_edgesSuspended) { _edgesSuspended = true; SetEdgesActive(false); }
|
||||||
_interactionTimer.Stop();
|
_interactionTimer.Stop();
|
||||||
_interactionTimer.Start();
|
_interactionTimer.Start();
|
||||||
@@ -966,6 +991,12 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private ModelNodeViewModel? _gizmoTarget;
|
private ModelNodeViewModel? _gizmoTarget;
|
||||||
private bool _gizmoDragActive; // 拖動中(邊線已暫停)
|
private bool _gizmoDragActive; // 拖動中(邊線已暫停)
|
||||||
private bool _gizmoBaking; // 烘焙中,忽略 Transform 變更回呼
|
private bool _gizmoBaking; // 烘焙中,忽略 Transform 變更回呼
|
||||||
|
private bool _gizmoBakePending; // 已排程烘焙,防同一次放開重複觸發
|
||||||
|
|
||||||
|
// 操作器疊圖層:另一個透明 Viewport3D 疊在主視窗上、相機同步、只放操作器 →
|
||||||
|
// 操作器不在主場景,永不被實體遮擋(always-on-top);空白處滑鼠穿透回主視窗
|
||||||
|
private System.Windows.Controls.Viewport3D? _overlayViewport;
|
||||||
|
private PerspectiveCamera? _overlayCamera;
|
||||||
|
|
||||||
partial void OnGizmoEnabledChanged(bool value) => UpdateGizmo();
|
partial void OnGizmoEnabledChanged(bool value) => UpdateGizmo();
|
||||||
|
|
||||||
@@ -977,7 +1008,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private void UpdateGizmo()
|
private void UpdateGizmo()
|
||||||
{
|
{
|
||||||
RemoveGizmo();
|
RemoveGizmo();
|
||||||
if (!GizmoEnabled || _viewport is null) return;
|
if (!GizmoEnabled || _viewport is null || _overlayViewport is null) return;
|
||||||
|
|
||||||
ModelNodeViewModel? root = SelectedNode is not null ? RootContaining(SelectedNode)
|
ModelNodeViewModel? root = SelectedNode is not null ? RootContaining(SelectedNode)
|
||||||
: Roots.Count == 1 ? Roots[0] : null;
|
: Roots.Count == 1 ? Roots[0] : null;
|
||||||
@@ -988,13 +1019,14 @@ public partial class MainViewModel : ObservableObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_gizmoTarget = root;
|
_gizmoTarget = root;
|
||||||
|
SyncOverlayCamera(); // 操作器出現前先對齊相機
|
||||||
|
|
||||||
Rect3D b = root.Bounds;
|
Rect3D b = root.Bounds;
|
||||||
var center = new Point3D(b.X + b.SizeX / 2, b.Y + b.SizeY / 2, b.Z + b.SizeZ / 2);
|
var center = new Point3D(b.X + b.SizeX / 2, b.Y + b.SizeY / 2, b.Z + b.SizeZ / 2);
|
||||||
double diag = new Vector3D(b.SizeX, b.SizeY, b.SizeZ).Length;
|
double diag = new Vector3D(b.SizeX, b.SizeY, b.SizeZ).Length;
|
||||||
|
|
||||||
_gizmoProxy = new ModelVisual3D();
|
_gizmoProxy = new ModelVisual3D();
|
||||||
_viewport.Children.Add(_gizmoProxy);
|
_overlayViewport.Children.Add(_gizmoProxy);
|
||||||
System.ComponentModel.DependencyPropertyDescriptor
|
System.ComponentModel.DependencyPropertyDescriptor
|
||||||
.FromProperty(Visual3D.TransformProperty, typeof(Visual3D))
|
.FromProperty(Visual3D.TransformProperty, typeof(Visual3D))
|
||||||
.AddValueChanged(_gizmoProxy, GizmoTransformChanged);
|
.AddValueChanged(_gizmoProxy, GizmoTransformChanged);
|
||||||
@@ -1004,7 +1036,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
man.Position = center;
|
man.Position = center;
|
||||||
man.Bind(_gizmoProxy);
|
man.Bind(_gizmoProxy);
|
||||||
_gizmoParts.Add(man);
|
_gizmoParts.Add(man);
|
||||||
_viewport!.Children.Add(man);
|
_overlayViewport!.Children.Add(man); // 放疊圖層 → 永不被實體遮擋
|
||||||
}
|
}
|
||||||
// 平移箭頭(X 紅 / Y 綠 / Z 藍 — 業界慣例)
|
// 平移箭頭(X 紅 / Y 綠 / Z 藍 — 業界慣例)
|
||||||
AddPart(new TranslateManipulator { Direction = new Vector3D(1, 0, 0), Color = Colors.Red, Length = diag * 0.22, Diameter = diag * 0.016 });
|
AddPart(new TranslateManipulator { Direction = new Vector3D(1, 0, 0), Color = Colors.Red, Length = diag * 0.22, Diameter = diag * 0.016 });
|
||||||
@@ -1025,12 +1057,12 @@ public partial class MainViewModel : ObservableObject
|
|||||||
System.ComponentModel.DependencyPropertyDescriptor
|
System.ComponentModel.DependencyPropertyDescriptor
|
||||||
.FromProperty(Visual3D.TransformProperty, typeof(Visual3D))
|
.FromProperty(Visual3D.TransformProperty, typeof(Visual3D))
|
||||||
.RemoveValueChanged(_gizmoProxy, GizmoTransformChanged);
|
.RemoveValueChanged(_gizmoProxy, GizmoTransformChanged);
|
||||||
_viewport?.Children.Remove(_gizmoProxy);
|
_overlayViewport?.Children.Remove(_gizmoProxy);
|
||||||
}
|
}
|
||||||
foreach (HelixToolkit.Wpf.Manipulator man in _gizmoParts)
|
foreach (HelixToolkit.Wpf.Manipulator man in _gizmoParts)
|
||||||
{
|
{
|
||||||
man.UnBind();
|
man.UnBind();
|
||||||
_viewport?.Children.Remove(man);
|
_overlayViewport?.Children.Remove(man);
|
||||||
}
|
}
|
||||||
_gizmoParts.Clear();
|
_gizmoParts.Clear();
|
||||||
_gizmoProxy = null;
|
_gizmoProxy = null;
|
||||||
@@ -1067,17 +1099,19 @@ public partial class MainViewModel : ObservableObject
|
|||||||
/// <summary>滑鼠放開(MainWindow 轉發,handledEventsToo):把累積變換烘進 B-rep 並重置操作器</summary>
|
/// <summary>滑鼠放開(MainWindow 轉發,handledEventsToo):把累積變換烘進 B-rep 並重置操作器</summary>
|
||||||
public void OnGizmoMouseUp()
|
public void OnGizmoMouseUp()
|
||||||
{
|
{
|
||||||
if (_gizmoTarget is null || _gizmoProxy is null) return;
|
if (_gizmoTarget is null || _gizmoProxy is null || _gizmoBakePending) return;
|
||||||
Matrix3D m = _gizmoProxy.Transform?.Value ?? Matrix3D.Identity;
|
Matrix3D m = _gizmoProxy.Transform?.Value ?? Matrix3D.Identity;
|
||||||
if (m.IsIdentity) return; // 只是點一下、沒拖操作器 → 不烘焙
|
if (m.IsIdentity) return; // 只是點一下、沒拖操作器 → 不烘焙
|
||||||
|
|
||||||
// 延後到 manipulator 自身的 mouse-up 處理(釋放捕捉等)完成後再烘焙,避免在其事件中改動視覺樹造成 reentrancy
|
// 延後到 manipulator 自身的 mouse-up 處理(釋放捕捉等)完成後再烘焙,避免在其事件中改動視覺樹造成 reentrancy
|
||||||
|
_gizmoBakePending = true;
|
||||||
_viewport?.Dispatcher.BeginInvoke(new Action(() => BakeGizmo(m)),
|
_viewport?.Dispatcher.BeginInvoke(new Action(() => BakeGizmo(m)),
|
||||||
System.Windows.Threading.DispatcherPriority.Background);
|
System.Windows.Threading.DispatcherPriority.Background);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BakeGizmo(Matrix3D m)
|
private void BakeGizmo(Matrix3D m)
|
||||||
{
|
{
|
||||||
|
_gizmoBakePending = false;
|
||||||
if (_gizmoTarget is null) return;
|
if (_gizmoTarget is null) return;
|
||||||
ModelNodeViewModel root = _gizmoTarget;
|
ModelNodeViewModel root = _gizmoTarget;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user