diff --git a/CADability.Avalonia/CADability.Avalonia.csproj b/CADability.Avalonia/CADability.Avalonia.csproj new file mode 100644 index 00000000..ac8fdfeb --- /dev/null +++ b/CADability.Avalonia/CADability.Avalonia.csproj @@ -0,0 +1,25 @@ + + + net8.0 + enable + app.manifest + true + + + + + + + + + + None + All + + + + + + + + diff --git a/CADability.Avalonia/CadCanvas.axaml b/CADability.Avalonia/CadCanvas.axaml new file mode 100644 index 00000000..2579c1b5 --- /dev/null +++ b/CADability.Avalonia/CadCanvas.axaml @@ -0,0 +1,5 @@ + + + diff --git a/CADability.Avalonia/CadCanvas.axaml.cs b/CADability.Avalonia/CadCanvas.axaml.cs new file mode 100644 index 00000000..84de944d --- /dev/null +++ b/CADability.Avalonia/CadCanvas.axaml.cs @@ -0,0 +1,207 @@ +using AvaloniaBase = Avalonia; +using Avalonia.Controls; +using AvaloniaKeyEventArgs = Avalonia.Input.KeyEventArgs; +using Avalonia.Input; +using Avalonia.Interactivity; +using CADability.GeoObject; +using CADability.Substitutes; +using CADability.UserInterface; +using System; +using System.Numerics; +using static Avalonia.OpenGL.GlConsts; + +namespace CADability.Avalonia +{ + public partial class CadCanvas: UserControl, ICanvas + { + private static CADability.Substitutes.Rectangle Subst(AvaloniaBase.Rect v) + { + return new Substitutes.Rectangle((int)v.X, (int)v.Y, (int)v.Width, (int)v.Height); + } + + private PaintToOpenGL paintTo3D; + private IFrame frame; + private IView view; + private String currentCursor; + public Substitutes.Point CurrentMousePosition { get; private set; } + public Substitutes.Keys ModifierKeys { get; private set; } + + public CadCanvas() + { + InitializeComponent(); + // CadCanvasControl canvasControl = new CadCanvasControl(); + paintTo3D = new PaintToOpenGL(1e-6); + paintTo3D.CadCanvas = this; + this.Content = paintTo3D; + this.CurrentMousePosition = new Substitutes.Point(0, 0); + this.ModifierKeys = Substitutes.Keys.None; + } + + void ICanvas.Invalidate() {} + + Rectangle ICanvas.ClientRectangle => Subst(base.Bounds); + public IFrame Frame { get; set; } + + string ICanvas.Cursor + { + get { return currentCursor; } + set { currentCursor = value; } //TODO + } + + IPaintTo3D ICanvas.PaintTo3D + { + get { return paintTo3D; } + } + + public event Action OnPaintDone; + + public void OnPaint(PaintEventArgs e) + { + view.OnPaint(e); + OnPaintDone?.Invoke(this); + } + + void ICanvas.ShowView(IView toShow) + { + view = toShow; + // TODO init paintTo3D here or in constructor? + // TODO view.Connect needed? + view.Connect(this); + } + + IView ICanvas.GetView() + { + return view; + } + + Substitutes.Point ICanvas.PointToClient(Substitutes.Point mousePosition) + { + // should already be client coordinates with avalonia + return mousePosition; + } + + void ICanvas.ShowContextMenu(MenuWithHandler[] contextMenu, Substitutes.Point viewPosition, System.Action collapsed) { + throw new NotImplementedException(); + } + + Substitutes.DragDropEffects ICanvas.DoDragDrop(GeoObjectList dragList, Substitutes.DragDropEffects all) + { + throw new NotImplementedException(); + } + + void ICanvas.ShowToolTip(string toDisplay) {} + + protected override void OnLoaded(RoutedEventArgs e) + { + var topLevel = TopLevel.GetTopLevel(this)!; + topLevel.KeyDown += OnKeyDown; + topLevel.KeyUp += OnKeyUp; + topLevel.PointerPressed += OnPointerPressed; + topLevel.PointerReleased += OnPointerReleased; + topLevel.PointerEntered += OnPointerEntered; + topLevel.PointerExited += OnPointerExited; + topLevel.PointerMoved += OnPointerMoved; + topLevel.PointerWheelChanged += OnPointerWheel; + base.OnLoaded(e); + } + + private Substitutes.Point Subst(AvaloniaBase.Point mousePosition) + { + return new Substitutes.Point((int)mousePosition.X, (int)mousePosition.Y); + } + + private Substitutes.MouseButtons SubstButton(PointerEventArgs e) + { + if (e is PointerReleasedEventArgs) { + var mb = (e as PointerReleasedEventArgs)?.InitialPressMouseButton; + if (Enum.TryParse(mb.ToString(), out Substitutes.MouseButtons res)) return res; + + } else { + if (e.Properties.IsLeftButtonPressed) return Substitutes.MouseButtons.Left; + if (e.Properties.IsRightButtonPressed) return Substitutes.MouseButtons.Right; + if (e.Properties.IsMiddleButtonPressed) return Substitutes.MouseButtons.Middle; + if (e.Properties.IsXButton1Pressed) return Substitutes.MouseButtons.XButton1; + if (e.Properties.IsXButton2Pressed) return Substitutes.MouseButtons.XButton2; + } + return Substitutes.MouseButtons.None; + } + + private CADability.Substitutes.MouseEventArgs Subst(PointerEventArgs v) + { + int clicks = (v is PointerPressedEventArgs) ? (v as PointerPressedEventArgs).ClickCount : 0; + int delta = (v is PointerWheelEventArgs) ? (int)((v as PointerWheelEventArgs).Delta.Y*10) : 0; + return new Substitutes.MouseEventArgs() + { + Button = SubstButton(v), + Clicks = clicks, + X = (int)v.GetPosition(this).X, + Y = (int)v.GetPosition(this).Y, + Delta = delta, + Location = new Substitutes.Point((int)v.GetPosition(this).X, (int)v.GetPosition(this).Y) + }; + } + + private Substitutes.Keys Subst(KeyModifiers key) + { + if (key == KeyModifiers.Meta) { + return Keys.LWin; + } + // TODO check if this works with multiple modifiers + return Enum.TryParse(key.ToString(), out Substitutes.Keys res) ? res : Substitutes.Keys.None; + } + + private void OnKeyDown(object sender, AvaloniaKeyEventArgs e) + { + ModifierKeys = Subst(e.KeyModifiers); + base.OnKeyDown(e); + } + + private void OnKeyUp(object sender, AvaloniaKeyEventArgs e) + { + ModifierKeys = Subst(e.KeyModifiers); + base.OnKeyUp(e); + } + + private void OnPointerPressed(object sender, PointerPressedEventArgs e) + { + ModifierKeys = Subst(e.KeyModifiers); + view?.OnMouseDown(Subst(e)); + base.OnPointerPressed(e); + } + + private void OnPointerReleased(object sender, PointerReleasedEventArgs e) + { + ModifierKeys = Subst(e.KeyModifiers); + view?.OnMouseUp(Subst(e)); + base.OnPointerReleased(e); + } + + private void OnPointerEntered(object sender, PointerEventArgs e) + { + view?.OnMouseEnter(e); + base.OnPointerEntered(e); + } + + private void OnPointerExited(object sender, PointerEventArgs e) + { + view?.OnMouseLeave(e); + base.OnPointerEntered(e); + } + + private void OnPointerMoved(object sender, PointerEventArgs e) + { + ModifierKeys = Subst(e.KeyModifiers); + CurrentMousePosition = Subst(e.GetPosition(this)); + view?.OnMouseMove(Subst(e)); + base.OnPointerMoved(e); + } + + private void OnPointerWheel(object sender, PointerWheelEventArgs e) + { + ModifierKeys = Subst(e.KeyModifiers); + view?.OnMouseWheel(Subst(e)); + base.OnPointerWheelChanged(e); + } + // TODO more mouse handling + } +} diff --git a/CADability.Avalonia/CadControl.axaml b/CADability.Avalonia/CadControl.axaml new file mode 100644 index 00000000..14ef72c4 --- /dev/null +++ b/CADability.Avalonia/CadControl.axaml @@ -0,0 +1,9 @@ + + + + + + diff --git a/CADability.Avalonia/CadControl.axaml.cs b/CADability.Avalonia/CadControl.axaml.cs new file mode 100644 index 00000000..a39af0c7 --- /dev/null +++ b/CADability.Avalonia/CadControl.axaml.cs @@ -0,0 +1,198 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using CADability.UserInterface; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using System.Xml; + + +namespace CADability.Avalonia +{ + public partial class CadControl : UserControl, ICommandHandler + { + private CadFrame cadFrame; // the frame, which knows about the views, the ControlCenter (PropertiesExplorer), the menu + // private ProgressForm progressForm; + public CadControl() + { + InitializeComponent(); // makes the cadCanvas and the propertiesExplorer + propertiesExplorer.Frame = cadFrame; + + // KeyPreview = true; // used to filter the escape key (and maybe some more?) + // cadFrame = new CadFrame(propertiesExplorer, cadCanvas, this); + cadFrame = new CadFrame(propertiesExplorer, cadCanvas, this); + // cadFrame.ProgressAction = (show, percent, title) => { this.ProgressForm.ShowProgressBar(show, percent, title); }; + cadCanvas.Frame = cadFrame; + // show this menu in the MainForm + // MenuWithHandler[] mainMenu = MenuResource.LoadMenuDefinition("SDI Menu", true, cadFrame); + // MenuManager.MakeMainMenu(mainMenu, dockPanel, this); TODO moved to Main Window + + // MainMenuStrip = MenuManager.MakeMainMenu(mainMenu); + // Controls.Add(MainMenuStrip); + // cadFrame.FormMenu = MainMenuStrip; + // open an existing Project or create a new one + // ToolBars.CreateOrRestoreToolbars(topToolStripContainer, cadFrame); + // Application.Idle += new EventHandler(OnIdle); // update the toolbars (menus are updated when they popup) + } + /// + /// Resets the main menu of the form. After MenuResource.SetMenuResource has been loaded, the mainmenu in CADability + /// is changed to the new resource. In order to set this menu in the form, call this method. + /// + /// if not null, specifies an additional menue class, which will be created by reflection + // TODO + // public void ResetMainMenu(string debuggerPlaygroundClass) + // { + // MenuWithHandler[] mainMenu = MenuResource.LoadMenuDefinition("SDI Menu", true, cadFrame); + // if (!string.IsNullOrEmpty(debuggerPlaygroundClass)) + // { + // // in the following lines a "DebuggerPlayground" object is created via reflection. This class is a playground to write testcode + // // which is not included in the sources. This is why it is constructed via reflection, there is no need to have this class in the project. + // Type dbgplygnd = Type.GetType(debuggerPlaygroundClass, false); + // if (dbgplygnd != null) + // { + // MethodInfo connect = dbgplygnd.GetMethod("Connect"); + // if (connect != null) mainMenu = connect.Invoke(null, new object[] { cadFrame, mainMenu }) as MenuWithHandler[]; + // } + // } + + // if (this.MainMenuStrip != null) + // { + // this.Controls.Remove(this.MainMenuStrip); + // this.MainMenuStrip.Dispose(); + // } + // MainMenuStrip = MenuManager.MakeMainMenu(mainMenu); + // this.Controls.Add(MainMenuStrip); + // cadFrame.FormMenu = MainMenuStrip; + // } + // Access the components of the MainForm from the CadFrame. + // TODO + // public void SetToolbar(XmlNode definition) + // { + // ToolBars.SetToolBar(topToolStripContainer, cadFrame, definition); + // } + // TODO + // public ProgressForm ProgressForm + // { + // get + // { + // if (progressForm == null) + // { + // progressForm = new ProgressForm + // { + // TopLevel = true, + // Owner = this, + // Visible = false + // }; + // } + // return progressForm; + // } + // } + public PropertiesExplorer PropertiesExplorer => propertiesExplorer; + public CadCanvas CadCanvas => cadCanvas; + public CadFrame CadFrame => cadFrame; + + // slowdown OnIdle polling: + readonly Stopwatch _idleSw = Stopwatch.StartNew(); + const int MinIdleCheckMs = 250; + bool _idleBusy; + // TODO Avalonia + // private void OnIdle(object sender, EventArgs e) + // { + // if (_idleBusy) return; + // if (_idleSw.ElapsedMilliseconds < MinIdleCheckMs) return; + + // _idleBusy = true; + // _idleSw.Restart(); + // try { ToolBars.UpdateCommandState(topToolStripContainer.TopToolStripPanel); } + // finally { _idleBusy = false; } + // } + // TODO + // protected override void OnClosing(CancelEventArgs e) + // { // maybe we need to save the project + // ToolBars.SaveToolbarPositions(topToolStripContainer); + // Settings.SaveGlobalSettings(); + // ToolStripManager.SaveSettings(this); // save the positions of the toolbars (doesn't work correctly) + // base.OnClosing(e); + // } + // TODO + // protected override void OnFormClosed(FormClosedEventArgs e) + // { + // cadFrame.Dispose(); + // MainMenuStrip.Dispose(); + // this.Dispose(); + // cadCanvas.Dispose(); + // propertiesExplorer.Dispose(); + // topToolStripContainer.Dispose(); + // splitContainer.Dispose(); + // if (progressForm != null) progressForm.Dispose(); + + // MainMenuStrip = null; + // cadFrame = null; + // cadCanvas = null; + // propertiesExplorer = null; + // topToolStripContainer = null; + // splitContainer = null; + // progressForm = null; + + // base.OnFormClosed(e); + // } + + protected override void OnLoaded(RoutedEventArgs e) + { + var topLevel = TopLevel.GetTopLevel(this)!; + topLevel.KeyDown += OnKeyDown; + base.OnLoaded(e); + } + + private Substitutes.Keys Subst(Key key) + { + return Enum.TryParse(key.ToString(), out Substitutes.Keys res) ? res : Substitutes.Keys.None; + } + + private void OnKeyDown(object sender, KeyEventArgs keyEvent) + { + Key key = keyEvent.Key; + bool preProcess = key >= Key.F1 && key <= Key.F24; + preProcess = preProcess || (key == Key.Escape); + preProcess = preProcess || (key == Key.Up) || (key == Key.Down); + preProcess = preProcess || (key == Key.Tab) || (key == Key.Enter); + preProcess = preProcess || keyEvent.KeyModifiers.HasFlag(KeyModifiers.Control) || keyEvent.KeyModifiers.HasFlag(KeyModifiers.Alt); // menu shortcut + if (propertiesExplorer.EntryWithTextBox == null) preProcess |= (key == Key.Delete); // the delete key is preferred by the textbox, if there is one + Substitutes.KeyEventArgs e = new Substitutes.KeyEventArgs(Subst(key)); + if (preProcess) + { + e.Handled = false; + cadFrame.PreProcessKeyDown(e); + if (e.Handled) { + keyEvent.Handled = true; + return; + } + } + CadFrame.PreProcessKeyDown(e); + if (e.Handled) { + keyEvent.Handled = true; + return; + } + base.OnKeyDown(keyEvent); + } + + public virtual bool OnCommand(string MenuId) + { + return false; + } + + public virtual bool OnUpdateCommand(string MenuId, CommandState CommandState) + { + return false; + } + + public virtual void OnSelected(MenuWithHandler selectedMenuItem, bool selected) + { + + } + + } +} diff --git a/CADability.Avalonia/CadFrame.cs b/CADability.Avalonia/CadFrame.cs new file mode 100644 index 00000000..4cf33b27 --- /dev/null +++ b/CADability.Avalonia/CadFrame.cs @@ -0,0 +1,500 @@ +using AvaloniaBase = Avalonia; +using Avalonia.Controls; +using CADability.GeoObject; +using CADability.UserInterface; +using System; +using System.Collections.Generic; +// using System.Drawing; +using System.Drawing.Printing; +using System.IO; +using System.Threading; +using Action = CADability.Actions.Action; + +namespace CADability.Avalonia +{ + /* TODO: change IFrame for "KernelOnly" KO + * Step by Step implement missing methods (copy implementation from SingleDocumentFrame) + * and remove interface methods from IFrame with "#if KO" ... + */ + /// + /// Implementation of the abstract FrameImpl, doing things, you cannot do in .NET Core + /// + public class CadFrame : FrameImpl, IUIService + { + #region PRIVATE FIELDS + + private ICommandHandler commandHandler; + private CadCanvas cadCanvas; + + private const string ClipFormat = "CADability.GeoObjectList.Json"; + + // Optional: COM-freie Verfügbarkeitsprüfung (robuster im Idle) + // TODO reimplement in Avalonia + // static class NativeClipboard + // { + // [DllImport("user32.dll", CharSet = CharSet.Unicode)] + // private static extern uint RegisterClipboardFormat(string lpszFormat); + + // [DllImport("user32.dll")] + // private static extern bool IsClipboardFormatAvailable(uint format); + + // private static uint _fmtId; + // public static bool HasMyFormat() + // { + // if (_fmtId == 0) _fmtId = RegisterClipboardFormat(ClipFormat); + // return IsClipboardFormatAvailable(_fmtId); + // } + // } + + #endregion PRIVATE FIELDS + + #region PUBLIC PROPERTIES + + /// + /// The parent form menu + /// + // TODO reimplement in Avalonia + // public MenuStrip FormMenu { set; private get; } + + /// + /// Action that delegate the progress. + /// In this way we can use a custom progress ui. + /// + public Action ProgressAction { get; set; } + + #endregion PUBLIC PROPERTIES + + /// + /// Constructor without form dependency. + /// + /// + /// + /// + // TODO reimplement in Avalonia + // public CadFrame(PropertiesExplorer propertiesExplorer, CadCanvas cadCanvas, ICommandHandler commandHandler) + // : base(propertiesExplorer, cadCanvas) + // { + // this.commandHandler = commandHandler; + // } + /// + /// Constructor without form dependency. + /// + /// + /// + // + // allows cadFrame.ControlCenter to work + public CadFrame(PropertiesExplorer propertiesExplorer, CadCanvas cadCanvas, ICommandHandler commandHandler) + : base(propertiesExplorer, cadCanvas) + { + this.commandHandler = commandHandler; + this.cadCanvas = cadCanvas; + } + + #region FrameImpl override + + public override bool OnCommand(string MenuId) + { + if (commandHandler != null && commandHandler.OnCommand(MenuId)) return true; + return base.OnCommand(MenuId); + } + + public override bool OnUpdateCommand(string MenuId, CommandState CommandState) + { + if (commandHandler != null && commandHandler.OnUpdateCommand(MenuId, CommandState)) return true; + return base.OnUpdateCommand(MenuId, CommandState); + } + + public override void UpdateMRUMenu(string[] mruFiles) + { + // if (this.FormMenu != null) + // { + // TODO reimplement in Avalonia + // foreach (ToolStripMenuItem mi in this.FormMenu.Items) + // { + // UpdateMRUMenu(mi, mruFiles); + // } + // } + } + + // TODO reimplement in Avalonia + // private void UpdateMRUMenu(ToolStripMenuItem mi, string[] mruFiles) + // { + // if (mi.HasDropDownItems) + // { + // foreach (ToolStripItem tsi in mi.DropDownItems) + // { + // if (tsi is ToolStripMenuItem mmi) UpdateMRUMenu(mmi, mruFiles); + // } + // } + // else + // { + // if (mi is MenuItemWithHandler mid) + // { + // if (mid.Tag is MenuWithHandler mwh) + // { + // string MenuId = mwh.ID; + // if (MenuId.StartsWith("MenuId.File.Mru.File")) + // { + // string filenr = MenuId.Substring("MenuId.File.Mru.File".Length); + // try + // { + // int n = int.Parse(filenr); + // if (n <= mruFiles.Length && n > 0) + // { + // string[] parts = mruFiles[mruFiles.Length - n].Split(';'); + // if (parts.Length > 1) + // mid.Text = parts[0]; + // } + // } + // catch (FormatException) { } + // catch (OverflowException) { } + // } + // } + // } + // } + // } + + #endregion FrameImpl override + + #region IUIService implementation + // TODO reimplement in Avalonia + public override IUIService UIService => this; + GeoObjectList IUIService.GetDataPresent(object data) + { + throw new NotImplementedException(); + } + // { + // if (data is IDataObject idata) + // { + // if (idata.GetDataPresent(System.Windows.Forms.DataFormats.Serializable)) + // { + // return idata.GetData(System.Windows.Forms.DataFormats.Serializable) as GeoObjectList; + // } + // } + // return null; + // } + + Substitutes.Keys IUIService.ModifierKeys => cadCanvas.ModifierKeys; + Substitutes.Point IUIService.CurrentMousePosition => cadCanvas.CurrentMousePosition; + + private static Dictionary directories = new Dictionary(); + // TODO reimplement in Avalonia + Substitutes.DialogResult IUIService.ShowOpenFileDlg(string id, string title, string filter, ref int filterIndex, out string fileName) + { + throw new NotImplementedException(); + } + // { + // OpenFileDialog openFileDialog = new OpenFileDialog(); + // openFileDialog.Filter = filter; + // openFileDialog.FilterIndex = filterIndex; + // if (!string.IsNullOrWhiteSpace(title)) openFileDialog.Title = title; + // if (!string.IsNullOrWhiteSpace(id) && directories.TryGetValue(id, out string directory)) + // { + // openFileDialog.InitialDirectory = directory; + // } + // else + // { + // openFileDialog.RestoreDirectory = true; + // } + + // Substitutes.DialogResult res = (Substitutes.DialogResult)openFileDialog.ShowDialog(Application.OpenForms[0]); + // if (res == Substitutes.DialogResult.OK) + // { + // filterIndex = openFileDialog.FilterIndex; + // fileName = openFileDialog.FileName; + // if (!string.IsNullOrWhiteSpace(id)) + // { + // directory = System.IO.Path.GetDirectoryName(fileName); + // directories[id] = directory; + // } + // } + // else + // { + // fileName = null; + // } + // return res; + // } + // TODO reimplement in Avalonia + Substitutes.DialogResult IUIService.ShowSaveFileDlg(string id, string title, string filter, ref int filterIndex, ref string fileName) + { + throw new NotImplementedException(); + } + // { + // SaveFileDialog saveFileDialog = new SaveFileDialog(); + // saveFileDialog.Filter = filter; + // saveFileDialog.FilterIndex = filterIndex; + // if (!string.IsNullOrWhiteSpace(title)) saveFileDialog.Title = title; + // if (!string.IsNullOrWhiteSpace(id) && directories.TryGetValue(id, out string directory)) + // { + // saveFileDialog.InitialDirectory = directory; + // } + // else + // { + // saveFileDialog.RestoreDirectory = true; + // } + // if (!string.IsNullOrWhiteSpace(fileName)) + // { + // saveFileDialog.FileName = fileName; + // } + // Substitutes.DialogResult res = (Substitutes.DialogResult)saveFileDialog.ShowDialog(Application.OpenForms[0]); + // if (res == Substitutes.DialogResult.OK) + // { + // filterIndex = saveFileDialog.FilterIndex; + // fileName = saveFileDialog.FileName; + // if (!string.IsNullOrWhiteSpace(id)) + // { + // directory = System.IO.Path.GetDirectoryName(fileName); + // directories[id] = directory; + // } + // } + // return res; + // } + // TODO reimplement in Avalonia + Substitutes.DialogResult IUIService.ShowMessageBox(string text, string caption, Substitutes.MessageBoxButtons buttons) + { + throw new NotImplementedException(); + } + // { + // return (Substitutes.DialogResult)MessageBox.Show(Application.OpenForms[0], text, caption, (System.Windows.Forms.MessageBoxButtons)buttons); + // } + // TODO reimplement in Avalonia + Substitutes.DialogResult IUIService.ShowColorDialog(ref Substitutes.Color color) + { + throw new NotImplementedException(); + } + // { + // ColorDialog colorDialog = new ColorDialog(); + // colorDialog.Color = Drawing(color); + // Substitutes.DialogResult dlgres = (Substitutes.DialogResult)colorDialog.ShowDialog(Application.OpenForms[0]); + // color = Subst(colorDialog.Color); + // return dlgres; + // } + + private Substitutes.Color Drawing(Substitutes.Color color) + { + return color; + // return Substitutes.Color.FromArgb(color.ToArgb()); + } + + private Substitutes.Color Subst(Substitutes.Color color) // TODO correct argument type + { + return color; + // return Substitutes.Color.FromArgb(color.ToArgb()); + } + + // TODO reimplement in Avalonia + void IUIService.ShowProgressBar(bool show, double percent, string title) + { + throw new NotImplementedException(); + } + // { + // this.ProgressAction?.Invoke(show, percent, title); + // } + /// + /// Returns a bitmap from the specified embeded resource. the name is in the form filename:index + /// + /// + /// + // TODO reimplement in Avalonia + object IUIService.GetBitmap(string name) + { + throw new NotImplementedException(); + } + // { + // string[] parts = name.Split(':'); + // if (parts.Length == 2) + // { + // ImageList il = BitmapTable.GetImageList(parts[0], 15, 15); + // if (il != null) + // { + // try + // { + // int ind = int.Parse(parts[1]); + // return il.Images[ind] as Bitmap; + // } + // catch (FormatException) { } + // catch (OverflowException) { } + // catch (ArgumentOutOfRangeException) { } + // } + // } + // return null; + // } + // TODO reimplement in Avalonia + IPaintTo3D IUIService.CreatePaintInterface(object paintToBitmap, double precision) + { + throw new NotImplementedException(); + } + // { + // PaintToOpenGL paintTo3D = new PaintToOpenGL(precision); + // paintTo3D.Init(paintToBitmap as Bitmap); + // return paintTo3D; + // } + // TODO reimplement in Avalonia + Substitutes.DialogResult IUIService.ShowPageSetupDlg(object printDocument, object pageSettings, out int width, out int height, out bool landscape) + { + throw new NotImplementedException(); + } + // { + // PageSetupDialog psd = new PageSetupDialog(); + // psd.AllowPrinter = true; + // psd.EnableMetric = true; + // psd.Document = (PrintDocument?)printDocument; + // Substitutes.DialogResult res = (Substitutes.DialogResult)(int)psd.ShowDialog(); + // if (res == Substitutes.DialogResult.OK) + // { + // psd.Document.OriginAtMargins = false; + // printDocument = psd.Document; + // width = psd.PageSettings.PaperSize.Width; + // height = psd.PageSettings.PaperSize.Height; + // landscape = psd.PageSettings.Landscape; + // } + // else + // { + // width = height = 0; + // landscape = false; + // } + // return res; + // } + // TODO reimplement in Avalonia + Substitutes.DialogResult IUIService.ShowPrintDlg(object printDocument) + { + throw new NotImplementedException(); + } + // { + // PrintDialog printDialog = new PrintDialog(); + // printDialog.Document = (PrintDocument?)printDocument; + // printDialog.AllowSomePages = false; + // printDialog.AllowCurrentPage = false; + // printDialog.AllowSelection = false; + // printDialog.AllowPrintToFile = false; + // Substitutes.DialogResult res = (Substitutes.DialogResult)printDialog.ShowDialog(); + // if (res == Substitutes.DialogResult.OK) + // { + // printDocument = printDialog.Document; + // } + // return res; + // } + // TODO reimplement in Avalonia + void IUIService.SetClipboardData(GeoObjectList objects, bool copy) + { + throw new NotImplementedException(); + } + // { + // // -> JSON in Byte-Array serialisieren + // byte[] payload; + // using (var ms = new MemoryStream()) + // { + // var js = new JsonSerialize(); + // js.ToStream(ms, objects, closeStream: false); + // payload = ms.ToArray(); + // } + + // // DataObject mit eigenem Format befüllen (ohne Auto-Konvertierung) + // var dob = new DataObject(); + // dob.SetData(ClipFormat, false, payload); + + // // Optional: eine menschenlesbare Text-Notiz (damit Paste in Notepad nicht leer ist) + // dob.SetText($"CADability GeoObjectList ({objects?.Count ?? 0} items)"); + + // // copy=true: Inhalte bleiben erhalten, auch wenn die App endet + // Clipboard.SetDataObject(dob, copy); + // } + // TODO reimplement in Avalonia + object IUIService.GetClipboardData(Type typeOfdata) + { + throw new NotImplementedException(); + } + // { + // try + // { + // // defensiv prüfen, ohne GetDataObject() + // if (!Clipboard.ContainsData(ClipFormat)) + // return null; + + // var data = Clipboard.GetData(ClipFormat); + // if (data is byte[] bytes) + // { + // using var ms = new MemoryStream(bytes); + // var js = new JsonSerialize(); + // return js.FromStream(ms); // -> GeoObjectList + // } + + // // Falls ein Stream geliefert wird (kann je nach Host passieren) + // if (data is Stream s) + // { + // using var ms = new MemoryStream(); + // s.CopyTo(ms); + // ms.Position = 0; + // var js = new JsonSerialize(); + // return js.FromStream(ms); + // } + + // // Worst case: String (sollten wir nicht bekommen, aber behandeln) + // if (data is string json) + // { + // return JsonSerialize.FromString(json); + // } + + // return null; + // } + // catch (ExternalException) + // { + // // Clipboard gerade gelockt o.ä. – einfach "kein Inhalt" signalisieren + // return null; + // } + // } + // TODO reimplement in Avalonia + bool IUIService.HasClipboardData(Type typeOfdata) + { + throw new NotImplementedException(); + } + // { + // try + // { + // // Leichtgewichtige .NET-Variante: + // if (Clipboard.ContainsData(ClipFormat)) return true; + + // // Optional, noch robuster & COM-frei (empfohlen im Idle): + // // return NativeClipboard.HasMyFormat(); + + // return false; + // } + // catch (ExternalException) + // { + // // z. B. wenn gerade ein anderer Prozess das Clipboard geöffnet hat + // return false; + // } + // } + + public Substitutes.FontFamily GetFontFamily(string fontFamilyName) + { + throw new NotImplementedException(); + // if (string.IsNullOrEmpty(fontFamilyName)) return new FontFamilyImpl(); + // else return new FontFamilyImpl(fontFamilyName); + } + + public string[] GetFontFamilies() + { + throw new NotImplementedException(); + // FontFamily[] ffs = FontFamily.Families; + // List result = new List(); + // for (int i = 0; i < ffs.Length; i++) result.Add(ffs[i].Name); + // return result.ToArray(); + } + + // TODO reimplement in Avalonia + event EventHandler IUIService.ApplicationIdle + { + add + { + // Application.Idle += value; + } + + remove + { + // Application.Idle -= value; + } + } + #endregion + } +} diff --git a/CADability.Avalonia/MarkupExtensions.cs b/CADability.Avalonia/MarkupExtensions.cs new file mode 100644 index 00000000..2cc8740a --- /dev/null +++ b/CADability.Avalonia/MarkupExtensions.cs @@ -0,0 +1,15 @@ +using CADability.UserInterface; +using System; + +namespace CADability.Avalonia +{ + public class StringTableExtension + { + public string Key { get; set; } = ""; + + public string ProvideValue(IServiceProvider serviceProvider) + { + return StringTable.GetString(Key) ?? Key; + } + } +} diff --git a/CADability.Avalonia/MenuManager.cs b/CADability.Avalonia/MenuManager.cs new file mode 100644 index 00000000..9033b5c2 --- /dev/null +++ b/CADability.Avalonia/MenuManager.cs @@ -0,0 +1,52 @@ +using Avalonia.Controls; +using CADability.UserInterface; +using System; +using System.Windows.Input; + +namespace CADability.Avalonia +{ + internal class MenuCommand : ICommand + { + ICommandHandler handler; + + public MenuCommand(ICommandHandler handler) + { + this.handler = handler; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return true; + } + + public void Execute(object parameter) + { + handler.OnCommand((string)parameter); + } + } + + public static class MenuManager + { + public static void MakeMainMenu(MenuWithHandler[] definition, Menu mainMenu, ICommandHandler handler) + { + CreateMenuItems(definition, mainMenu, handler); + } + + private static void CreateMenuItems(MenuWithHandler[] definition, ItemsControl menu, ICommandHandler handler) + { + if (definition is null) return; + + foreach (var menuDefinition in definition) { + MenuItem item = new MenuItem { + Header = menuDefinition.Text, + Command = new MenuCommand(menuDefinition), + CommandParameter = menuDefinition.ID, + }; + CreateMenuItems(menuDefinition.SubMenus, item, handler); + menu.Items.Add(item); + } + } + } +} diff --git a/CADability.Avalonia/PaintToOpenGL.cs b/CADability.Avalonia/PaintToOpenGL.cs new file mode 100644 index 00000000..eb461032 --- /dev/null +++ b/CADability.Avalonia/PaintToOpenGL.cs @@ -0,0 +1,869 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Linq; +using System.Runtime.InteropServices; +using Silk.NET.OpenGL; +using Avalonia.Controls; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Controls; +using Avalonia.Threading; +using CADability; +using CADability.Attribute; +using CADability.GeoObject; +using CADability.Substitutes; +using MathNet.Numerics.LinearAlgebra.Single; + +using static Avalonia.OpenGL.GlConsts; + +namespace CADability.Avalonia +{ + class PaintToOpenGL : OpenGlControlBase, IPaintTo3D + { + private uint _shaderProgram; + private uint _vertexShader; + private uint _fragmentShader; + private uint _indexBufferObject; + private int modelViewLocation; + private int projectionLocation; + private int colorLocation; + private int lightPositionLocation; + private int ambientFactorLocation; + + private GL _gl; // use Silk.NET gl interface, as Avalonia only has incomplete bindings + private GlInterface _aGl; // Avalonia gl interface + private Color _backgroundColor; + + private VertexArrayObject currentVao; + private Dictionary vaos; + + private GeoVector projectionDirection; + private bool isPerspective; + private bool paintSurfaces; + private bool paintEdges; + private bool paintSurfaceEdges; + private bool useLineWidth; + private bool selectMode; + private bool delayText; + private bool delayAll; + private bool triangulateText; + private bool dontRecalcTriangulation; + private bool isBitmap; + private bool colorOverride; + private double precision; + private double pixelToWorld; + private Color selectColor; + private PaintCapabilities capabilities; + + public CadCanvas CadCanvas { get; set; } + + public PaintToOpenGL(double precision = 1e-6) + { + this.precision = precision; + paintSurfaces = true; + paintEdges = true; + paintSurfaceEdges = true; + selectColor = Substitutes.Color.Yellow; + vaos = new Dictionary(); + } + + private void GlCheckError() + { + GLEnum err; + while ((err = _gl.GetError()) != GLEnum.NoError) { + Console.WriteLine(err); + } + } + + private string GetShader(string shader) + { + string data = "#version 330\n"; + if (GlVersion.Type == GlProfileType.OpenGLES) { + data += "precision mediump float;\n"; + } + data += shader; + return data; + } + + private void ConfigureShaders() + { + _shaderProgram = _gl.CreateProgram(); + + _vertexShader = _gl.CreateShader(ShaderType.VertexShader); + Console.WriteLine(_aGl.CompileShaderAndGetError((int)_vertexShader, VertexShaderSource)); + _gl.AttachShader(_shaderProgram, _vertexShader); + + _fragmentShader = _gl.CreateShader(ShaderType.FragmentShader); + Console.WriteLine(_aGl.CompileShaderAndGetError((int)_fragmentShader, FragmentShaderSource)); + _gl.AttachShader(_shaderProgram, _fragmentShader); + + Console.WriteLine(_aGl.LinkProgramAndGetError((int)_shaderProgram)); + _gl.UseProgram(_shaderProgram); + + modelViewLocation = _gl.GetUniformLocation(_shaderProgram, "modelview"); + projectionLocation = _gl.GetUniformLocation(_shaderProgram, "projection"); + colorLocation = _gl.GetUniformLocation(_shaderProgram, "color"); + lightPositionLocation = _gl.GetUniformLocation(_shaderProgram, "lightPosition"); + ambientFactorLocation = _gl.GetUniformLocation(_shaderProgram, "ambientFactor"); + GlCheckError(); + } + + protected override void OnOpenGlInit(GlInterface gl) + { + base.OnOpenGlInit(gl); + + _aGl = gl; + _gl = GL.GetApi(gl.GetProcAddress); + + ConfigureShaders(); + + GlCheckError(); + } + + protected override void OnOpenGlDeinit(GlInterface gl) + { + base.OnOpenGlDeinit(gl); + + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0); + _gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, 0); + _gl.BindVertexArray(0); + _gl.UseProgram(0); + _gl.DeleteProgram(_shaderProgram); + _gl.DeleteShader(_vertexShader); + _gl.DeleteShader(_fragmentShader); + + GlCheckError(); + } + + protected override void OnOpenGlRender(GlInterface _, int fb) + { + Substitutes.PaintEventArgs paintEventArgs = new Substitutes.PaintEventArgs() + { + ClipRectangle = new Substitutes.Rectangle(0, 0, (int)Bounds.Width, (int)Bounds.Height), + // ClipRectangle = new Substitutes.Rectangle((int)Bounds.X, (int)Bounds.Y, (int)Bounds.Width, (int)Bounds.Height), + Graphics = null // TODO should be fine, is there a better solution? + }; + CadCanvas?.OnPaint(paintEventArgs); + GlCheckError(); + + Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); + } + + string VertexShaderSource => GetShader(@" + layout(location = 0) in vec3 vertex; + layout(location = 1) in vec3 normal; + out vec3 fragmentPosition; + out vec3 fragmentNormal; + uniform mat4 modelview; + uniform mat4 projection; + + void main() + { + fragmentPosition = vertex; + fragmentNormal = normal; + gl_Position = projection * modelview * vec4(vertex, 1.0); + } + "); + + string FragmentShaderSource => GetShader(@" + in vec3 fragmentPosition; + in vec3 fragmentNormal; + layout(location = 0) out vec4 diffuseColor; + uniform vec4 color; + uniform vec3 lightPosition; + uniform float ambientFactor; + vec3 lightColor = vec3(1.0, 1.0, 1.0); + + void main() + { + vec3 normal = normalize(fragmentNormal); + vec3 lightDirection = normalize(lightPosition - fragmentPosition); + float diffuse = max(dot(normal, lightDirection), 0.0); + diffuseColor = vec4((ambientFactor + diffuse) * lightColor * color.rgb, color.a); + } + "); + + bool IPaintTo3D.PaintSurfaces + { + get { return paintSurfaces; } + } + + bool IPaintTo3D.PaintEdges + { + get { return paintEdges; } + } + + bool IPaintTo3D.PaintSurfaceEdges + { + get { return paintSurfaceEdges; } + set { paintSurfaceEdges = value; } + } + + bool IPaintTo3D.UseLineWidth + { + get { return useLineWidth; } + set { useLineWidth = value; } + } + + double IPaintTo3D.Precision + { + get { return precision; } + set { precision = value; } + } + + double IPaintTo3D.PixelToWorld + { + get { return pixelToWorld; } + } + + bool IPaintTo3D.SelectMode + { + get { return selectMode; } + set { selectMode = value; } + } + + Color IPaintTo3D.SelectColor + { + get { return selectColor; } + set { selectColor = value; } + } + + bool IPaintTo3D.DelayText + { + get { return delayText; } + set { delayText = value; } + } + + bool IPaintTo3D.DelayAll + { + get { return delayAll; } + set { delayAll = value; } + } + + bool IPaintTo3D.TriangulateText + { + get { return triangulateText; } + set { triangulateText = value; } + } + + bool IPaintTo3D.DontRecalcTriangulation + { + get { return dontRecalcTriangulation; } + set { dontRecalcTriangulation = value; } + } + + PaintCapabilities IPaintTo3D.Capabilities + { + get { return capabilities; } + } + + bool IPaintTo3D.IsBitmap + { + get { return isBitmap; } + } + IDisposable IPaintTo3D.FacesBehindEdgesOffset { + get { throw new NotImplementedException(); } + } + + void IPaintTo3D.MakeCurrent() + { + // noop, handled by Avalonia + } + + private void SetFragmentColor(Color color) + { + // _gl.Uniform4(colorLocation, (float)color.R / 255f, (float)color.G / 255f, (float)color.B / 255f, (float)color.A / 255f); + _gl.Uniform4(colorLocation, (float)color.R / 255f, (float)color.G / 255f, (float)color.B / 255f, 1.0f); + GlCheckError(); + } + + void IPaintTo3D.SetColor(Color color, int lockColor = 0) + { + Color res; + if (!colorOverride) + { + if (color.R == _backgroundColor.R && color.G == _backgroundColor.G && color.B == _backgroundColor.B) + { + if (color.R + color.G + color.B < 3 * 128) + { + res = Color.FromArgb(color.A, 255, 255, 255); + } + else + { + res = Color.FromArgb(color.A, 0, 0, 0); + } + } + else + { + res = color; + } + + if (currentVao != null) { + currentVao.Color = res; + } + else + { + this.SetFragmentColor(res); + } + } + if (lockColor == 1) colorOverride = true; + else if (lockColor == -1) colorOverride = false; + } + void IPaintTo3D.AvoidColor(Color color) + { + _backgroundColor = color; + } + void IPaintTo3D.SetLineWidth(LineWidth lineWidth) + { + if (lineWidth != null) { + // Console.WriteLine("SetLineWidth: " + lineWidth.Width); + } + // throw new NotImplementedException(); + } + void IPaintTo3D.SetLinePattern(LinePattern pattern) + { + // throw new NotImplementedException(); + } + unsafe void IPaintTo3D.Polyline(GeoPoint[] points) + { + VertexArrayObject vao = currentVao; + if (vao == null) vao = new VertexArrayObject("single Polyline VAO", _gl); + + vao.addVertices(points, GLEnum.LineStrip, (uint)(3 * sizeof(float))); + + if (currentVao == null) { + vao.Close(); + + (this as IPaintTo3D).List(vao); + } + + GlCheckError(); + } + + void IPaintTo3D.FilledPolyline(GeoPoint[] points) + { + throw new NotImplementedException(); + } + void IPaintTo3D.Points(GeoPoint[] points, float size, PointSymbol pointSymbol) + { + throw new NotImplementedException(); + } + unsafe void IPaintTo3D.Triangle(GeoPoint[] vertices, GeoVector[] normals, int[] indextriples) + { + VertexArrayObject vao = currentVao; + if (vao == null) vao = new VertexArrayObject("single Triangle VAO", _gl); + + // TODO correctly sort indices depending on direction and enable culling + + vao.addIndexedVertices(vertices, indextriples, GLEnum.Triangles, (uint)(6 * sizeof(float)), normals); + // vao.addIndexedVertices(vertices, indextriples, GLEnum.Triangles, (uint)(3 * sizeof(float))); + + if (currentVao == null) { + vao.Close(); + + (this as IPaintTo3D).List(vao); + } + + GlCheckError(); + } + void IPaintTo3D.PrepareText(string fontName, string textString, object fontStyle) + { + + } + void IPaintTo3D.PreparePointSymbol(PointSymbol pointSymbol) + { + + } + void IPaintTo3D.PrepareIcon(object icon) + { + + } + void IPaintTo3D.PrepareBitmap(object bitmap, int xoffset, int yoffset) + { + throw new NotImplementedException(); + } + void IPaintTo3D.PrepareBitmap(object bitmap) + { + throw new NotImplementedException(); + } + void IPaintTo3D.RectangularBitmap(object bitmap, GeoPoint location, GeoVector directionWidth, GeoVector directionHeight) + { + throw new NotImplementedException(); + } + void IPaintTo3D.Text(GeoVector lineDirection, GeoVector glyphDirection, GeoPoint location, string fontName, string textString, object fontStyle, CADability.GeoObject.Text.AlignMode alignment, CADability.GeoObject.Text.LineAlignMode lineAlignment) + { + + } + unsafe void IPaintTo3D.List(IPaintTo3DList paintThisList) + { + VertexArrayObject vao; + if (paintThisList is VertexArrayObject) { + vao = (VertexArrayObject)paintThisList; + } + else if (!vaos.TryGetValue(paintThisList.Name, out vao)) + { + throw new ApplicationException("paintThisList does not exist: " + paintThisList.Name); + } + + if (!vao.IsClosed) throw new ApplicationException("Can only paint closed VAOs"); + + _gl.BindVertexArray(vao.VAO); + + if (vao.Color != null) { + this.SetFragmentColor(vao.Color); + } + if (vao.ModelView != null) { + _gl.UniformMatrix4(modelViewLocation, false, vao.ModelView); + // TODO should we reset to identity after drawing? + } + + if (!vao.HasIndices) { + // for simple lines, just drawing the whole VBO with DrawArrays should be fine. + // However, this does not work for line strips + // _gl.DrawArrays(vao.Primitive, 0, vao.FloatCount); + fixed (void* offsets = CollectionsMarshal.AsSpan(vao.SegmentOffsets), counts = CollectionsMarshal.AsSpan(vao.SegmentCounts)) { + _gl.MultiDrawArrays(vao.Primitive, (int*)offsets, (uint*)counts, (uint)vao.SegmentOffsets.Count); + } + } + else + { + // for triangles, we could just use DrawElements and not care about multiple objects + // in one VBO. However, it could be a problem for other primitives and triangle strips + // _gl.DrawElements(vao.Primitive, vao.IndicesCount, DrawElementsType.UnsignedInt, (void*)0); + fixed (void* offsets = vao.OffsetPointers, counts = CollectionsMarshal.AsSpan(vao.SegmentCounts)) { + _gl.MultiDrawElements(vao.Primitive, (uint*)counts, GLEnum.UnsignedInt, (void**)offsets, (uint)vao.SegmentOffsets.Count); + } + } + + _gl.BindVertexArray(0); + } + void IPaintTo3D.SelectedList(IPaintTo3DList paintThisList, int wobbleRadius) + { + // throw new NotImplementedException(); + (this as IPaintTo3D).List(paintThisList); // TODO select color + } + void IPaintTo3D.Nurbs(GeoPoint[] poles, double[] weights, double[] knots, int degree) + { + throw new NotImplementedException(); + } + void IPaintTo3D.Line2D(int sx, int sy, int ex, int ey) + { + throw new NotImplementedException(); + } + void IPaintTo3D.Line2D(PointF p1, PointF p2) + { + throw new NotImplementedException(); + } + void IPaintTo3D.FillRect2D(PointF p1, PointF p2) + { + throw new NotImplementedException(); + } + void IPaintTo3D.Point2D(int x, int y) + { + throw new NotImplementedException(); + } + void IPaintTo3D.DisplayIcon(GeoPoint p, object icon) + { + + } + void IPaintTo3D.DisplayBitmap(GeoPoint p, object bitmap) + { + throw new NotImplementedException(); + } + + // helper to get an identity matrix. TODO use library function? + private float[] getIdentity4x4() { + float[] mat = new float[16]; + mat[0] = 1.0f; + mat[1] = 0.0f; + mat[2] = 0.0f; + mat[3] = 0.0f; + mat[4] = 0.0f; + mat[5] = 1.0f; + mat[6] = 0.0f; + mat[7] = 0.0f; + mat[8] = 0.0f; + mat[9] = 0.0f; + mat[10] = 1.0f; + mat[11] = 0.0f; + mat[12] = 0.0f; + mat[13] = 0.0f; + mat[14] = 0.0f; + mat[15] = 1.0f; + return mat; + } + + void IPaintTo3D.SetProjection(Projection projection, BoundingBox boundingCube) + { + _gl.Viewport(0, 0, (uint)Bounds.Width, (uint)Bounds.Height); + + // TODO correct culling + _gl.Enable(GLEnum.CullFace); + + // the role of the bounding cube is to tell the projection, which z values are relevant + // when the drawing is flat (z always 0) you cannot show temporary objects, which have a positive or negative z value + // e.g. there is a circle and you want to shoe a sphere with the circle as equator. The sphere has positive and negative z values. + // When the model changes, the bounding cube is automatically updated, but temporary objects don't change the bounding cube + // There is no foolproof way to handle this, but at least the most common cases should work when we alway use a equilateral (regular) cube + double size = Math.Max(boundingCube.XDiff, Math.Max(boundingCube.YDiff, boundingCube.ZDiff)); + GeoPoint center = boundingCube.GetCenter(); + BoundingBox boundingCubeEquilateral = new BoundingBox(new GeoPoint(center.x - size / 2, center.y - size / 2, center.z - size / 2), + new GeoPoint(center.x + size / 2, center.y + size / 2, center.z + size / 2)); + + double [,] mm = projection.GetOpenGLProjection(0, (int)Bounds.Width, 0, (int)Bounds.Height, boundingCubeEquilateral); + float[] pmat = new float[16]; + // ACHTUNG: Matrix ist vertauscht!!! + pmat[0] = (float) mm[0, 0]; + pmat[1] = (float) mm[1, 0]; + pmat[2] = (float) mm[2, 0]; + pmat[3] = (float) mm[3, 0]; + pmat[4] = (float) mm[0, 1]; + pmat[5] = (float) mm[1, 1]; + pmat[6] = (float) mm[2, 1]; + pmat[7] = (float) mm[3, 1]; + pmat[8] = (float) mm[0, 2]; + pmat[9] = (float) mm[1, 2]; + pmat[10] = (float) mm[2, 2]; + pmat[11] = (float) mm[3, 2]; + pmat[12] = (float) mm[0, 3]; + pmat[13] = (float) mm[1, 3]; + pmat[14] = (float) mm[2, 3]; + pmat[15] = (float) mm[3, 3]; + + // GeoVector v = projection.Direction; + projectionDirection = projection.Direction; + isPerspective = projection.IsPerspective; + // TODO use v for lighting position + GeoVector v; + // v = projection.InverseProjection * new GeoVector(0.5, 0.3, -1.0); + v = projection.InverseProjection * new GeoVector(100.0, 300.0, 1000.0); + pixelToWorld = projection.DeviceToWorldFactor; + _gl.Enable(GLEnum.DepthTest); + + float[] modelViewMat = getIdentity4x4(); + _gl.UniformMatrix4(modelViewLocation, 1, false, modelViewMat); + _gl.UniformMatrix4(projectionLocation, 1, false, pmat); + + _gl.Uniform3(lightPositionLocation, (float)v.x, (float)v.y, (float)v.z); + _gl.Uniform1(ambientFactorLocation, 0.2f); + GlCheckError(); + } + void IPaintTo3D.Clear(Color background) + { + _backgroundColor = background; + _gl.Viewport(0, 0, (uint)Bounds.Width, (uint)Bounds.Height); + _gl.ClearColor(background.R / 255.0f, background.G / 255.0f, background.B / 255.0f, 1.0f); + _gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + GlCheckError(); + } + void IPaintTo3D.Resize(int width, int height) + { + // TODO save bounds? + } + void IPaintTo3D.OpenList(string name) + { + if (name == null) throw new ApplicationException("List name cannot be empty."); + if (currentVao != null) throw new ApplicationException("VAOs cannot be nested!"); + + currentVao = new VertexArrayObject(name, _gl); + // this overwrites any previously existing vao with this name + vaos[name] = currentVao; + GlCheckError(); + } + IPaintTo3DList IPaintTo3D.CloseList() + { + if (currentVao != null) currentVao.Close(); + VertexArrayObject res = currentVao; + currentVao = null; + GlCheckError(); + return res; + } + IPaintTo3DList IPaintTo3D.MakeList(List sublists) + { + throw new NotImplementedException(); + } + void IPaintTo3D.OpenPath() + { + throw new NotSupportedException("OpenGL does not support paths"); + } + void IPaintTo3D.ClosePath(Color color) + { + throw new NotSupportedException("OpenGL does not support paths"); + } + void IPaintTo3D.CloseFigure() + { + throw new NotSupportedException("OpenGL does not support paths"); + } + void IPaintTo3D.Arc(GeoPoint center, GeoVector majorAxis, GeoVector minorAxis, double startParameter, double sweepParameter) + { + throw new NotSupportedException("OpenGL does not support paths"); + } + void IPaintTo3D.FreeUnusedLists() + { + throw new NotImplementedException(); + } + void IPaintTo3D.UseZBuffer(bool use) + { + if (use) + { + _gl.Enable(GLEnum.DepthTest); + _gl.DepthFunc(GLEnum.Always); // TODO this makes the test always pass + } + else + { + _gl.Disable(GLEnum.DepthTest); + } + } + void IPaintTo3D.Blending(bool on) + { + // throw new NotImplementedException(); + } + void IPaintTo3D.FinishPaint() + { + // throw new NotImplementedException(); + } + unsafe void IPaintTo3D.PaintFaces(PaintTo3D.PaintMode paintMode) + { + if (paintMode == PaintTo3D.PaintMode.FacesOnly) + { + if (isPerspective) + { + throw new NotImplementedException(); + } + else + { + Matrix modelViewMat = DenseMatrix.OfArray(new float[,] { + { 1.0f, 0.0f, 0.0f, (float)(2 * precision * projectionDirection.x)}, + { 0.0f, 1.0f, 0.0f, (float)(2 * precision * projectionDirection.y)}, + { 0.0f, 0.0f, 1.0f, (float)(2 * precision * projectionDirection.z)}, + { 0.0f, 0.0f, 0.0f, 1.0f } + }); + + if (currentVao != null) + { + currentVao.ModelView = modelViewMat.ToColumnMajorArray(); + } + else + { + _gl.UniformMatrix4(modelViewLocation, false, modelViewMat.ToColumnMajorArray()); + } + GlCheckError(); + + } + paintSurfaces = true; + paintEdges = false; + } + else if (paintMode == PaintTo3D.PaintMode.CurvesOnly) + { + Matrix identity = DenseMatrix.CreateIdentity(4); + _gl.UniformMatrix4(modelViewLocation, false, identity.ToColumnMajorArray()); + paintSurfaces = false; + paintEdges = true; + } + else if (paintMode == PaintTo3D.PaintMode.All) + { + Matrix identity = DenseMatrix.CreateIdentity(4); + _gl.UniformMatrix4(modelViewLocation, false, identity.ToColumnMajorArray()); + paintSurfaces = true; + paintEdges = true; + } + else + { + throw new NotImplementedException("Invalid paintMode"); + } + GlCheckError(); + } + void IPaintTo3D.Dispose() + { + + } + void IPaintTo3D.PushState() + { + // throw new NotImplementedException(); + // TODO do we have to do something here? new vao? + } + void IPaintTo3D.PopState() + { + // throw new NotImplementedException(); + // TODO do we have to do something here? new vao? + } + void IPaintTo3D.PushMultModOp(ModOp insertion) + { + // throw new NotImplementedException(); + // TODO do we have to do something here? new vao? + } + void IPaintTo3D.PopModOp() + { + // throw new NotImplementedException(); + // TODO do we have to do something here? new vao? + } + void IPaintTo3D.SetClip(Substitutes.Rectangle clipRectangle) + { + throw new NotImplementedException(); + } + + internal class VertexArrayObject : IPaintTo3DList + { + private string name; + private GL _gl; + private uint vertexArrayObject; + private uint vertexBufferObject; + private uint elementBufferObject; + private List vertices; + private List indices; + private GLEnum primitiveType; + private bool hasIndices; + private bool hasNormals; + private bool closed; + private uint floatCount; + private uint indicesCount; + private uint stride; + private Substitutes.Color color; + private float[] modelView; // column major order 4x4 matrix + private List segmentOffsets; + private List segmentCounts; + unsafe private uint*[] offsetPointers; + + public VertexArrayObject(string name, GL gl) + { + this.name = name; + this._gl = gl; + this.closed = false; + this.hasIndices = false; + this.hasNormals = false; + this.primitiveType = GLEnum.False; + this.stride = 0; + vertexArrayObject = _gl.GenVertexArray(); + vertexBufferObject = _gl.GenBuffer(); + elementBufferObject = _gl.GenBuffer(); + vertices = new List(); + indices = new List(); + segmentOffsets = new List(); + segmentCounts = new List(); + } + + public string Name + { + get { return name; } + set { throw new NotImplementedException(); } + } + public List containedSubLists + { + set { throw new NotImplementedException(); } + } + + public uint VAO => vertexArrayObject; + public uint VBO => vertexBufferObject; + public uint EBO => elementBufferObject; + public GLEnum Primitive => primitiveType; + + public bool HasIndices => hasIndices; + public uint FloatCount => floatCount; + public uint IndicesCount => indicesCount; + public uint Stride => stride; + public bool IsClosed => closed; + public bool HasNormals => hasNormals; + public List SegmentOffsets => segmentOffsets; + public List SegmentCounts => segmentCounts; + unsafe public uint*[] OffsetPointers => offsetPointers; + public Substitutes.Color Color { set; get; } + public float[] ModelView { set; get; } + + public void addVertices(GeoPoint[] points, GLEnum primitiveType, uint stride, GeoVector[] normals = null, bool addSegment = true) { + if (this.primitiveType != GLEnum.False && primitiveType != this.primitiveType) { + throw new ApplicationException("Only supports one primitive type in a VertexArrayObject"); + } + if (this.stride != 0 && this.stride != stride) { + throw new ApplicationException("Cannot change stride of VertexArrayObject"); + } + if (closed) throw new ApplicationException("Cannot add to closed VertexArrayObject"); + + if (addSegment) { + int offset = segmentOffsets.LastOrDefault() + (int)segmentCounts.LastOrDefault(); + segmentCounts.Add((uint)points.Length); + segmentOffsets.Add(offset); + } + + if (normals == null) + { + this.vertices.AddRange(points.SelectMany( vertex => new float[]{(float)vertex.x, (float)vertex.y, (float)vertex.z})); + } + else { + this.hasNormals = true; + this.vertices.AddRange( + points + .Zip(normals, (p, n) => new float[]{(float)p.x, (float)p.y, (float)p.z, (float)n.x, (float)n.y, (float)n.z}) + .SelectMany(f => f) + ); + } + this.stride = stride; + this.primitiveType = primitiveType; + } + + public void addIndexedVertices(GeoPoint[] points, int[] indices, GLEnum primitiveType, uint stride, GeoVector[] normals = null) { + // only apply an offset if not drawing in segments and offsetting the vertex attrib pointer + uint offset = (uint) (this.vertices.Count / (stride / sizeof(float))); + + this.addVertices(points, primitiveType, stride, normals, false); + + segmentCounts.Add((uint)indices.Length); + segmentOffsets.Add(this.indices.Count * sizeof(uint)); + + this.indices.AddRange(indices.Select(i => (uint) i + offset)); + this.hasIndices = true; + this.primitiveType = primitiveType; + } + + unsafe public void Close() + { + closed = true; + _gl.BindVertexArray(vertexArrayObject); + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, vertexBufferObject); + float[] verticesArray = vertices.ToArray(); + fixed(float* pData = verticesArray) + _gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(sizeof(float) * vertices.Count), + pData, BufferUsageARB.StaticDraw); + + if (hasIndices) { + _gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, elementBufferObject); + uint[] indicesArray = indices.ToArray(); + fixed(uint* pIData = indicesArray) + _gl.BufferData(BufferTargetARB.ElementArrayBuffer, (nuint)(sizeof(uint) * indices.Count), + pIData, BufferUsageARB.StaticDraw); + } + + _gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, (void*)0); + _gl.EnableVertexAttribArray(0); + + if (this.HasNormals) + { + _gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float))); + _gl.EnableVertexAttribArray(1); + } + + _gl.BindVertexArray(0); + if (this.hasIndices) { + // we have to convert segment offsets to pointers, c# does not support pointers in lists/generic type expressions + offsetPointers = new uint*[this.segmentOffsets.Count]; + for (int i = 0; i < this.segmentOffsets.Count; i++) { + offsetPointers[i] = (uint*)this.segmentOffsets[i]; + } + } + floatCount = (uint)vertices.Count; + indicesCount = (uint)indices.Count; + vertices.Clear(); + indices.Clear(); + vertices = null; + indices = null; + } + + public void Dispose() + { + _gl.DeleteBuffer(vertexBufferObject); + _gl.DeleteBuffer(elementBufferObject); + _gl.DeleteVertexArray(vertexArrayObject); + } + } + } +} diff --git a/CADability.Avalonia/PropertiesExplorer.axaml b/CADability.Avalonia/PropertiesExplorer.axaml new file mode 100644 index 00000000..7f6a6050 --- /dev/null +++ b/CADability.Avalonia/PropertiesExplorer.axaml @@ -0,0 +1,7 @@ + + + + diff --git a/CADability.Avalonia/PropertiesExplorer.axaml.cs b/CADability.Avalonia/PropertiesExplorer.axaml.cs new file mode 100644 index 00000000..684e7eaf --- /dev/null +++ b/CADability.Avalonia/PropertiesExplorer.axaml.cs @@ -0,0 +1,96 @@ +using Avalonia.Controls; +using CADability.Substitutes; +using CADability.UserInterface; +using System.Collections.Generic; + +namespace CADability.Avalonia +{ + + public partial class PropertiesExplorer: UserControl, IControlCenter + { + private Dictionary tabPages; + + public PropertiesExplorer() + { + InitializeComponent(); + tabPages = new Dictionary(); + } + + IPropertyPage IControlCenter.AddPropertyPage(string titleId, int iconId) + { + PropertyPage page = new PropertyPage(titleId, iconId, this); + tabPages[titleId] = page; + TabItem tab = new TabItem { + Header = StringTable.GetString(titleId + "TabPage", StringTable.Category.label), // TODO correct string + Content = page, + }; + tabControl.Items.Add(tab); + + return page; + } + + public IPropertyPage ActivePropertyPage => (tabControl.SelectedItem as PropertyPage); + + public IFrame Frame { get; set; } + public IPropertyEntry EntryWithTextBox { get; private set; } + + IPropertyPage IControlCenter.GetPropertyPage(string titleId) + { + PropertyPage ret; + tabPages.TryGetValue(titleId, out ret); + return ret; + } + + void IControlCenter.DisplayHelp(string helpID) + { + throw new System.NotImplementedException(); + } + + bool IControlCenter.ShowPropertyPage(string titleId) + { + if (tabPages.ContainsKey(titleId)) { + tabControl.SelectedItem = tabPages[titleId]; + return true; + } + return false; + } + + bool IControlCenter.RemovePropertyPage(string titleId) + { + if (tabPages.ContainsKey(titleId)) { + tabControl.Items.Remove(tabPages[titleId]); + tabPages.Remove(titleId); + return true; + } + return false; + } + + void IControlCenter.PreProcessKeyDown(KeyEventArgs e) + { + switch (e.KeyData) { + // case Keys.Tab: // TODO use ctrl to switch tabs + // case Keys.Enter: + // if (EntryWithTextBox != null) + // { + // // TODO end editing of text box + // // EntryWithTextBox.EndEdit(false, textBox.Modified, textBox.Text); + // } + + } + if (!e.SuppressKeyPress) { + (ActivePropertyPage as PropertyPage)?.PreProcessKeyDown(e); + } + } + + void IControlCenter.HideEntry(string entryId, bool hide) + { + throw new System.NotImplementedException(); + } + + IPropertyEntry IControlCenter.FindItem(string name) + { + throw new System.NotImplementedException(); + } + } + +} diff --git a/CADability.Avalonia/PropertyEntry.axaml b/CADability.Avalonia/PropertyEntry.axaml new file mode 100644 index 00000000..9b0f5d62 --- /dev/null +++ b/CADability.Avalonia/PropertyEntry.axaml @@ -0,0 +1,9 @@ + + + + + + diff --git a/CADability.Avalonia/PropertyEntry.axaml.cs b/CADability.Avalonia/PropertyEntry.axaml.cs new file mode 100644 index 00000000..73568b73 --- /dev/null +++ b/CADability.Avalonia/PropertyEntry.axaml.cs @@ -0,0 +1,70 @@ +using Avalonia.Controls; +using Avalonia.Layout; +using GridPanel = Avalonia.Controls.Grid; +using CADability.UserInterface; + +namespace CADability.Avalonia +{ + public partial class PropertyEntry: UserControl + { + private IPropertyPage parent; + private bool showOpen; + public IPropertyEntry Prop { get; private set; } + + public PropertyEntry(IPropertyEntry prop, IPropertyPage parent, bool showOpen) + { + InitializeComponent(); + this.Prop = prop; + this.parent = parent; + this.showOpen = showOpen; + (parent as PropertyPage).AddToHash(this); + createProp(prop); + if (showOpen) { + createSubEntries(prop); + } + } + + private void createProp(IPropertyEntry prop) + { + prop.Added(parent); + prop.Parent = parent; + TextBlock text = new TextBlock() { + Text = prop.Label, + [GridPanel.ColumnProperty] = 0, + }; + this.panel.Children.Add(text); + + if (prop.Flags.HasFlag(PropertyEntryType.ValueEditable)) { + TextBox value = new TextBox { + Text = prop.Label, + HorizontalAlignment = HorizontalAlignment.Right, + [GridPanel.ColumnProperty] = 1, + }; + this.panel.Children.Add(value); + } else { + TextBlock value = new TextBlock() { + Text = prop.Value, + HorizontalAlignment = HorizontalAlignment.Right, + [GridPanel.ColumnProperty] = 1, + }; + this.panel.Children.Add(value); + } + + } + + private void createSubEntries(IPropertyEntry prop) + { + if (prop.SubItems == null) return; + foreach (IPropertyEntry subProp in prop.SubItems) { + PropertyEntry subEntry = new PropertyEntry(subProp, parent, subProp.IsOpen); + this.childPanel.Children.Add(subEntry); + } + } + + public void Refresh() + { + // TODO + // check if subproperties have been opened? + } + } +} diff --git a/CADability.Avalonia/PropertyPage.axaml b/CADability.Avalonia/PropertyPage.axaml new file mode 100644 index 00000000..5f7c9ee7 --- /dev/null +++ b/CADability.Avalonia/PropertyPage.axaml @@ -0,0 +1,6 @@ + + + diff --git a/CADability.Avalonia/PropertyPage.axaml.cs b/CADability.Avalonia/PropertyPage.axaml.cs new file mode 100644 index 00000000..9abc2d0f --- /dev/null +++ b/CADability.Avalonia/PropertyPage.axaml.cs @@ -0,0 +1,139 @@ +using Avalonia.Controls; +using CADability.UserInterface; +using System; +using System.Collections.Generic; + +namespace CADability.Avalonia +{ + public partial class PropertyPage : UserControl, IPropertyPage + { + private PropertiesExplorer propertiesExplorer; + private string TitleId { get; } + private Dictionary properties; + + public event PreProcessKeyDown OnPreProcessKeyDown; + public event SelectionChanged OnSelectionChanged; + + public PropertyPage(string titleId, int iconId, PropertiesExplorer propExplorer) + { + InitializeComponent(); + this.propertiesExplorer = propExplorer; + this.TitleId = titleId; + properties = new Dictionary(); + } + + IPropertyEntry IPropertyPage.Selected { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + + public IFrame Frame => (propertiesExplorer as IControlCenter).Frame; + + public IView ActiveView => Frame.ActiveView; + + public void PreProcessKeyDown(Substitutes.KeyEventArgs e) + { + OnPreProcessKeyDown?.Invoke(e); + } + + public void AddToHash(PropertyEntry toAdd) + { + properties[toAdd.Prop] = toAdd; + } + + void IPropertyPage.Add(IPropertyEntry toAdd, bool showOpen) + { + Console.WriteLine("Label: " + toAdd.Label + " Index: " + toAdd.Index); + + PropertyEntry prop = new PropertyEntry(toAdd, this, showOpen); + panel.Children.Add(prop); + } + + void IPropertyPage.BringToFront() + { + (propertiesExplorer as IControlCenter).ShowPropertyPage(this.TitleId); + } + + void IPropertyPage.Clear() + { + panel.Children.Clear(); + properties.Clear(); + } + + bool IPropertyPage.ContainsEntry(IPropertyEntry entryWithTextBox) + { + throw new System.NotImplementedException(); + } + + IPropertyEntry IPropertyPage.FindFromHelpLink(string helpResourceID, bool searchTreeAndOpen) + { + throw new System.NotImplementedException(); + } + + IPropertyEntry IPropertyPage.GetCurrentSelection() + { + // throw new System.NotImplementedException(); + return null; // TODO + } + + IFrame IPropertyPage.GetFrame() => (this as IPropertyPage).Frame; + + IPropertyEntry IPropertyPage.GetParent(IShowProperty child) + { + throw new System.NotImplementedException(); + } + + bool IPropertyPage.IsOnTop() + { + throw new System.NotImplementedException(); + } + + bool IPropertyPage.IsOpen(IShowProperty toTest) + { + throw new System.NotImplementedException(); + } + + void IPropertyPage.MakeVisible(IPropertyEntry toShow) + { + throw new System.NotImplementedException(); + } + + void IPropertyPage.OpenSubEntries(IPropertyEntry toOpenOrClose, bool open) + { + throw new System.NotImplementedException(); + } + + void IPropertyPage.Refresh(IPropertyEntry toRefresh) + { + if (!properties.ContainsKey(toRefresh)) return; + + properties[toRefresh].Refresh(); + // PropertyEntry oldProp = properties[toRefresh]; + // // For now, just recreate it. Can be improved later. + // PropertyEntry prop = new PropertyEntry(toRefresh, this, toRefresh.IsOpen); + // properties[toRefresh] = prop; + // panel.Children.Insert(panel.Children.IndexOf(oldProp), prop); + // panel.Children.Remove(oldProp); + } + + void IPropertyPage.Remove(IPropertyEntry toRemove) + { + if (properties.ContainsKey(toRemove)) { + PropertyEntry res = properties[toRemove]; + if (!panel.Children.Contains(res)) { + // TODO recursive removal + throw new NotImplementedException(); + } + panel.Children.Remove(res); + } + } + + void IPropertyPage.SelectEntry(IPropertyEntry toSelect) + { + // throw new System.NotImplementedException(); + // TODO focus that entry + } + + void IPropertyPage.StartEditLabel(IPropertyEntry ToEdit) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/CADability.Avalonia/app.manifest b/CADability.Avalonia/app.manifest new file mode 100644 index 00000000..09474690 --- /dev/null +++ b/CADability.Avalonia/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/CADability/BooleanOperation.cs b/CADability/BooleanOperation.cs index 162c4244..b66c6e72 100644 --- a/CADability/BooleanOperation.cs +++ b/CADability/BooleanOperation.cs @@ -5,7 +5,9 @@ using CADability.Substitutes; using Point = CADability.GeoObject.Point; using MathNet.Numerics; +#if !AVALONIA using Microsoft.VisualStudio.DebuggerVisualizers; +#endif using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -20,8 +22,8 @@ namespace CADability /* This is the attempt to simplify the implementation of BRepIntersection/BRepOperation * There were too many exceptions and special cases in BRepOperation, especially concerning the overlapping faces, both * same oriantation and opposite orientation. I think, we don't need to consider overlapping faces if we consequently intersect faces - * which have edges on faces of the other shell. - * + * which have edges on faces of the other shell. + * * - put all faces (of both shells) in a single octtree * - use the octtree nodes to find pairs of faces (of different shells) which intersect each other and compute the intersection edges * - check whether edges of one face intersect with the other face @@ -49,13 +51,13 @@ namespace CADability */ /// - /// + /// /// public class BooleanOperation : OctTree { private Shell shell1; // the two shells, which are intersected private Shell shell2; // either shell1 and shell2 are set or multipleFaces is set - private List multipleFaces; // the faces, which are intersected. + private List multipleFaces; // the faces, which are intersected. private Dictionary originalToClonedEdges; // points from the original edges to the clones we are working on private Dictionary originalToClonedVertices; // points from the original vertices to the clones we are working on private Dictionary originalToClonedFaces; // points from the original faces to the clones we are working on @@ -80,7 +82,7 @@ public class BooleanOperation : OctTree Dictionary<(Edge edge, Face face), (List vertices, bool curveIsInSurface)> FaceEdgeIntersections; // to avoid duplicate calls to GetFaceEdgeIntersection HashSet commonOverlappingFaces; // Faces, which have been newly created as common parts of overlapping faces - HashSet cancelledfaces; // Faces, which cancel each other, they have the same area but are opposite oriented + HashSet cancelledfaces; // Faces, which cancel each other, they have the same area but are opposite oriented Dictionary> faceToIntersectionEdges; // faces of both shells with their intersection edges Dictionary> faceToCommonFaces; // faces which have overlapping common parts on them Dictionary> edgesToSplit; @@ -587,7 +589,7 @@ from j in Enumerable.Range(i + 1, list.Count - i - 1) // Problme wäre die Genauigkeit, wenn beim BooleanOperation.generateCycles die Richtung genommen wird... if (con1 is InterpolatedDualSurfaceCurve.ProjectedCurve pon1 && con2 is InterpolatedDualSurfaceCurve.ProjectedCurve pon2 && tr is InterpolatedDualSurfaceCurve idsc) - { // con1 und con2 müssen auf tr verweisen, sonst kann man das Face später nicht mit "ReverseOrientation" umdrehen. Dort wird nämlich die + { // con1 und con2 müssen auf tr verweisen, sonst kann man das Face später nicht mit "ReverseOrientation" umdrehen. Dort wird nämlich die // surface verändert, und die muss bei allen Kurven die selbe sein pon1.SetCurve3d(idsc); pon2.SetCurve3d(idsc); @@ -1213,7 +1215,7 @@ public enum Operation { union, intersection, difference, clip, connectMultiple, // createEdgeFaceIntersections(); // find intersection of edges with faces from different shells // splitEdges(); // split the edges at the found intersection positions // combineVerticesMultipleFaces(); // combine geometric close vertices - // removeIdenticalOppositeFaces(); // + // removeIdenticalOppositeFaces(); // // createNewEdges(); // populate faceToIntersectionEdges : for each face a list of intersection curves // createInnerFaceIntersections(); // find additional intersection curves where faces intersect, but edges don't intersect (rare) // TrimmIntersectionEdges(); @@ -1921,7 +1923,7 @@ public static (Shell[] upperPart, Shell[] lowerPart) SplitByFace(Shell toSplit, } /// /// Chamfer or bevel the provided edges. the edges must be connected and only two edges may have a common vertex. The edges must build a path. - /// We have two distances from the edge to make chamfers with different angles. All edges must belong to the . + /// We have two distances from the edge to make chamfers with different angles. All edges must belong to the . /// The first distance is on the primary face, the second on the other face (in most cases the distances are equal). /// /// @@ -1971,7 +1973,7 @@ public static Shell ChamferEdges(Face primaryFace, Edge[] edges, double primaryD // List fillets = new List(); // faces, that make the fillet // Dictionary> tangentialIntersectionEdges = new Dictionary>(); // Dictionary> rawFillets = new Dictionary>(); // the axis curve and simple fillets for each edge without joints - // Dictionary> joiningVertices = new Dictionary>(); // for each vertex there may be up to three involved edges + // Dictionary> joiningVertices = new Dictionary>(); // for each vertex there may be up to three involved edges // // 1.: create the simple fillets // foreach (Edge edg in edges) // { @@ -2190,7 +2192,7 @@ public static Shell ChamferEdges(Face primaryFace, Edge[] edges, double primaryD // Shell[] bores = bo.Result(); // if (bores != null && bores.Length == 1) // { - // if (bores[0].Faces.Length == 2) // this should be the case: + // if (bores[0].Faces.Length == 2) // this should be the case: // { // GeoObjectList fcs = bores[0].Decompose(); // if (fcs.Count == 2) @@ -2850,7 +2852,7 @@ public static bool TryFindIntersectingEdges(IEnumerable edges, out Edge e1 } listAB.Add(edge); - // fB → fA + // fB → fA if (!adj.TryGetValue(fB, out var nbrB)) { nbrB = new Dictionary>(); @@ -2892,11 +2894,11 @@ public static bool TryFindIntersectingEdges(IEnumerable edges, out Edge e1 public Shell[] Result() { // this method relies on - // - faceToIntersectionEdges: contains all faces, which intersect with other faces as keys and all the intersection edges, + // - faceToIntersectionEdges: contains all faces, which intersect with other faces as keys and all the intersection edges, // which are produced by other faces on this face as values. // - overlappingFaces and oppositeFaces: they contain pairs of faces, which overlap, with either same or oppostie orientation. - // - // The main algorithm does the following: split (or trimm or cut) a face according to the intersection edges on this face. + // + // The main algorithm does the following: split (or trimm or cut) a face according to the intersection edges on this face. // All edges are oriented, so it is possible to find outer edges and holes. A face might produce zero, one or multiple trimmed faces. // When all faces are trimmed, connect the trimmed faces with the untouched faces. this is the result. // @@ -3259,7 +3261,7 @@ public Shell[] Result() ++dbgc; } #endif - // each outline (only one in most cases) creates a new Face. + // each outline (only one in most cases) creates a new Face. for (int i = 0; i < numOutlines; i++) { Face fc = Face.Construct(); @@ -3291,7 +3293,7 @@ public Shell[] Result() if (operation == Operation.clip) return ClippedParts(trimmedFaces); if (dontReturnShell2) discardedFaces.UnionWith(shell2.Faces); - // Now trimmedFaces contains all faces which are cut by faces of the relative other shell, even those, where the other shell cuts + // Now trimmedFaces contains all faces which are cut by faces of the relative other shell, even those, where the other shell cuts // exactly along existing edges and nothing has been created. // The faces, which have been cut, i.e. faceToIntersectionEdges.Keys, are invalid now, we disconnect all edges from these faces #if DEBUG // show all trimmed faces @@ -3388,7 +3390,7 @@ public Shell[] Result() } } else if (edg.SecondaryFace == null || !trimmedFaces.Contains(edg.SecondaryFace) || !trimmedFaces.Contains(edg.PrimaryFace)) - { // only those edges, which + { // only those edges, which if (trimmedEdges.TryGetValue(dvk, out Edge other)) { if (other == edg) continue; @@ -3544,7 +3546,7 @@ public Shell[] Result() } ConnectOpenEdges(openEdges); // What about "nonManifoldEdges"? - // + // List res = new List(); // the result of this method. // allFaces now contains all the trimmed faces plus the faces, which are (directly or indirectly) connected (via edges) to the trimmed faces List nonManifoldParts = new List(); @@ -4107,7 +4109,7 @@ private List FindLoop(Edge edg, Vertex startVertex, Face onThisFace, HashS if (connected.Count == 0 || intersectionEdgeEndHere) { // (connected.Count == 0): dead end, no connection at endVertex - // intersectionEdgeEndHere: cannot go on, because we are following original edges and crossing at a vertex, + // intersectionEdgeEndHere: cannot go on, because we are following original edges and crossing at a vertex, // where an intersection edge ends. This is not allowed (e.g.: breps4) intersectionEdges.ExceptWith(res); originalEdges.ExceptWith(res); @@ -4432,7 +4434,7 @@ private Dictionary, List>> SortLoopsTopologically(List - /// Takes a set of edges and tries to connect them to closed loops. + /// Takes a set of edges and tries to connect them to closed loops. /// /// work on this set, which will be emptied /// orientation in respect to this face @@ -4541,8 +4543,8 @@ private List> FindPath(HashSet set, Vertex vertex1, Vertex vert } internal static bool SameEdge(Edge e1, Edge e2, double precision) - { // it is assumed that the two edges connect the same vertices - // it is tested whether they have the same geometry (but maybe different directions) + { // it is assumed that the two edges connect the same vertices + // it is tested whether they have the same geometry (but maybe different directions) // (two half circles may connect the same vertices but are not geometrically identical when they describe differnt parts of the same circle) if (e1.Curve3D != null && e2.Curve3D != null) return e1.Curve3D.DistanceTo(e2.Curve3D.PointAt(0.5)) < 10 * precision; // nur precision war zu knapp return false; @@ -4701,7 +4703,7 @@ private void createNewEdges() faceToIntersectionEdges = new Dictionary>(); edgesNotToUse = new HashSet(); // wir haben eine Menge Schnittpunkte, die Face-Paaren zugeordnet sind. Für jedes Face-Paar, welches Schnittpunkte enthält sollen hier die neuen Kanten bestimmt werden - // Probleme dabei sind: + // Probleme dabei sind: // - es ist bei mehr als 2 Schnittpunkten nicht klar, welche Abschnitte dazugehören // - zwei Surfaces können mehr als eine Schnittkurve haben HashSet created = new HashSet(new EdgeComparerByVertexAndFace()); @@ -4873,8 +4875,8 @@ private void createNewEdges() // aus den mehreren Punkten (meist 2) unter Berücksichtigung der Richtungen Kanten erzeugen // bei nicht geschlossenen Kurven sollten immer zwei aufeinanderfolgende Punkte einen gültigen Kurvenabschnitt bilden. // Obwohl auch hierbei ein doppelt auftretenden Punkt ein Problem machen könnte (Fälle sind schwer vorstellbar). - // Im schlimmsten Fall müssten man für alle Abschnitte (nicht nur die geraden) - // Bei geschlossenen Kurven ist nicht klar, welche Punktepaare gültige Kurvenabschnitte erzeugen. + // Im schlimmsten Fall müssten man für alle Abschnitte (nicht nur die geraden) + // Bei geschlossenen Kurven ist nicht klar, welche Punktepaare gültige Kurvenabschnitte erzeugen. if (c3ds[i].IsClosed) { if (item.Key.face1.Area.Contains(crvsOnSurface1[i].StartPoint, false) && item.Key.face2.Area.Contains(crvsOnSurface2[i].StartPoint, false)) @@ -4956,7 +4958,7 @@ private void createNewEdges() // Problme wäre die Genauigkeit, wenn beim BooleanOperation.generateCycles die Richtung genommen wird... if (con1 is InterpolatedDualSurfaceCurve.ProjectedCurve && con2 is InterpolatedDualSurfaceCurve.ProjectedCurve && tr is InterpolatedDualSurfaceCurve) - { // con1 und con2 müssen auf tr verweisen, sonst kann man das Face später nicht mit "ReverseOrientation" umdrehen. Dort wird nämlich die + { // con1 und con2 müssen auf tr verweisen, sonst kann man das Face später nicht mit "ReverseOrientation" umdrehen. Dort wird nämlich die // surface verändert, und die muss bei allen Kurven die selbe sein (con1 as InterpolatedDualSurfaceCurve.ProjectedCurve).SetCurve3d(tr as InterpolatedDualSurfaceCurve); (con2 as InterpolatedDualSurfaceCurve.ProjectedCurve).SetCurve3d(tr as InterpolatedDualSurfaceCurve); @@ -5014,7 +5016,7 @@ private void createNewEdges() HashSet onFace1 = existingEdges.Intersect(item.Key.face1.Edges).ToHashSet(); HashSet onFace2 = existingEdges.Intersect(item.Key.face2.Edges).ToHashSet(); //bool edgFound = false; - //// it was Precision.eps before, but a tangential intersection at "Difference2.cdb.json" failed, which should have been there + //// it was Precision.eps before, but a tangential intersection at "Difference2.cdb.json" failed, which should have been there //// in many cases we are close to an edge on one of the faces or both. //// find this edge and step a little inside into this face //foreach (Edge edg in onFace1) diff --git a/CADability/CADability.csproj b/CADability/CADability.csproj index 71eb2da9..2ada9c72 100644 --- a/CADability/CADability.csproj +++ b/CADability/CADability.csproj @@ -1,4 +1,9 @@  + + false + false + + netstandard2.0 @@ -22,21 +27,22 @@ false latest + AVALONIA true - NET4X;TRACE;DEBUG;xTESTNEWCONTEXTMENU, xUSENONPRIODICSURFACES + $(DefineConstants);NET4X;TRACE;DEBUG;xTESTNEWCONTEXTMENU, xUSENONPRIODICSURFACES true - TRACE;xPARALLEL, xTESTNEWCONTEXTMENU, xUSENONPRIODICSURFACES, WEBASSEMBLY + $(DefineConstants);TRACE;xPARALLEL, xTESTNEWCONTEXTMENU, xUSENONPRIODICSURFACES, WEBASSEMBLY true - NET4X;xTESTNEWCONTEXTMENU + $(DefineConstants);NET4X;xTESTNEWCONTEXTMENU True full true @@ -45,7 +51,7 @@ true - xPARALLEL, xTESTNEWCONTEXTMENU + $(DefineConstants);xPARALLEL, xTESTNEWCONTEXTMENU false full true @@ -59,7 +65,7 @@ full - + diff --git a/CADability/Frame.cs b/CADability/Frame.cs index ca0f2c86..6fa7bfd3 100644 --- a/CADability/Frame.cs +++ b/CADability/Frame.cs @@ -52,7 +52,7 @@ public class ActiveFrame /// Set to true, if you have processed this command and no further action is required, leave unmodified if CADability should process this command public delegate void ProcessCommandDelegate(string MenuId, ref bool Processed); /// - /// + /// /// /// /// @@ -102,7 +102,7 @@ public interface IFrame /// void RemoveActiveAction(); /// - /// Returns the currently active action. Call to find out more about + /// Returns the currently active action. Call to find out more about /// that action. /// Action ActiveAction { get; } @@ -120,7 +120,7 @@ public interface IFrame /// event ProcessCommandDelegate ProcessCommandEvent; /// - /// Provide a event handler if you want to control the appearnce of commands in the menu or toolbar + /// Provide a event handler if you want to control the appearnce of commands in the menu or toolbar /// (set the enabled and check flags). /// event UpdateCommandDelegate UpdateCommandEvent; @@ -142,7 +142,7 @@ public interface IFrame /// event ProcessContextMenuDelegate ProcessContextMenuEvent; /// - /// Provide a event handler if you want to control the appearance of commands in a context the menu + /// Provide a event handler if you want to control the appearance of commands in a context the menu /// (set the enabled and check flags). /// event UpdateContextMenuDelegate UpdateContextMenuEvent; @@ -178,7 +178,7 @@ public interface IFrame /// Settings GlobalSettings { get; set; } /// - /// Gets the for the provided . First the s settings are + /// Gets the for the provided . First the s settings are /// checked, if it is not defined there, the global settings will be queried. /// /// @@ -615,7 +615,7 @@ public Settings GlobalSettings } } private void OnSettingChanged(string Name, object NewValue) - { + { object o = this.GetSetting(Name); SettingChangedEvent?.Invoke(Name, NewValue); // } @@ -756,7 +756,7 @@ public bool PreProcessMouseWheel(MouseEventArgs e) } public virtual void PreProcessKeyDown(KeyEventArgs e) { - // it is difficult to find an order of processing: + // it is difficult to find an order of processing: // the construct-action processes the enter key, no chance to use it in a list-box for the selection // the property page processes the tab key, no chance to use it in the action for "next modal input field" // we need to replace the KeyEventArgs class by a CADability class anyhow, we could introduce a first-pass, second-pass member @@ -2410,7 +2410,7 @@ private void OnFileOpen(string fileName) { // get a raw model extent to know a precision for the triangulation // parallel triangulate all faces with this precision to show a progress bar - // then zoom total + // then zoom total ModelView mv = FirstModelView; // display the first ModelView if (mv != null) diff --git a/CADability/ModelView.cs b/CADability/ModelView.cs index 4fa56d04..d9161aba 100644 --- a/CADability/ModelView.cs +++ b/CADability/ModelView.cs @@ -59,7 +59,7 @@ public interface ICanvas /// IView GetView(); /// - /// + /// /// /// /// @@ -159,7 +159,7 @@ void OnLayerAdded(LayerList sender, Layer added) } #region IShowProperty Members /// - /// Overrides , + /// Overrides , /// returns . /// public override ShowPropertyEntryType EntryType @@ -180,7 +180,7 @@ public override ShowPropertyLabelFlags LabelType } } /// - /// Overrides , + /// Overrides , /// returns the number of subentries in this property view. /// public override int SubEntriesCount @@ -192,7 +192,7 @@ public override int SubEntriesCount } IShowProperty[] subEntries; /// - /// Overrides , + /// Overrides , /// returns the subentries in this property view. /// public override IShowProperty[] SubEntries @@ -1374,7 +1374,7 @@ void IView.Invalidate(PaintBuffer.DrawingAspect aspect, Rectangle ToInvalidate) } void IView.InvalidateAll() { - // ForceInvalidateAll(); // das ist definitiv zuviel, bei FeedbackObjekten wird das aufgerufen und es bräuchte nur ein + // ForceInvalidateAll(); // das ist definitiv zuviel, bei FeedbackObjekten wird das aufgerufen und es bräuchte nur ein // neuzeichnen der Feedback Objekte canvas?.Invalidate(); } @@ -1531,7 +1531,7 @@ public override string LabelText } } /// - /// Overrides , + /// Overrides , /// returns . /// public override ShowPropertyEntryType EntryType @@ -1577,7 +1577,7 @@ public override void EndEdit(bool aborted, bool modified, string newValue) } } /// - /// Overrides , + /// Overrides , /// returns the context menu with the id "MenuId.ModelView". /// (see ) /// @@ -1593,7 +1593,7 @@ public override MenuWithHandler[] ContextMenu IShowProperty[] subEntries; /// - /// Overrides , + /// Overrides , /// returns the number of subentries in this property view. /// public override int SubEntriesCount @@ -1604,7 +1604,7 @@ public override int SubEntriesCount } } /// - /// Overrides , + /// Overrides , /// returns the subentries in this property view. /// public override IShowProperty[] SubEntries diff --git a/CADability/StringTableDeutsch.xml b/CADability/StringTableDeutsch.xml index eea1f3ab..d93aee4f 100644 --- a/CADability/StringTableDeutsch.xml +++ b/CADability/StringTableDeutsch.xml @@ -790,21 +790,27 @@ Größe der Hotspots zum Verändern der markierten Objekte (Skalieren, Drehen, Verschieben, Fixpunkt) + Informationen und Eingabemöglichkeiten für die gerade aktive Aktion (z.B. Markieren, Zeichnen, Verändern) + Anzeige und Änderungsmöglichkeit für die Vorgaben der projektbezogenen Systemeinstellungen + Anzeige und Änderungsmöglichkeit für die Vorgaben der globalen Systemeinstellungen + Einstellungen der Darstellung: Layout mit Ausschnitt, Modell mit Projektion, Zeichenebene. + Symbole zur Verwendung im Projekt + Modellierwerkzeuge zum Verändern der Geometrie eines Körpers @@ -6147,4 +6153,4 @@ Daten des regelmäßigen Polygons - \ No newline at end of file + diff --git a/CADability/StringTableEnglish.xml b/CADability/StringTableEnglish.xml index dd161a71..96c24df8 100644 --- a/CADability/StringTableEnglish.xml +++ b/CADability/StringTableEnglish.xml @@ -787,21 +787,27 @@ Size of the handles (hotspots) + Properties and input fields for the active action + Properties or settings for this project + Global properties and settings + Properties or settings for the different views of this project + Symbols available for use in the project + Modelling mode where you can change the geometry of a solid body @@ -6048,7 +6054,7 @@ ??? - + - \ No newline at end of file + diff --git a/ShapeIt/ChamferEdgesAction.cs b/ShapeIt/ChamferEdgesAction.cs index 937ed8e7..9bcd31b3 100644 --- a/ShapeIt/ChamferEdgesAction.cs +++ b/ShapeIt/ChamferEdgesAction.cs @@ -3,7 +3,9 @@ using CADability.Actions; using CADability.Attribute; using CADability.GeoObject; +#if !AVALONIA using ExCSS; +#endif using System; using System.Collections.Generic; using System.Linq; diff --git a/ShapeIt/FeedbackArrow.cs b/ShapeIt/FeedbackArrow.cs index c0d8d550..82d9820e 100644 --- a/ShapeIt/FeedbackArrow.cs +++ b/ShapeIt/FeedbackArrow.cs @@ -7,7 +7,9 @@ using System.Threading.Tasks; using static CADability.Projection; using System.Globalization; +#if !AVALONIA using CADability.Forms; +#endif using CADability.Attribute; using CADability.Substitutes; @@ -268,7 +270,7 @@ public static GeoObjectList MakeArcArrow(Shell onThisShell, Axis axis, GeoVector else startArrow.ColorDef = blackParts; if (flags.HasFlag(ArrowFlags.secondRed)) endArrow.ColorDef = redParts; else endArrow.ColorDef = blackParts; - res.Add(startArrow); + res.Add(startArrow); res.Add(endArrow); Text txt = Text.Construct(); diff --git a/ShapeIt/MainForm.Designer.cs b/ShapeIt/MainForm.Designer.cs index 0ced6b6f..676f09c1 100644 --- a/ShapeIt/MainForm.Designer.cs +++ b/ShapeIt/MainForm.Designer.cs @@ -1,4 +1,5 @@ -namespace ShapeIt +#if !AVALONIA +namespace ShapeIt { partial class MainForm { @@ -37,4 +38,4 @@ private void InitializeComponent() #endregion } } - +#endif diff --git a/ShapeIt/MainForm.cs b/ShapeIt/MainForm.cs index 5960e477..51ce8d1c 100644 --- a/ShapeIt/MainForm.cs +++ b/ShapeIt/MainForm.cs @@ -1,4 +1,5 @@ -using CADability; +#if !AVALONIA +using CADability; using CADability.Actions; using CADability.Attribute; using CADability.Forms.NET8; @@ -99,7 +100,7 @@ void FadeOutPictureBox(PictureBox pb) private void ShowLogo() { Control pex = FindControlByName(this, "propertiesExplorer"); - // Create PictureBox + // Create PictureBox logoBox = new PictureBox(); Assembly ThisAssembly = Assembly.GetExecutingAssembly(); using (System.IO.Stream str = ThisAssembly.GetManifestResourceStream("ShapeIt.Resources.ShapeIt2.png")) @@ -555,7 +556,7 @@ protected override void OnLoad(EventArgs e) { base.OnLoad(e); - // this is for recording the session with 1280x720 pixel. + // this is for recording the session with 1280x720 pixel. this.Size = new Size(1294, 727); } @@ -770,3 +771,4 @@ private void Debug() #endif } } +#endif diff --git a/ShapeIt/MainWindow.axaml b/ShapeIt/MainWindow.axaml new file mode 100644 index 00000000..a9a9d972 --- /dev/null +++ b/ShapeIt/MainWindow.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/ShapeIt/MainWindow.axaml.cs b/ShapeIt/MainWindow.axaml.cs new file mode 100644 index 00000000..cca23523 --- /dev/null +++ b/ShapeIt/MainWindow.axaml.cs @@ -0,0 +1,815 @@ +using Avalonia.Controls; +using Avalonia.Input; +using CADability.Avalonia; +using CADability; +using CADability.Actions; +using CADability.Attribute; +using CADability.GeoObject; +using CADability.UserInterface; +using MathNet.Numerics.LinearAlgebra.Factorization; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Reflection; +using System.Linq; +using System.IO; +using System.Xml; +using System.Xml.Linq; +using System.Text; + +namespace ShapeIt +{ + // TODO move gui related things to CADability.Avalonia.CadForm and inherit from that here? + // or just implement ICommandHandler here? + public partial class MainWindow : Window, ICommandHandler + { + // public MainForm() + // { + // InitializeComponent(); + // cadFrame = new CadFrame(propertiesExplorer, cadCanvas, this); + // } + // TODO + // create CAD project + // create FrameImpl (or implement own subclass) + // in CadForm (here named CadControl?): create Frame/cadFrame (type x -> FrameImpl -> IFrame) and + // set cadCanvas.Frame = cadFrame (in CadForm) + // + + // currently here because we dont inherit from CadForm + // private CadFrame cadFrame; + + // private PictureBox logoBox; + private ModellingPropertyEntries modellingPropertyEntries; + private DateTime lastSaved; // time, when the current file has been saved the last time, see OnIdle + private bool modifiedSinceLastAutosave = false; + bool projectionChanged = false; // to handle projection changes in OnIdle + bool crashChecked = false; + + // TODO needed? how to do in Avalonia? + // private Control FindControlByName(Control parent, string name) + // { + // foreach (Control child in parent.Controls) + // { + // if (child.Name == name) + // return child; + + // Control found = FindControlByName(child, name); + // if (found != null) + // return found; + // } + + // return null; + // } + + // TODO reimplement in Avalonia + // void FadeOutPictureBox(PictureBox pb) + // { + // var timer = new System.Windows.Forms.Timer(); + // timer.Interval = 50; + // double alpha = 1.0; + + // Image original = pb.Image; + // Bitmap faded = new Bitmap(original.Width, original.Height); + + // timer.Tick += (s, e) => + // { + // alpha -= 0.01; + // if (alpha <= 0) + // { + // timer.Stop(); + // pb.Parent.Controls.Remove(pb); + // //pb.Visible = false; + // pb.Dispose(); + // return; + // } + + // using (Graphics g = Graphics.FromImage(faded)) + // { + // g.Clear(Color.Transparent); + // ColorMatrix matrix = new ColorMatrix + // { + // Matrix33 = (float)alpha // Alpha-Kanal + // }; + // ImageAttributes attributes = new ImageAttributes(); + // attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); + + // g.DrawImage(original, + // new Rectangle(0, 0, faded.Width, faded.Height), + // 0, 0, original.Width, original.Height, + // GraphicsUnit.Pixel, + // attributes); + // } + + // pb.Image = (Image)faded.Clone(); // neues Bild setzen + // }; + + // timer.Start(); + // } + + // TODO reimplement in Avalonia + // private void ShowLogo() + // { + // Control pex = FindControlByName(this, "propertiesExplorer"); + // // Create PictureBox + // logoBox = new PictureBox(); + // Assembly ThisAssembly = Assembly.GetExecutingAssembly(); + // using (System.IO.Stream str = ThisAssembly.GetManifestResourceStream("ShapeIt.Resources.ShapeIt2.png")) + // { + // logoBox.Image = new Bitmap(str); + // } + // logoBox.SizeMode = PictureBoxSizeMode.Zoom; + + // double aspectRatio = (double)logoBox.Image.Height / logoBox.Image.Width; + + // // Zielbreite übernehmen + // int targetWidth = pex.ClientSize.Width - 4; + // int berechneteHoehe = (int)(targetWidth * aspectRatio); + + // // Größe setzen + // logoBox.Size = new Size(targetWidth, berechneteHoehe); + + // // Position am unteren Rand + // logoBox.Location = new Point(2, pex.ClientSize.Height - berechneteHoehe - 2); + + // // Logo zum Ziel-Control hinzufügen + // pex.Controls.Add(logoBox); + // logoBox.BringToFront(); + + // FadeOutPictureBox(logoBox); + + // pex.Resize += (s, e) => + // { + // int newWidth = pex.ClientSize.Width - 4; + // int newHeight = (int)(newWidth * aspectRatio); + // logoBox.Size = new Size(newWidth, newHeight); + // logoBox.Location = new Point(2, pex.ClientSize.Height - newHeight - 2); + // }; + // } + + private string ReadEmbeddedVersion() + { + var asm = typeof(Program).Assembly; + using var s = asm.GetManifestResourceStream("App.Version"); + using var sr = new StreamReader(s!); + return sr.ReadToEnd().Trim(); + } + + // TODO taken from CadForm, as we don't inherit from it + + public bool OnCommand(string menuId) + { + Console.WriteLine("MainWindow.OnCommand(" + menuId + ")"); + if (modellingPropertyEntries.OnCommand(menuId)) return true; + if (menuId == "MenuId.App.Exit") { + Close(); + } + return CadFrame.OnCommand(menuId); + } + public bool OnUpdateCommand(string menuId, CommandState commandState) + { + throw new NotImplementedException(); + } + public void OnSelected(MenuWithHandler selectedMenu, bool selected) + { + throw new NotImplementedException(); + } + + // delegate to cadControl (old: cadForm) + // public PropertiesExplorer PropertiesExplorer => propertiesExplorer; + public CadCanvas CadCanvas => cadControl.CadCanvas; + public CadFrame CadFrame => cadControl.CadFrame; + + public MainWindow(string[] args) // TODO inherit from CadForm? : base(args) + { // interpret the command line arguments as a name of a file, which should be opened + + InitializeComponent(); + // ShowLogo(); TODO + // this.Icon = Properties.Resources.Icon; + Assembly ThisAssembly = Assembly.GetExecutingAssembly(); + System.IO.Stream str; + // TODO + // using (str = ThisAssembly.GetManifestResourceStream("ShapeIt.Resources.Icon.ico")) + // { + // this.Icon = new System.Drawing.Icon(str); + // } + + string fileName = ""; + for (int i = 0; i < args.Length; i++) + { + if (!args[i].StartsWith("-")) + { + fileName = args[i]; + break; + } + } + Project toOpen = null; + if (!String.IsNullOrWhiteSpace(fileName)) + { + try + { + toOpen = Project.ReadFromFile(fileName); + Console.WriteLine("Read Project from file: " + fileName); + } + catch { } + } + if (toOpen == null) CadFrame.GenerateNewProject(); + else CadFrame.Project = toOpen; + + string version = ReadEmbeddedVersion(); // version from version.txt + // this.Text = $"ShapeIt with CADability – Version: {version}"; + + if (!Settings.GlobalSettings.ContainsSetting("UserInterface")) + { + Settings UserInterface = new Settings("UserInterface"); + Settings.GlobalSettings.AddSetting("UserInterface", UserInterface); + IntegerProperty ToolbarButtonSize = new IntegerProperty("ToolbarButtonSize", "ToolbarButtonSize"); + ToolbarButtonSize.IntegerValue = 0; + UserInterface.AddSetting("ToolbarButtonSize", ToolbarButtonSize); + IntegerProperty MenuSize = new IntegerProperty("MenuSize", "MenuSize"); + MenuSize.IntegerValue = 0; + UserInterface.AddSetting("MenuSize", MenuSize); + } + Settings.GlobalSettings.SetValue("Construct.3D_Delete2DBase", false); + bool exp = Settings.GlobalSettings.GetBoolValue("Experimental.TestNewContextMenu", false); + bool tst = Settings.GlobalSettings.GetBoolValue("ShapeIt.Initialized", false); + // TODO do we need to load colorSettings here? + Settings.GlobalSettings.SetValue("ShapeIt.Initialized", true); + CadFrame.FileNameChangedEvent += (name) => + { + // if (string.IsNullOrEmpty(name)) this.Text = "ShapeIt with CADability"; + // else this.Text = "ShapeIt -- " + name; + lastSaved = DateTime.Now; // a new file has been opened + }; + CadFrame.ProjectClosedEvent += OnProjectClosed; + CadFrame.ProjectOpenedEvent += OnProjectOpened; + // CadFrame.UIService.ApplicationIdle += OnIdle; TODO + CadFrame.ViewsChangedEvent += OnViewsChanged; + if (CadFrame.ActiveView != null) OnViewsChanged(CadFrame); + CadFrame.ControlCenter.RemovePropertyPage("View"); + using (str = ThisAssembly.GetManifestResourceStream("ShapeIt.StringTableDeutsch.xml")) + { + XmlDocument stringXmlDocument = new XmlDocument(); + stringXmlDocument.Load(str); + StringTable.AddStrings(stringXmlDocument); + } + + using (str = ThisAssembly.GetManifestResourceStream("ShapeIt.StringTableEnglish.xml")) + { + XmlDocument stringXmlDocument = new XmlDocument(); + stringXmlDocument.Load(str); + StringTable.AddStrings(stringXmlDocument); + } + using (str = ThisAssembly.GetManifestResourceStream("ShapeIt.MenuResource.xml")) + { + XmlDocument menuDocument = new XmlDocument(); + menuDocument.Load(str); +#if DEBUG + // inject an additional menu "Debug", which we can use to directly execute some debugging code + XmlNode mainMenu = menuDocument.SelectSingleNode("Menus/MainMenu/Popup[@MenuId='MenuId.Extras']"); + if (mainMenu != null) + { + // Create a new MenuItem element. + XmlElement debugMenuItem = menuDocument.CreateElement("MenuItem"); + // Set the MenuId attribute to "MenuId.Debug". + debugMenuItem.SetAttribute("MenuId", "MenuId.Debug"); + // Append the new MenuItem element to the MainMenu node. + mainMenu.AppendChild(debugMenuItem); + } +#endif + XmlNode toolbar = menuDocument.SelectSingleNode("Menus/Popup[@MenuId='Toolbar']"); + // SetToolbar(toolbar); // TODO CadForm + MenuResource.SetMenuResource(menuDocument); + + // TODO move this to CadControl? + MenuWithHandler[] mainMenuDefinition = MenuResource.LoadMenuDefinition("SDI Menu", true, this); + // MenuManager.MakeMainMenu(mainMenuDefinition, dockPanel, this); + MenuManager.MakeMainMenu(mainMenuDefinition, mainMenuObject, this); + + // ResetMainMenu(null); // TODO CadForm + } + + lastSaved = DateTime.Now; + AppDomain.CurrentDomain.UnhandledException += (sender, exobj) => + { + // exobj.ExceptionObject as Exception; + try + { + string path = System.IO.Path.GetTempPath(); + path = System.IO.Path.Combine(path, "ShapeIt"); + DirectoryInfo dirInfo = Directory.CreateDirectory(path); + string currentFileName = CadFrame.Project.FileName; + if (string.IsNullOrEmpty(CadFrame.Project.FileName)) + { + path = System.IO.Path.Combine(path, "crash_" + DateTime.Now.ToString("yyMMddHHmm") + ".cdb.json"); + currentFileName = "unknown"; + } + else + { + string crashFileName = System.IO.Path.GetFileNameWithoutExtension(CadFrame.Project.FileName); + if (crashFileName.EndsWith(".cdb")) crashFileName = System.IO.Path.GetFileNameWithoutExtension(crashFileName); // we usually have two extensions: .cdb.json + path = System.IO.Path.Combine(path, crashFileName + "_X.cdb.json"); + } + CadFrame.Project.WriteToFile(path); + File.WriteAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), @"ShapeIt\Crash.txt"), currentFileName + "\n" + path); + } + catch (Exception) { } + ; + }; + // the following installs the property page for modelling. This connects all modelling + // tasks of ShapeIt with CADability + IPropertyPage modellingPropPage = CadFrame.ControlCenter.AddPropertyPage("Modelling", 6); + modellingPropertyEntries = new ModellingPropertyEntries(CadFrame); + modellingPropPage.Add(modellingPropertyEntries, false); + CadFrame.ControlCenter.ShowPropertyPage("Modelling"); + } + + private void OnViewsChanged(IFrame theFrame) + { + theFrame.ActiveView.Projection.ProjectionChangedEvent -= OnProjectionChanged; // not to double it? + theFrame.ActiveView.Projection.ProjectionChangedEvent += OnProjectionChanged; + } + + private void OnProjectionChanged(Projection sender, EventArgs args) + { + // projectionChanged = true; + modellingPropertyEntries.OnProjectionChanged(); // TODO move to OnIdle? + } + +// // TODO reimplement in Avalonia +// protected override void OnShown(EventArgs e) +// { +// // check for crash +// if (!crashChecked) +// { +// crashChecked = true; +// string crashPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), @"ShapeIt\Crash.txt"); +// // if (File.Exists(crashPath)) +// // { +// // string[] lines = File.ReadAllLines(System.IO.Path.Combine(System.IO.Path.GetTempPath(), @"ShapeIt\Crash.txt")); +// // if (lines.Length == 2) +// // { +// // string ask = StringTable.GetFormattedString("ShapeIt.RestoreAfterCrash", lines[0]); +// // if (CadFrame.UIService.ShowMessageBox(ask, "ShapeIt", CADability.Substitutes.MessageBoxButtons.YesNo) == CADability.Substitutes.DialogResult.Yes) +// // { +// // CadFrame.Project = Project.ReadFromFile(lines[1]); +// // CadFrame.Project.FileName = lines[0]; + +// // this.Text = "ShapeIt -- " + lines[0]; +// // } +// // } +// // File.Delete(crashPath); +// // } +// #if DEBUG +// AutoDebug(); +// #endif +// } +// base.OnActivated(e); +// } +#if DEBUG + private void AutoDebug() + { + return; + string? filename = @"C:\Users\gerha\Documents\Zeichnungen\RoundEdgesTest2.cdb.json"; + // add code here to be executed automatically upon start in debug mode + // there is no mouse interaction before this code is finished + if (string.IsNullOrEmpty(filename)) + { + string[] mru = MRUFiles.GetMRUFiles(); + if (mru.Length > 0) filename = mru.Last().Split(';')[0]; + } + if (!string.IsNullOrEmpty(filename)) CadFrame.Project = Project.ReadFromFile(filename); + + string command = ""; + List slds = new List(); + Solid operand1 = null, operand2 = null; + List difference = new List(); + List intersection = new List(); + List union = new List(); + List edgeMarkers = new List(); + foreach (CADability.GeoObject.IGeoObject go in CadFrame.Project.GetActiveModel().AllObjects) + { + if (go is CADability.GeoObject.Solid sld) + { + slds.Add(sld); + if (sld.Style != null) + { + if (sld.Style.Name == "Operand1") operand1 = sld; + else if (sld.Style.Name == "Operand2") operand2 = sld; + else if (sld.Style.Name == "Difference") difference.Add(sld); + else if (sld.Style.Name == "Union") union.Add(sld); + else if (sld.Style.Name == "Intersection") intersection.Add(sld); + } + } + if (go is ICurve curve) + { + if (go.Style!=null && go.Style.Name == "EdgeMarker") + { + edgeMarkers.Add(curve); + } + } + if (go is Text txt) + { + command = txt.TextString; // there sould only be one + } + } + if (operand1 != null && operand2 != null) + { + //if (difference.Count > 0) + //{ + // Solid[] sres = NewBooleanOperation.Subtract(operand1, operand2); + // if (sres.Length > 0) + // { + // Project proj = Project.CreateSimpleProject(); + // proj.GetActiveModel().Add(sres); + // proj.WriteToFile("c:\\Temp\\subtract.cdb.json"); + // } + //} + if (command.StartsWith("Difference",StringComparison.OrdinalIgnoreCase)) + { + Solid[] sres = NewBooleanOperation.Subtract(operand1, operand2); + } + if (command.Equals("Union", StringComparison.OrdinalIgnoreCase) || command.Equals("Unite", StringComparison.OrdinalIgnoreCase)) + { + Solid sres = NewBooleanOperation.Unite(operand1, operand2); + } + } + if (slds.Count == 2) + { + //Solid un = NewBooleanOperation.Unite(slds[0], slds[1]); + //Solid[] sld; + //if (slds[0].Volume(0.1) > slds[1].Volume(0.1)) + //{ + // sld = NewBooleanOperation.Subtract(slds[0], slds[1]); + //} + //else + //{ + // sld = NewBooleanOperation.Subtract(slds[1], slds[0]); + //} + //if (sld.Length > 0) + //{ + // Project proj = Project.CreateSimpleProject(); + // proj.GetActiveModel().Add(sld); + // proj.WriteToFile("c:\\Temp\\subtract.cdb.json"); + //} + } + if (edgeMarkers.Count > 0) + { + List edgesToRound = []; + Shell? shellToRound = null; + foreach (Solid s in slds) + { + foreach (Edge edg in s.Shells[0].Edges) + { + foreach (ICurve em in edgeMarkers) + { + if (edg.Curve3D.SameGeometry(em, 0.1)) + { + edgesToRound.Add(edg); + shellToRound = s.Shells[0]; + } + } + } + } + if (command.StartsWith("RoundEdges", StringComparison.OrdinalIgnoreCase)) + { + string[] parts = command.Split(':'); + if (parts.Length==2) + { + double d = double.Parse(parts[1]); + if (d>0) + { + Shell? rounded = shellToRound?.RoundEdges(edgesToRound, d); + } + } + } + if (command.StartsWith("ChamferEdges", StringComparison.OrdinalIgnoreCase)) + { + string[] parts = command.Split(':'); + if (parts.Length == 2) + { + double d = double.Parse(parts[1]); + if (d > 0) + { + Shell? rounded = shellToRound?.ChamferEdges(edgesToRound, d, d); + // CadFrame.Project.GetActiveModel().Add(rounded); + } + } + } + } + } +#endif + + /// + /// Filter the escape key for the modelling property page + /// + /// + /// + /// + // protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + // { + // Keys nmKeyData = (Keys)((int)keyData & 0x0FFFF); + // CADability.Substitutes.KeyEventArgs e = new CADability.Substitutes.KeyEventArgs((CADability.Substitutes.Keys)keyData); + // if (nmKeyData == Keys.Escape) + // { + // if (modellingPropertyEntries.OnEscape()) return true; + // } + // return base.ProcessCmdKey(ref msg, keyData); + // } + protected override void OnKeyDown(KeyEventArgs keyEvent) + { + Console.WriteLine("Process Key: " + keyEvent.Key); + if (keyEvent.Key == Key.Escape) { + if (modellingPropertyEntries.OnEscape()) { + keyEvent.Handled = true; + return; + } + } + base.OnKeyDown(keyEvent); + } + /// + /// Called when CADability is idle. We use it to save the current project data to a temp file in case of a crash + /// + /// + /// + // slowdown OnIdle polling: + readonly Stopwatch _idleSw = Stopwatch.StartNew(); + const int MinIdleCheckMs = 250; + bool _idleBusy; + void OnIdle(object sender, EventArgs e) + { + if (_idleBusy) return; + if (_idleSw.ElapsedMilliseconds < MinIdleCheckMs) return; + + _idleBusy = true; + _idleSw.Restart(); + try + { + if (projectionChanged) + { + projectionChanged = false; + modellingPropertyEntries.OnProjectionChanged(); // to update the feedback objects, which are projection dependant + } + if (modifiedSinceLastAutosave && (DateTime.Now - lastSaved).TotalMinutes > 2) + { + modifiedSinceLastAutosave = false; + string path = System.IO.Path.GetTempPath(); + path = System.IO.Path.Combine(path, "ShapeIt"); + DirectoryInfo dirInfo = Directory.CreateDirectory(path); + string currentFileName = CadFrame.Project.FileName; + if (string.IsNullOrEmpty(CadFrame.Project.FileName)) path = System.IO.Path.Combine(path, "noname.cdb.json"); + else + { + string fileName = System.IO.Path.GetFileNameWithoutExtension(CadFrame.Project.FileName); + if (fileName.EndsWith(".cdb")) fileName = System.IO.Path.GetFileNameWithoutExtension(fileName); // we usually have two extensions: .cdb.json + path = System.IO.Path.Combine(path, fileName + "_.cdb.json"); + } + CadFrame.Project.WriteToFile(path); + CadFrame.Project.FileName = currentFileName; // Project.WriteToFile changes the Project.FileName, restore the current name + lastSaved = DateTime.Now; + } + } + finally { _idleBusy = false; } + } + void OnProjectClosed(Project theProject, IFrame theFrame) + { + // manage autosave OnIdle, remove autosaved files + } + /// + /// When a new or exisiting project has been opened + /// + /// + /// + private void OnProjectOpened(Project theProject, IFrame theFrame) + { + theProject.ProjectModifiedEvent += (Project sender) => + { // register modifications of the project to manage autosave + if (sender == theProject) modifiedSinceLastAutosave = true; + }; + } +// // TODO reimplement in Avalonia + // protected override void OnLoad(EventArgs e) + // { + // base.OnLoad(e); + + // // this is for recording the session with 1280x720 pixel. + // this.Size = new Size(1294, 727); + + // } + /// + /// Give the user a chance to save the modified project + /// + /// +// // TODO reimplement in Avalonia + // protected override void OnFormClosing(FormClosingEventArgs e) + // { + // if (!CadFrame.Project.SaveModified()) e.Cancel = true; + // base.OnFormClosing(e); + // } +// // TODO reimplement in Avalonia +// public override bool OnCommand(string MenuId) +// { +// // forward to modellingPropertyEntries first +// if (modellingPropertyEntries.OnCommand(MenuId)) return true; +// if (MenuId == "MenuId.App.Exit") +// { // this command cannot be handled by CADability.dll +// Application.Exit(); +// return true; +// } +// #if DEBUG +// else if (MenuId == "MenuId.Debug") +// { +// Debug(); +// return true; +// } +// #endif +// else return base.OnCommand(MenuId); +// } +// // TODO reimplement in Avalonia + // public override bool OnUpdateCommand(string MenuId, CommandState CommandState) + // { + // // forward to modellingPropertyEntries first + // if (modellingPropertyEntries.OnUpdateCommand(MenuId, CommandState)) return true; + // return base.OnUpdateCommand(MenuId, CommandState); + // } +// // TODO reimplement in Avalonia + // public override void OnSelected(MenuWithHandler selectedMenuItem, bool selected) + // { + // modellingPropertyEntries.OnSelected(selectedMenuItem, selected); + // base.OnSelected(selectedMenuItem, selected); + // } +#if DEBUG + private static Random rnd = new Random(); + private GeoVector RandomVector(double len) + { + double fx = 1.0, fy = 1.0, fz = 1.0; + if (rnd.NextDouble() < 0.5) fx = -fx; + if (rnd.NextDouble() < 0.5) fy = -fy; + if (rnd.NextDouble() < 0.5) fz = -fz; + return len * (new GeoVector(fx * rnd.NextDouble(), fy * rnd.NextDouble(), fz * rnd.NextDouble())).Normalized; + } + private double RandomDouble(double min, double max) + { + return min + (max - min) * rnd.NextDouble(); + } + /// + /// Here we can add some debug code + /// + private void DebugX() + { + Model model = CadFrame.Project.GetActiveModel(); + Style stl = CadFrame.Project.StyleList.GetDefault(CADability.Attribute.Style.EDefaultFor.Solids); + for (int i = 0; i < 64; i++) + { + double fx = 1.0, fy = 1.0, fz = 1.0; + if ((i & 0x1) != 0) fx = -fx; + if ((i & 0x2) != 0) fy = -fy; + if ((i & 0x4) != 0) fz = -fz; + GeoVector dir = RandomVector(RandomDouble(40, 60)); + GeoPoint center = GeoPoint.Origin + dir; + Solid sld = Make3D.MakeSphere(center, 10 + 20 * rnd.NextDouble()); + ModOp rotate = ModOp.Rotate(center, RandomVector(1.0), SweepAngle.Deg(rnd.NextDouble() * 360.0)); + sld.Modify(rotate); + sld.Style = stl; + model.Add(sld); + } + Solid mainSphere = Make3D.MakeSphere(GeoPoint.Origin, 50); + mainSphere.Style = stl; + model.Add(mainSphere); + } + + + private void DebugY() + { + List slds = new List(); + CADability.GeoObject.Solid sldbig = null; + Face cone = null; + Face upper = null; + Face lower = null; + foreach (CADability.GeoObject.IGeoObject go in CadFrame.Project.GetActiveModel().AllObjects) + { + if (go is CADability.GeoObject.Solid sld) + { + if (sld.Volume(0.1) > 500000) sldbig = sld; + else slds.Add(sld); + } + } + + var rng = new Random(71); + var order = Enumerable.Range(0, slds.Count).ToArray(); + + // Fisher–Yates + for (int i = slds.Count - 1; i > 0; i--) + { + int j = rng.Next(i + 1); + (order[i], order[j]) = (order[j], order[i]); + } + + + if (slds.Count > 1 && sldbig != null) + { + + for (int i = 0; i < slds.Count; i++) + { + Solid[] res = NewBooleanOperation.Subtract(sldbig, slds[order[i]]); + if (res != null && res.Length >= 1) sldbig = res[0]; + else { } + //System.Diagnostics.Debug.WriteLine("Unite " + i.ToString() + " -> " + sldbig.GetExtent(0.0).Size.ToString("F0")); + } + } + } + private void Debug() + { + List slds = new List(); + Solid operand1 = null, operand2 = null; + List difference = new List(); + List intersection = new List(); + List union = new List(); + List edgeMarkers = new List(); + foreach (CADability.GeoObject.IGeoObject go in CadFrame.Project.GetActiveModel().AllObjects) + { + if (go is CADability.GeoObject.Solid sld) + { + slds.Add(sld); + if (sld.Style != null) + { + if (sld.Style.Name == "Operand1") operand1 = sld; + else if (sld.Style.Name == "Operand2") operand2 = sld; + else if (sld.Style.Name == "Difference") difference.Add(sld); + else if (sld.Style.Name == "Union") union.Add(sld); + else if (sld.Style.Name == "Intersection") intersection.Add(sld); + } + } + if (go is ICurve curve) + { + if (go.Style.Name == "EdgeMarker") + { + edgeMarkers.Add(curve); + } + } + } + if (operand1 != null && operand2 != null) + { + if (difference.Count > 0) + { + Solid[] sres = NewBooleanOperation.Subtract(operand1, operand2); + if (sres.Length > 0) + { + Project proj = Project.CreateSimpleProject(); + proj.GetActiveModel().Add(sres); + proj.WriteToFile("c:\\Temp\\subtract.cdb.json"); + } + } + } + if (slds.Count == 2) + { + //Solid un = NewBooleanOperation.Unite(slds[0], slds[1]); + Solid[] sld; + if (slds[0].Volume(0.1) > slds[1].Volume(0.1)) + { + sld = NewBooleanOperation.Subtract(slds[0], slds[1]); + } + else + { + sld = NewBooleanOperation.Subtract(slds[1], slds[0]); + } + //if (sld.Length > 0) + //{ + // Project proj = Project.CreateSimpleProject(); + // proj.GetActiveModel().Add(sld); + // proj.WriteToFile("c:\\Temp\\subtract.cdb.json"); + //} + } + if (edgeMarkers.Count > 0) + { + foreach (Solid s in slds) + { + foreach (Edge edg in s.Shells[0].Edges) + { + foreach (ICurve em in edgeMarkers) + { + if (edg.Curve3D.SameGeometry(em, 0.1)) + { + Shell rounded = s.Shells[0].RoundEdges(new Edge[] { edg }, 2); + } + } + } + } + } + //if (slds.Count == 1) + //{ + // Shell shell = slds[0].Shells[0]; + // foreach (var vtx in shell.Vertices) + // { + // if ((vtx.Position | new GeoPoint(58, 12, 17)) < 3) + // { + // Shell rounded = shell.RoundEdges(vtx.Edges, 2); + // } + // } + //} + } +#endif + } +} diff --git a/ShapeIt/MateFacesAction.cs b/ShapeIt/MateFacesAction.cs index 546d787e..169e21fd 100644 --- a/ShapeIt/MateFacesAction.cs +++ b/ShapeIt/MateFacesAction.cs @@ -7,7 +7,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +#if !AVALONIA using System.Windows.Forms; +#endif namespace ShapeIt { diff --git a/ShapeIt/ModellingPropertyEntries.cs b/ShapeIt/ModellingPropertyEntries.cs index 395b56db..77d967ff 100644 --- a/ShapeIt/ModellingPropertyEntries.cs +++ b/ShapeIt/ModellingPropertyEntries.cs @@ -15,8 +15,10 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +#if !AVALONIA using System.Windows.Forms.Design; using System.Windows.Forms.VisualStyles; +#endif using System.Xml.Linq; using Wintellect.PowerCollections; using static CADability.Projection; @@ -43,7 +45,7 @@ internal class ModellingPropertyEntries : PropertyEntryImpl, ICommandHandler private bool isDragging = false; // the user is dragging a selection rectangle private bool isMoving = false; // the user is moving the selected objects private bool isHotspotMoving = false; // the user is movind a hotspot - private HashSet selectedObjects = new HashSet(); // the currently selected root objects + private HashSet selectedObjects = new HashSet(); // the currently selected root objects private GeoObjectList movingObjects = new GeoObjectList(); // a copy of the selected objects, used for moving private GeoPoint moveObjectsDownPoint; private bool downOnSelectedObjects; @@ -408,7 +410,7 @@ internal bool OnEscape() #endregion #region PropertyEntry implementation - public override PropertyEntryType Flags => PropertyEntryType.Selectable | PropertyEntryType.GroupTitle | PropertyEntryType.Checkable | PropertyEntryType.HasSubEntries; // PropertyEntryType.ContextMenu | + public override PropertyEntryType Flags => PropertyEntryType.Selectable | PropertyEntryType.GroupTitle | PropertyEntryType.Checkable | PropertyEntryType.HasSubEntries; // PropertyEntryType.ContextMenu | public override IPropertyEntry[] SubItems => subEntries.ToArray(); public override string Value { @@ -576,7 +578,7 @@ private string GetCursor(Point location, IView vw) private string resourceIdOfLastSelectedCategory = String.Empty; // what was selected last time? edge, face, solid etc. private void ComposeModellingEntries(GeoObjectList objectsUnderCursor, IView vw, PickArea? pickArea, bool alsoParent = true, bool addRemove = false) - { // a mouse left button up took place. Compose all the entries for objects, which can be handled by + { // a mouse left button up took place. Compose all the entries for objects, which can be handled by // the object(s) under the mouse cursor // what to focus after the selection changed? IPropertyEntry pe = propertyPage.GetCurrentSelection(); @@ -690,7 +692,7 @@ private void ComposeModellingEntries(GeoObjectList objectsUnderCursor, IView vw, } } - // show actions for all vertices, edges, faces and curves in + // show actions for all vertices, edges, faces and curves in // distibute objects into their categories List faces = new List(); List shells = new List(); // only Shells, which are not part of a Solid @@ -861,7 +863,7 @@ private void ComposeModellingEntries(GeoObjectList objectsUnderCursor, IView vw, } else if (edges.Any()) subEntries.AddIfNotNull(GetEdgeProperties(vw, edges.First(), clickBeam)); - // add the menus for Solids + // add the menus for Solids if (solids.Count > 1) { SelectEntry multipleSolids = new SelectEntry("MultipleSolids.Properties", true); @@ -1165,7 +1167,7 @@ private IPropertyEntry[] GetEdgesProperties(List curves) { List res = new List(); List edges = new List(curves.Select(c => (c as IGeoObject).Owner as Edge)); - DirectMenuEntry makeCurves = new DirectMenuEntry("MenuId.EdgesToCurves"); // too bad, no icon yet, + DirectMenuEntry makeCurves = new DirectMenuEntry("MenuId.EdgesToCurves"); // too bad, no icon yet, makeCurves.ExecuteMenu = (frame) => { GeoObjectList allEdgesAsCurves = new GeoObjectList(curves.Select(c => (c as IGeoObject).Clone())); // need to clone, since the curves ARE the actual edges! @@ -1186,7 +1188,7 @@ private IPropertyEntry[] GetEdgesProperties(List curves) return true; }; res.Add(makeCurves); - DirectMenuEntry makeFillet = new DirectMenuEntry("MenuId.Fillet"); // too bad, no icon yet, + DirectMenuEntry makeFillet = new DirectMenuEntry("MenuId.Fillet"); // too bad, no icon yet, makeFillet.ExecuteMenu = (frame) => { cadFrame.ControlCenter.ShowPropertyPage("Action"); @@ -1615,7 +1617,7 @@ private void Clear() if (selection != null) { selection.UnSelected(selection); // to regenerate the feedback display - // and by passing selected as "previousSelected" parameter, they can only regenerate projection dependant feedback + // and by passing selected as "previousSelected" parameter, they can only regenerate projection dependant feedback } subEntries.Clear(); pp.Remove(this); // to reflect this newly composed entry @@ -1769,7 +1771,7 @@ private IPropertyEntry GetCurveProperties(IView vw, ICurve curve, bool suppresRu if ((sphere != null)) { cadFrame.Project.StyleList.GetDefault(Style.EDefaultFor.Solids)?.Apply(sphere); - vw.Model.Add(sphere); // add the sphere to the model + vw.Model.Add(sphere); // add the sphere to the model ComposeModellingEntries(new GeoObjectList(sphere), frame.ActiveView, null); // and show it in the control center } return true; @@ -2118,7 +2120,7 @@ private IPropertyEntry GetSolidProperties(IView vw, Solid sld, List fromFa if (fromBox[i] is Solid solid && sld != solid && solid.GetExtent(0.0).Interferes(sld.GetExtent(0.0))) otherSolids.Add(solid); } if (otherSolids.Count > 0) - { // there are other solids close to this solid, it is not guaranteed that these other solids interfere with this solid + { // there are other solids close to this solid, it is not guaranteed that these other solids interfere with this solid Func ShowThisAndAll = (selected, frame) => { // this is the standard selection behaviour for BRep operations feedback.Clear(); @@ -2473,7 +2475,7 @@ private IPropertyEntry GetEdgeProperties(IView vw, Edge edg, Axis clickBeam) } } DirectMenuEntry mhdist = new DirectMenuEntry("MenuId.Parametrics.DistanceTo"); - // mhdist.Target = new ParametricsDistanceActionOld(edg, selectAction.Frame); + // mhdist.Target = new ParametricsDistanceActionOld(edg, selectAction.Frame); // TODO! edgeMenus.Add(mhdist); @@ -3103,10 +3105,10 @@ private IPropertyEntry GetFeatureProperties(IView vw, IEnumerable featureF } /// /// There is a face pointed at by . We try to find a loop of curves on faces - /// passing through pa. This loop msut be perpendicular to all edges it crosses, so that it could have been used + /// passing through pa. This loop msut be perpendicular to all edges it crosses, so that it could have been used /// in an extrusion operation to build all faces, which are touched by the loop curves. To each loop curve there is a face, - /// on which this curve resides. There may be several (or none) loops and sets of faces as a result. For each result there - /// is also a plane, in which all loop curves reside and which is the plane at which the shell can be split to extent or shrink + /// on which this curve resides. There may be several (or none) loops and sets of faces as a result. For each result there + /// is also a plane, in which all loop curves reside and which is the plane at which the shell can be split to extent or shrink /// the shape along the edges. /// /// @@ -3243,7 +3245,7 @@ internal void OnProjectionChanged() if (selection != null) { selection.Selected(selection); // to regenerate the feedback display - // and by passing selected as "previousSelected" parameter, they can only regenerate projection dependant feedback + // and by passing selected as "previousSelected" parameter, they can only regenerate projection dependant feedback } } diff --git a/ShapeIt/ParametricPositionAction.cs b/ShapeIt/ParametricPositionAction.cs index 450221f6..7178ad24 100644 --- a/ShapeIt/ParametricPositionAction.cs +++ b/ShapeIt/ParametricPositionAction.cs @@ -7,13 +7,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +#if !AVALONIA using System.Windows.Forms; +#endif namespace ShapeIt { /// /// This action starts with a object to position. This might be one or more faces (like a feature) and a touching point or axis where to meassure. - /// + /// /// internal class ParametricPositionAction : ConstructAction { diff --git a/ShapeIt/Program.cs b/ShapeIt/Program.cs index 263061b3..0da30cd5 100644 --- a/ShapeIt/Program.cs +++ b/ShapeIt/Program.cs @@ -1,14 +1,33 @@ +#if AVALONIA +using Avalonia; +#endif using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +#if !AVALONIA using System.Windows.Forms; +#endif namespace ShapeIt { internal static class Program { +#if AVALONIA + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); + +#else /// /// The main entry point for the application. /// @@ -20,5 +39,6 @@ static void Main(string[] args) ApplicationConfiguration.Initialize(); Application.Run(new MainForm(args)); } +#endif } -} \ No newline at end of file +} diff --git a/ShapeIt/Properties/Settings.Designer.cs b/ShapeIt/Properties/Settings.Designer.cs index 35a2d457..901d2cf5 100644 --- a/ShapeIt/Properties/Settings.Designer.cs +++ b/ShapeIt/Properties/Settings.Designer.cs @@ -7,16 +7,17 @@ // the code is regenerated. // //------------------------------------------------------------------------------ +#if !AVALONIA namespace ShapeIt.Properties { - - + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.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; @@ -24,3 +25,4 @@ public static Settings Default { } } } +#endif diff --git a/ShapeIt/RoundEdges.cs b/ShapeIt/RoundEdges.cs index 7e1c8e54..7777d635 100644 --- a/ShapeIt/RoundEdges.cs +++ b/ShapeIt/RoundEdges.cs @@ -7,7 +7,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +#if !AVALONIA using System.Windows.Forms.VisualStyles; +#endif using static ShapeIt.ShellExtensions; namespace ShapeIt @@ -554,7 +556,7 @@ private bool curveIntersectsShell(ICurve curve) #endif Face sweptFace; // how to test for correct orientation of the sweptCircle? - // The normal of the swept circle must point towards the filletAxisCurve, it must be a concave surface + // The normal of the swept circle must point towards the filletAxisCurve, it must be a concave surface GeoPoint facnt = filletAxisCurve.Curve3D.PointAt(0.5); GeoPoint lecnt = leadingEdge.PointAt(0.5); GeoPoint2D facnt2d = sweptCircle.PositionOf(new GeoPoint(facnt, lecnt)); diff --git a/ShapeIt/ShapeIt.axaml b/ShapeIt/ShapeIt.axaml new file mode 100644 index 00000000..75d818b7 --- /dev/null +++ b/ShapeIt/ShapeIt.axaml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ShapeIt/ShapeIt.axaml.cs b/ShapeIt/ShapeIt.axaml.cs new file mode 100644 index 00000000..d7ea96c7 --- /dev/null +++ b/ShapeIt/ShapeIt.axaml.cs @@ -0,0 +1,23 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace ShapeIt; + +public partial class ShapeIt : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(desktop.Args); + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/ShapeIt/ShapeIt.csproj b/ShapeIt/ShapeIt.csproj index c30ae605..a42b098e 100644 --- a/ShapeIt/ShapeIt.csproj +++ b/ShapeIt/ShapeIt.csproj @@ -1,12 +1,39 @@  - + WinExe net8.0-windows enable true disable + + false + false + + + + + WinExe + net8.0 + enable + false + disable + true + AVALONIA + + + + + + + + + + None + All + + True @@ -37,11 +64,16 @@ - + + + + + + True @@ -107,4 +139,4 @@ - \ No newline at end of file + diff --git a/ShapeIt/ShellExtensions.cs b/ShapeIt/ShellExtensions.cs index d809d51e..fcfd7f01 100644 --- a/ShapeIt/ShellExtensions.cs +++ b/ShapeIt/ShellExtensions.cs @@ -15,7 +15,9 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +#if !AVALONIA using System.Windows.Forms.VisualStyles; +#endif using Wintellect.PowerCollections; namespace ShapeIt @@ -703,14 +705,14 @@ public static Shell[] GetOffsetNew(this Shell shell, double offset) // or the fillets with the spherical faces. Since each breop operation creates new faces and edges, we attach this information to the user data of the original parts // and retrieve them after the brep operation is done. HashSet<(Edge, Face)> dontIntersect = new HashSet<(Edge, Face)>(); // NOT USED ANY MORE, these pairs will be connected and there is no need to calculate the intersection - // set UserData with the original face and edge references to + // set UserData with the original face and edge references to foreach (Face face in shell.Faces) { // makeparallel faces with the provided offset ISurface offsetSurface = face.Surface.GetOffsetSurface(offset); if (offsetSurface == null) continue; // a sphere, cylinder or torus shrinking to 0 GeoPoint2D cnt = face.Domain.GetCenter(); // if the orentation is reversed (e.g. a cylinder will have a negativ radius) or the surface disappears, don't use it - if (offsetSurface != null) // + if (offsetSurface != null) // { List outline = new List(); foreach (Edge edge in face.OutlineEdges)