using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Windows; using devDept.Eyeshot; using devDept.Eyeshot.Control; using devDept.Eyeshot.Entities; using devDept.Geometry; using WpfEngineeringSketch.Models.Scene; using WpfEngineeringSketch.Tools3D; using WpfEngineeringSketch.ViewModels; namespace WpfEngineeringSketch.Rendering { public static class SceneBinder { // Map SceneObject -> Eyeshot Entity private static readonly Dictionary _objectMap = new Dictionary(); private static readonly Dictionary _entityMap = new Dictionary(); // Flag to prevent infinite loops during two-way updates private static bool _isSyncing = false; public static SceneObject? GetSceneObjectFromEntity(Entity entity) { if (_entityMap.TryGetValue(entity, out var obj)) return obj; return null; } public static readonly DependencyProperty ViewModelProperty = DependencyProperty.RegisterAttached( "ViewModel", typeof(MainViewModel), typeof(SceneBinder), typeof(SceneBinder).GetProperty("ViewModel") != null ? new PropertyMetadata(null) : new PropertyMetadata(null, OnViewModelChanged)); public static MainViewModel? GetViewModel(DependencyObject obj) { return (MainViewModel?)obj.GetValue(ViewModelProperty); } public static void SetViewModel(DependencyObject obj, MainViewModel value) { obj.SetValue(ViewModelProperty, value); } private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is Design design) { if (e.OldValue is MainViewModel oldVm) { design.MouseDown -= OnMouseDown; design.MouseMove -= OnMouseMove; design.MouseUp -= OnMouseUp; design.SelectionChanged -= OnSelectionChanged; oldVm.PropertyChanged -= (s, args) => OnVmPropertyChanged(s, args, design); } if (e.NewValue is MainViewModel newVm) { design.MouseDown += OnMouseDown; design.MouseMove += OnMouseMove; design.MouseUp += OnMouseUp; design.SelectionChanged += OnSelectionChanged; // Listen to tool changes newVm.PropertyChanged += (s, args) => OnVmPropertyChanged(s, args, design); // Initial setup UpdateDesignMode(design, newVm); } } } private static void OnVmPropertyChanged(object? sender, PropertyChangedEventArgs e, Design design) { if (e.PropertyName == nameof(MainViewModel.CurrentTool)) { if (sender is MainViewModel vm) { UpdateDesignMode(design, vm); } } } private static void UpdateDesignMode(Design design, MainViewModel vm) { if (vm.CurrentTool is SelectTool3D) { // Enable standard selection and Gizmo design.ActionMode = actionType.SelectVisibleByPick; // Enable ObjectManipulator // design.ObjectManipulator.Visible = true; // design.ObjectManipulator.Enabled = true; if (design.Viewports.Count > 0) { design.CoordinateSystemIcon.Visible = true; design.ViewCubeIcon.Visible = true; } } else { // Disable standard selection for other tools design.ActionMode = actionType.None; // design.ObjectManipulator.Visible = false; // design.ObjectManipulator.Enabled = false; // design.ObjectManipulator.Cancel(); design.Entities.ClearSelection(); } design.Invalidate(); } private static void OnSelectionChanged(object? sender, EventArgs e) { if (_isSyncing) return; if (sender is Design design) { var vm = GetViewModel(design); if (vm == null) return; _isSyncing = true; try { // Sync Eyeshot selection -> ViewModel // We need to match vm.SelectedObjects with design.Entities.Where(x => x.Selected) var selectedEntities = design.Entities.Where(x => x.Selected).ToList(); var selectedObjects = selectedEntities.Select(ent => _entityMap.TryGetValue(ent, out var obj) ? obj : null).Where(o => o != null).Cast().ToList(); // Update IsSelected on all objects foreach (var obj in vm.Objects) { bool shouldBeSelected = selectedObjects.Contains(obj); if (obj.IsSelected != shouldBeSelected) { obj.IsSelected = shouldBeSelected; } } // Update SelectedObjects collection vm.SelectedObjects.Clear(); foreach(var obj in selectedObjects) vm.SelectedObjects.Add(obj); // Update primary selection vm.SelectedObject = selectedObjects.LastOrDefault(); } finally { _isSyncing = false; } } } private static void SyncTransforms(Design design) { if (_isSyncing) return; _isSyncing = true; try { foreach(var ent in design.Entities) { if (_entityMap.TryGetValue(ent, out var obj)) { // Sync generic position if possible var center = (ent.BoxMin + ent.BoxMax) / 2; // This is an approximation/placeholder logic for sync } } } finally { _isSyncing = false; } } private static void OnMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (sender is Design design) { var vm = GetViewModel(design); vm?.CurrentTool?.OnMouseDown(vm, design, e); } } private static void OnMouseMove(object sender, System.Windows.Input.MouseEventArgs e) { if (sender is Design design) { var vm = GetViewModel(design); vm?.CurrentTool?.OnMouseMove(vm, design, e); } } private static void OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (sender is Design design) { var vm = GetViewModel(design); vm?.CurrentTool?.OnMouseUp(vm, design, e); // Try sync transform SyncTransforms(design); } } public static readonly DependencyProperty ObjectsProperty = DependencyProperty.RegisterAttached( "Objects", typeof(ObservableCollection), typeof(SceneBinder), new PropertyMetadata(null, OnObjectsChanged)); public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.RegisterAttached( "SelectedObject", typeof(SceneObject), typeof(SceneBinder), new PropertyMetadata(null, OnSelectedObjectChanged)); public static ObservableCollection? GetObjects(DependencyObject obj) => (ObservableCollection?)obj.GetValue(ObjectsProperty); public static void SetObjects(DependencyObject obj, ObservableCollection value) => obj.SetValue(ObjectsProperty, value); public static SceneObject? GetSelectedObject(DependencyObject obj) => (SceneObject?)obj.GetValue(SelectedObjectProperty); public static void SetSelectedObject(DependencyObject obj, SceneObject value) => obj.SetValue(SelectedObjectProperty, value); private static void OnObjectsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is Design design) { if (e.OldValue is ObservableCollection oldColl) { oldColl.CollectionChanged -= (s, args) => OnCollectionChanged(s, args, design); design.Entities.Clear(); _objectMap.Clear(); _entityMap.Clear(); } if (e.NewValue is ObservableCollection newColl) { newColl.CollectionChanged += (s, args) => OnCollectionChanged(s, args, design); foreach (var obj in newColl) AddEntity(design, obj); } design.Invalidate(); } } private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, Design design) { if (e.Action == NotifyCollectionChangedAction.Add) { foreach (SceneObject item in e.NewItems!) AddEntity(design, item); } else if (e.Action == NotifyCollectionChangedAction.Remove) { foreach (SceneObject item in e.OldItems!) RemoveEntity(design, item); } else if (e.Action == NotifyCollectionChangedAction.Reset) { design.Entities.Clear(); _objectMap.Clear(); _entityMap.Clear(); } design.Invalidate(); } private static void AddEntity(Design design, SceneObject obj) { Entity? entity = null; switch (obj.Type) { case BlockType.Cube: entity = Mesh.CreateBox(1, 1, 1); entity.Color = System.Drawing.Color.Blue; break; case BlockType.Sphere: entity = Mesh.CreateSphere(0.5, 16, 16); entity.Color = System.Drawing.Color.Green; break; case BlockType.Cylinder: entity = Mesh.CreateCylinder(0.5, 1, 16); entity.Color = System.Drawing.Color.Orange; break; case BlockType.Column: entity = Mesh.CreateBox(0.3, 0.3, 3); entity.Color = System.Drawing.Color.Gray; break; case BlockType.Beam: entity = Mesh.CreateBox(5, 0.3, 0.5); // Length X, Width Y, Height Z entity.Color = System.Drawing.Color.SteelBlue; break; case BlockType.CMUWall: entity = Mesh.CreateBox(0.2, 2, 3); entity.Color = System.Drawing.Color.LightGray; break; case BlockType.GridLine: entity = Mesh.CreateCylinder(0.05, 10, 8); entity.Color = System.Drawing.Color.DarkGray; break; case BlockType.UIIndicator: entity = Mesh.CreateBox(0.3, 0.3, 0.3); entity.Color = System.Drawing.Color.LightGreen; break; case BlockType.Connection: entity = Mesh.CreateSphere(0.2, 10, 10); entity.Color = System.Drawing.Color.Red; break; } if (entity != null) { ApplyTransformToEntity(entity, obj); // Bind properties obj.PropertyChanged += (s, e) => { if (_isSyncing) return; // Ignore property changes if we originated them if (e.PropertyName == nameof(SceneObject.Position) || e.PropertyName == nameof(SceneObject.Rotation) || e.PropertyName == nameof(SceneObject.Scale)) { if (_objectMap.TryGetValue(obj, out var existing)) { design.Entities.Remove(existing); _objectMap.Remove(obj); _entityMap.Remove(existing); } AddEntity(design, obj); design.Invalidate(); } else if (e.PropertyName == nameof(SceneObject.IsSelected)) { if (_objectMap.TryGetValue(obj, out var ent)) { ent.Selected = obj.IsSelected; } design.Invalidate(); } }; design.Entities.Add(entity); _objectMap[obj] = entity; _entityMap[entity] = obj; } } private static void RemoveEntity(Design design, SceneObject obj) { if (_objectMap.TryGetValue(obj, out var entity)) { design.Entities.Remove(entity); _objectMap.Remove(obj); _entityMap.Remove(entity); } } private static void ApplyTransformToEntity(Entity entity, SceneObject obj) { try { entity.Scale(obj.Scale.X, obj.Scale.Y, obj.Scale.Z); entity.Rotate(devDept.Geometry.Utility.DegToRad(obj.Rotation.X), devDept.Geometry.Vector3D.AxisX); entity.Rotate(devDept.Geometry.Utility.DegToRad(obj.Rotation.Y), devDept.Geometry.Vector3D.AxisY); entity.Rotate(devDept.Geometry.Utility.DegToRad(obj.Rotation.Z), devDept.Geometry.Vector3D.AxisZ); entity.Translate(obj.Position.X, obj.Position.Y, obj.Position.Z); } catch { } } private static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (_isSyncing) return; if (d is Design design && e.NewValue is SceneObject obj && _objectMap.TryGetValue(obj, out var entity)) { design.Entities.ClearSelection(); entity.Selected = true; design.Invalidate(); } else if (d is Design d2 && e.NewValue == null) { d2.Entities.ClearSelection(); d2.Invalidate(); } } } }