From 4a3a97eb21c9089e3c58d265d1aa64462adac89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B0=D1=87=D0=BA=D0=BE=D0=B2=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D0=BC?= Date: Wed, 24 Jan 2024 10:41:41 +0300 Subject: [PATCH 01/11] navisworks connector --- src/OpenProject.Navisworks/Application/App.cs | 67 ++ .../Command/AddInManagerCommand.cs | 107 +++ .../Command/AddinManagerBase.cs | 159 ++++ .../Command/DocpanelCommand.cs | 35 + .../Data/NavisworksUtils.cs | 198 +++++ .../Data/ProjectPositionWrapper.cs | 63 ++ .../DockPanel/AddinManagerPane.cs | 47 ++ .../DockPanel/DockPaneBase.cs | 20 + .../Entry/ActiveViewChangedZoomListener.cs | 65 ++ src/OpenProject.Navisworks/Entry/CmdMain.cs | 26 + .../Entry/CmdMainSettings.cs | 22 + .../Entry/IpcHandler.cs | 180 +++++ .../Entry/OpenViewpointEventHandler.cs | 729 ++++++++++++++++++ .../Entry/RibbonButtonClickHandler.cs | 286 +++++++ .../Extensions/BcfExtensions.cs | 354 +++++++++ .../NavisWorksDocumentExtensions.cs | 245 ++++++ src/OpenProject.Navisworks/Model/Addin.cs | 95 +++ src/OpenProject.Navisworks/Model/AddinItem.cs | 137 ++++ .../Model/AddinItemComparer.cs | 12 + src/OpenProject.Navisworks/Model/AddinType.cs | 10 + src/OpenProject.Navisworks/Model/Addins.cs | 101 +++ .../Model/AssemLoader.cs | 311 ++++++++ .../Model/BitmapSourceConverter.cs | 69 ++ .../Model/CodeListener.cs | 27 + .../Model/DefaultSetting.cs | 50 ++ src/OpenProject.Navisworks/Model/FileUtils.cs | 240 ++++++ .../Model/FolderTooBigDialog.cs | 26 + .../Model/IAddinNode.cs | 8 + .../Model/IEConverter.cs | 25 + src/OpenProject.Navisworks/Model/IniFile.cs | 56 ++ .../Model/LogMessageString.cs | 33 + .../Model/ManifestFile.cs | 422 ++++++++++ .../Model/NavisAddin.cs | 27 + .../Model/NavisWorksWrapper.cs | 38 + .../Model/ProcessManager.cs | 53 ++ .../Model/StaticUtil.cs | 26 + .../Model/ViewModelBase.cs | 73 ++ .../Model/VisibilityMode.cs | 22 + .../OpenProject.Navisworks.csproj | 210 +++++ .../PackageContents.xml | 23 + .../Properties/Resources.Designer.cs | 63 ++ .../Properties/Resources.resx | 101 +++ .../Properties/Settings.Designer.cs | 26 + .../Properties/Settings.settings | 6 + .../Properties/launchSettings.json | 18 + .../Resource.Designer.cs | 165 ++++ src/OpenProject.Navisworks/Resource.resx | 154 ++++ .../Resources/BCFierIcon16x16.png | Bin 0 -> 831 bytes .../Resources/BCFierIcon32x32.png | Bin 0 -> 1874 bytes .../Resources/OpenProjectLogo16.png | Bin 0 -> 335 bytes .../Resources/OpenProjectLogo32.png | Bin 0 -> 651 bytes .../Resources/Settings32.png | Bin 0 -> 583 bytes src/OpenProject.Navisworks/Resources/dev.ico | Bin 0 -> 104966 bytes src/OpenProject.Navisworks/Resources/dev.png | Bin 0 -> 5378 bytes .../Resources/dev16x16.png | Bin 0 -> 575 bytes .../Resources/dev32x32.png | Bin 0 -> 1365 bytes .../Resources/folder.png | Bin 0 -> 664 bytes src/OpenProject.Navisworks/Resources/icon.ico | Bin 0 -> 32038 bytes .../Resources/lab16x16.png | Bin 0 -> 330 bytes .../Resources/lab32x32.png | Bin 0 -> 657 bytes .../Resources/openproject.ico | Bin 0 -> 67646 bytes .../Services/MessageHandler.cs | 36 + .../Services/StatusBarService.cs | 36 + .../View/AssemblyLoader.xaml | 87 +++ .../View/AssemblyLoader.xaml.cs | 78 ++ .../View/Control/ExtendedTreeView.cs | 35 + .../View/Control/LogControl.xaml | 86 +++ .../View/Control/LogControl.xaml.cs | 39 + .../View/Control/MouseDoubleClick.cs | 60 ++ .../View/Control/RelayCommand.cs | 82 ++ .../View/Control/VirtualToggleButton.cs | 262 +++++++ .../View/FrmAddInManager.xaml | 232 ++++++ .../View/FrmAddInManager.xaml.cs | 107 +++ .../ViewModel/AddInManagerViewModel.cs | 478 ++++++++++++ .../ViewModel/AddinManager.cs | 268 +++++++ .../ViewModel/AddinModel.cs | 75 ++ .../ViewModel/AddinsApplication.cs | 72 ++ .../ViewModel/AddinsCommand.cs | 75 ++ .../ViewModel/LogControlViewModel.cs | 199 +++++ .../en-US/AddinManagerRibbon.xaml | 77 ++ src/OpenProject.Navisworks/postbuild.ps1 | 105 +++ 81 files changed, 7719 insertions(+) create mode 100644 src/OpenProject.Navisworks/Application/App.cs create mode 100644 src/OpenProject.Navisworks/Command/AddInManagerCommand.cs create mode 100644 src/OpenProject.Navisworks/Command/AddinManagerBase.cs create mode 100644 src/OpenProject.Navisworks/Command/DocpanelCommand.cs create mode 100644 src/OpenProject.Navisworks/Data/NavisworksUtils.cs create mode 100644 src/OpenProject.Navisworks/Data/ProjectPositionWrapper.cs create mode 100644 src/OpenProject.Navisworks/DockPanel/AddinManagerPane.cs create mode 100644 src/OpenProject.Navisworks/DockPanel/DockPaneBase.cs create mode 100644 src/OpenProject.Navisworks/Entry/ActiveViewChangedZoomListener.cs create mode 100644 src/OpenProject.Navisworks/Entry/CmdMain.cs create mode 100644 src/OpenProject.Navisworks/Entry/CmdMainSettings.cs create mode 100644 src/OpenProject.Navisworks/Entry/IpcHandler.cs create mode 100644 src/OpenProject.Navisworks/Entry/OpenViewpointEventHandler.cs create mode 100644 src/OpenProject.Navisworks/Entry/RibbonButtonClickHandler.cs create mode 100644 src/OpenProject.Navisworks/Extensions/BcfExtensions.cs create mode 100644 src/OpenProject.Navisworks/Extensions/NavisWorksDocumentExtensions.cs create mode 100644 src/OpenProject.Navisworks/Model/Addin.cs create mode 100644 src/OpenProject.Navisworks/Model/AddinItem.cs create mode 100644 src/OpenProject.Navisworks/Model/AddinItemComparer.cs create mode 100644 src/OpenProject.Navisworks/Model/AddinType.cs create mode 100644 src/OpenProject.Navisworks/Model/Addins.cs create mode 100644 src/OpenProject.Navisworks/Model/AssemLoader.cs create mode 100644 src/OpenProject.Navisworks/Model/BitmapSourceConverter.cs create mode 100644 src/OpenProject.Navisworks/Model/CodeListener.cs create mode 100644 src/OpenProject.Navisworks/Model/DefaultSetting.cs create mode 100644 src/OpenProject.Navisworks/Model/FileUtils.cs create mode 100644 src/OpenProject.Navisworks/Model/FolderTooBigDialog.cs create mode 100644 src/OpenProject.Navisworks/Model/IAddinNode.cs create mode 100644 src/OpenProject.Navisworks/Model/IEConverter.cs create mode 100644 src/OpenProject.Navisworks/Model/IniFile.cs create mode 100644 src/OpenProject.Navisworks/Model/LogMessageString.cs create mode 100644 src/OpenProject.Navisworks/Model/ManifestFile.cs create mode 100644 src/OpenProject.Navisworks/Model/NavisAddin.cs create mode 100644 src/OpenProject.Navisworks/Model/NavisWorksWrapper.cs create mode 100644 src/OpenProject.Navisworks/Model/ProcessManager.cs create mode 100644 src/OpenProject.Navisworks/Model/StaticUtil.cs create mode 100644 src/OpenProject.Navisworks/Model/ViewModelBase.cs create mode 100644 src/OpenProject.Navisworks/Model/VisibilityMode.cs create mode 100644 src/OpenProject.Navisworks/OpenProject.Navisworks.csproj create mode 100644 src/OpenProject.Navisworks/PackageContents.xml create mode 100644 src/OpenProject.Navisworks/Properties/Resources.Designer.cs create mode 100644 src/OpenProject.Navisworks/Properties/Resources.resx create mode 100644 src/OpenProject.Navisworks/Properties/Settings.Designer.cs create mode 100644 src/OpenProject.Navisworks/Properties/Settings.settings create mode 100644 src/OpenProject.Navisworks/Properties/launchSettings.json create mode 100644 src/OpenProject.Navisworks/Resource.Designer.cs create mode 100644 src/OpenProject.Navisworks/Resource.resx create mode 100644 src/OpenProject.Navisworks/Resources/BCFierIcon16x16.png create mode 100644 src/OpenProject.Navisworks/Resources/BCFierIcon32x32.png create mode 100644 src/OpenProject.Navisworks/Resources/OpenProjectLogo16.png create mode 100644 src/OpenProject.Navisworks/Resources/OpenProjectLogo32.png create mode 100644 src/OpenProject.Navisworks/Resources/Settings32.png create mode 100644 src/OpenProject.Navisworks/Resources/dev.ico create mode 100644 src/OpenProject.Navisworks/Resources/dev.png create mode 100644 src/OpenProject.Navisworks/Resources/dev16x16.png create mode 100644 src/OpenProject.Navisworks/Resources/dev32x32.png create mode 100644 src/OpenProject.Navisworks/Resources/folder.png create mode 100644 src/OpenProject.Navisworks/Resources/icon.ico create mode 100644 src/OpenProject.Navisworks/Resources/lab16x16.png create mode 100644 src/OpenProject.Navisworks/Resources/lab32x32.png create mode 100644 src/OpenProject.Navisworks/Resources/openproject.ico create mode 100644 src/OpenProject.Navisworks/Services/MessageHandler.cs create mode 100644 src/OpenProject.Navisworks/Services/StatusBarService.cs create mode 100644 src/OpenProject.Navisworks/View/AssemblyLoader.xaml create mode 100644 src/OpenProject.Navisworks/View/AssemblyLoader.xaml.cs create mode 100644 src/OpenProject.Navisworks/View/Control/ExtendedTreeView.cs create mode 100644 src/OpenProject.Navisworks/View/Control/LogControl.xaml create mode 100644 src/OpenProject.Navisworks/View/Control/LogControl.xaml.cs create mode 100644 src/OpenProject.Navisworks/View/Control/MouseDoubleClick.cs create mode 100644 src/OpenProject.Navisworks/View/Control/RelayCommand.cs create mode 100644 src/OpenProject.Navisworks/View/Control/VirtualToggleButton.cs create mode 100644 src/OpenProject.Navisworks/View/FrmAddInManager.xaml create mode 100644 src/OpenProject.Navisworks/View/FrmAddInManager.xaml.cs create mode 100644 src/OpenProject.Navisworks/ViewModel/AddInManagerViewModel.cs create mode 100644 src/OpenProject.Navisworks/ViewModel/AddinManager.cs create mode 100644 src/OpenProject.Navisworks/ViewModel/AddinModel.cs create mode 100644 src/OpenProject.Navisworks/ViewModel/AddinsApplication.cs create mode 100644 src/OpenProject.Navisworks/ViewModel/AddinsCommand.cs create mode 100644 src/OpenProject.Navisworks/ViewModel/LogControlViewModel.cs create mode 100644 src/OpenProject.Navisworks/en-US/AddinManagerRibbon.xaml create mode 100644 src/OpenProject.Navisworks/postbuild.ps1 diff --git a/src/OpenProject.Navisworks/Application/App.cs b/src/OpenProject.Navisworks/Application/App.cs new file mode 100644 index 00000000..27c04bf5 --- /dev/null +++ b/src/OpenProject.Navisworks/Application/App.cs @@ -0,0 +1,67 @@ +using Autodesk.Navisworks.Api.Plugins; +using OpenProjectNavisworks.Command; +using OpenProjectNavisworks.Properties; +using Serilog; +using Test = OpenProjectNavisworks.Command.Test; +using OpenProject.Shared.Logging; +using System; + + +namespace OpenProjectNavisworks.Application; + +[Plugin("IDP", "PavelNedviga,TachkovMaksim", DisplayName = "IDP-ViewPoint")] +[RibbonLayout("AddinManagerRibbon.xaml")] +[RibbonTab("ID_AddinManager_TAB", DisplayName = "IDP")] +[Command("IDE_ButtonConnectOpenProject", + DisplayName = "IDP-ViewPoint", + Icon = "Resources\\OpenProjectLogo16.png", + LargeIcon = "Resources\\OpenProjectLogo32.png", + ToolTip = "Plugin IDP-ViewPoint")] + + +public class App : CommandHandlerPlugin +{ + public override int ExecuteCommand(string name, params string[] parameters) + { + + + // Buttons + switch (name) + { + case "ID_ButtonAddinManagerManual": + AddInManagerManual addInManagerManual = new AddInManagerManual(); + addInManagerManual.Execute(); + break; + case "ID_ButtonAddinManagerFaceless": + AddInManagerFaceLess addInManagerFaceless = new AddInManagerFaceLess(); + addInManagerFaceless.Execute(); + break; + case "ID_ButtonDockPanelCommand": + DockPanelCommand dockPanelCommand = new DockPanelCommand(); + dockPanelCommand.Execute(); + break; + case "ID_ButtonTest": + Test test = new Test(); + test.Execute(); + break; + case "IDE_ButtonConnectOpenProject": + ConnectOpenProject connOP = new ConnectOpenProject(); + connOP.Execute(); + break; + case "IDE_ButtonSettingsOpenProject": + ConnectSettingsOpenProject connSetOP = new ConnectSettingsOpenProject(); + connSetOP.Execute(); + break; + case "IDE_ButtonGuide": + Guide guide = new Guide(); + guide.Execute(); + break; + case "IDE_ButtonInfo": + Info info = new Info(); + info.Execute(); + break; + } + + return 0; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Command/AddInManagerCommand.cs b/src/OpenProject.Navisworks/Command/AddInManagerCommand.cs new file mode 100644 index 00000000..f8175682 --- /dev/null +++ b/src/OpenProject.Navisworks/Command/AddInManagerCommand.cs @@ -0,0 +1,107 @@ +using System.Data.Common; +using System.Diagnostics; +using System.IO; +using System.Windows; +using Autodesk.Navisworks.Api.Plugins; +using OpenProjectNavisworks.Model; +using OpenProjectNavisworks.Entry; +using MessageBox = System.Windows.MessageBox; + +namespace OpenProjectNavisworks.Command; + +public abstract class IAddinCommand +{ + public abstract int Action(params string[] parameters); + + public void Execute(params string[] parameters) + { + try + { + Action(parameters); + } + catch (Exception e) + { + MessageBox.Show(e.ToString()); + } + } +} +/// +/// Executer the command Addin Manager Manual +/// +public class AddInManagerManual : IAddinCommand +{ + public override int Action(params string[] parameters) + { + Debug.Listeners.Clear(); + Trace.Listeners.Clear(); + CodeListener codeListener = new CodeListener(); + Debug.Listeners.Add(codeListener); + return AddinManagerBase.Instance.ExecuteCommand(false, parameters); + } +} + +/// +/// Execute the command Addin Manager Faceless +/// +public class AddInManagerFaceLess : IAddinCommand +{ + public override int Action(params string[] parameters) + { + return AddinManagerBase.Instance.ExecuteCommand(true, parameters); + } +} + + +/// +/// Execute the command for connection to project +/// +public class ConnectOpenProject : IAddinCommand +{ + public override int Action(params string[] parameters) + { + Model.NavisworksWrapper.Document = Autodesk.Navisworks.Api.Application.ActiveDocument; + //Model.NavisworksWrapper.Document Autodesk.Navisworks.Api.Application; + return CmdMain.ExecuteCommand(true, parameters); + } +} + + +/// +/// Execute the command for connection settings to project +/// +public class ConnectSettingsOpenProject : IAddinCommand +{ + public override int Action(params string[] parameters) + { + Model.NavisworksWrapper.Document = Autodesk.Navisworks.Api.Application.ActiveDocument; + return CmdMainSettings.ExecuteCommand(true, parameters); + } +} + + +public class Test : IAddinCommand +{ + public override int Action(params string[] parameters) + { + MessageBox.Show("Hello World"); + return 0; + } +} + +public class Guide : IAddinCommand +{ + public override int Action(params string[] parameters) + { + Process.Start("http://books.ide-spb.com/books/autodesk-revit/page/idp-viewpoint"); + return 0; + } +} + +public class Info : IAddinCommand +{ + public override int Action(params string[] parameters) + { + Process.Start("https://gitlab.ide-spb.com/Nedviga.Pavel/openprojectnavisbcf/-/releases"); + return 0; + } +} diff --git a/src/OpenProject.Navisworks/Command/AddinManagerBase.cs b/src/OpenProject.Navisworks/Command/AddinManagerBase.cs new file mode 100644 index 00000000..ac62d085 --- /dev/null +++ b/src/OpenProject.Navisworks/Command/AddinManagerBase.cs @@ -0,0 +1,159 @@ +using System.IO; +using System.Windows; +using Autodesk.Navisworks.Api.Plugins; +using OpenProjectNavisworks.Model; +using OpenProjectNavisworks.View; +using OpenProjectNavisworks.ViewModel; +using MessageBox = System.Windows.MessageBox; + +namespace OpenProjectNavisworks.Command; + +public sealed class AddinManagerBase +{ + public int ExecuteCommand(bool faceless, params string[] parameters) + { + var vm = new AddInManagerViewModel(); + if (_activeCmd != null && faceless) + { + return RunActiveCommand(vm, parameters); + } + var FrmAddInManager = new FrmAddInManager(vm); + FrmAddInManager.WindowStartupLocation = WindowStartupLocation.CenterScreen; + FrmAddInManager.SetNavisAsWindowOwner(); + FrmAddInManager.ShowDialog(); + return 0; + } + + public string ActiveTempFolder + { + get => _activeTempFolder; + set => _activeTempFolder = value; + } + + public int RunActiveCommand(AddInManagerViewModel vm, params string[] parameters) + { + var filePath = _activeCmd.FilePath; + if (!File.Exists(filePath)) + { + MessageBox.Show("File not found: " + filePath,DefaultSetting.AppName, MessageBoxButton.OK, MessageBoxImage.Error); + return 0; + } + int result; + try + { + vm.AssemLoader.HookAssemblyResolve(); + var assembly = vm.AssemLoader.LoadAddinsToTempFolder(filePath, false); + if (null == assembly) + { + result = 0; + } + else + { + _activeTempFolder = vm.AssemLoader.TempFolder; + if (assembly.CreateInstance(_activeCmdItem.FullClassName) is not AddInPlugin AddInPlugin) + { + result = 0; + } + else + { + _activeEc = AddInPlugin; + return _activeEc.Execute(parameters); + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString()); + result = 0; + } + finally + { + vm.AssemLoader.UnhookAssemblyResolve(); + vm.AssemLoader.CopyGeneratedFilesBack(); + } + + return result; + } + + public static AddinManagerBase Instance + { + get + { + if (_instance == null) + { +#pragma warning disable RCS1059 // Avoid locking on publicly accessible instance. + lock (typeof(AddinManagerBase)) + { + if (_instance == null) + { + _instance = new AddinManagerBase(); + } + } +#pragma warning restore RCS1059 // Avoid locking on publicly accessible instance. + } + + return _instance; + } + } + + private AddinManagerBase() + { + _addinManager = new AddinManager(); + _activeCmd = null; + _activeCmdItem = null; + _activeApp = null; + _activeAppItem = null; + } + + public AddInPlugin ActiveEC + { + get => _activeEc; + set => _activeEc = value; + } + + public Addin ActiveCmd + { + get => _activeCmd; + set => _activeCmd = value; + } + + public AddinItem ActiveCmdItem + { + get => _activeCmdItem; + set => _activeCmdItem = value; + } + + public Addin ActiveApp + { + get => _activeApp; + set => _activeApp = value; + } + + public AddinItem ActiveAppItem + { + get => _activeAppItem; + set => _activeAppItem = value; + } + + public AddinManager AddinManager + { + get => _addinManager; + set => _addinManager = value; + } + + private string _activeTempFolder = string.Empty; + + private static volatile AddinManagerBase _instance; + + private AddInPlugin _activeEc; + + private Addin _activeCmd; + + private AddinItem _activeCmdItem; + + private Addin _activeApp; + + private AddinItem _activeAppItem; + + private AddinManager _addinManager; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Command/DocpanelCommand.cs b/src/OpenProject.Navisworks/Command/DocpanelCommand.cs new file mode 100644 index 00000000..02fb2180 --- /dev/null +++ b/src/OpenProject.Navisworks/Command/DocpanelCommand.cs @@ -0,0 +1,35 @@ +using Autodesk.Navisworks.Api.ApplicationParts; +using Autodesk.Navisworks.Api.Plugins; +using OpenProjectNavisworks.Model; + +namespace OpenProjectNavisworks.Command; + +public class DockPanelCommand : IAddinCommand +{ + public override int Action(params string[] parameters) + { + string PluginId = $"OpenProjectNavisworks.ShowHidePanel.ChuongMep"; + if (Autodesk.Navisworks.Api.Application.IsAutomated) + { + throw new InvalidOperationException("Invalid when running using Automation"); + } + ApplicationPlugins applicationPlugins = Autodesk.Navisworks.Api.Application.Plugins; + PluginRecord pr = applicationPlugins.FindPlugin(PluginId); + if (pr != null && pr is DockPanePluginRecord && pr.IsEnabled) + { + //check if it needs loading + if (pr.LoadedPlugin == null) + { + pr.LoadPlugin(); + } + + if (pr.LoadedPlugin is DockPanePlugin dpp) + { + //switch the Visible flag + dpp.Visible = !dpp.Visible; + } + } + + return 0; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Data/NavisworksUtils.cs b/src/OpenProject.Navisworks/Data/NavisworksUtils.cs new file mode 100644 index 00000000..5f00ac85 --- /dev/null +++ b/src/OpenProject.Navisworks/Data/NavisworksUtils.cs @@ -0,0 +1,198 @@ +using System; +//using Autodesk.Revit.DB; +using Autodesk.Navisworks.Api; +using OpenProject.Shared.Math3D; +using decMath = DecimalMath.DecimalEx; +using OpenProjectNavisworks.Model; + +namespace OpenProjectNavisworks.Data +{ + public static class NavisworksUtils + { + public static Dictionary ModelItems { get; set; } + public static Dictionary> ModelModelItems { get; set; } + public static IEnumerable HiddenModelItems { get; set; } + + /// + /// Converts a camera position according to a projects base position. + /// It takes a position in coordinates of the project base location and + /// transform them to global coordinates or does so in reverse, if the flag is set to true. + /// + /// The revit project base location + /// The source position + /// Default is false, if set to true, transformation is done from global coordinates to + /// revit project base location coordinates + /// The resulting position + public static Position TransformCameraPosition( + ProjectPositionWrapper projectBase, + Position position, + bool reverse = false) + { + var i = reverse ? -1 : 1; + + Vector3 translation = projectBase.GetTranslation() * i; + var rotation = i * projectBase.Angle; + + // do translation before rotation if we transform from global to local coordinates + Vector3 center = reverse + ? new Vector3( + position.Center.X + translation.X, + position.Center.Y + translation.Y, + position.Center.Z + translation.Z) + : position.Center; + + // rotation + var centerX = center.X * decMath.Cos(rotation) - center.Y * decMath.Sin(rotation); + var centerY = center.X * decMath.Sin(rotation) + center.Y * decMath.Cos(rotation); + + // do translation after rotation if we transform from local to global coordinates + Vector3 newCenter = reverse + ? new Vector3(centerX, centerY, center.Z) + : new Vector3(centerX + translation.X, centerY + translation.Y, center.Z + translation.Z); + + var forwardX = position.Forward.X * decMath.Cos(rotation) - + position.Forward.Y * decMath.Sin(rotation); + var forwardY = position.Forward.X * decMath.Sin(rotation) + + position.Forward.Y * decMath.Cos(rotation); + var newForward = new Vector3(forwardX, forwardY, position.Forward.Z); + + var upX = position.Up.X * decMath.Cos(rotation) - position.Up.Y * decMath.Sin(rotation); + var upY = position.Up.X * decMath.Sin(rotation) + position.Up.Y * decMath.Cos(rotation); + var newUp = new Vector3(upX, upY, position.Up.Z); + + return new Position(newCenter, newForward, newUp); + } + + /// + /// Converts a object into a + /// + /// The position object + /// the converted view orientation object + public static Tuple ToViewOrientation3D(this Position position) => + //new(position.Center.ToNavisworksPoint3D(), + // position.Up.ToNavisworksPoint3D(), + // position.Forward.ToNavisworksPoint3D()); + new(position.Center.ToNavisworksPoint3DWithoutUnitsConversion(), + position.Up.ToNavisworksPoint3DWithoutUnitsConversion(), + position.Forward.ToNavisworksPoint3DWithoutUnitsConversion()); + + /// + /// Converts a object into a + /// + /// The vector3 object + /// The Revit vector object + public static Point3D ToNavisworksPoint3D(this Vector3 vec) => + new ( + ToInternalAppUnit(Convert.ToDouble(vec.X)), + ToInternalAppUnit(Convert.ToDouble(vec.Y)), + ToInternalAppUnit(Convert.ToDouble(vec.Z))); + public static Point3D ToNavisworksPoint3DWithoutUnitsConversion(this Vector3 vec) => + new( + Convert.ToDouble(vec.X), + Convert.ToDouble(vec.Y), + Convert.ToDouble(vec.Z)); + + /// + /// Converts a object into a + /// + /// The vector3 object + /// The Revit vector object + public static Vector3 ToVector3(this Point3D vec) => new(vec.X.ToDecimal(), vec.Y.ToDecimal(), vec.Z.ToDecimal()); + + /// + /// Converts some basic revit view values to a view box height and a view box width. + /// The revit views are defined by coordinates in project space. + /// + /// The top right corner of the revit view. + /// The bottom left corner of the revit view. + /// The right direction of the revit view. + /// A tuple of the height and the width of the view box. + //public static (double viewBoxHeight, double viewBoxWidth) ConvertToViewBoxValues( + // Point3D topRight, Point3D bottomLeft, Point3D right) + //{ + // var diagonal = topRight.Subtract(bottomLeft); + // var distance = topRight.DistanceTo(bottomLeft); + // var angleBetweenBottomAndDiagonal = diagonal.Angle(right.ToVector3D()); + + // var height = distance * Math.Sin(angleBetweenBottomAndDiagonal); + // var width = distance * Math.Cos(angleBetweenBottomAndDiagonal); + + // return (height, width); + //} + + /// + /// Converts feet units to meters. Feet are the internal Revit units. + /// + /// Value in internal Revit units to be converted to meters + /// + public static double ToMeters(this double internalUnits) + { + return UnitConversion.ScaleFactor(OpenProjectNavisworks.Model.NavisworksWrapper.Document.Units/*Units.Meters*/, Units.Meters)* internalUnits; +//#if !(Version2021 || Version2022) +// return UnitUtils.ConvertFromInternalUnits(internalUnits, DisplayUnitType.DUT_METERS); +//#else +// return UnitUtils.ConvertFromInternalUnits(internalUnits, UnitTypeId.Meters); +//#endif + } + + /// + /// Converts meters units to feet. Feet are the internal Revit units. + /// + /// Value in feet to be converted to feet + /// + public static double ToInternalAppUnit(this double meters) + { + return meters*UnitConversion.ScaleFactor(Units.Meters, OpenProjectNavisworks.Model.NavisworksWrapper.Document.Units); +//#if Version2021 || Version2022 +// return UnitUtils.ConvertToInternalUnits(meters, UnitTypeId.Meters); +//#else +// return UnitUtils.ConvertToInternalUnits(meters, DisplayUnitType.DUT_METERS); +//#endif + } + + /// + /// Converts a vector containing values in feet units to meter. Feet are the internal Revit units. + /// + /// The vector with values in feet + /// The vector with values in meter + public static Vector3 ToMeters(this Vector3 vec) + { + var x = vec.X.IsFinite() ? Convert.ToDouble(vec.X).ToMeters().ToDecimal() : vec.X; + var y = vec.Y.IsFinite() ? Convert.ToDouble(vec.Y).ToMeters().ToDecimal() : vec.Y; + var z = vec.Z.IsFinite() ? Convert.ToDouble(vec.Z).ToMeters().ToDecimal() : vec.Z; + + return new Vector3(x, y, z); + } + + /// + /// Converts a vector containing values in meter units to feet. Feet are the internal Revit units. + /// + /// The vector with values in meter + /// The vector with values in feet + public static Vector3 ToInternalUnits(this Vector3 vec) + { + var x = vec.X.IsFinite() ? Convert.ToDouble(vec.X).ToInternalAppUnit().ToDecimal() : vec.X; + var y = vec.Y.IsFinite() ? Convert.ToDouble(vec.Y).ToInternalAppUnit().ToDecimal() : vec.Y; + var z = vec.Z.IsFinite() ? Convert.ToDouble(vec.Z).ToInternalAppUnit().ToDecimal() : vec.Z; + + return new Vector3(x, y, z); + } + + /// + /// Converts a position containing values in feet units to meter. Feet are the internal Revit units. + /// + /// The position with values in feet + /// The position with values in meter + public static Position ToMeters(this Position pos) => + new(pos.Center.ToMeters(), pos.Forward.ToMeters(), pos.Up.ToMeters()); + + /// + /// Converts a position containing values in meter units to feet. Feet are the internal Revit units. + /// + /// The position with values in meter + /// The position with values in feet + public static Position ToInternalUnits(this Position pos) => + //new(pos.Center.ToInternalUnits(), pos.Forward.ToInternalUnits(), pos.Up.ToInternalUnits()); + new(pos.Center.ToInternalUnits(), pos.Forward, pos.Up); + } +} diff --git a/src/OpenProject.Navisworks/Data/ProjectPositionWrapper.cs b/src/OpenProject.Navisworks/Data/ProjectPositionWrapper.cs new file mode 100644 index 00000000..9a5f3aa4 --- /dev/null +++ b/src/OpenProject.Navisworks/Data/ProjectPositionWrapper.cs @@ -0,0 +1,63 @@ +//using Autodesk.Revit.DB; +using OpenProject.Shared.Math3D; + +namespace OpenProjectNavisworks.Data +{ + /// + /// A immutable wrapper class around the + /// + public sealed class ProjectPositionWrapper + { + /// + /// The translation of the project into east-west, mostly translated into a translation into x-direction. + /// + public decimal EastWest { get; } + + /// + /// The translation of the project into north-south, mostly translated into a translation into y-direction. + /// + public decimal NorthSouth { get; } + + /// + /// The elevation of the project, mostly translated into a translation into z-direction. + /// + public decimal Elevation { get; } + + /// + /// The angle of the project north to true north. + /// + public decimal Angle { get; } + + public ProjectPositionWrapper(decimal eastWest, decimal northSouth, decimal elevation, decimal angle) + { + EastWest = eastWest; + NorthSouth = northSouth; + Elevation = elevation; + Angle = angle; + } + + public ProjectPositionWrapper() + { + EastWest = 0; + NorthSouth = 0; + Elevation = 0; + Angle = 0; + } + + //public ProjectPositionWrapper(ProjectPosition projectPosition) + //{ + // EastWest = projectPosition.EastWest.ToDecimal(); + // NorthSouth = projectPosition.NorthSouth.ToDecimal(); + // Elevation = projectPosition.Elevation.ToDecimal(); + // Angle = projectPosition.Angle.ToDecimal(); + //} + + /// + /// Returns the project position's translation as a . The east-west translation is taken as x, + /// the north-south as y and the elevation as z. This represents the coordinates of the project as an + /// xyz-coordinate system. + /// + /// The project position's location as a vector. + public Vector3 GetTranslation() => new(EastWest, NorthSouth, Elevation); + } +} diff --git a/src/OpenProject.Navisworks/DockPanel/AddinManagerPane.cs b/src/OpenProject.Navisworks/DockPanel/AddinManagerPane.cs new file mode 100644 index 00000000..94b59797 --- /dev/null +++ b/src/OpenProject.Navisworks/DockPanel/AddinManagerPane.cs @@ -0,0 +1,47 @@ +using System.Windows.Forms; +using System.Windows.Forms.Integration; +using Autodesk.Navisworks.Api.Plugins; +using OpenProjectNavisworks.View.Control; + +namespace OpenProjectNavisworks.DockPanel +{ + [Plugin($"OpenProjectNavisworks.ShowHidePanel", "ChuongMep", DisplayName = "Trace/Debug Output", ToolTip = "Add-in-Manager")] + [DockPanePlugin(350, 600, AutoScroll = true, MinimumHeight = 300)] + class AddinManagerPane : DockPaneBase + { + public override Control CreateControlPane() + { + //create an ElementHost + ElementHost elementHost = new ElementHost + { + //assign the control + AutoSize = true, + Child = new LogControl(), + + + }; + elementHost.CreateControl(); + elementHost.ParentChanged += this.ElementHost_ParentChanged; + //return the ElementHost + return elementHost; + } + + public override void DestroyControlPane(System.Windows.Forms.Control pane) + { + pane.Dispose(); + } + + /// + /// Resizing DockPane implies resizing WPF-based ElementHost + /// + /// ElementHost + /// + private void ElementHost_ParentChanged(object sender, EventArgs e) + { + if (sender is ElementHost elementHost) + { + elementHost.Dock = DockStyle.Fill; + } + } + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/DockPanel/DockPaneBase.cs b/src/OpenProject.Navisworks/DockPanel/DockPaneBase.cs new file mode 100644 index 00000000..a0d30cea --- /dev/null +++ b/src/OpenProject.Navisworks/DockPanel/DockPaneBase.cs @@ -0,0 +1,20 @@ +using System.Windows.Forms; +using Autodesk.Navisworks.Api.Plugins; + +namespace OpenProjectNavisworks.DockPanel +{ + public abstract class DockPaneBase : DockPanePlugin + { + /// + /// Add a control in navis + /// + /// + public abstract override Control CreateControlPane(); + + /// + /// dispose control from created in navis + /// + /// + public abstract override void DestroyControlPane(Control control); + } +} diff --git a/src/OpenProject.Navisworks/Entry/ActiveViewChangedZoomListener.cs b/src/OpenProject.Navisworks/Entry/ActiveViewChangedZoomListener.cs new file mode 100644 index 00000000..ef702554 --- /dev/null +++ b/src/OpenProject.Navisworks/Entry/ActiveViewChangedZoomListener.cs @@ -0,0 +1,65 @@ +////using Autodesk.Revit.DB; +////using Autodesk.Revit.UI; +////using Autodesk.Revit.UI.Events; +//using Autodesk.Navisworks.Internal; +//using Autodesk.Navisworks.Api; +//using System; +//using System.Linq; +//using OpenProjectNavisworks.Data; +//using OpenProjectNavisworks.Services; +//using Serilog; + +//namespace OpenProjectNavisworks.Entry +//{ +// /// +// /// This class collects static functions that registers asynchronous callbacks to the idling state of the +// /// , which deregister themselves after execution. +// /// +// public static class AppIdlingCallbackListener +// { +// /// +// /// Sets a callback that applies a zoom to the current view. +// /// +// /// The current UI application. +// /// The view ID for the view to be zoomed. +// /// The zoom in decimal precision. +// public static void SetPendingZoomChangedCallback(UIApplication app, ElementId viewId, decimal zoom) +// { +// void Callback(object sender, IdlingEventArgs args) +// { +// StatusBarService.SetStatusText("Zooming to scale '" + zoom + "' ..."); +// UIView currentView = app.ActiveUIDocument.GetOpenUIViews().First(); +// if (currentView.ViewId != viewId) return; + +// UIDocument uiDoc = app.ActiveUIDocument; +// View activeView = uiDoc.ActiveView; + +// var zoomCorners = currentView.GetZoomCorners(); +// XYZ bottomLeft = zoomCorners[0]; +// XYZ topRight = zoomCorners[1]; +// var (currentHeight, currentWidth) = +// RevitUtils.ConvertToViewBoxValues(topRight, bottomLeft, activeView.RightDirection); + +// var zoomedViewBoxHeight = Convert.ToDouble(zoom).ToInternalRevitUnit(); +// var zoomedViewBoxWidth = zoomedViewBoxHeight * currentWidth / currentHeight; + +// XYZ newTopRight = activeView.Origin +// .Add(activeView.UpDirection.Multiply(zoomedViewBoxHeight / 2)) +// .Add(activeView.RightDirection.Multiply(zoomedViewBoxWidth / 2)); +// XYZ newBottomLeft = activeView.Origin +// .Subtract(activeView.UpDirection.Multiply(zoomedViewBoxHeight / 2)) +// .Subtract(activeView.RightDirection.Multiply(zoomedViewBoxWidth / 2)); + +// Log.Information("Zoom to {topRight} | {bottomLeft} ...", newTopRight.ToString(), newBottomLeft.ToString()); +// currentView.ZoomAndCenterRectangle(newTopRight, newBottomLeft); + +// StatusBarService.ResetStatusBarText(); +// Log.Information("Finished applying zoom for orthogonal view."); +// app.Idling -= Callback; +// } + +// Log.Information("Append zoom callback for orthogonal view to idle state of Revit application ..."); +// app.Idling += Callback; +// } +// } +//} diff --git a/src/OpenProject.Navisworks/Entry/CmdMain.cs b/src/OpenProject.Navisworks/Entry/CmdMain.cs new file mode 100644 index 00000000..634501b6 --- /dev/null +++ b/src/OpenProject.Navisworks/Entry/CmdMain.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using Autodesk.Navisworks.Api.Plugins; +namespace OpenProjectNavisworks.Entry; + +/// +/// Obfuscation Ignore for External Interface +/// +[Obfuscation(Exclude = true, ApplyToMembers = false)] +//[Transaction(TransactionMode.Manual)] +//[Regeneration(RegenerationOption.Manual)] +public sealed class CmdMain +{ + /// + /// Main Command Entry Point + /// + /// + /// + /// + /// + public static int ExecuteCommand(bool apply, params string[] parameters) + { + var message = string.Empty; + return RibbonButtonClickHandler.OpenMainPluginWindow(Model.NavisworksWrapper.Document, ref message); + } +} + diff --git a/src/OpenProject.Navisworks/Entry/CmdMainSettings.cs b/src/OpenProject.Navisworks/Entry/CmdMainSettings.cs new file mode 100644 index 00000000..ef79f853 --- /dev/null +++ b/src/OpenProject.Navisworks/Entry/CmdMainSettings.cs @@ -0,0 +1,22 @@ +//using Autodesk.Revit.Attributes; +//using Autodesk.Revit.DB; +//using Autodesk.Revit.UI; +using System.Reflection; + +namespace OpenProjectNavisworks.Entry +{ + /// + /// Obfuscation Ignore for External Interface + /// + [Obfuscation(Exclude = true, ApplyToMembers = false)] + //[Transaction(TransactionMode.Manual)] + //[Regeneration(RegenerationOption.Manual)] + public sealed class CmdMainSettings + { + public static int ExecuteCommand(bool apply, params string[] parameters) + { + var message = string.Empty; + return RibbonButtonClickHandler.OpenSettingsPluginWindow(Model.NavisworksWrapper.Document, ref message); + } + } +} diff --git a/src/OpenProject.Navisworks/Entry/IpcHandler.cs b/src/OpenProject.Navisworks/Entry/IpcHandler.cs new file mode 100644 index 00000000..18cb51d5 --- /dev/null +++ b/src/OpenProject.Navisworks/Entry/IpcHandler.cs @@ -0,0 +1,180 @@ +using Autodesk.Navisworks.Gui; +using Autodesk.Navisworks.Api; +using Autodesk.Navisworks.Internal; +using OpenProject.Shared; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using iabi.BCF.APIObjects.V21; +using Newtonsoft.Json.Converters; +using OpenProjectNavisworks.Extensions; +using OpenProjectNavisworks.Services; +using OpenProject.Shared.BcfApi; +using ZetaIpc.Runtime.Client; +using ZetaIpc.Runtime.Server; +using ZetaIpc.Runtime.Helper; +using OpenProjectNavisworks.Data; + +namespace OpenProjectNavisworks.Entry; +public class IpcHandler +{ + private readonly Document _uiApp; + private Action _sendData; + private static readonly object _callbackStackLock = new(); + private static readonly Stack _callbackStack = new(); + + public IpcHandler(Document uiApp) + { + _uiApp = uiApp ?? throw new ArgumentNullException(nameof(uiApp)); + + + Autodesk.Navisworks.Api.Application.Idle += (_, _) => + { + lock (_callbackStackLock) + { + if (!_callbackStack.Any()) return; + + _callbackStack.Pop().Invoke(); + } + }; + } + + public int StartLocalServerAndReturnPort() + { + var freePort = FreePortHelper.GetFreePort(); + var server = new IpcServer(); + server.Start(freePort); + server.ReceivedRequest += (_, e) => + { + var eventArgs = JsonConvert.DeserializeObject(e.Request); + if (eventArgs == null) return; + + var localMessageType = eventArgs.MessageType; + var localTrackingId = eventArgs.TrackingId; + var localMessagePayload = eventArgs.MessagePayload; + + lock (_callbackStackLock) + { + _callbackStack.Push(() => + { + switch (localMessageType) + { + case MessageTypes.VIEWPOINT_DATA: + { + try + { + BcfViewpointWrapper bcfViewpoint = MessageDeserializer.DeserializeBcfViewpoint( + new WebUiMessageEventArgs(localMessageType, localTrackingId, localMessagePayload)); + + // TODO: (if else) Hiding objects feature + //if(NavisworksUtils.ModelItems != null) + OpenViewpointEventHandler.ShowBcfViewpoint(bcfViewpoint); + //else + //{ + // MessageHandler.ShowWarning( + // "Object dictionary is not ready yet!", + // "Object dictionary is ready, you can use ViewPoint now", + // "Object dictionary is ready"); + //} + } + catch (Exception exception) + { + MessageHandler.ShowError(exception, "Error opening a viewpoint."); + } + + break; + } + case MessageTypes.VIEWPOINT_GENERATION_REQUESTED: + try + { + + AddViewpoint(localTrackingId); + + } + catch (Exception exception) + { + MessageHandler.ShowError(exception, "Error generating a viewpoint."); + } + + break; + } + }); + } + }; + + return freePort; + } + + public void StartLocalClient(int ipcWinServerPort) + { + var client = new IpcClient(); + client.Initialize(ipcWinServerPort); + _sendData = message => + { + try + { + client.Send(message); + } + catch (System.Net.WebException) + { + // We can ignore the WebException, it's raised after + // the shutdown event due to the other side just closing + // the open TCP connection. This is what's expected😊 + } + }; + } + + public void SendShutdownRequestToDesktopApp() + { + var eventArgs = new WebUiMessageEventArgs(MessageTypes.CLOSE_DESKTOP_APPLICATION, "0", string.Empty); + var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); + _sendData(jsonEventArgs); + } + + public void SendOpenSettingsRequestToDesktopApp() + { + var eventArgs = new WebUiMessageEventArgs(MessageTypes.GO_TO_SETTINGS, "0", string.Empty); + var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); + _sendData(jsonEventArgs); + } + + public void SendBringBrowserToForegroundRequestToDesktopApp() + { + var eventArgs = new WebUiMessageEventArgs(MessageTypes.SET_BROWSER_TO_FOREGROUND, "0", string.Empty); + var jsonEventArgs = JsonConvert.SerializeObject(eventArgs); + _sendData(jsonEventArgs); + } + + /// + /// Generates a viewpoint from the active view and sends the data as an event to bridge. + /// + /// The local message tracking id. + private void AddViewpoint(string trackingId) + { + JObject payload = GenerateJsonViewpoint(); + + // TODO: remove hack of snapshot data once #39135 is deployed + payload["snapshot"] = payload["snapshot"]?["snapshot_data"]; + + var eventArgs = new WebUiMessageEventArgs(MessageTypes.VIEWPOINT_GENERATED, trackingId, payload.ToString()); + var so = JsonConvert.SerializeObject(eventArgs); + _sendData(so); + } + + private JObject GenerateJsonViewpoint() + { + var serializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + }; + + serializerSettings.Converters.Add(new StringEnumConverter(new SnakeCaseNamingStrategy(), false)); + + Viewpoint_POST viewpoint = _uiApp.GenerateViewpoint(); + return JObject.Parse(JsonConvert.SerializeObject(viewpoint, serializerSettings)); + } +} + diff --git a/src/OpenProject.Navisworks/Entry/OpenViewpointEventHandler.cs b/src/OpenProject.Navisworks/Entry/OpenViewpointEventHandler.cs new file mode 100644 index 00000000..fb022918 --- /dev/null +++ b/src/OpenProject.Navisworks/Entry/OpenViewpointEventHandler.cs @@ -0,0 +1,729 @@ +//using Autodesk.Revit.DB; +//using Autodesk.Revit.UI; +using Autodesk.Navisworks.Api; +using OpenProjectNavisworks.Data; +using OpenProjectNavisworks.Extensions; +using OpenProjectNavisworks.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using OpenProject.Shared.BcfApi; +using OpenProject.Shared.Math3D; +using OpenProject.Shared.Math3D.Enumeration; +using Serilog; +using System.Windows.Forms; +using iabi.BCF.APIObjects.V10.Viewpoint.Components; +using iabi.BCF.APIObjects.V21; +using System.Xml.Linq; +using System.Windows.Controls; +using System.Xml.Linq; +using OpenProject.Shared; +using System.Windows; +using System.IO; +using Optional.Collections; + +namespace OpenProjectNavisworks.Entry; + +/// +/// Obfuscation Ignore for External Interface +/// +public class OpenViewpointEventHandler +{ + private const decimal _viewpointAngleThresholdRad = 0.087266462599716m; + + /// + public void Execute(Document app) + { + try + { + ShowBCfViewpointInternal(app); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to load BCF viewpoint"); + MessageHandler.ShowError(ex, "exception: " + ex); + } + } + + /// + public string GetName() => nameof(OpenViewpointEventHandler); + + private BcfViewpointWrapper _bcfViewpoint; + + private static OpenViewpointEventHandler _instance; + + private static OpenViewpointEventHandler Instance + { + get + { + if (_instance != null) return _instance; + + _instance = new OpenViewpointEventHandler(); + //ExternalEvent = ExternalEvent.Create(_instance); + + return _instance; + } + } + + //private static ExternalEvent ExternalEvent { get; set; } + + /// + /// Wraps the raising of the external event and thus the execution of the event callback, + /// that show given bcf viewpoint. + /// + /// + /// http://help.autodesk.com/view/RVT/2014/ENU/?guid=GUID-0A0D656E-5C44-49E8-A891-6C29F88E35C0 + /// http://matteocominetti.com/starting-a-transaction-from-an-external-application-running-outside-of-api-context-is-not-allowed/ + /// + /// The bcf viewpoint to be shown in current view. + public static void ShowBcfViewpoint(BcfViewpointWrapper bcfViewpoint) + { + Log.Information("Received 'Opening BCF Viewpoint event'. Attempting to open viewpoint ..."); + Instance._bcfViewpoint = bcfViewpoint; + //ExternalEvent.Raise(); + Instance.Execute(OpenProjectNavisworks.Model.NavisworksWrapper.Document); + } + + private void ShowBCfViewpointInternal(Document document) + { + var hasCamera = _bcfViewpoint.GetCamera().Match( + camera => + { + Log.Information("Found camera type {t}, opening related OpenProject view ...", camera.Type.ToString()); + + var openProjectView = document.GetOpenProjectView(camera.Type); + + //ResetView(document, openProjectView); + //Log.Information("Reset view..."); + + var viewPoint = document.CurrentViewpoint.CreateCopy(); + + Log.Information("Applied view point clipping planes in '{v}'.", "view"); + ApplyClippingPlanes(document, ref viewPoint); + + ApplyViewOrientationAndVisibility(document, camera, ref viewPoint); + Log.Information("Applied view orientation and visibility in '{v}'.", "view"); + + + + //if (!document.ActiveView.Id.Equals(openProjectView.Id)) + //{ + // Log.Information("Setting view '{t}' as active view ...", openProjectView.Name); + // document.ActiveView = openProjectView; + //} + + //document.RefreshActiveView(); + document.CurrentViewpoint.CopyFrom(viewPoint); + Log.Information("Refreshed active view."); + StatusBarService.ResetStatusBarText(); + + // MaksT + //SavedViewpoint saveViewPoint = new SavedViewpoint(document.CurrentViewpoint.ToViewpoint()); + //saveViewPoint.DisplayName = "gettedView"; + //document.SavedViewpoints.AddCopy(saveViewPoint); + + ZoomIfNeeded(document, camera, ref viewPoint); + document.CurrentViewpoint.CopyFrom(viewPoint); + Log.Information("Finished loading BCF viewpoint."); + + return true; + }, + () => false); + + if (!hasCamera) Log.Error("BCF viewpoint has no camera information. Aborting ..."); + } + + + /// + /// Zoom the view to the correct scale, if necessary. + /// + /// In Revit, orthogonal views do not change their camera positions, when zooming in or out. Hence, + /// the values stored in the BCF viewpoint are not sufficient to restore the previously exported viewpoint. + /// In order to get correct zooming, the scale value (view box height) is used, to calculate the correct zoom + /// corners according to view center. + /// See https://thebuildingcoder.typepad.com/blog/2020/10/save-and-restore-3d-view-camera-settings.html + /// + private static void ZoomIfNeeded(Document app, Camera camera, ref Viewpoint viewPoint) + { + if (camera.Type != CameraType.Orthogonal || camera is not OrthogonalCamera orthoCam) return; + + Log.Information("Found orthogonal camera, setting zoom callback ..."); + StatusBarService.SetStatusText("Waiting for view to render to apply zoom ..."); + + //AppIdlingCallbackListener.SetPendingZoomChangedCallback(app, viewId, orthoCam.ViewToWorldScale); + //var cam1 = _bcfViewpoint.GetCamera(). + + //app.CurrentViewpoint.Value.HeightField = (double)orthoCam.ViewToWorldScale; + viewPoint.HeightField = NavisworksUtils.ToInternalAppUnit((double)orthoCam.ViewToWorldScale); + + + } + + private static void ResetView(Document doc, Autodesk.Navisworks.Api.View view) + { + Log.Information("Removing current selection ..."); + doc.CurrentSelection.Clear(); + view.TrySetClippingPlanes(""); + view.Dispose(); + + var models = doc.Models; + + foreach (var model in models) + { + ModelItem rootItem = model.RootItem; + ModelItemEnumerableCollection modelItems = rootItem.DescendantsAndSelf; + doc.Models.SetHidden(modelItems, false); + Log.Information("Unhide {n} currently hidden elements in model {m}...", modelItems.Count(), model.FileName); + } + } + + private void ApplyViewOrientationAndVisibility(Document doc, Camera camera, ref Viewpoint viewPoint) + { + StatusBarService.SetStatusText("Loading view point data ..."); + Log.Information("Calculating view orientation from camera position ..."); + + var viewOrientation3D = NavisworksUtils.TransformCameraPosition( + new ProjectPositionWrapper(), + camera.Position.ToInternalUnits(), + true) + .ToViewOrientation3D(); + + if (camera.Type == CameraType.Perspective) + { + Log.Information("Setting active far viewer bound to zero ..."); + viewPoint.Projection = ViewpointProjection.Perspective; + var fov = _bcfViewpoint.Viewpoint.Perspective_camera.Field_of_view; + //var fieldOfView = _bcfViewpoint.Viewpoint.Perspective_camera.Field_of_view; + viewPoint.HeightField = fov; + } + else + { + viewPoint.Projection = ViewpointProjection.Orthographic; + } + + + + Log.Information("Applying new view orientation ..."); + viewPoint.Position = camera.Position.Center.ToNavisworksPoint3D(); + //Rotation3D rt3d = new Rotation3D(viewPoint.Rotation.ToAxisAndAngle().Axis, + // new UnitVector3D( + // Convert.ToDouble(-camera.Position.Forward.X), + // Convert.ToDouble(-camera.Position.Forward.Y), + // Convert.ToDouble(-camera.Position.Forward.Z))); + //Rotation3D rt3d = new Rotation3D(viewPoint.Rotation.ToAxisAndAngle().Axis, + //viewPoint.Rotation = rt3d; + //Rotation3D rt3d = new Rotation3D(viewPoint.Rotation.ToAxisAndAngle().Axis, new UnitVector3D( + // Convert.ToDouble(-camera.Position.Forward.X), + // Convert.ToDouble(-camera.Position.Forward.Y), + // Convert.ToDouble(-camera.Position.Forward.Z))); + //viewPoint.AlignDirection(new Vector3D( + // Convert.ToDouble(camera.Position.Forward.X), + // Convert.ToDouble(camera.Position.Forward.Y), + // Convert.ToDouble(camera.Position.Forward.Z))); + var focusPoint = new Point3D( + camera.Position.Center.ToNavisworksPoint3D().X + Convert.ToDouble(camera.Position.Forward.X), + camera.Position.Center.ToNavisworksPoint3D().Y + Convert.ToDouble(camera.Position.Forward.Y), + camera.Position.Center.ToNavisworksPoint3D().Z + Convert.ToDouble(camera.Position.Forward.Z)); + + viewPoint.PointAt(focusPoint); + + Double forwardX = Convert.ToDouble(camera.Position.Forward.X); + Double forwardY = Convert.ToDouble(camera.Position.Forward.Y); + Double forwardZ = Convert.ToDouble(camera.Position.Forward.Z); + + if (forwardX < 0.000001 && forwardY < 0.000001 && Math.Abs(forwardZ) == 1) // We should align by another vector in case of UP and DOWN view + { + viewPoint.AlignUp(new Vector3D(0, 1, 0)); + } + else { + viewPoint.AlignUp(new Vector3D(0, 0, 1)); + } + + + + //Convert.ToDouble(camera.Position.Up.X), + //Convert.ToDouble(camera.Position.Up.Y), + //Convert.ToDouble(camera.Position.Up.Z))); + + + + Log.Information("Applying element visibility ..."); + + doc.Models.ResetAllHidden(); + + // -------------------------------------------------------------------- + // SELECTION + // -------------------------------------------------------------------- + // -------------------------------------------------------------------- + // METHOD 0. OLDMETHOD BRUTEFORCE + + //var currentlyVisibleElements = doc.GetVisibleElementsOfView(); + //var map = doc.GetIfcGuidElementIdMap(currentlyVisibleElements); + //var exceptionElements = GetViewpointVisibilityExceptions(map); + //var myexceptionElements = _bcfViewpoint.GetVisibilityExceptions().ToList(); + + //var selectedElements = _bcfViewpoint.GetSelection().Select(x => (doc.Models.RootItemDescendants.WhereInstanceGuid(IfcGuid.FromIfcGUID(x.Ifc_guid))).FirstOrDefault()).Distinct().ToList(); + + //var interestedGuids = _bcfViewpoint.GetSelection().Select(x => IfcGuid.FromIfcGUID(x.Ifc_guid)); + //var selectedElements = doc.Models.RootItemDescendants.Where(elem => interestedGuids.Contains(elem.InstanceGuid)).Distinct().ToList(); + + + //var selectedElements = GetViewpointSelection(map); + //if (exceptionElements.Any()) + //{ + // //if (_bcfViewpoint.GetVisibilityDefault()) // Сделали всегда True + // //{ + // ////create a store for the visible items + // //ModelItemCollection visible = new ModelItemCollection(); + + // //doc.CurrentSelection.SelectAll(); + // ////Add all the items that are visible to the visible collection + // //foreach (ModelItem item in Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.SelectedItems) + // //{ + // // if (item.AncestorsAndSelf != null) + // // visible.AddRange(item.AncestorsAndSelf); + // // if (item.Descendants != null) + // // visible.AddRange(item.Descendants); + // //} + // //view.HideElementsTemporary(exceptionElements); + // //doc.Models.SetHidden(visible, false); + + // //doc.Models.SetHidden(exceptionElements, true); + // doc.Models.SetHidden(exceptionElements, true); + // //selectedElements = selectedElements.Where(id => !exceptionElements.Contains(id)).ToList(); + // //} + // //else + // //{ + // //view.IsolateElementsTemporary(exceptionElements); + + // //var sel = doc.CurrentSelection.SelectedItems; + // //sel.Clear(); + // //sel.AddRange(exceptionElements); + // //sel.Invert(doc); + // //doc.Models.SetHidden(sel, true); + // //selectedElements = selectedElements.Where(id => exceptionElements.Contains(id)).ToList(); + // // Hello world + + // // doc.CurrentSelection.Clear(); + // // doc.Models.ResetAllHidden(); + // // doc.CurrentSelection.AddRange(exceptionElements); + // // selectedElements = selectedElements.Where(id => exceptionElements.Contains(id)).ToList(); + + // //} + // doc.CurrentSelection.Clear(); + //} + ////view.ConvertTemporaryHideIsolateToPermanent(); + + // -------------------------------------------------------------------- + // METHOD 1. BRUTEFORCE WHEREINSTANCE | SPEED 1/5 + //var selectedElements1 = GetSelectedElementsBrute(doc); + // -------------------------------------------------------------------- + // METHOD 2. BYSEARCH | SPEED 3/5 + //List selectedElements2 = GetSelectedElementsBySearch(doc); + // -------------------------------------------------------------------- + + // -------------------------------------------------------------------- + // METHOD 3.1 BY MODELS BRUTE MINE | SPEED 3/5 + //List selectedElements2 = GetSelectedElementsByModels(doc); + // METHOD 3.2 BY MODELS SEARCH | Не удалось реализовать + //List selectedElements3 = GetSelectedElementsByModelsAndSearch(doc); + // METHOD 3.3 BY MODELS BRUTE WHERE | + List selectedElements3 = GetSelectedElementsByModelsWere(doc); + + // -------------------------------------------------------------------- + // METHOD 4. DICT | SPEED 5/5 + //Dictionary dict = NavisworksUtils.ModelItems; + ////Dictionary> dict = NavisworksUtils.ModelModelItems; + ////List exceptions = NavisworksUtils.HiddenModelItems.ToList(); + //List selectedElements4 = new List(); + //if (dict != null) + //{ + // selectedElements4 = GetSelectedElementsByDict(dict); + // //selectedElements = GetSelectedElementsByModelDict(dict); + // //exceptions = GetExceptionsByDict(dict); + + // if (selectedElements4.Any()) + // { + // Log.Information("Select {n} elements ...", selectedElements4.Count()); + // //doc.CurrentSelection.SelectedItems.Clear(); + // //doc.CurrentSelection.SelectedItems.AddRange(selectedElements); + + // doc.CurrentSelection.Clear(); + // doc.CurrentSelection.AddRange(selectedElements4); + // } + + // //if (exceptions != null) + // //{ + // // doc.Models.ResetAllHidden(); + // // doc.Models.SetHidden(exceptions, true); + // //} + //} + //else + //{ + // MessageHandler.ShowWarning( + // "Необходимо подождать", + // "Словарь объектов модели еще не сформирован, необходимо подождать. Операция может занимать несколько минут", + // "Словарь объектов не сформирован"); + //} + // -------------------------------------------------------------------- + if (selectedElements3.Any()) + { + //Log.Information("Select {n} elements ...", selectedElements3.Count()); + //doc.CurrentSelection.SelectedItems.Clear(); + //doc.CurrentSelection.SelectedItems.AddRange(selectedElements); + + doc.CurrentSelection.Clear(); + doc.CurrentSelection.AddRange(selectedElements3); + } + + var stop = 1; + // -------------------------------------------------------------------- + // SELECTION + // -------------------------------------------------------------------- + + + + + } + private List GetSelectedElementsBrute(Document doc) + { + + var selectedElements = _bcfViewpoint.GetSelection().Select(x => (doc.Models.RootItemDescendants.WhereInstanceGuid(IfcGuid.FromIfcGUID(x.Ifc_guid))).FirstOrDefault()).Distinct().ToList(); + + return selectedElements; + } + private List GetSelectedElementsBySearch(Document doc) + { + List selectedElements = new List(); + foreach (Component component in _bcfViewpoint.GetSelection()) + { + Guid id = IfcGuid.FromIfcGUID(component.Ifc_guid); + string strID = id.ToString(); + + Search search = new Search(); + search.Selection.SelectAll(); + + //var modelFileNameBCF = component.Authoring_tool_id; + //SearchCondition searchCondition2 = SearchCondition.HasPropertyByName("LcOaNode", "LcOaNodeSourceFile"); + //searchCondition2 = searchCondition2.EqualValue(VariantData.FromDisplayString(modelFileNameBCF)); + //search.SearchConditions.Add(searchCondition2); + SearchCondition searchCondition = SearchCondition.HasPropertyByName("LcOaNode", "LcOaNodeGuid"); + searchCondition = searchCondition.EqualValue(VariantData.FromDisplayString(strID)); + search.SearchConditions.Add(searchCondition); + + + + ModelItem item = search.FindFirst(doc, false); + + if (item != null) + { + selectedElements.Add(item); + } + } + return selectedElements; + } + private List GetSelectedElementsByModels(Document doc) + { + List selectedElements = new List(); + + var models = doc.Models; + foreach (var component in _bcfViewpoint.GetSelection()) + { + var modelFileNameBCF = component.Authoring_tool_id; + + foreach (var model in models) + { + if (modelFileNameBCF == Path.GetFileName(model.FileName)) + { + var descendantsModelItems = model.RootItem.Descendants; + + foreach (var descendant in descendantsModelItems) + { + if (descendant.InstanceGuid == IfcGuid.FromIfcGUID(component.Ifc_guid)) + { + selectedElements.Add(descendant); + break; + } + + } + + break; + } + } + } + + return selectedElements; + } + private List GetSelectedElementsByModelsWere(Document doc) + { + List selectedElements = new List(); + + var models = RibbonButtonClickHandler.GetItemFromLastModelBranch(doc.Models.ToList()); + foreach (var component in _bcfViewpoint.GetSelection()) + { + var modelFileNameBCF = component.Authoring_tool_id; + + foreach (var model in models) + { + if (modelFileNameBCF == Path.GetFileNameWithoutExtension(model.SourceFileName)) + { + //selectedElements = _bcfViewpoint.GetSelection().Select(x => (model.RootItem.Descendants.WhereInstanceGuid(IfcGuid.FromIfcGUID(x.Ifc_guid))).FirstOrDefault()).Distinct().ToList(); + selectedElements.Add(model.RootItem.Descendants.WhereInstanceGuid(IfcGuid.FromIfcGUID(component.Ifc_guid)).First()); + break; + + } + } + } + + return selectedElements; + } + private List GetSelectedElementsByModelsAndSearch(Document doc) + { + List selectedElements = new List(); + Search search = new Search(); + SearchCondition searchCondition = SearchCondition.HasPropertyByName("LcOaNode", "LcOaNodeGuid"); + + var models = doc.Models; + foreach (var component in _bcfViewpoint.GetSelection()) + { + search.Clear(); + Guid id = IfcGuid.FromIfcGUID(component.Ifc_guid); + string strID = id.ToString(); + var modelFileNameBCF = component.Authoring_tool_id; + foreach (var model in models) + { + if (modelFileNameBCF == Path.GetFileName(model.FileName)) + { + var descendantsModelItems = model.RootItem.Descendants; + search.Selection.Clear(); + search.Selection.Dispose(); + //search.Selection.SelectAll(); + search.Selection.CopyFrom(descendantsModelItems); + + searchCondition = searchCondition.EqualValue(VariantData.FromDisplayString(strID)); + search.SearchConditions.Add(searchCondition); + + ModelItem item = search.FindFirst(doc, false); + if (item != null) + { + selectedElements.Add(item); + } + break; + } + } + } + + return selectedElements; + } + + private List GetSelectedElementsByDict(Dictionary dict) + { + List selectedElements = new List(); + + foreach (var component in _bcfViewpoint.GetSelection()) + { + var modelFileNameBCF = component.Authoring_tool_id; + var modelGuidBCF = IfcGuid.FromIfcGUID(component.Ifc_guid).ToString(); + + if(dict.TryGetValue(modelGuidBCF, out ModelItem foundModelItem)) + { + selectedElements.Add(foundModelItem); + } + } + + return selectedElements; + } + private List GetSelectedElementsByModelDict(Dictionary> dict) + { + List selectedElements = new List(); + + foreach (var component in _bcfViewpoint.GetSelection()) + { + var modelFileNameBCF = component.Authoring_tool_id; + var modelGuidBCF = IfcGuid.FromIfcGUID(component.Ifc_guid).ToString(); + + if (dict.TryGetValue(modelFileNameBCF, out Dictionary foundModelDict)) + { + if (foundModelDict.TryGetValue(modelGuidBCF, out ModelItem foundModelItem)) + { + selectedElements.Add(foundModelItem); + } + + } + } + + return selectedElements; + } + private List GetExceptionsByDict(Dictionary dict) + { + List exceptions = new List(); + + foreach (var component in _bcfViewpoint.GetVisibilityExceptions()) + { + var modelFileNameBCF = component.Authoring_tool_id; + var modelGuidBCF = IfcGuid.FromIfcGUID(component.Ifc_guid).ToString(); + + if (dict.TryGetValue(modelGuidBCF, out ModelItem foundModelItem)) + { + exceptions.Add(foundModelItem); + } + } + + return exceptions; + } + + private void ApplyClippingPlanes(Document doc, ref Viewpoint viewPoint) + { + //Test + //var cvp = doc.CurrentViewpoint; + //var vp = cvp.Value; + //var cps = vp.InternalClipPlanes; + //var mode = cps.GetMode(); + //BoundingBox3D box = new BoundingBox3D(); + //Rotation3D rotation = new Rotation3D(); + //cps.GetOrientedBox(box, rotation); + //Vector3D dims = box.Size; + //box = box.Extend(new Point3D(-dims.X * 0.25, -dims.Y * 0.25, -dims.Z * 0.25)); + //cps.SetOrientedBox(box, rotation); + //doc.CurrentViewpoint.CopyFrom(vp); + // + + //using var trans = new Transaction(doc.Document); + //if (trans.Start($"Apply view point clipping planes in '{view.Name}'") != TransactionStatus.Started) + // return; + + Log.Information("Retrieving viewpoint clipping planes " + + "and converting them into an axis aligned bounding box ..."); + AxisAlignedBoundingBox boundingBox = GetViewpointClippingBox(); + IEnumerable clippingPlanes = _bcfViewpoint.GetClippingPlanes(); + //ClipPlane + + + if (!boundingBox.Equals(AxisAlignedBoundingBox.Infinite)) + { + Log.Information("Found axis aligned clipping planes. Setting resulting section box ..."); + string json = doc.ActiveView.GetClippingPlanes(); + + string minCorner = string.Format("{0},{1},{2}", + Convert.ToDouble(boundingBox.Min.X).ToInternalAppUnit().ToString("0.00000", System.Globalization.CultureInfo.InvariantCulture), + Convert.ToDouble(boundingBox.Min.Y).ToInternalAppUnit().ToString("0.00000", System.Globalization.CultureInfo.InvariantCulture), + Convert.ToDouble(boundingBox.Min.Z).ToInternalAppUnit().ToString("0.00000", System.Globalization.CultureInfo.InvariantCulture)); + string maxCorner = string.Format("{0},{1},{2}", + Convert.ToDouble(boundingBox.Max.X).ToInternalAppUnit().ToString("0.00000", System.Globalization.CultureInfo.InvariantCulture), + Convert.ToDouble(boundingBox.Max.Y).ToInternalAppUnit().ToString("0.00000", System.Globalization.CultureInfo.InvariantCulture), + Convert.ToDouble(boundingBox.Max.Z).ToInternalAppUnit().ToString("0.00000", System.Globalization.CultureInfo.InvariantCulture)); + + // apply the section box. + string ClippingBox = + "{\"Type\":\"ClipPlaneSet\",\"Version\":1,\"OrientedBox\":{\"Type\":\"OrientedBox3D\",\"Version\":1,\"Box\":[[" + + minCorner + + "],[" + + maxCorner + + "]],\"Rotation\":[0,0,0]},\"Enabled\":true}"; + + //MaksT ClipPlanes + string ClippingPlanes = + "{\"Type\":\"ClipPlaneSet\",\"Version\":1,\"Planes\":["; + foreach (Clipping_plane clippingPlane in clippingPlanes) + { + decimal x = Convert.ToDecimal(Convert.ToDouble(clippingPlane.Location.X).ToInternalAppUnit()); + decimal y = Convert.ToDecimal(Convert.ToDouble(clippingPlane.Location.Y).ToInternalAppUnit()); + decimal z = Convert.ToDecimal(Convert.ToDouble(clippingPlane.Location.Z).ToInternalAppUnit()); + decimal nX = Convert.ToDecimal(Convert.ToDouble(clippingPlane.Direction.X)); + decimal nY = Convert.ToDecimal(Convert.ToDouble(clippingPlane.Direction.Y)); + decimal nZ = Convert.ToDecimal(Convert.ToDouble(clippingPlane.Direction.Z)); + Vector3 n = new Vector3(nX, nY, nZ); + + decimal distance = -(nX * x + nY * y + nZ * z); + double dbDistande = Convert.ToDouble(distance); + string strDistance = dbDistande.ToString("0.0000000000", System.Globalization.CultureInfo.InvariantCulture); + + + ClippingPlanes += "{\"Type\":\"ClipPlane\",\"Version\":1," + + "\"Normal\":[" + (-nX).ToString("0.0000000000", System.Globalization.CultureInfo.InvariantCulture) + + "," + (-nY).ToString("0.0000000000", System.Globalization.CultureInfo.InvariantCulture) + + "," + (-nZ).ToString("0.0000000000", System.Globalization.CultureInfo.InvariantCulture) + "]," + + "\"Distance\":" + strDistance + "," + + "\"Enabled\":" + "true" + "}," + ; + } + ClippingPlanes = ClippingPlanes.Remove(ClippingPlanes.Length - 1); + ClippingPlanes += "],\"Linked\":false, \"Enabled\":true}"; + + // apply clipping to current view + //doc.ActiveView.SetClippingPlanes(ClippingPlanes); + Autodesk.Navisworks.Api.View activeView = doc.ActiveView; + + var clipplBefore = activeView.GetClippingPlanes(); + + + //SavedViewpoint activeViewPointBefore = new SavedViewpoint(doc.CurrentViewpoint.ToViewpoint()); + //activeViewPointBefore.DisplayName = "bcf-point-before"; + //doc.SavedViewpoints.AddCopy(activeViewPointBefore); + + activeView.SetClippingPlanes(ClippingPlanes); + viewPoint = doc.CurrentViewpoint.CreateCopy(); + + //var clipplAfter = activeView.GetClippingPlanes(); + //SavedViewpoint activeViewPointAfter = new SavedViewpoint(doc.CurrentViewpoint.ToViewpoint()); + //activeViewPointAfter.DisplayName = "bcf-point-after"; + //doc.SavedViewpoints.AddCopy(activeViewPointAfter); + + //activeView.LookFromFrontRightTop(); + + + + // doc.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All); + + + //doc.ActiveView.SetClippingPlanes(json); + //view.SetSectionBox(ToNavisSectionBox(boundingBox)); + //view.IsSectionBoxActive = true; + } + + //if clippingPlanes.Count() > 0 { + + //} + //else + //{ + + //} + } + + private AxisAlignedBoundingBox GetViewpointClippingBox() + { + var clippingPlanes = _bcfViewpoint.GetClippingPlanes() + .Select(p => p.ToAxisAlignedBoundingBox(_viewpointAngleThresholdRad)) + .Aggregate(AxisAlignedBoundingBox.Infinite, (current, nextBox) => current.MergeReduce(nextBox)); + return clippingPlanes; + } + + private List GetViewpointVisibilityExceptions(IReadOnlyDictionary filterMap) + => _bcfViewpoint.GetVisibilityExceptions() + .Where(bcfComponentException => filterMap.ContainsKey(bcfComponentException.Ifc_guid)) + .Select(bcfComponentException => filterMap[bcfComponentException.Ifc_guid]) + .ToList(); + + + private List GetViewpointSelection(IReadOnlyDictionary filterMap) + => _bcfViewpoint.GetSelection() + .Where(selectedElement => filterMap.ContainsKey(selectedElement.Ifc_guid)) + .Select(selectedElement => filterMap[selectedElement.Ifc_guid]) + .ToList(); + + + + private static BoundingBox3D ToNavisSectionBox(AxisAlignedBoundingBox box) + { + var min = new Point3D( + box.Min.X == decimal.MinValue ? double.MinValue : ((double)box.Min.X).ToInternalAppUnit(), + box.Min.Y == decimal.MinValue ? double.MinValue : ((double)box.Min.Y).ToInternalAppUnit(), + box.Min.Z == decimal.MinValue ? double.MinValue : ((double)box.Min.Z).ToInternalAppUnit()); + var max = new Point3D( + box.Max.X == decimal.MaxValue ? double.MaxValue : ((double)box.Max.X).ToInternalAppUnit(), + box.Max.Y == decimal.MaxValue ? double.MaxValue : ((double)box.Max.Y).ToInternalAppUnit(), + box.Max.Z == decimal.MaxValue ? double.MaxValue : ((double)box.Max.Z).ToInternalAppUnit()); + + return new BoundingBox3D ( min, max ); + } +} diff --git a/src/OpenProject.Navisworks/Entry/RibbonButtonClickHandler.cs b/src/OpenProject.Navisworks/Entry/RibbonButtonClickHandler.cs new file mode 100644 index 00000000..0bb294c2 --- /dev/null +++ b/src/OpenProject.Navisworks/Entry/RibbonButtonClickHandler.cs @@ -0,0 +1,286 @@ +//using Autodesk.Revit.UI; +using Autodesk.Navisworks.Api; +using ComApi = Autodesk.Navisworks.Api.Interop.ComApi; +using ComBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using OpenProjectNavisworks.Services; +using OpenProject.Shared; +using Serilog; +using ZetaIpc.Runtime.Helper; +using System.Windows.Controls; +using OpenProjectNavisworks.Data; +using System.Windows.Media.Media3D; +using static Autodesk.Navisworks.Gui.Roamer.CommandLineConfig; +using OpenProject.Shared.Logging; + + +namespace OpenProjectNavisworks.Entry; + public static class RibbonButtonClickHandler + { +#if N2024 + public const string NavisAPIVersion = "21"; + public const string NavisVersion = "2024"; +#elif N2023 + public const string NavisAPIVersion = "20"; + public const string NavisVersion = "2023"; +#elif N2022 + public const string NavisAPIVersion = "19"; + public const string NavisVersion = "2022"; +#elif N2021 + public const string NavisAPIVersion = "18"; + public const string NavisVersion = "2021"; +#elif N2020 + public const string NavisAPIVersion = "17"; + public const string NavisVersion = "2020"; +#else + public const string NavisAPIVersion = "16"; + public const string NavisVersion = "2019"; +#endif + + private static Process _opBrowserProcess; + public static IpcHandler IpcHandler { get; private set; } + + public static int OpenMainPluginWindow(Document commandData, ref string message) + { + try + { + Logger.ConfigureLogger("OpenProject.Navisworks.Log..txt"); + EnsureExternalOpenProjectAppIsRunning(commandData); + Log.Information("EnsureExternalOpenProjectAppIsRunning"); + IpcHandler.SendBringBrowserToForegroundRequestToDesktopApp(); + Log.Information("SendBringBrowserToForegroundRequestToDesktopApp"); + + return 1; + } + catch (Exception exception) + { + Logger.ConfigureLogger("OpenProject.Navisworks.Log..txt"); + + message = exception.Message; + Log.Error(exception, message); + return 0; + } + } + + public static int OpenSettingsPluginWindow(Document commandData, ref string message) + { + try + { + EnsureExternalOpenProjectAppIsRunning(commandData); + IpcHandler.SendOpenSettingsRequestToDesktopApp(); + IpcHandler.SendBringBrowserToForegroundRequestToDesktopApp(); + return 1; + } + catch (Exception exception) + { + message = exception.Message; + Logger.ConfigureLogger("OpenProject.Navisworks.Log..txt"); + Log.Error(exception, message); + return 0; + } + } + + private static async void EnsureExternalOpenProjectAppIsRunning(Document commandData) + { + Logger.ConfigureLogger("OpenProject.Navisworks.Log..txt"); + + //Version check + if (!Autodesk.Navisworks.Api.Application.Version.ApiMajor.ToString().Contains(NavisAPIVersion)) + { + MessageHandler.ShowWarning( + "Unexpected version", + "The Navisworks version does not match the expectations.", + $"This Add-In was built and tested only for Navisworks {NavisVersion}. Further usage is at your own risk"); + } + + if (_opBrowserProcess is { HasExited: false }) + return; + else + { + // Clear dict after closing browser window + NavisworksUtils.ModelItems = null; + } + + + IpcHandler = new IpcHandler(Autodesk.Navisworks.Api.Application.ActiveDocument); + var revitServerPort = IpcHandler.StartLocalServerAndReturnPort(); + + var openProjectBrowserExecutablePath = GetOpenProjectBrowserExecutable(); + if (!File.Exists(openProjectBrowserExecutablePath)) + throw new SystemException("Browser executable not found."); + + var opBrowserServerPort = FreePortHelper.GetFreePort(); + var processArguments = $"ipc {opBrowserServerPort} {revitServerPort}"; + Environment.CurrentDirectory = System.IO.Path.GetDirectoryName(openProjectBrowserExecutablePath); + _opBrowserProcess = Process.Start(openProjectBrowserExecutablePath, processArguments); + IpcHandler.StartLocalClient(opBrowserServerPort); + Log.Information("IPC bridge started between port {port1} and {port2}.", + opBrowserServerPort, revitServerPort); + + + // Initialize document ModelItems dictionary -- for hiding objects. Disabled, because not optimized. + NavisworksUtils.ModelModelItems = null; + if (NavisworksUtils.ModelModelItems == null) + { + //NavisworksUtils.ModelItems = null; + //await GenerateModelItemDictAsync(Model.NavisworksWrapper.Document); + //MessageHandler.ShowWarning("Need to wait", + // "Dictionary of objects is in process, need to wait. This can take several minutes", + // "Dictionary of objects is in process, need to wait. This can take several minutes"); + //NavisworksUtils.ModelModelItems = new Dictionary>(); + // BuildModelDictionary(Model.NavisworksWrapper.Document).Wait(); + //BuildModelDictionary(Model.NavisworksWrapper.Document); + //NavisworksUtils.ModelItems = await BuildModelDictionaryAsync(); + //GenerateModelItemDict(Model.NavisworksWrapper.Document); + } + + } + + private static string GetOpenProjectBrowserExecutable() + { + var currentAssemblyPathUri = Assembly.GetExecutingAssembly().CodeBase; + var currentAssemblyPath = Uri.UnescapeDataString(new Uri(currentAssemblyPathUri).AbsolutePath).Replace("/", "\\"); + var currentFolder = Path.GetDirectoryName(currentAssemblyPath) ?? string.Empty; + + return Path.Combine(currentFolder, ConfigurationConstant.OpenProjectBrowserExecutablePath); + } + + + + // TODO: Next functions are for hiding objects feature. Disabled for now, because not optimized -------------------------------------------------------------------- + static void GenerateModelItemDict(Document doc) + { + Dictionary modelItems = new Dictionary(); + List hiddenModelItems = new List(); + foreach (ModelItem mi in doc.Models.RootItemDescendantsAndSelf) + { + var k = mi.InstanceGuid; + if (k != null) + { + var kStr = k.ToString(); + if (!modelItems.ContainsKey(kStr)) + modelItems.Add(kStr, mi); + // if (mi.IsHidden) + // hiddenModelItems.Add(mi); + } + + } + NavisworksUtils.ModelItems = modelItems; + //NavisworksUtils.HiddenModelItems = hiddenModelItems; + MessageHandler.ShowWarning( + "Object dictionary is not ready yet!", + "Object dictionary is ready, you can use ViewPoint now", + "Object dictionary is ready"); + } + + static async Task GenerateModelItemDictAsync(Document doc) + { + await Task.Run(() => GenerateModelItemDict(doc)); + } + + + static async Task BuildModelDictionary(Document doc) + { + + // Get all models + //var models = GetDocumentModels(doc); + var models = GetItemFromLastModelBranch(doc.Models.ToList()); + + // Async process each model + //foreach (var model in models) + //{ + // Task.Run(() => ProcessModelAsync(model)); + //} + foreach (var md in models) + { + if( md == null) + { + var stop =1 ; + } + // ProcessModelAsync(md); + } + + var tasks = models.Where(t => t != null).Select(ProcessModelAsync); + await Task.WhenAll(tasks); + + MessageHandler.ShowWarning( + "Object dictionary is not ready yet!", + "Object dictionary is ready, you can use ViewPoint now", + "Object dictionary is ready"); + + var dct = NavisworksUtils.ModelModelItems; + + return true; + } + static void ProcessModel(Autodesk.Navisworks.Api.Model model) + { + // Go through each ModelItem in model + Dictionary modelItems = new Dictionary(); + + var rootItem = model.RootItem; + var descendants = rootItem.Descendants; + + foreach (ModelItem item in descendants) + { + + // Getting GUID attribute for each ModelItem + var guid = item.InstanceGuid.ToString(); + + if (guid != null) + { + if (!modelItems.ContainsKey(guid)) + { + modelItems.Add(guid, item); + } + } + } + var modelFileName = Path.GetFileName(model.SourceFileName); + if (!NavisworksUtils.ModelModelItems.ContainsKey(modelFileName)) + NavisworksUtils.ModelModelItems.Add(modelFileName, modelItems); + } + + static async Task ProcessModelAsync(Autodesk.Navisworks.Api.Model model) + { + await Task.Run(() => ProcessModel(model)); + } + + public static List GetDocumentModels(Document doc) + { + var models = doc.Models; + + + if (models.Count == 1) + { + var mainModel = models[0]; + var mainModelModelItem = mainModel.RootItem; + var mainModelChildren = mainModelModelItem.Children; + return models[0].RootItem.Children.Select(ch => ch.Model).ToList(); + } + else + { + return models.ToList(); + } + + } + public static List GetItemFromLastModelBranch(List models) + { + List modelsResult = new List(); + foreach (var model in models) + { + var modelChildren = model.RootItem.Children.ToList(); + if (modelChildren.Count == 0 || modelChildren[0].Model == null) + { + modelsResult.Add(model); + } + else + { + modelsResult.AddRange(GetItemFromLastModelBranch(modelChildren.Select(t => t.Model).ToList())); + } + } + return modelsResult; + } +} + diff --git a/src/OpenProject.Navisworks/Extensions/BcfExtensions.cs b/src/OpenProject.Navisworks/Extensions/BcfExtensions.cs new file mode 100644 index 00000000..5327438c --- /dev/null +++ b/src/OpenProject.Navisworks/Extensions/BcfExtensions.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Autodesk.Navisworks.Api; +using iabi.BCF.APIObjects.V21; +using OpenProjectNavisworks.Data; +using OpenProject.Shared; +using OpenProject.Shared.BcfApi; +using OpenProject.Shared.Math3D; +using OpenProject.Shared.Math3D.Enumeration; +using Newtonsoft.Json.Linq; +using System.Xml.Linq; +using Newtonsoft.Json; +using System.Windows.Controls; +using Autodesk.Navisworks.Gui.Roamer; + +namespace OpenProjectNavisworks.Extensions +{ + /// + /// A namespace for static extensions for Revit document classes with focus on getting BCF data out + /// of the current state. + /// + public static class BcfExtensions + { + /// + /// Generates a model meant to send a viewpoint creation request to the BCF API from a Revit UI document. + /// + /// The Revit UI document + /// A BCF model for creating viewpoints. + public static Viewpoint_POST GenerateViewpoint(this Document uiDocument) + { + (Orthogonal_camera ortho, Perspective_camera perspective) = uiDocument.GetBcfCameraValues(); + + return new Viewpoint_POST + { + Clipping_planes = uiDocument.GetBcfClippingPlanes(), + Snapshot = uiDocument.GetBcfSnapshotData(), + Components = uiDocument.GetBcfComponents(), + Orthogonal_camera = ortho, + Perspective_camera = perspective + }; + } + + private static List GetBcfClippingPlanes(this Document uiDocument) + { + var sectionBox = uiDocument.GetBoundingBox(true); + var clippl = uiDocument.ActiveView.GetClippingPlanes(); + var myCleanJsonObject = JObject.Parse(clippl); + var orBox = myCleanJsonObject.GetValue("OrientedBox"); + var planes = myCleanJsonObject["Planes"]; + List cp = new List(); + + Vector3 minCorner, maxCorner, rotation; + // If section was created by box + if (orBox != null) { + var box = orBox.Value("Box"); + var minC = box.Value(0); + var maxC = box.Value(1); + Point3D minPoint = new Point3D(minC.Value(0), minC.Value(1), minC.Value(2)); + Point3D maxPoint = new Point3D(maxC.Value(0), maxC.Value(1), maxC.Value(2)); + minCorner = minPoint.ToVector3().ToMeters(); + maxCorner = maxPoint.ToVector3().ToMeters(); + + var rot = orBox.Value("Rotation"); + var rotX = rot.Value(0); + var rotY = rot.Value(1); + var rotZ = rot.Value(2); + + Point3D rotPoint = new Point3D(rotX, rotY, rotZ); + rotation = rotPoint.ToVector3(); + + cp = BcfApiExtensions.ToClippingPlanes(minCorner, maxCorner, rotation); + } + // If section was created by planes. TODO: not implemented yet, now just takes all objects (sectionBox) + else if (planes != null) + { + minCorner = sectionBox.Min.ToVector3().ToMeters(); + maxCorner = sectionBox.Max.ToVector3().ToMeters(); + Point3D rotPoint = new Point3D(0, 0, 0); + rotation = rotPoint.ToVector3(); + + cp = BcfApiExtensions.ToClippingPlanes(minCorner, maxCorner, rotation); + } + // If there was no sections + else + { + minCorner = sectionBox.Min.ToVector3().ToMeters(); + maxCorner = sectionBox.Max.ToVector3().ToMeters(); + Point3D rotPoint = new Point3D(0, 0, 0); + rotation = rotPoint.ToVector3(); + + cp = BcfApiExtensions.ToClippingPlanes(minCorner, maxCorner, rotation); + } + + // Checking if clipping planes enabled + var isSectionEnabled = myCleanJsonObject.Value("Enabled"); + //if (isSectionEnabled) + //{ + return cp; + //} + //else + //{ + // return null; + //} + + + } + + private static Snapshot_POST GetBcfSnapshotData(this Document uiDocument) + { + return new Snapshot_POST + { + Snapshot_type = Snapshot_type.Png, + Snapshot_data = "data:image/png;base64," + uiDocument.GetBase64RevitSnapshot() + }; + } + + private static Components GetBcfComponents(this Document uiDocument) + { + + //var selectedComponents2 = uiDocument.CurrentSelection.ToSelection() + // .GetSelectedItems() // ModelItemCollection + // .SelectMany(x => NavisworksDocumentExtensions.ElementDescendants(x)) + // .Distinct() + // .ToList(); + //foreach (var item in selectedComponents2) + //{ + // var parModel = item.Model; + // if (parModel != null) + // { var parModelName = parModel.FileName; } + + //} + + + // SELECTION + var selectedComponents = uiDocument.CurrentSelection.ToSelection() + .GetSelectedItems() // ModelItemCollection + .SelectMany(x => NavisworksDocumentExtensions.ElementDescendants(x)) + .Distinct() + .Select(uiDocument.ElementIdToComponentSelector()) + .ToList(); + + //// + //IEnumerable selectedComponents2 = uiDocument.CurrentSelection.ToSelection().GetSelectedItems(); + //var enumSel = selectedComponents2.GetEnumerator(); + + //List lstMI = new List(); + //while (enumSel.MoveNext()) + //{ + // lstMI.Add(enumSel.Current); + // var descItems = enumSel.Current.DescendantsAndSelf; + //} + //// + //var selectedComponents3 = uiDocument.CurrentSelection.ToSelection() + // .GetSelectedItems().SelectMany(x => NavisworksDocumentExtensions.ElementDescendants(x)).Distinct().ToList(); + + + //// VISIBILITY -- commeted for better times + ////create a store for the visible items + //ModelItemCollection visible = new ModelItemCollection(); + + + + ////uiDocument.CurrentSelection.SelectAll(); + ////var currentSelection = Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.SelectedItems; + ////Add all the items that are visible to the visible collection + ////foreach (ModelItem item in Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.SelectedItems) + ////{ + //// if (item.AncestorsAndSelf != null) + //// visible.AddRange(item.AncestorsAndSelf); + //// if (item.Descendants != null) + //// visible.AddRange(item.Descendants); + ////} + //var hiddenElementsOfView = uiDocument.Models.RootItemDescendantsAndSelf + // //.SelectMany(t=>t.DescendantsAndSelf) + // .Where(t => t.IsHidden); + //var hiddenElementsOfViewCount = hiddenElementsOfView.Count(); + ////new List(); + + //var visibleElementsOfViewCount = uiDocument.Models.RootItemDescendantsAndSelf.Count() - hiddenElementsOfViewCount; + ////.SelectMany(t => t.DescendantsAndSelf) + ////.Where(t => !t.IsHidden) + ////.Count(); //new List(); + ////foreach (ModelItem item in visible) + ////{ + //// if (item.IsHidden) + //// { + //// hiddenElementsOfView.Add(item); + //// } + //// else { visibleElementsOfView.Add(item); } + ////} + + //// Optimizing visibility + //var defaultVisibility = (hiddenElementsOfViewCount < visibleElementsOfViewCount); + //var exceptions = (defaultVisibility ? + // hiddenElementsOfView + // : + // uiDocument.Models.RootItemDescendantsAndSelf + // .Except(hiddenElementsOfView)); + ////.SelectMany(t => t.DescendantsAndSelf) + ////.Where(t => !t.IsHidden)); + + var exceptions = Enumerable.Empty(); + //var exceptions = NavisworksUtils.HiddenModelItems; + var defaultVisibility = true; + + + + var componentVisibility = new Components + { + Selection = selectedComponents, + Visibility = new iabi.BCF.APIObjects.V21.Visibility + { + Default_visibility = defaultVisibility, + Exceptions = exceptions + .Select(uiDocument.ElementIdToComponentSelector()) + .ToList() + } + }; + + // uiDocument.CurrentSelection.Clear(); + + return componentVisibility; + } + + private static (Orthogonal_camera orthogonalCamera, Perspective_camera perspectiveCamera) GetBcfCameraValues( + this Document uiDocument) + { + var view3D = uiDocument.CurrentViewpoint; + + bool isPerspective = view3D.Value.Projection == ViewpointProjection.Perspective; + CameraType cameraType = isPerspective ? CameraType.Perspective : CameraType.Orthogonal; + Position cameraPosition = uiDocument.GetCameraPosition(isPerspective); + Vector3 center = cameraPosition.Center.ToMeters(); + + var cameraViewpoint = new iabi.BCF.APIObjects.V21.Point + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(center.Z) + }; + + // Getting vector perpendicular to view + var nwv3d = cameraPosition.Up.ToNavisworksPoint3D().ToVector3D(); + var right = cameraPosition.Forward.ToNavisworksPoint3D().ToVector3D().Cross(nwv3d); + var upVorView = right.Cross(cameraPosition.Forward.ToNavisworksPoint3D().ToVector3D()).Normalize().Negate(); + + var cameraUpVector = new Direction + { + X = Convert.ToSingle(cameraPosition.Up.X), + Y = Convert.ToSingle(cameraPosition.Up.Y), + Z = Convert.ToSingle(cameraPosition.Up.Z) + }; + + var cameraDirection = new Direction + { + X = Convert.ToSingle(cameraPosition.Forward.X * 1), + Y = Convert.ToSingle(cameraPosition.Forward.Y * 1), + Z = Convert.ToSingle(cameraPosition.Forward.Z * 1) + }; + + Orthogonal_camera ortho = null; + Perspective_camera perspective = null; + + switch (cameraType) + { + case CameraType.Perspective: + var fov = (float)view3D.ToViewpoint().HeightField; + perspective = new Perspective_camera + { + Field_of_view = fov, + Camera_direction = cameraDirection, + Camera_up_vector = cameraUpVector, + Camera_view_point = cameraViewpoint + }; + break; + case CameraType.Orthogonal: + ortho = new Orthogonal_camera + { + View_to_world_scale = uiDocument.GetViewBoxHeight(), + Camera_direction = cameraDirection, + Camera_up_vector = cameraUpVector, + Camera_view_point = cameraViewpoint + }; + break; + default: + throw new ArgumentOutOfRangeException(nameof(cameraType), cameraType, "invalid camera type"); + } + + return (ortho, perspective); + } + + private static float GetViewBoxHeight(this Document uiDocument) + { + var sectionBox = uiDocument.GetBoundingBox(true); + var bottomLeft = sectionBox.Min; + var topRight = sectionBox.Max; + var heightVector = topRight - bottomLeft; + var widthVector = new Point3D(topRight.X,topRight.Y,0) - new Point3D(bottomLeft.X,bottomLeft.Y,0); + + var (viewBoxHeight, _) = (heightVector.Length, widthVector.Length); + //NavisworksUtils.ConvertToViewBoxValues(topRight, bottomLeft, uiDocument.ActiveView); + + var ViewToWorldScale = uiDocument.CurrentViewpoint.Value.HeightField; + + return Convert.ToSingle(ViewToWorldScale.ToMeters()); + } + + private static Position GetCameraPosition(this Document uiDocument, bool isPerspective) + { + var zoomCorners = uiDocument.GetBoundingBox(true); + var bottomLeft = zoomCorners.Min; + var topRight = zoomCorners.Max; + var viewCenter = uiDocument.CurrentViewpoint.Value.Position; + if (!isPerspective) + viewCenter = zoomCorners.Center; + var aa = uiDocument.CurrentViewpoint.Value.Rotation.ToAxisAndAngle(); + JObject json = JObject.Parse(uiDocument.CurrentViewpoint.Value.GetCamera()); + var vd = json["ViewDirection"]; + var ud = json["UpDirection"]; + var viewDirection = aa.Axis.ToVector3D(); + + return NavisworksUtils.TransformCameraPosition( + new ProjectPositionWrapper(), + new Position( + viewCenter.ToVector3(), + new Vector3(double.Parse(vd[0].ToString()).ToDecimal(), double.Parse(vd[1].ToString()).ToDecimal(), double.Parse(vd[2].ToString()).ToDecimal()), //viewDirection.Y + (new Vector3D( + double.Parse(ud[0].ToString()), + double.Parse(ud[1].ToString()), + double.Parse(ud[2].ToString()))) + .ToPoint3D().ToVector3())); //uiDocument.CurrentViewpoint.Value.WorldUpVector.ToVector3D() + } + + private static string GetBase64RevitSnapshot(this Document uiDocument) + { +#if N2024 + Bitmap image = uiDocument.ActiveView.GenerateImage(ImageGenerationStyle.Scene, 1200, 900, true); +#else + Bitmap image = uiDocument.ActiveView.GenerateImage(ImageGenerationStyle.Scene, 1200, 900); +#endif + + byte[] buffer; + using (var stream = new MemoryStream()) + { + image.Save(stream, System.Drawing.Imaging.ImageFormat.Png); + buffer = stream.ToArray(); + } + return Convert.ToBase64String(buffer); + } + + } +} diff --git a/src/OpenProject.Navisworks/Extensions/NavisWorksDocumentExtensions.cs b/src/OpenProject.Navisworks/Extensions/NavisWorksDocumentExtensions.cs new file mode 100644 index 00000000..f55b95a1 --- /dev/null +++ b/src/OpenProject.Navisworks/Extensions/NavisWorksDocumentExtensions.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +//using Autodesk.Revit.DB; +using OpenProjectNavisworks.Entry; +using Autodesk.Navisworks.Api; +using Autodesk.Navisworks.Api.Data; +using Autodesk.Navisworks.Api.Plugins; +using iabi.BCF.APIObjects.V21; +using OpenProject.Shared; +using OpenProject.Shared.Math3D.Enumeration; +using Serilog; +using System.IO; + +namespace OpenProjectNavisworks.Extensions +{ + /// + /// Extensions written for handling of classes of the Revit API. + /// + public static class NavisworksDocumentExtensions + { + private const string _openProjectOrthogonalViewName = "OpenProject Orthogonal"; + private const string _openProjectPerspectiveViewName = "OpenProject Perspective"; + + /// + /// Creates a map between revit element ids and their IFC GUIDs inside the given document. + /// + /// A revit document + /// A list of element ids + /// The map between IFC GUIDs and revit element ids. + public static Dictionary GetIfcGuidElementIdMap(this Document doc, + List elements) + { + var map = new Dictionary(); + foreach (ModelItem element in elements) + { + var ifcGuid = IfcGuid.ToIfcGuid(element.InstanceGuid); + if (!map.ContainsKey(ifcGuid)) + map.Add(ifcGuid, element); + } + + return map; + } + + /// + /// + /// + /// Navisworks document + /// + public static List GetVisibleElementsOfView(this Document doc) + { + var ids = new HashSet(); + var modelItems = new List(); + GetVisibleItems(doc.Models.RootItems, modelItems); + modelItems.ForEach(m => + { + if (m.InstanceGuid != Guid.Empty) + { + ids.Add(m); //NWGetter.GetIfcGuid(m) + } + else + { + var item = m.Ancestors.First(i => i.InstanceGuid != Guid.Empty); + ids.Add(item); //NWGetter.GetIfcGuid(m) + } + }); + + return ids.ToList(); + } + + private static void GetVisibleItems(ModelItemEnumerableCollection modelItems, List visibileItems) + { + foreach (var item in modelItems) + { + if (item.IsHidden == false) + { + if (item.HasGeometry) + visibileItems.Add(item); + else + GetVisibleItems(item.Children, visibileItems); + } + } + } + + /// + /// Gets all visible elements in the given view of the document. + /// + /// The Revit document + /// The Revit view + /// A list of element ids of all elements, that are currently visible. + //public static List GetVisibleElementsOfView(this Document doc, Autodesk.Navisworks.Api.View view) => + // new FilteredElementCollector(doc, view.Id) + // .WhereElementIsNotElementType() + // .WhereElementIsViewIndependent() + // .Where(element => element.CanBeHidden(view)) + // .Select(element => element.Id); + + /// + /// Gets all invisible elements in the given view of the document. + /// + /// The Revit document + /// The Revit view + /// A list of element ids of all elements, that are currently hidden. + //public static IEnumerable GetHiddenElementsOfView(this Document doc, View view) + //{ + // bool ElementIsHiddenInView(Element element) => + // element.IsHidden(view) || + // element.Category is { CategoryType: CategoryType.Model } && + // view.GetCategoryHidden(element.Category.Id); + + // return new FilteredElementCollector(doc) + // .WhereElementIsNotElementType() + // .WhereElementIsViewIndependent() + // .Where(ElementIsHiddenInView) + // .Select(element => element.Id); + //} + + /// + /// Gets a selector, that converts Revit element ids into BCF API components. + /// This is done in the context of a specific Revit Document. + /// + /// The Revit document + /// A selector converting to . + public static Func ElementIdToComponentSelector(this Document doc) + { + + return modelItem => new Component + { + Originating_system = "Navisworks " + OpenProjectNavisworks.Entry.RibbonButtonClickHandler.NavisVersion, + Ifc_guid = IfcGuid.ToIfcGuid(modelItem.InstanceGuid), //где-то лежит айдишник из ifc + //Authoring_tool_id = modelItem.InstanceGuid.ToString() + Authoring_tool_id = GetModelFileName(modelItem) + }; + } + + public static IEnumerable ElementDescendants(ModelItem modelItem) + { + if(Guid.Empty == modelItem.InstanceGuid) + { + return modelItem.Descendants.SelectMany(x => ElementDescendants(x)).ToList(); + } + else + return new List().Append(modelItem).AsEnumerable(); + } + public static ModelItem ElementParentModel(ModelItem modelItem) + { + if (modelItem.Parent != null) + { + if (modelItem.Model != null) + { + return modelItem; + } + return ElementParentModel(modelItem.Parent); + } + else + { + return modelItem; + } + } + public static String GetModelFileName(ModelItem modelItem) + { + + ModelItem parentModel = ElementParentModel(modelItem); + + if (parentModel.Model != null) + { + return Path.GetFileNameWithoutExtension(parentModel.Model.SourceFileName); + + } + else + { + return null; + } + } + + /// + /// Gets the correct 3D view for displaying OpenProject content. The type of the view is dependent of the requested + /// camera type, either orthogonal or perspective. If the view is not yet available, it is created. + /// + /// The current revit document. + /// The camera type for the requested view. + /// A with the correct settings to display OpenProject content. + /// Throws, if camera type is neither orthogonal nor perspective. + public static Autodesk.Navisworks.Api.View GetOpenProjectView(this Document doc, CameraType type) + { + var viewName = type switch + { + CameraType.Orthogonal => _openProjectOrthogonalViewName, + CameraType.Perspective => _openProjectPerspectiveViewName, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, "invalid camera type") + }; + + //if (openProjectView != null) + //{ + // Log.Information("View '{name}' already existent. Finished getting related view.", viewName); + // return openProjectView; + //} + + Log.Information("View '{name}' doesn't exist yet. Creating new view ...", viewName); + var openProjectView = doc.ActiveView;//.CurrentViewpoint.CreateCopy(); + + + + //using var trans = new Transaction(doc); + //trans.Start("Create open project view"); + + //openProjectView = type switch + //{ + // CameraType.Orthogonal => View3D.CreateIsometric(doc, doc.GetFamilyViews().First().Id), + // CameraType.Perspective => View3D.CreatePerspective(doc, doc.GetFamilyViews().First().Id), + // _ => throw new ArgumentOutOfRangeException(nameof(type), type, "invalid camera type") + //}; + + //openProjectView.Name = viewName; + //openProjectView.CropBoxActive = false; + //openProjectView.CropBoxVisible = false; + //openProjectView.DetailLevel = ViewDetailLevel.Fine; + //openProjectView.DisplayStyle = DisplayStyle.Realistic; + + //foreach (Category category in doc.Settings.Categories) + // if (category.CategoryType == CategoryType.Annotation && category.Name == "Levels") + // openProjectView.SetCategoryHidden(category.Id, true); + + //trans.Commit(); + + Log.Information("View '{name}' created. Finished getting related view.", viewName); + return openProjectView; + } + + //private static IEnumerable GetFamilyViews(this Document doc) + //{ + // return from elem in new FilteredElementCollector(doc).OfClass(typeof(ViewFamilyType)) + // let type = elem as ViewFamilyType + // where type.ViewFamily == ViewFamily.ThreeDimensional + // select type; + //} + + //private static IEnumerable Get3DViews(this Document doc) + //{ + // return from elem in new FilteredElementCollector(doc).OfClass(typeof(View3D)) + // let view = elem as View3D + // select view; + //} + } +} diff --git a/src/OpenProject.Navisworks/Model/Addin.cs b/src/OpenProject.Navisworks/Model/Addin.cs new file mode 100644 index 00000000..1c72858f --- /dev/null +++ b/src/OpenProject.Navisworks/Model/Addin.cs @@ -0,0 +1,95 @@ +using OpenProjectNavisworks.Command; + +namespace OpenProjectNavisworks.Model; + +public class Addin : IAddinNode +{ + public List ItemList + { + get => itemList; + set => itemList = value; + } + + public string FilePath + { + get => filePath; + set => filePath = value; + } + + public bool Save + { + get => save; + set => save = value; + } + + public bool Hidden + { + get => hidden; + set => hidden = value; + } + + public Addin(string filePath) + { + itemList = new List(); + this.filePath = filePath; + save = true; + } + + public Addin(string filePath, List itemList) + { + this.itemList = itemList; + this.filePath = filePath; + SortAddinItem(); + save = true; + } + + public void SortAddinItem() + { + itemList.Sort(new AddinItemComparer()); + } + + public void RemoveItem(AddinItem item) + { + itemList.Remove(item); + if (itemList.Count == 0) + { + AddinManagerBase.Instance.AddinManager.RemoveAddin(this); + } + } + + public void SaveToLocalIni(IniFile file) + { + if (itemList == null || itemList.Count == 0) + { + return; + } + file.WriteSection("ExternalCommands"); + file.Write("ExternalCommands", "ECCount", 0); + var num = 0; + foreach (var addinItem in itemList) + { + if (addinItem.Save) + { + WriteExternalCommand(file, addinItem, ++num); + } + } + + file.Write("ExternalCommands", "ECCount", num); + } + + private void WriteExternalCommand(IniFile file, AddinItem item, int number) + { + file.Write("ExternalCommands", "ECName" + number, item.Name); + file.Write("ExternalCommands", "ECClassName" + number, item.FullClassName); + file.Write("ExternalCommands", "ECAssembly" + number, item.AssemblyName); + file.Write("ExternalCommands", "ECDescription" + number, item.Description); + } + + private List itemList; + + private string filePath; + + private bool save; + + private bool hidden; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/AddinItem.cs b/src/OpenProject.Navisworks/Model/AddinItem.cs new file mode 100644 index 00000000..74c52c1c --- /dev/null +++ b/src/OpenProject.Navisworks/Model/AddinItem.cs @@ -0,0 +1,137 @@ +using System.IO; + +namespace OpenProjectNavisworks.Model; + +public class AddinItem : IAddinNode +{ + public AddinItem(AddinType type) + { + AddinType = type; + clientId = Guid.NewGuid(); + ClientIdString = clientId.ToString(); + assemblyPath = string.Empty; + AssemblyName = string.Empty; + FullClassName = string.Empty; + name = string.Empty; + Save = true; + VisibilityMode = VisibilityMode.AlwaysVisible; + } + + public AddinItem(string assemblyPath, Guid clientId, string fullClassName, AddinType type) + { + + AddinType = type; + this.assemblyPath = assemblyPath; + AssemblyName = Path.GetFileName(this.assemblyPath); + this.clientId = clientId; + ClientIdString = clientId.ToString(); + FullClassName = fullClassName; + var num = fullClassName.LastIndexOf(".", StringComparison.Ordinal); + name = fullClassName.Substring(num + 1); + Save = true; + VisibilityMode = VisibilityMode.AlwaysVisible; + } + + public void SaveToManifest() + { + var manifestFile = new ManifestFile(name + DefaultSetting.FormatExAddin); + if (AddinType == AddinType.Application) + { + manifestFile.Applications.Add(this); + } + else if (AddinType == AddinType.Command) + { + manifestFile.Commands.Add(this); + } + manifestFile.Save(); + } + + public AddinType AddinType { get; set; } + + public string AssemblyPath + { + get => assemblyPath; + set + { + assemblyPath = value; + AssemblyName = Path.GetFileName(assemblyPath); + } + } + + public string AssemblyName { get; set; } + + public Guid ClientId + { + get => clientId; + set + { + clientId = value; + ClientIdString = clientId.ToString(); + } + } + + protected internal string ClientIdString { get; set; } + + public string FullClassName { get; set; } + + public string Name + { + get + { + if (string.IsNullOrEmpty(name)) + { + return "External Tool"; + } + return name; + } + set + { + if (!string.IsNullOrEmpty(value)) + { + name = value; + return; + } + name = "External Tool"; + } + } + + public string Description + { + get + { + if (string.IsNullOrEmpty(description)) + { + return string.Empty; + } + return description; + } + set + { + if (string.IsNullOrEmpty(value)) + { + description = string.Empty; + return; + } + description = value; + } + } + + public VisibilityMode VisibilityMode { get; set; } + + public bool Save { get; set; } + + public bool Hidden { get; set; } + + public override string ToString() + { + return name; + } + + protected string assemblyPath; + + protected Guid clientId; + + private string name; + + private string description; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/AddinItemComparer.cs b/src/OpenProject.Navisworks/Model/AddinItemComparer.cs new file mode 100644 index 00000000..cdb51962 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/AddinItemComparer.cs @@ -0,0 +1,12 @@ +namespace OpenProjectNavisworks.Model; + +/// +/// Compare Sort By Full Class Name Method +/// +public class AddinItemComparer : IComparer +{ + public int Compare(AddinItem x, AddinItem y) + { + return string.Compare(x.FullClassName, y.FullClassName, StringComparison.Ordinal); + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/AddinType.cs b/src/OpenProject.Navisworks/Model/AddinType.cs new file mode 100644 index 00000000..be7f21c2 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/AddinType.cs @@ -0,0 +1,10 @@ +namespace OpenProjectNavisworks.Model; + +[Flags] +public enum AddinType +{ + Invalid = 0, + Command = 1, + Application = 2, + Mixed = 3 +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/Addins.cs b/src/OpenProject.Navisworks/Model/Addins.cs new file mode 100644 index 00000000..8fe222e8 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/Addins.cs @@ -0,0 +1,101 @@ +using System.IO; +using System.Reflection; +using System.Windows; +using Autodesk.Navisworks.Api.Plugins; + +namespace OpenProjectNavisworks.Model; + +public abstract class Addins +{ + public SortedDictionary AddinDict + { + get => addinDict; + set => addinDict = value; + } + + public int Count => addinDict.Count; + + public Addins() + { + addinDict = new SortedDictionary(); + } + + public void SortAddin() + { + foreach (var addin in addinDict.Values) + { + addin.SortAddinItem(); + } + } + + public void AddAddIn(Addin addin) + { + var fileName = Path.GetFileName(addin.FilePath); + if (addinDict.ContainsKey(fileName)) + { + addinDict.Remove(fileName); + } + addinDict[fileName] = addin; + } + + public bool RemoveAddIn(Addin addin) + { + var fileName = Path.GetFileName(addin.FilePath); + if (addinDict.ContainsKey(fileName)) + { + addinDict.Remove(fileName); + return true; + } + return false; + } + + public void AddItem(AddinItem item) + { + var assemblyName = item.AssemblyName; + if (!addinDict.ContainsKey(assemblyName)) + { + addinDict[assemblyName] = new Addin(item.AssemblyPath); + } + addinDict[assemblyName].ItemList.Add(item); + } + + public List LoadItems(Assembly assembly, string fullName, string originalAssemblyFilePath, AddinType type) + { + var list = new List(); + Type[] array; + try + { + array = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + array = ex.Types; + if (array == null) + { + return list; + } + } + foreach (var type2 in array) + { + try + { + if (!(null == type2) && type2.IsSubclassOf(typeof(AddInPlugin))) + { + var item = new AddinItem(originalAssemblyFilePath, Guid.NewGuid(), type2.FullName, type); + list.Add(item); + } + } + catch (Exception e) + { + throw new ArgumentException(e.ToString()); + } + } + return list; + } + + protected SortedDictionary addinDict; + + protected int maxCount = 100; + + protected int count; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/AssemLoader.cs b/src/OpenProject.Navisworks/Model/AssemLoader.cs new file mode 100644 index 00000000..42404b40 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/AssemLoader.cs @@ -0,0 +1,311 @@ +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Windows; +using OpenProjectNavisworks.View; + +namespace OpenProjectNavisworks.Model; + +public class AssemLoader +{ + public string OriginalFolder + { + get => originalFolder; + set => originalFolder = value; + } + + public string TempFolder + { + get => tempFolder; + set => tempFolder = value; + } + + public AssemLoader() + { + tempFolder = string.Empty; + refedFolders = new List(); + copiedFiles = new Dictionary(); + } + + public void CopyGeneratedFilesBack() + { + var files = Directory.GetFiles(tempFolder, "*.*", SearchOption.AllDirectories); + if(!files.Any()) return; + foreach (var text in files) + { + if (copiedFiles.ContainsKey(text)) + { + var t = copiedFiles[text]; + var fileInfo = new FileInfo(text); + if (fileInfo.LastWriteTime > t) + { + var str = text.Remove(0, tempFolder.Length); + var destinationFilename = originalFolder + str; + FileUtils.CopyFile(text, destinationFilename); + } + } + else + { + var str2 = text.Remove(0, tempFolder.Length); + var destinationFilename2 = originalFolder + str2; + FileUtils.CopyFile(text, destinationFilename2); + } + } + } + + public void HookAssemblyResolve() + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + + public void UnhookAssemblyResolve() + { + AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve; + } + + public Assembly LoadAddinsToTempFolder(string originalFilePath, bool parsingOnly) + { + if (string.IsNullOrEmpty(originalFilePath) || !File.Exists(originalFilePath)) + { + return null; + } + this.parsingOnly = parsingOnly; + originalFolder = Path.GetDirectoryName(originalFilePath); + var stringBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(originalFilePath)); + if (parsingOnly) + { + stringBuilder.Append("-Parsing-"); + } + else + { + stringBuilder.Append("-Executing-"); + } + tempFolder = FileUtils.CreateTempFolder(stringBuilder.ToString()); + var assembly = CopyAndLoadAddin(originalFilePath, parsingOnly); + + if (null == assembly || !IsAPIReferenced(assembly)) + { + return null; + } + return assembly; + } + + private Assembly CopyAndLoadAddin(string srcFilePath, bool onlyCopyRelated) + { + var text = string.Empty; + if (!FileUtils.FileExistsInFolder(srcFilePath, tempFolder)) + { + var directoryName = Path.GetDirectoryName(srcFilePath); + if (!refedFolders.Contains(directoryName)) + { + refedFolders.Add(directoryName); + } + var list = new List(); + text = FileUtils.CopyFileToFolder(srcFilePath, tempFolder, onlyCopyRelated, list); + if (string.IsNullOrEmpty(text)) + { + return null; + } + foreach (var fileInfo in list) + { + copiedFiles.Add(fileInfo.FullName, fileInfo.LastWriteTime); + } + } + return LoadAddin(text); + } + + private Assembly LoadAddin(string filePath) + { + Assembly result = null; + try + { + Monitor.Enter(this); + //Agree this error to load depend event assembly, see https://github.com/chuongmep/RevitAddInManager/issues/7 + result = Assembly.LoadFile(filePath); + } + finally + { + Monitor.Exit(this); + } + return result; + } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + Assembly result; + new AssemblyName(args.Name); + var text = SearchAssemblyFileInTempFolder(args.Name); + if (File.Exists(text)) + { + result = LoadAddin(text); + } + else + { + text = SearchAssemblyFileInOriginalFolders(args.Name); + if (string.IsNullOrEmpty(text)) + { + var array = args.Name.Split(new char[] + { + ',' + }); + var text2 = array[0]; + if (array.Length > 1) + { + var text3 = array[2]; + if (text2.EndsWith(".resources", StringComparison.CurrentCultureIgnoreCase) && !text3.EndsWith("neutral", StringComparison.CurrentCultureIgnoreCase)) + { + text2 = text2.Substring(0, text2.Length - ".resources".Length); + } + text = SearchAssemblyFileInTempFolder(text2); + if (File.Exists(text)) + { + return LoadAddin(text); + } + text = SearchAssemblyFileInOriginalFolders(text2); + } + } + if (string.IsNullOrEmpty(text)) + { + var loader = new AssemblyLoader(args.Name); + loader.WindowStartupLocation = WindowStartupLocation.CenterScreen; + if (loader.ShowDialog() != true) + { + return null; + } + text = loader.resultPath; + } + result = CopyAndLoadAddin(text, true); + } + + return result; + } + + private string SearchAssemblyFileInTempFolder(string assemName) + { + try + { + var array = new string[] { ".dll", ".exe" }; + var text = string.Empty; + var str = assemName.Substring(0, assemName.IndexOf(',')); + foreach (var str2 in array) + { + text = tempFolder + "\\" + str + str2; + + if (File.Exists(text)) + { + return text; + } + } + } + catch (Exception e) + { + throw new ArgumentException(e.ToString()); + } + return string.Empty; + } + + private string SearchAssemblyFileInOriginalFolders(string assemName) + { + var array = new string[] + { + ".dll", + ".exe" + }; + string text; + var text2 = assemName.Substring(0, assemName.IndexOf(',')); + foreach (var str in array) + { + text = dotnetDir + "\\" + text2 + str; + if (File.Exists(text)) + { + return text; + } + } + foreach (var str2 in array) + { + foreach (var str3 in refedFolders) + { + text = str3 + "\\" + text2 + str2; + if (File.Exists(text)) + { + return text; + } + } + } + try + { + var directoryInfo = new DirectoryInfo(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName)); + var path = directoryInfo.Parent?.FullName + "\\Regression\\_RegressionTools\\"; + if (Directory.Exists(path)) + { + foreach (var text3 in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)) + { + if (Path.GetFileNameWithoutExtension(text3).Equals(text2, StringComparison.OrdinalIgnoreCase)) + { + return text3; + } + } + } + } + catch (Exception e) + { + throw new ArgumentException(e.ToString()); + } + + try + { + var num = assemName.IndexOf("XMLSerializers", StringComparison.OrdinalIgnoreCase); + if (num != -1) + { + assemName = "System.XML" + assemName.Substring(num + "XMLSerializers".Length); + return SearchAssemblyFileInOriginalFolders(assemName); + } + } + catch (Exception e) + { + throw new ArgumentException(e.ToString()); + } + return null; + } + + private bool IsAPIReferenced(Assembly assembly) + { + + if (string.IsNullOrEmpty(revitApiAssemblyFullName)) + { + foreach (var assembly2 in AppDomain.CurrentDomain.GetAssemblies()) + { + if (String.Compare(assembly2.GetName().Name, DefaultSetting.AssNavisName, StringComparison.OrdinalIgnoreCase) == 0) + { + revitApiAssemblyFullName = assembly2.GetName().Name; + break; + } + } + } + foreach (var assemblyName in assembly.GetReferencedAssemblies()) + { + if (revitApiAssemblyFullName == assemblyName.Name) + { + return true; + } + } + return false; + } + + private readonly List refedFolders; + + private readonly Dictionary copiedFiles; + + private bool parsingOnly; + + private string originalFolder; + + private string tempFolder; + + private static string dotnetDir = Environment.GetEnvironmentVariable("windir") + "\\Microsoft.NET\\Framework\\v2.0.50727"; + + public static string ResolvedAssemPath = string.Empty; + + private string revitApiAssemblyFullName; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/BitmapSourceConverter.cs b/src/OpenProject.Navisworks/Model/BitmapSourceConverter.cs new file mode 100644 index 00000000..48b4b66d --- /dev/null +++ b/src/OpenProject.Navisworks/Model/BitmapSourceConverter.cs @@ -0,0 +1,69 @@ +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace OpenProjectNavisworks.Model; + +public static class BitmapSourceConverter +{ + public enum ImageType + { + Small, + Large + } + + public static ImageSource ToImageSource(Bitmap bitmap, ImageType imageType) + { + switch (imageType) + { + case ImageType.Small: + return ToImageSource(bitmap).Resize(16); + + case ImageType.Large: + return ToImageSource(bitmap).Resize(32); + + default: + throw new ArgumentOutOfRangeException(nameof(imageType), imageType, null); + } + } + + public static BitmapImage ToImageSource(Bitmap bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms, ImageFormat.Png); + var image = new BitmapImage(); + image.BeginInit(); + ms.Seek(0, SeekOrigin.Begin); + image.StreamSource = ms; + image.EndInit(); + return image; + } + + /// + /// Resize ImageResource + /// + /// + /// + /// + public static ImageSource Resize(this ImageSource imageSource, int size) + { + return Thumbnail(imageSource, size); + } + + private static ImageSource Thumbnail(ImageSource source, int size) + { + var rect = new Rect(0, 0, size, size); + var drawingVisual = new DrawingVisual(); + using (var drawingContext = drawingVisual.RenderOpen()) + { + drawingContext.DrawImage(source, rect); + } + var resizedImage = new RenderTargetBitmap((int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default); + resizedImage.Render(drawingVisual); + + return resizedImage; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/CodeListener.cs b/src/OpenProject.Navisworks/Model/CodeListener.cs new file mode 100644 index 00000000..65ed5dc5 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/CodeListener.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using System.IO; + +namespace OpenProjectNavisworks.Model; + +public class CodeListener : TraceListener +{ + public override void Write(string message) + { + WriteMessage(message); + } + + public override void WriteLine(string message) + { + WriteMessage(message); + } + + void WriteMessage(string message) + { + using (StreamWriter st = new StreamWriter(DefaultSetting.PathLogFile, true)) + { + string join = string.Join(": ", $"{DateTime.Now}", message); + st.WriteLine(join); + st.Close(); + } + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/DefaultSetting.cs b/src/OpenProject.Navisworks/Model/DefaultSetting.cs new file mode 100644 index 00000000..962a5882 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/DefaultSetting.cs @@ -0,0 +1,50 @@ +using System.IO; + +namespace OpenProjectNavisworks.Model; + +/// +/// All setting name default for addin +/// +public static class DefaultSetting +{ + public static string AppName = "Navis Addin Manager"; + public static string FileName = "ExternalTool"; + public static string AssNavisName = "Autodesk.Navisworks.Api"; + public static string FormatExAddin = ".addin"; + public static string FormatDisable = ".disable"; + + public static string IniName = "Navis.ini"; + + public static string TempFolderName = "NavisAddins"; + + public static string AimInternalName = "AimInternalNavis.ini"; + public static string DirLogFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "AppData", + "Local", + "Temp",TempFolderName); + + private static string pathLogFile; + public static string PathLogFile + { + get + { + + bool flag = Directory.Exists(DirLogFile); + if (!flag) Directory.CreateDirectory(DirLogFile); + DirectoryInfo directoryInfo = new DirectoryInfo(DirLogFile); + FileInfo fileInfo = directoryInfo.GetFiles("*.txt",SearchOption.TopDirectoryOnly).OrderBy(x=>x.LastWriteTime).LastOrDefault(); + if (fileInfo==null) + { + pathLogFile = Path.Combine(DirLogFile, $"{Guid.NewGuid()}.txt"); + File.Create(pathLogFile).Close(); + return pathLogFile; + } + else + { + pathLogFile = fileInfo.FullName; + } + return fileInfo.FullName; + } + set => pathLogFile = value; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/FileUtils.cs b/src/OpenProject.Navisworks/Model/FileUtils.cs new file mode 100644 index 00000000..ba5b9dca --- /dev/null +++ b/src/OpenProject.Navisworks/Model/FileUtils.cs @@ -0,0 +1,240 @@ +using System.IO; +using System.Windows; +using MessageBox = System.Windows.MessageBox; + +namespace OpenProjectNavisworks.Model; + +public static class FileUtils +{ + public static DateTime GetModifyTime(string filePath) + { + return File.GetLastWriteTime(filePath); + } + + public static string CreateTempFolder(string prefix) + { + var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var tempPath = Path.Combine(folderPath, "Temp"); + var directoryInfo = new DirectoryInfo(Path.Combine(tempPath, DefaultSetting.TempFolderName)); + if (!directoryInfo.Exists) + { + directoryInfo.Create(); + } + foreach (var directoryInfo2 in directoryInfo.GetDirectories()) + { + try + { + Directory.Delete(directoryInfo2.FullName, true); + } + catch + { + // ignored + } + } + var str = $"{DateTime.Now:yyyyMMdd_HHmmss_ffff}"; + var path = Path.Combine(directoryInfo.FullName, prefix + str); + var directoryInfo3 = new DirectoryInfo(path); + directoryInfo3.Create(); + return directoryInfo3.FullName; + } + + public static void SetWriteable(string fileName) + { + if (File.Exists(fileName)) + { + var fileAttributes = File.GetAttributes(fileName) & ~FileAttributes.ReadOnly; + File.SetAttributes(fileName, fileAttributes); + } + } + + public static bool SameFile(string file1, string file2) + { + return 0 == String.Compare(file1.Trim(), file2.Trim(), StringComparison.OrdinalIgnoreCase); + } + + public static bool CreateFile(string filePath) + { + if (File.Exists(filePath)) + { + return true; + } + try + { + var directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + using (new FileInfo(filePath).Create()) + { + SetWriteable(filePath); + } + } + catch (Exception) + { + return false; + } + return File.Exists(filePath); + } + + public static void DeleteFile(string fileName) + { + if (File.Exists(fileName)) + { + var fileAttributes = File.GetAttributes(fileName) & ~FileAttributes.ReadOnly; + File.SetAttributes(fileName, fileAttributes); + try + { + File.Delete(fileName); + } + catch (Exception e) + { + MessageBox.Show(e.ToString()); + } + } + } + + public static bool FileExistsInFolder(string filePath, string destFolder) + { + var path = Path.Combine(destFolder, Path.GetFileName(filePath)); + return File.Exists(path); + } + + public static string CopyFileToFolder(string sourceFilePath, string destFolder, bool onlyCopyRelated, List allCopiedFiles) + { + if (!File.Exists(sourceFilePath)) + { + return null; + } + var directoryName = Path.GetDirectoryName(sourceFilePath); + if (onlyCopyRelated) + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(sourceFilePath); + var searchPattern = fileNameWithoutExtension + ".*"; + var files = Directory.GetFiles(directoryName, searchPattern, SearchOption.TopDirectoryOnly); + foreach (var text in files) + { + var fileName = Path.GetFileName(text); + var text2 = Path.Combine(destFolder, fileName); + var flag = CopyFile(text, text2); + if (flag) + { + var item = new FileInfo(text2); + allCopiedFiles.Add(item); + } + } + } + else + { + var folderSize = GetFolderSize(directoryName); + if (folderSize > 50L) + { + switch (FolderTooBigDialog.Show(directoryName, folderSize)) + { + case MessageBoxResult.Yes: + CopyDirectory(directoryName, destFolder, allCopiedFiles); + break; + + case MessageBoxResult.No: + CopyFileToFolder(sourceFilePath, destFolder, true, allCopiedFiles); + break; + + default: + return null; + } + } + else + { + CopyDirectory(directoryName, destFolder, allCopiedFiles); + } + } + var text3 = Path.Combine(destFolder, Path.GetFileName(sourceFilePath)); + if (File.Exists(text3)) + { + return text3; + } + return null; + } + + public static bool CopyFile(string sourceFilename, string destinationFilename) + { + if (!File.Exists(sourceFilename)) + { + return false; + } + var fileAttributes = File.GetAttributes(sourceFilename) & ~FileAttributes.ReadOnly; + File.SetAttributes(sourceFilename, fileAttributes); + if (File.Exists(destinationFilename)) + { + var fileAttributes2 = File.GetAttributes(destinationFilename) & ~FileAttributes.ReadOnly; + File.SetAttributes(destinationFilename, fileAttributes2); + File.Delete(destinationFilename); + } + try + { + if (!Directory.Exists(Path.GetDirectoryName(destinationFilename))) + { + Directory.CreateDirectory(Path.GetDirectoryName(destinationFilename)); + } + File.Copy(sourceFilename, destinationFilename, true); + } + catch (Exception) + { + return false; + } + return File.Exists(destinationFilename); + } + + public static void CopyDirectory(string sourceDir, string desDir, List allCopiedFiles) + { + try + { + var directories = Directory.GetDirectories(sourceDir, "*.*", SearchOption.AllDirectories); + foreach (var text in directories) + { + var str = text.Replace(sourceDir, ""); + var path = desDir + str; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + } + var files = Directory.GetFiles(sourceDir, "*.*", SearchOption.AllDirectories); + foreach (var text2 in files) + { + var str2 = text2.Replace(sourceDir, ""); + var text3 = desDir + str2; + if (!Directory.Exists(Path.GetDirectoryName(text3))) + { + Directory.CreateDirectory(Path.GetDirectoryName(text3)); + } + if (CopyFile(text2, text3)) + { + allCopiedFiles.Add(new FileInfo(text3)); + } + } + } + catch + { + // ignored + } + } + + public static long GetFolderSize(string folderPath) + { + var directoryInfo = new DirectoryInfo(folderPath); + var num = 0L; + foreach (var fileSystemInfo in directoryInfo.GetFileSystemInfos()) + { + if (fileSystemInfo is FileInfo) + { + num += ((FileInfo)fileSystemInfo).Length; + } + else + { + num += GetFolderSize(fileSystemInfo.FullName); + } + } + return num / 1024L / 1024L; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/FolderTooBigDialog.cs b/src/OpenProject.Navisworks/Model/FolderTooBigDialog.cs new file mode 100644 index 00000000..6d3fdffa --- /dev/null +++ b/src/OpenProject.Navisworks/Model/FolderTooBigDialog.cs @@ -0,0 +1,26 @@ +using System.Text; +using System.Windows; +using MessageBox = System.Windows.MessageBox; + +namespace OpenProjectNavisworks.Model; + +internal static class FolderTooBigDialog +{ + /// + /// Show a waring if file dll too large + /// + /// folder contains file resource + /// limit of dll size + /// + public static MessageBoxResult Show(string folderPath, long sizeInMB) + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Folder [" + folderPath + "]"); + stringBuilder.AppendLine("is " + sizeInMB + "MB large."); + stringBuilder.AppendLine("AddinManager will attempt to copy all the files to temp folder"); + stringBuilder.AppendLine("Select [Yes] to copy all the files to temp folder"); + stringBuilder.AppendLine("Select [No] to only copy test script DLL"); + var text = stringBuilder.ToString(); + return MessageBox.Show(text, DefaultSetting.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Asterisk); + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/IAddinNode.cs b/src/OpenProject.Navisworks/Model/IAddinNode.cs new file mode 100644 index 00000000..b884d993 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/IAddinNode.cs @@ -0,0 +1,8 @@ +namespace OpenProjectNavisworks.Model; + +public interface IAddinNode +{ + bool Save { get; set; } + + bool Hidden { get; set; } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/IEConverter.cs b/src/OpenProject.Navisworks/Model/IEConverter.cs new file mode 100644 index 00000000..6f498709 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/IEConverter.cs @@ -0,0 +1,25 @@ +using System.Collections.ObjectModel; + +namespace OpenProjectNavisworks.Model; + +/// +/// Quick convert collection +/// +public static class IEConverter +{ + /// + /// Convert IEnumerableUtils To ObservableCollection + /// + /// + /// + /// + public static ObservableCollection ToObservableCollection(this IEnumerable source) + { + var newSource = new ObservableCollection(); + foreach (var t in source) + { + newSource.Add(t); + } + return newSource; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/IniFile.cs b/src/OpenProject.Navisworks/Model/IniFile.cs new file mode 100644 index 00000000..a94b262e --- /dev/null +++ b/src/OpenProject.Navisworks/Model/IniFile.cs @@ -0,0 +1,56 @@ +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace OpenProjectNavisworks.Model; + +public class IniFile +{ + public string FilePath => filePath; + + public IniFile(string filePath) + { + this.filePath = filePath; + if (!File.Exists(this.filePath)) + { + FileUtils.CreateFile(this.filePath); + FileUtils.SetWriteable(this.filePath); + } + } + + public void WriteSection(string iniSection) + { + WritePrivateProfileSection(iniSection, null, filePath); + } + + public void Write(string iniSection, string iniKey, object iniValue) + { + WritePrivateProfileString(iniSection, iniKey, iniValue.ToString(), filePath); + } + + public string ReadString(string iniSection, string iniKey) + { + var stringBuilder = new StringBuilder(255); + GetPrivateProfileString(iniSection, iniKey, "", stringBuilder, 255, filePath); + return stringBuilder.ToString(); + } + + public int ReadInt(string iniSection, string iniKey) + { + return GetPrivateProfileInt(iniSection, iniKey, 0, filePath); + } + + [DllImport("kernel32.dll")] + private static extern int WritePrivateProfileSection(string lpAppName, string lpString, string lpFileName); + + [DllImport("kernel32", CharSet = CharSet.Auto)] + private static extern int WritePrivateProfileString(string section, string key, string val, string filePath); + + [DllImport("kernel32")] + private static extern int GetPrivateProfileInt(string section, string key, int def, string filePath); + + [DllImport("kernel32", CharSet = CharSet.Auto)] + private static extern int GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder retVal, int size, string filePath); + + private readonly string filePath; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/LogMessageString.cs b/src/OpenProject.Navisworks/Model/LogMessageString.cs new file mode 100644 index 00000000..186557af --- /dev/null +++ b/src/OpenProject.Navisworks/Model/LogMessageString.cs @@ -0,0 +1,33 @@ +using System.Windows; + +namespace OpenProjectNavisworks.Model; + +public class LogMessageString +{ + private int DEFAULD_FONT_SIZE = 12; + public string Message { get; set; } + public System.Windows.Media.Brush MessageColor { get; set; } + public FontWeight FontWeight { get; set; } + public int FontSize { get; set; } + public LogMessageString(string message, System.Windows.Media.Brush colore) + { + Message = message; + MessageColor = colore; + FontWeight = FontWeights.Normal; + FontSize = DEFAULD_FONT_SIZE; + } + public LogMessageString(string message, System.Windows.Media.Brush colore, FontWeight fontWeight) + { + Message = message; + MessageColor = colore; + FontWeight = fontWeight; + FontSize = DEFAULD_FONT_SIZE; + } + public LogMessageString(string message, System.Windows.Media.Brush colore, FontWeight fontWeight, int fontSize) + { + Message = message; + MessageColor = colore; + FontWeight = fontWeight; + FontSize = fontSize; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/ManifestFile.cs b/src/OpenProject.Navisworks/Model/ManifestFile.cs new file mode 100644 index 00000000..df346913 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/ManifestFile.cs @@ -0,0 +1,422 @@ +using System.IO; +using System.Reflection; +using System.Text; +using System.Xml; + +namespace OpenProjectNavisworks.Model; + +public class ManifestFile +{ + public ManifestFile() + { + local = false; + applications = new List(); + commands = new List(); + } + + public ManifestFile(string fileName) : this() + { + this.fileName = fileName; + if (string.IsNullOrEmpty(filePath)) + { + var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AddIn"); + filePath = Path.Combine(path, this.fileName); + } + } + + public ManifestFile(bool local) : this() + { + this.local = local; + } + + public void Load() + { + xmlDoc = new XmlDocument(); + xmlDoc.Load(filePath); + var documentElement = xmlDoc.DocumentElement; + if (!documentElement.Name.Equals(ROOT_NODE)) + { + throw new ArgumentException(INCORRECT_NODE); + } + if (documentElement.ChildNodes.Count == 0) + { + throw new ArgumentException(EMPTY_ADDIN); + } + applications.Clear(); + commands.Clear(); + foreach (var obj in documentElement.ChildNodes) + { + var xmlNode = (XmlNode)obj; + if (!xmlNode.Name.Equals(ADDIN_NODE) || xmlNode.Attributes.Count != 1) + { + throw new ArgumentException(INCORRECT_NODE); + } + var xmlAttribute = xmlNode.Attributes[0]; + if (xmlAttribute.Value.Equals(APPLICATION_NODE)) + { + ParseExternalApplications(xmlNode); + } + else + { + if (!xmlAttribute.Value.Equals(COMMAND_NODE)) + { + throw new ArgumentException(INCORRECT_NODE); + } + ParseExternalCommands(xmlNode); + } + } + } + + public void Save() + { + SaveAs(filePath); + } + + public void SaveAs(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentNullException(FILENAME_NULL_OR_EMPTY); + } + if (!filePath.ToLower().EndsWith(DefaultSetting.FormatExAddin)) + { + throw new ArgumentException(FILENAME_INCORRECT_WARNING + filePath); + } + var directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + var fileInfo = new FileInfo(filePath); + xmlDoc = new XmlDocument(); + CreateXmlForManifest(); + if (File.Exists(filePath)) + { + File.SetAttributes(filePath, FileAttributes.Normal); + } + TextWriter w = new StreamWriter(filePath, false, Encoding.UTF8); + var xmlTextWriter = new XmlTextWriter(w); + xmlTextWriter.Formatting = Formatting.Indented; + xmlDoc.Save(xmlTextWriter); + xmlTextWriter.Close(); + this.filePath = fileInfo.FullName; + fileName = Path.GetFileName(fileInfo.FullName); + } + + public string FileName + { + get => fileName; + set => fileName = value; + } + + public bool Local + { + get => local; + set => local = value; + } + + private string _vendorDescription; + + public string VendorDescription + { + get => _vendorDescription; + set => _vendorDescription = value; + } + + public string FilePath + { + get + { + if (string.IsNullOrEmpty(filePath)) + { + var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AddIn"); + filePath = Path.Combine(path, DefaultSetting.AimInternalName); + } + return filePath; + } + set => filePath = value; + } + + public List Applications + { + get => applications; + set => applications = value; + } + + public List Commands + { + get => commands; + set => commands = value; + } + + private XmlDocument CreateXmlForManifest() + { + var xmlNode = xmlDoc.AppendChild(xmlDoc.CreateElement(ROOT_NODE)); + foreach (var currentApp in applications) + { + var xmlElement = xmlDoc.CreateElement(ADDIN_NODE); + xmlElement.SetAttribute(TYPE_ATTRIBUTE, APPLICATION_NODE); + xmlNode.AppendChild(xmlElement); + AddApplicationToXmlElement(xmlElement, currentApp); + var xmlElement2 = xmlDoc.CreateElement(VENDORID); + xmlElement2.InnerText = "ADSK"; + xmlElement.AppendChild(xmlElement2); + xmlElement2 = xmlDoc.CreateElement(VENDORDESCRIPTION); + xmlElement2.InnerText = "Autodesk, www.autodesk.com"; + xmlElement.AppendChild(xmlElement2); + } + foreach (var command in commands) + { + var xmlElement3 = xmlDoc.CreateElement(ADDIN_NODE); + xmlElement3.SetAttribute(TYPE_ATTRIBUTE, COMMAND_NODE); + xmlNode.AppendChild(xmlElement3); + AddCommandToXmlElement(xmlElement3, command); + var xmlElement4 = xmlDoc.CreateElement(VENDORID); + xmlElement4.InnerText = "ADSK"; + xmlElement3.AppendChild(xmlElement4); + xmlElement4 = xmlDoc.CreateElement(VENDORDESCRIPTION); + if (VendorDescription == string.Empty) xmlElement4.InnerText = "Autodesk, www.autodesk.com"; + else xmlElement4.InnerText = VendorDescription; + xmlElement3.AppendChild(xmlElement4); + } + return xmlDoc; + } + + private void AddAddInItemToXmlElement(XmlElement xmlEle, AddinItem addinItem) + { + if (!string.IsNullOrEmpty(addinItem.AssemblyPath)) + { + var xmlElement = xmlDoc.CreateElement(ASSEMBLY); + if (local) + { + xmlElement.InnerText = addinItem.AssemblyName; + } + else + { + xmlElement.InnerText = addinItem.AssemblyPath; + } + xmlEle.AppendChild(xmlElement); + } + if (!string.IsNullOrEmpty(addinItem.ClientIdString)) + { + var xmlElement2 = xmlDoc.CreateElement(CLIENTID); + xmlElement2.InnerText = addinItem.ClientIdString; + xmlEle.AppendChild(xmlElement2); + } + if (!string.IsNullOrEmpty(addinItem.FullClassName)) + { + var xmlElement3 = xmlDoc.CreateElement(FULLCLASSNAME); + xmlElement3.InnerText = addinItem.FullClassName; + xmlEle.AppendChild(xmlElement3); + } + } + + private void AddApplicationToXmlElement(XmlElement appEle, AddinItem currentApp) + { + if (!string.IsNullOrEmpty(currentApp.Name)) + { + var xmlElement = xmlDoc.CreateElement(NAME_NODE); + xmlElement.InnerText = currentApp.Name; + appEle.AppendChild(xmlElement); + } + AddAddInItemToXmlElement(appEle, currentApp); + } + + private void AddCommandToXmlElement(XmlElement commandEle, AddinItem command) + { + AddAddInItemToXmlElement(commandEle, command); + XmlElement xmlElement; + if (!string.IsNullOrEmpty(command.Name)) + { + xmlElement = xmlDoc.CreateElement(TEXT); + xmlElement.InnerText = command.Name; + commandEle.AppendChild(xmlElement); + } + if (!string.IsNullOrEmpty(command.Description)) + { + xmlElement = xmlDoc.CreateElement(DESCRIPTION); + xmlElement.InnerText = command.Description; + commandEle.AppendChild(xmlElement); + } + var text = command.VisibilityMode.ToString(); + if (!string.IsNullOrEmpty(text)) + { + text = text.Replace(",", " |"); + } + xmlElement = xmlDoc.CreateElement(VISIBILITYMODE); + xmlElement.InnerText = text; + commandEle.AppendChild(xmlElement); + } + + private void ParseExternalApplications(XmlNode nodeApplication) + { + var addinItem = new AddinItem(AddinType.Application); + ParseApplicationItems(addinItem, nodeApplication); + applications.Add(addinItem); + } + + private void ParseExternalCommands(XmlNode nodeCommand) + { + var addinItem = new AddinItem(AddinType.Command); + ParseCommandItems(addinItem, nodeCommand); + commands.Add(addinItem); + } + + private void ParseApplicationItems(AddinItem addinApp, XmlNode nodeAddIn) + { + ParseAddInItem(addinApp, nodeAddIn); + var xmlElement = nodeAddIn[NAME_NODE]; + if (xmlElement != null && !string.IsNullOrEmpty(xmlElement.InnerText)) + { + addinApp.Name = xmlElement.InnerText; + } + } + + private void ParseCommandItems(AddinItem command, XmlNode nodeAddIn) + { + ParseAddInItem(command, nodeAddIn); + var xmlElement = nodeAddIn[TEXT]; + if (xmlElement != null) + { + command.Name = xmlElement.InnerText; + } + xmlElement = nodeAddIn[DESCRIPTION]; + if (xmlElement != null) + { + command.Description = xmlElement.InnerText; + } + xmlElement = nodeAddIn[VISIBILITYMODE]; + if (xmlElement != null && !string.IsNullOrEmpty(xmlElement.InnerText)) + { + command.VisibilityMode = ParseVisibilityMode(xmlElement.InnerText); + } + } + + private void ParseAddInItem(AddinItem addinItem, XmlNode nodeAddIn) + { + var xmlElement = nodeAddIn[ASSEMBLY]; + if (xmlElement != null) + { + if (local) + { + addinItem.AssemblyName = xmlElement.InnerText; + } + else + { + addinItem.AssemblyPath = xmlElement.InnerText; + } + } + xmlElement = nodeAddIn[CLIENTID]; + if (xmlElement != null) + { + try + { + if (!string.IsNullOrEmpty(xmlElement.InnerText)) + { + addinItem.ClientId = new Guid(xmlElement.InnerText); + } + else + { + addinItem.ClientId = Guid.Empty; + } + } + catch (Exception) + { + addinItem.ClientId = Guid.Empty; + addinItem.ClientIdString = xmlElement.InnerText; + } + } + xmlElement = nodeAddIn[FULLCLASSNAME]; + if (xmlElement != null) + { + addinItem.FullClassName = xmlElement.InnerText; + } + } + + private VisibilityMode ParseVisibilityMode(string visibilityModeString) + { + var visibilityMode = VisibilityMode.AlwaysVisible; + VisibilityMode result; + try + { + var text = "|"; + var separator = text.ToCharArray(); + var array = visibilityModeString.Replace(" | ", "|").Split(separator); + foreach (var value in array) + { + var visibilityMode2 = (VisibilityMode)Enum.Parse(typeof(VisibilityMode), value); + visibilityMode |= visibilityMode2; + } + result = visibilityMode; + } + catch (Exception) + { + throw new ArgumentException(UNKNOW_VISIBILITYMODE); + } + return result; + } + + private string getFullPath(string fileName) + { + FileInfo fileInfo = null; + try + { + fileInfo = new FileInfo(fileName); + } + catch (Exception ex) + { + throw new ArgumentException(fileName + Environment.NewLine + ex.ToString()); + } + return fileInfo.FullName; + } + + private string fileName; + + private bool local; + + private string filePath; + + private List applications; + + private List commands; + + private string ROOT_NODE = "RevitAddIns"; + + private string ADDIN_NODE = "AddIn"; + + private string APPLICATION_NODE = "Application"; + + private string COMMAND_NODE = "Command"; + + private string TYPE_ATTRIBUTE = "Type"; + + private string INCORRECT_NODE = "incorrect node in addin file!"; + + private string EMPTY_ADDIN = "empty addin file!"; + + private string ASSEMBLY = "Assembly"; + + private string CLIENTID = "ClientId"; + + private string FULLCLASSNAME = "FullClassName"; + + private string NAME_NODE = "Name"; + + private string TEXT = "Text"; + + private string DESCRIPTION = "Description"; + + private string VENDORID = "VendorId"; + + private string VENDORDESCRIPTION = "VendorDescription"; + + private string VISIBILITYMODE = "VisibilityMode"; + + private string UNKNOW_VISIBILITYMODE = "Unrecognizable VisibilityMode!"; + + private string FILENAME_INCORRECT_WARNING = "File name is incorrect, not .addin file ."; + + private string FILENAME_NULL_OR_EMPTY = "File name for RevitAddInManifest is null or empty"; + + private XmlDocument xmlDoc; +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/NavisAddin.cs b/src/OpenProject.Navisworks/Model/NavisAddin.cs new file mode 100644 index 00000000..12e88a62 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/NavisAddin.cs @@ -0,0 +1,27 @@ +namespace OpenProjectNavisworks.Model; + +public class NavisAddin : ViewModelBase +{ + public bool IsReadOnly { get; set; } + public VisibleModel State { get; set; } + public string FilePath { get; set; } + public string Assembly { get; set; } + public string ClientId { get; set; } + public string Name { get; set; } + public string NameNotEx { get; set; } + public string FullClassName { get; set; } + public string Text { get; set; } + public string VisibilityMode { get; set; } + public string LanguageType { get; set; } + public string VendorId { get; set; } + public string VendorDescription { get; set; } + public AddinType AddinType { get; set; } + + private bool _IsChecked; + + public bool IsChecked + { + get => _IsChecked; + set => OnPropertyChanged(ref _IsChecked, value); + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/NavisWorksWrapper.cs b/src/OpenProject.Navisworks/Model/NavisWorksWrapper.cs new file mode 100644 index 00000000..77a0a71a --- /dev/null +++ b/src/OpenProject.Navisworks/Model/NavisWorksWrapper.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Autodesk.Navisworks.Api; +using ComApi = Autodesk.Navisworks.Api.Interop.ComApi; +using ComBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge; + + +namespace OpenProjectNavisworks.Model +{ + public static class NavisworksWrapper + { + public static Document Document { get; set; } + public static string DocumentGUID { get; set; } + public static ComBridge ComBridge { get; set; } + + //public static List GetAllSearchSets() + //{ + // List result = new List(); + // var selectionSets = Document.SelectionSets; + // var savedItemCollection = selectionSets.Value; + + // foreach (var saveItem in savedItemCollection) + // { + // if (!saveItem.IsGroup) + // { + // SelectionSet selectionSet = saveItem as SelectionSet; + + // if (selectionSet != null) + // { + // result.Add(selectionSet); + // } + // } + // } + + // return result; + //} + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/ProcessManager.cs b/src/OpenProject.Navisworks/Model/ProcessManager.cs new file mode 100644 index 00000000..3f799741 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/ProcessManager.cs @@ -0,0 +1,53 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace OpenProjectNavisworks.Model +{ + public static class ProcessManager + { + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + public static void SetNavisAsWindowOwner(this Window window) + { + if (null == window) { return; } + window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + WindowInteropHelper helper = new WindowInteropHelper(window); + helper.Owner = GetActivateWindow(); + window.Closing += SetActivateWindow; + } + + private static void SetActivateWindow(object sender, CancelEventArgs e) + { + SetActivateWindow(); + } + + /// + /// Set process revert use revit + /// + /// + public static bool SetActivateWindow() + { + IntPtr ptr = GetActivateWindow(); + if (ptr != IntPtr.Zero) + { + return SetForegroundWindow(ptr); + } + return false; + } + + /// + /// return active windows is active + /// + /// + public static IntPtr GetActivateWindow() + { + return Process.GetCurrentProcess().MainWindowHandle; + } + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/StaticUtil.cs b/src/OpenProject.Navisworks/Model/StaticUtil.cs new file mode 100644 index 00000000..a5d95aa0 --- /dev/null +++ b/src/OpenProject.Navisworks/Model/StaticUtil.cs @@ -0,0 +1,26 @@ +using System.Windows; +using Autodesk.Navisworks.Api.Plugins; +using MessageBox = System.Windows.MessageBox; + +namespace OpenProjectNavisworks.Model; + +public static class StaticUtil +{ + public static void ShowWarning(string msg) + { + MessageBox.Show(msg, DefaultSetting.AppName, MessageBoxButton.OK, MessageBoxImage.Exclamation); + } + public static string CommandFullName = typeof(AddInPlugin).FullName; + + /// + /// CaseInsensitiveContains + /// + /// + /// + /// + /// + public static bool CaseInsensitiveContains(this string text, string value, StringComparison stringComparison = StringComparison.CurrentCultureIgnoreCase) + { + return text.IndexOf(value, stringComparison) >= 0; + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/ViewModelBase.cs b/src/OpenProject.Navisworks/Model/ViewModelBase.cs new file mode 100644 index 00000000..10e7feca --- /dev/null +++ b/src/OpenProject.Navisworks/Model/ViewModelBase.cs @@ -0,0 +1,73 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace OpenProjectNavisworks.Model; + +public class ViewModelBase : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler PropertyChanged; + + public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue) + { + OnPropertyChanged(propertyName); + } + + public void OnPropertyChanged(ref T property, T value, [CallerMemberName] string propertyName = "") + { + property = value; + var handler = PropertyChanged; + if (handler != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + /// + /// Sets the property value. + /// + /// The type of the property. + /// The field. + /// The value. + /// Name of the property. + /// + /// True if the property was set. + /// + /// This method uses the CallerMemberNameAttribute to determine the property name. + protected bool SetValue(ref T field, T value, [CallerMemberName] string propertyName = "") + { + // ReSharper disable once RedundantNameQualifier + if (object.Equals(field, value)) + { + return false; + } + VerifyProperty(propertyName); + //// this.OnPropertyChanging(propertyName, field, value); + var oldValue = field; + field = value; + OnPropertyChanged(propertyName, oldValue, value); + return true; + } + + /// + /// Verifies the property name. + /// + /// Name of the property. + [Conditional("DEBUG")] + private void VerifyProperty(string propertyName) + { + var type = GetType(); + + // Look for a public property with the specified name. + var propertyInfo = type.GetTypeInfo().GetDeclaredProperty(propertyName); + + Debug.Assert(propertyInfo != null, string.Format(CultureInfo.InvariantCulture, "{0} is not a property of {1}", propertyName, type.FullName)); + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Model/VisibilityMode.cs b/src/OpenProject.Navisworks/Model/VisibilityMode.cs new file mode 100644 index 00000000..6fd75a6d --- /dev/null +++ b/src/OpenProject.Navisworks/Model/VisibilityMode.cs @@ -0,0 +1,22 @@ +namespace OpenProjectNavisworks.Model; + +[Flags] +public enum VisibilityMode +{ + AlwaysVisible = 0, + NotVisibleInProject = 1, + NotVisibleInFamily = 2, + NotVisibleWhenNoActiveDocument = 4, + NotVisibleInArchitecture = 8, + NotVisibleInStructure = 16, + NotVisibleInMechanical = 32, + NotVisibleInElectrical = 64, + NotVisibleInPlumbing = 128, + NotVisibleInMEP = 224 +} + +public enum VisibleModel +{ + Enable, + Disable +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/OpenProject.Navisworks.csproj b/src/OpenProject.Navisworks/OpenProject.Navisworks.csproj new file mode 100644 index 00000000..2b2b93fc --- /dev/null +++ b/src/OpenProject.Navisworks/OpenProject.Navisworks.csproj @@ -0,0 +1,210 @@ + + + + true + latest + x64 + enable + true + net48 + Debug-2022;Debug-2024;Release-2022;Release-2024; + $(Configurations);Release-2022;;Release-2024; + OpenProjectNavisworks + + true + true + + true + + + + + true + full + $(DefineConstants);DEBUG + + + true + none + $(DefineConstants);RELEASE + + + + 2020 + $(DefineConstants);N2020 + + + 2021 + $(DefineConstants);N2021 + + + 2022 + $(DefineConstants);N2022 + + + 2023 + $(DefineConstants);N2023 + + + 2024 + $(DefineConstants);N2024 + + + + $(NavisVersion) + true + false + A Project Support for developer in Navisworks + true + true + + + + + + + + + + + + + + Always + + + Always + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + True + True + Resource.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + PublicResXFileCodeGenerator + Resource.Designer.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + MSBuild:Compile + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OpenProject.Navisworks/PackageContents.xml b/src/OpenProject.Navisworks/PackageContents.xml new file mode 100644 index 00000000..ba19b594 --- /dev/null +++ b/src/OpenProject.Navisworks/PackageContents.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Properties/Resources.Designer.cs b/src/OpenProject.Navisworks/Properties/Resources.Designer.cs new file mode 100644 index 00000000..bdd4d8b8 --- /dev/null +++ b/src/OpenProject.Navisworks/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OpenProjectNavisworks.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenProjectNavisworks.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/OpenProject.Navisworks/Properties/Resources.resx b/src/OpenProject.Navisworks/Properties/Resources.resx new file mode 100644 index 00000000..4fdb1b6a --- /dev/null +++ b/src/OpenProject.Navisworks/Properties/Resources.resx @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Properties/Settings.Designer.cs b/src/OpenProject.Navisworks/Properties/Settings.Designer.cs new file mode 100644 index 00000000..a10a0443 --- /dev/null +++ b/src/OpenProject.Navisworks/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OpenProjectNavisworks.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/src/OpenProject.Navisworks/Properties/Settings.settings b/src/OpenProject.Navisworks/Properties/Settings.settings new file mode 100644 index 00000000..049245f4 --- /dev/null +++ b/src/OpenProject.Navisworks/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/OpenProject.Navisworks/Properties/launchSettings.json b/src/OpenProject.Navisworks/Properties/launchSettings.json new file mode 100644 index 00000000..c0e19c21 --- /dev/null +++ b/src/OpenProject.Navisworks/Properties/launchSettings.json @@ -0,0 +1,18 @@ +{ + "profiles": { + "Navis2022": { + "commandName": "Executable", + "executablePath": "C:\\Program Files\\Autodesk\\Navisworks Manage 2022\\Roamer.exe", + "commandLineArgs": "-lang en-US" + }, + "Navis2023": { + "commandName": "Executable", + "executablePath": "C:\\Program Files\\Autodesk\\Navisworks Manage 2023\\Roamer.exe" + }, + "Navis2024": { + "commandName": "Executable", + "executablePath": "C:\\Program Files\\Autodesk\\Navisworks Manage 2024\\Roamer.exe", + "commandLineArgs": "-lang en-US" + } + } +} \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Resource.Designer.cs b/src/OpenProject.Navisworks/Resource.Designer.cs new file mode 100644 index 00000000..d5803134 --- /dev/null +++ b/src/OpenProject.Navisworks/Resource.Designer.cs @@ -0,0 +1,165 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OpenProjectNavisworks { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenProjectNavisworks.Resource", typeof(Resource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Navis Addin Manager. + /// + public static string AppName { + get { + return ResourceManager.GetString("AppName", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon dev { + get { + object obj = ResourceManager.GetObject("dev", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap dev1 { + get { + object obj = ResourceManager.GetObject("dev1", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to File Not Exit, Please Check !. + /// + public static string FileNotExit { + get { + return ResourceManager.GetString("FileNotExit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File Not Found, Please Check File Again. + /// + public static string FileNotFound { + get { + return ResourceManager.GetString("FileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap folder { + get { + object obj = ResourceManager.GetObject("folder", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to File Load Is Cancelld. + /// + public static string LoadCancelled { + get { + return ResourceManager.GetString("LoadCancelled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File Load Is Invalid. + /// + public static string LoadInvalid { + get { + return ResourceManager.GetString("LoadInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please Check Again Directory And Open Full Access Admin!. + /// + public static string NeedAdmin { + get { + return ResourceManager.GetString("NeedAdmin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Any Item Selected. + /// + public static string NoItemSelected { + get { + return ResourceManager.GetString("NoItemSelected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully. + /// + public static string Successfully { + get { + return ResourceManager.GetString("Successfully", resourceCulture); + } + } + } +} diff --git a/src/OpenProject.Navisworks/Resource.resx b/src/OpenProject.Navisworks/Resource.resx new file mode 100644 index 00000000..7731cd26 --- /dev/null +++ b/src/OpenProject.Navisworks/Resource.resx @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navis Addin Manager + + + + Resources\dev.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Resources\dev.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + File Not Exit, Please Check ! + + + File Not Found, Please Check File Again + + + Resources\folder.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + File Load Is Cancelld + + + File Load Is Invalid + + + Please Check Again Directory And Open Full Access Admin! + + + Not Any Item Selected + + + Successfully + + \ No newline at end of file diff --git a/src/OpenProject.Navisworks/Resources/BCFierIcon16x16.png b/src/OpenProject.Navisworks/Resources/BCFierIcon16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..39e117ef2b16ccc486fe053455fcf324ee069574 GIT binary patch literal 831 zcmV-F1Hk-=P)6TLcmica6t_mkh#|&px{y&HZ_f9Pc|8rMDqCiJB%!Qbc59iL}vUs7Yc09eG4?- z76gT;S7JVqd-s$W*i?hLju^a->)>Nb4j{mTNMg!Dw#Y2NVZVY3Y7_nJQv%P2M21y8 zQCQOx$xB-l7GohODFiAqF=`ZY#_&`h6*VIWgaYLN7_$^amj18LxM&T};Eml~}<h?1c2e?xh^t@AT!*S)s<{gr*}N%m{E3h8loDg;gjK zT5Id@T?@lcRm%)4&auOGxkQ;2=APQ~QYc@5Q8oI+37JV)%do44KxDtN@FIVd-!4^5 zk17ZxJ*4432BWvjZ>DUI~KKz|IDI z3&*Cv#M+EsGNiG~y+WvmT1$#l->V2N%fxgEWLBTtWRf7bA3M?IQkp^wA#PWDPnU}G z-R0tFjgl4Jw~dHjwQ)v_xU&lp&zgAi{NB}F^X2B>x3JTB9_L(MUe}|>h8_mA5wobG zGzfbxv^q9)*k$OjDrWc6+rERnsh5BQ!?rsw494wpPUgPR?A1W1!5Z;x;K;dXvttz^ zUM+LvYusO`B5002ov JPDHLkV1g~vd#eBd literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/BCFierIcon32x32.png b/src/OpenProject.Navisworks/Resources/BCFierIcon32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..4b45eddbffc4f9a6b8a5bd50e09c6486dadc585e GIT binary patch literal 1874 zcmV-Y2d(&tP)H(o;1F+jdIawY~S?3w0$xfleTC~tR|hBn2?xA2~Q>@tO^N6 z2~(;fiO4Q$2oUHjz3<%o{&Qxw+ghN-reDu?c4xlt{Fm=P%h_FiKHc_721^du2V}Xp z8dZNJ4;+sex8FGXrpq4ihc7^EVrd6DIS6?aF_i^kF2w<T~$88O`OQD0D!CU42odKQFF%;TE9tW!wiJ@YWqMK}v;L?vj!tO`(kQnW z$vCH}sMF8w%}k#=al9fRuJ&V;V?CNB*Q-Q8SoiLc-u=BPHB;x0QH^BN;mqcS@^z~= znCLAT4{wtlBAK2X^9$!zq*rMXO9O}#7a~IWwgnL!BB+b-o@})^vpup1v)3JE4!wUMZG}Aa`m5kOQ>=a)8yC z711L$PT@o;RVqCZ0rKuD@^%Vv$VC~tY838oxOygEtp;__fqQd+=%VGGzYk>1z!gnl zrjisy+(l)DSri3D+)O<#qELYgs$L2^s=a+iQQ5d|useFnnb5Qi^U3K4X+o7p9mzA$ zE!Jy)j+&`3>Zrl)YvKr`uo@xEO}0pj!sm2##qI3I(z%?cCjCVTx!Ad@r(iDn z{>Dq+-q-VKHdGvw^CntAIB3NA*Rgvd*Wo8h#4^f!U#bZvhfMEg=N%B2u=^53fV{2g zmlK7>cso)y81J8Ch)M@xKG`+3B|(j32LL=@6?|vQm}5?8x;d^<&J7~B_Y8kCwAkUY z*ZIJqLXDqk_CMd7)gqa5wX#y(&Ehx)2@MmrdHlBmLZ5{IA{LldweF=(IM}lj#m_`Z zT2;H9lWHDcv$1m*gJh^-=2{knYzmkJ@Y;Uq=c9$C3ZS7BNOBUfwR-ARuW)Zip~D2! z_B}p+$wk$h)N%w8aSyyzxvhw*2PCaOLp&$m`cG@c*X;huW*(KK?Lk;UmPfCRri^d_TiPo7m3I~F~A2N zZR_u>@!3#1j0Vqrb;!AwfRds1jx1NuQM`2qy?SE<>YL(NHiHTM#Y&8_9`J{hc92S-g|m`2YEJBTV>p=3J2rK$tWwLg>t5x zit93+I73m$<&I}!5i<&R@tlr?e6D~fa>yPvaWDyCn*puXyTAYBPAgh-&l8O%ZqAie z*2E>T83vNrg9uDKVJbYX_8=}aPlR8jz=5E)%l1KM!9yt~pNYyJrNMJs$M7*yVhCXl zkRul03*GOZ{J3(Sd#tTLs|U=2=q8ms9idlbZ_Cq9MFh`d9lZGELHoYPa@4^9EVvbu zPY9qnP|_1iHprj~K6yPS`zCJ>G2udtS$|(`;G5H`4g)jHDb*NWJ6YPg{np>^>Z!55 zRmIcJqP`a(3YoH^o(HKZ$y3QZU=w&aIV5>RAp2X?i2(sMfsf+!Se*}eWgJFk0I@o2 z3g0&_>6_7wbvs+9R5^-?e2mC7Nsd|&Mn!%h1w}n#rO`Ko|>9sswcS%OG6@iUKN|r}L?IASKt14O9CoiP zo?i3><%{{yjrBXT_+r+A+-E_asTKh9c2Em>v|``N;txw6-Tw+O0CKM23r4&o-2eap M07*qoM6N<$g5#oU%>V!Z literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/OpenProjectLogo16.png b/src/OpenProject.Navisworks/Resources/OpenProjectLogo16.png new file mode 100644 index 0000000000000000000000000000000000000000..8a08a2cf967f8a05ef99a55c9a8079354314ac02 GIT binary patch literal 335 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 literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/dev.png b/src/OpenProject.Navisworks/Resources/dev.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e67df974b0dc1fb2de6b841cbe7b87a8bd663c GIT binary patch literal 5378 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 N002ovPDHLkV1ieh1|$Fg literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/dev32x32.png b/src/OpenProject.Navisworks/Resources/dev32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..96eb5ddd25cf2b3e8d62b5425fd0314cbdd34a8d GIT binary patch literal 1365 zcmV-b1*-aqP)+>wC?b{s)n2T%wh*b}H4+{KH9-x=L=Y56s6<2oi$xNtZ7K>B=-rt)d--r? zhUsOd6(4-zpG;=wWaoVAti8^^_rQhvpUd=SQsHYAjxmfunl0E9OtzorJ^i$eol{)= zmJ@IS4}vC3+8}GI@?KbMxHs@8Lc|SWLFwATz(HVWB6!FRF)C{~e+L2_`@IgIFF6Vi z2l2Q?*{`fzda~CJ6qK$V7*2mjxP+|jVEbi*2Qp{Y!(2H$6Jtz_8)2a^RAT*edBp=0 zI#5)$)+cctm?=n42u8=kUDu^gN-}Nj?KqAP;a$7$&Z$focP*>ey-!nf3+ZWToNa8z zINr0_sBj&z(wo+5w}Bta3v)vq9FQ1-*aehF;2t$p->1}AuFsjT2eXaTPr&x!u6dKU`86lMUf!6 zu1jj)zD&M;0^_rG@lTt1ZXW3S<|O9x?HHyXmMVHpg#q(7D4@5gbHK*F^aYh6YR%V65A zIgH90!J(>ZYU=7EIly|$5oN_J0Vjt*xDdhpRpR5Ped7JUZ)P($iA;>Ci8%d3!w#XBxwA$GJcvHFOaGR7olo zceWNy?0EudeG73GQ#0fZK1GM96j7 zRk;rnPIvezqm3vswpQ&(Q!7eVRd&Zocpg|$RK5u`Beu7@u8l&BFshJa|F}BDL(Lk_?G_E6tQus-$&RT3lX`+a5b_(ers1S<4zLEet!e z5wzl+vV!wJmPcyIOC=7j9WCiTNpF|s=QegXZ{f2mF98-Jwn+AucghQT{9|&V{`dL^ XnWaH&N^v^900000NkvXXu0mjf$c}Z& literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/folder.png b/src/OpenProject.Navisworks/Resources/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..ba66da7158856f2b610450f53ea645925923634f GIT binary patch literal 664 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 literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/lab32x32.png b/src/OpenProject.Navisworks/Resources/lab32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..a00be3d3cbd8cebb95990e5de2a69d74a64da620 GIT binary patch literal 657 zcmV;C0&e|@P)3+&kwv&-)zG zum_*9KGFUU1}EAr##=0i>l65fy_F`^Uo>MU#$gzKVmV$GbySm>zz2-RYP`b_?89N) zO!Qd@EASZm@dlGp9i{HT7F@+a%n=ImUTP94Scnds#A{3#$l$%0fz6mW(1Dks8$Bh| z9c6`((g()iECxlIj`5K;KyN{!$AdtkHUQ{ z#UtVVm)bX?9aHhK+6f#Hc5Fw6w+P!Z>!a8p+>TwudNW?9`(8 ztqLxqtqi{wvoNKEza3Yq6?`r3C_g)_5{mH#Mr8OkxEpONLalJ^S`%#N3cX=uo>E^m za0qq6)hdU@*qT`%6Y8KBr_m{~T{j7%cYTh|c&y1#U5UIt`uOM2k~b3`5YF~Oj!sYX z(IL!;T_y4^VR~+r@Yj5*@7}&rD8lRhF2XEY@T6#P_%8JRN--Q?|0T@(7km;%tj-*r zCBlf_i+15CvKSkEl|^0+%oK{~dX7$4qz93132iuwwy1M1$4^-_clC1yc>Q4E2?>kBREeS00000NkvXXu0mjfelINM literal 0 HcmV?d00001 diff --git a/src/OpenProject.Navisworks/Resources/openproject.ico b/src/OpenProject.Navisworks/Resources/openproject.ico new file mode 100644 index 0000000000000000000000000000000000000000..eeaf4d8e7769973707274088e8fe169071880e62 GIT binary patch literal 67646 zcmeHQYmD4h6?O>F@@k+Y-L*aGrlE*HK?n*Glt++|paKLHs6bRoX~^0$TZQoW!w=e> zAMgVefj|l+MI_m=w~VT?fv!= zE81Ez|IY|80*nA7zz8q`i~u9R2rvSS03*N%FanGKBftn`L!gOCr_igsuHiYKY52~a zf$#hp+rxq9{2hN!rER$8F{J$q(%pmYU|6-Mk9yl*kxeVUx@ZJKfATers`Is&=O(zz zdwV4C+*{#Smltgd=RY_Cb5-XJsIxx;->U_1zL5`|M>&TZ`zEd!TuV-wHv$XZ&Mn~K zAhs1vym$M;Gt?d|Tz}Q`^R|ui>yH51_&0*{M~B3Fw;%k0#|QoS%JXdKJ0An@%Z6|t z`2zZ%r^2f9wmj|Ryw)QS__hb$|7Q~Sf-YwpRr|f`wdNyvATZ}U9|G3H>g&7P{9f|e zIp2M29`S@ANc^y8#0I45I8zLc@?<7P)7dgo?yOq`OC5)Fy2?=KL1!5xnI+{ z>eR=ZL3QirYZE(xW7hHh6UgIW!?!;iPEWp>@=V);Y3CYTvj_K@bNKr-wsxPc^7qss z&v~Ib3gdfafVz_b`*WBlx-BdAcX`fM=<#jnk>%OKp7CW--Ev^AI_Cd(P_4X6z2x;* z237k8=tIwaq}u?D1AekTM?TUI0eZ%&f;FyLY50}*^~+oJ9NLo^MV-zZ@~(cKYPsWh zUFf+tsbJmk-ES0$S5S3-rlJ?m3CghojBi)K`7HI0$rf z(OQP3YO5)nPO6ze83x*mE>Zw_`KTcI5l`0C}&XE%<0!eq}x% z`0mL8aOm^{O_PufYE=Ma)KNf9&{2Tdu(W z-J`_0Xbbb-^Zrj9^D_U!eR7${f4HaM`Y$v9T>rWL%l3y{|Ah)v@?Ea~T>rWLr}_Yo z|9JdY@|J#M^|}2|t^0Hz$n~G=|Hc)lU#bsq`_Juv$y@r3)#vsAXUY^w8{$6?hoo|-MLe{h&0$4BmU0MFIZhDDhF7!jSJm>KK z&ljZm$GrgJFWp!kD_PTC2v96F75?dcnPYpmJH7Jd;uE~ff)ZkQ5>~4 zH@g02h`q!8LjL{21iR<;2+VG4_L@`@tjM+JEImy0{n1p)m+O`-dO=3Vt9LM=AgNqp`i`#50>O zio6VCpJHT^FT`tzPQ-KDW6O!=cJaxDnQbo~2JdNQQoKA~Z07R`0;cg#nuYr>y##A#`o*H|Ii{k%B(aY7(hx9_f6^J>;dr}J8{oO$PqUfN zCkU9zKWT`%{0mthz&+PPiFLe}hJeZZgZrCh`A7Qm!&oblhO?>Xjpl!*a-{di7!c_2O+>Y0STAjOf*e`PYlLX{9m$rZJ*dALd^# z-lmnt{F}yzUVWHZ9kz%2+={erc&CYM;s11M{r$%$^Q?`wsL8?+@;J{NzdBN#+z~x9 zq;Fjp_YH#+Xme&vjp=R<8^hBi{vHyZ9oh%tZ>B#0fA|-+XC?gN4AMT{iuvpwqUR8_ z?r(TjamkDTBftnS0*nA7zz8q`i~u9R2rvSS03*N%FanGKBTz;Jn&$m1YqO}-3SgZT z4xENOCCH1AFB0TBWHAGX)~4kAD#=zNQ}$v3%aOc$EddoL7T7H>Md!DyrSFy(qw|S^ zi`JIrlA!RSwY#}2$aNUIA{bDIuB#i!Z4>Hbo3(khjZrMGAlvA-CQo(9%WVwf^DOtn zrv+W~Kk*^{*2}Zv>1Mr6M0|9y4g8ucI46#^{3UXlDCJUXH~OF4T9iqZmOsXcW}EOO z;WyQUF9|=mX@?BAY}JQ24HY0HkKAriI6$f{2{I|rQ6S`w3}zvVC6XNdB7YO=T_g)w zMKaZm_!r4bUGnL5@>ECq(+Sqm_1$uGuysYWJMMBfJ{>ZAXT7{T!5h9L@Uucqn!sPw zq}3C?;1uL3YuCqaINQ#zhul8h_Oe8-lUoW^DUyYLV3E!X@)F6=vHgqcQPPaJ+ + /// This service is based on the idea of Revit developer Jeremy Tammik (https://thebuildingcoder.typepad.com/blog/2011/02/status-bar-text.html) + /// + public static class StatusBarService + { + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern int SetWindowText(IntPtr hWnd, string lpString); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, + string lpszWindow); + + /// + /// Sets a text to the revit main window status bar. + /// + /// The text to be displayed in the status bar. + public static void SetStatusText(string text) + { + IntPtr revitHandle = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; + IntPtr statusBar = FindWindowEx(revitHandle, IntPtr.Zero, "msctls_statusbar32", ""); + + if (statusBar != IntPtr.Zero) + SetWindowText(statusBar, text); + } + + /// + /// Resets the status bar text. + /// + public static void ResetStatusBarText() => SetStatusText("Ready"); + } +} diff --git a/src/OpenProject.Navisworks/View/AssemblyLoader.xaml b/src/OpenProject.Navisworks/View/AssemblyLoader.xaml new file mode 100644 index 00000000..4db935c8 --- /dev/null +++ b/src/OpenProject.Navisworks/View/AssemblyLoader.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + +