├── SolidModelBrowser
├── icon3a.ico
├── app.config
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── Controls
│ ├── DoubleValuesInput.xaml
│ ├── PropertyPanel.xaml
│ ├── Scene.xaml
│ ├── NumericBox.xaml
│ ├── ColorInput.xaml
│ ├── FileNavigationPanel.xaml
│ ├── ColorInput.xaml.cs
│ ├── DoubleValuesInput.xaml.cs
│ ├── TextEncodedImage.cs
│ ├── UCamera.cs
│ ├── NumericBox.xaml.cs
│ ├── PropertyPanel.xaml.cs
│ ├── Scene.xaml.cs
│ └── FileNavigationPanel.xaml.cs
├── App.xaml
├── App.xaml.cs
├── Exports
│ ├── Export.cs
│ ├── ExportOBJ.cs
│ ├── ExportSTLAscii.cs
│ ├── ExportSTLBinary.cs
│ ├── ExportPLYAscii.cs
│ ├── ExportPLYBinary.cs
│ └── Export3MF.cs
├── HelpString.cs
├── PropertyInfoAttribute.cs
├── SettingsWindow.xaml.cs
├── SettingsWindow.xaml
├── Imports
│ ├── Import.cs
│ ├── ImportOBJ.cs
│ ├── ImportSTL.cs
│ ├── ImportGCODE.cs
│ ├── Import3MF.cs
│ └── ImportPLY.cs
├── MainWindowSettings.cs
├── Styles
│ ├── Colors.xaml
│ └── ColorsLight.xaml
├── SolidModelBrowser.csproj
├── MainWindow.xaml
├── Settings.cs
└── MainWindow.xaml.cs
├── LICENSE.txt
├── SolidModelBrowser.sln
├── README.md
├── changelog.md
├── .gitattributes
└── .gitignore
/SolidModelBrowser/icon3a.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/questfulcat/SolidModelBrowser/HEAD/SolidModelBrowser/icon3a.ico
--------------------------------------------------------------------------------
/SolidModelBrowser/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/DoubleValuesInput.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/SolidModelBrowser/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/PropertyPanel.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/SolidModelBrowser/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 |
4 | namespace SolidModelBrowser
5 | {
6 | public partial class App : Application
7 | {
8 | public static void SetTheme(string theme)
9 | {
10 | Current.Resources.Clear();
11 |
12 | ResourceDictionary rdColors = new ResourceDictionary();
13 | rdColors.Source = new Uri("pack://application:,,,/SolidModelBrowser;component/Styles/" + theme);
14 | Current.Resources.MergedDictionaries.Add(rdColors);
15 |
16 | ResourceDictionary rdStyles = new ResourceDictionary();
17 | rdStyles.Source = new Uri("pack://application:,,,/SolidModelBrowser;component/Styles/Styles.xaml");
18 | Current.Resources.MergedDictionaries.Add(rdStyles);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/Export.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Windows.Media.Media3D;
6 |
7 | namespace SolidModelBrowser
8 | {
9 | internal abstract class Export
10 | {
11 | public string Extension { get; protected set; } = "";
12 | public string Description { get; protected set; } = "";
13 | public bool InitialXRotationNeeded { get; protected set; }
14 |
15 | public abstract void Save(MeshGeometry3D mesh, string filename);
16 |
17 | public static List Exports = new List();
18 |
19 | static Export()
20 | {
21 | Type thisT = MethodBase.GetCurrentMethod().DeclaringType;
22 | var importTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.BaseType != null && t.BaseType == thisT);
23 | foreach (var importType in importTypes) Exports.Add((Export)Activator.CreateInstance(importType));
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/Scene.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 questfulcat
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/ExportOBJ.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using System.Windows.Media.Media3D;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | internal class ExportOBJ : Export
8 | {
9 | public ExportOBJ()
10 | {
11 | Extension = "obj";
12 | Description = "OBJ";
13 | InitialXRotationNeeded = true;
14 | }
15 |
16 | public override void Save(MeshGeometry3D mesh, string filename)
17 | {
18 | StringBuilder sb = new StringBuilder();
19 | sb.AppendLine($"# Exported from SolidModelBrowser");
20 | sb.AppendLine("o default");
21 |
22 | int q = mesh.TriangleIndices.Count;
23 | int nq = mesh.Normals.Count;
24 |
25 | foreach (var p in mesh.Positions) sb.AppendLine($"v {p.X} {p.Y} {p.Z}");
26 | foreach (var n in mesh.Normals) sb.AppendLine($"vn {n.X} {n.Y} {n.Z}");
27 | for (int c = 0; c < q; c += 3) sb.AppendLine($"f {mesh.TriangleIndices[c] + 1} {mesh.TriangleIndices[c + 1] + 1} {mesh.TriangleIndices[c + 2] + 1}");
28 |
29 | File.WriteAllText(filename, sb.ToString());
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Этот код создан программой.
4 | // Исполняемая версия:4.0.30319.42000
5 | //
6 | // Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
7 | // повторной генерации кода.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace SolidModelBrowser.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SolidModelBrowser.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32421.90
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolidModelBrowser", "SolidModelBrowser\SolidModelBrowser.csproj", "{CE656CE1-8F90-4831-B3DA-FAFFD375DDE6}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {CE656CE1-8F90-4831-B3DA-FAFFD375DDE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {CE656CE1-8F90-4831-B3DA-FAFFD375DDE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {CE656CE1-8F90-4831-B3DA-FAFFD375DDE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {CE656CE1-8F90-4831-B3DA-FAFFD375DDE6}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {A142B785-A2E6-49B9-8F6B-B60503B28F68}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/SolidModelBrowser/HelpString.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace SolidModelBrowser
8 | {
9 | public partial class MainWindow
10 | {
11 | string help = $@"Solid Model Browser v {version}
12 |
13 | F1 - Show/hide this help
14 | F2 - Diffuse material on/off
15 | F3 - Specular material on/off
16 | F4 - Emissive material on/off
17 | F5 - Reload file, reset camera
18 | F6 - Save image, snapshot to PNG
19 | F7 - Open current file in external application (if defined)
20 | F8 - Switch camera Perspective/Orthographic modes
21 | C - Turn camera at model center
22 | A, D - Rotate model around Z axis
23 | S, W - Rotate model around X axis
24 | E, Q - Rotate model around Y axis
25 | G, R - Set Camera at model frontside/backside view
26 | T, B - Set Camera at model top/bottom view
27 | F, H - Set Camera at model left/right view
28 | O - Show/hide XYZ axes
29 | P - Show/hide ground polygon
30 | CTRL+F - Recreate smooth mesh in flat mode (flat polygon lighting)
31 | CTRL+W - Wireframe mode
32 | CTRL+S - Export current model
33 | I - show/hide model info
34 | LeftShift - hold left shift to drag window by viewport
35 |
36 | Grigoriy E. (questfulcat) 2025 (C)";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/NumericBox.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/ColorInput.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | A
11 |
12 | R
13 |
14 | G
15 |
16 | B
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/FileNavigationPanel.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/ColorInput.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Controls;
3 | using System.Windows.Media;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | public partial class ColorInput : UserControl
8 | {
9 | public ColorInput()
10 | {
11 | InitializeComponent();
12 | NBAlpha.ValueChanged += NB_ValueChanged;
13 | NBRed.ValueChanged += NB_ValueChanged;
14 | NBGreen.ValueChanged += NB_ValueChanged;
15 | NBBlue.ValueChanged += NB_ValueChanged;
16 | }
17 |
18 | Color getColor()
19 | {
20 | return Color.FromArgb((byte)NBAlpha.Value, (byte)NBRed.Value, (byte)NBGreen.Value, (byte)NBBlue.Value);
21 | }
22 |
23 | void NB_ValueChanged(object sender, System.EventArgs e)
24 | {
25 | BorderColorPreview.Background = new SolidColorBrush(getColor());
26 | ValueChanged?.Invoke(this, EventArgs.Empty);
27 | }
28 |
29 | public event EventHandler ValueChanged;
30 |
31 | public Color ColorValue
32 | {
33 | get
34 | {
35 | return getColor();
36 | }
37 | set
38 | {
39 | NBAlpha.Value = value.A;
40 | NBRed.Value = value.R;
41 | NBGreen.Value = value.G;
42 | NBBlue.Value = value.B;
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/ExportSTLAscii.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using System.Windows.Media.Media3D;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | internal class ExportSTLAscii : Export
8 | {
9 | public ExportSTLAscii()
10 | {
11 | Extension = "stl";
12 | Description = "STL Ascii";
13 | }
14 |
15 | public override void Save(MeshGeometry3D mesh, string filename)
16 | {
17 | string solidname = "SolidModelBrowserExport";
18 | StringBuilder sb = new StringBuilder();
19 | sb.AppendLine($"solid {solidname}");
20 |
21 | int q = mesh.TriangleIndices.Count / 3;
22 | int nq = mesh.Normals.Count;
23 |
24 | for (int c = 0; c < q; c++)
25 | {
26 | Point3D p1 = mesh.Positions[mesh.TriangleIndices[c * 3]];
27 | Point3D p2 = mesh.Positions[mesh.TriangleIndices[c * 3 + 1]];
28 | Point3D p3 = mesh.Positions[mesh.TriangleIndices[c * 3 + 2]];
29 | Vector3D n = c * 3 < nq ? mesh.Normals[c * 3] : Utils.GenerateNormal(p1, p2, p3);
30 |
31 | sb.AppendLine($"facet normal {n.X} {n.Y} {n.Z}");
32 | sb.AppendLine(" outer loop");
33 | sb.AppendLine($" vertex {p1.X} {p1.Y} {p1.Z}");
34 | sb.AppendLine($" vertex {p2.X} {p2.Y} {p2.Z}");
35 | sb.AppendLine($" vertex {p3.X} {p3.Y} {p3.Z}");
36 | sb.AppendLine(" endloop");
37 | sb.AppendLine("endfacet");
38 | }
39 |
40 | sb.AppendLine($"endsolid {solidname}");
41 | File.WriteAllText(filename, sb.ToString());
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/DoubleValuesInput.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 |
6 | namespace SolidModelBrowser
7 | {
8 | public partial class DoubleValuesInput : UserControl
9 | {
10 | Thickness th1 = new Thickness(2, 0, 8, 0);
11 |
12 | public DoubleValuesInput()
13 | {
14 | InitializeComponent();
15 | }
16 |
17 | List boxes = new List();
18 | public DoubleValuesInput(string[] fieldNames, double min, double max, int roundDigits)
19 | {
20 | InitializeComponent();
21 | foreach (string field in fieldNames)
22 | {
23 | panel.Children.Add(new TextBlock { Text = field });
24 | var nb = new NumericBox() { MinValue = min, MaxValue = max, RoundDigits = roundDigits, Width = 100, Margin = th1 };
25 | nb.ValueChanged += (sender, e) => { ValueChanged?.Invoke(this, EventArgs.Empty); };
26 | panel.Children.Add(nb);
27 | boxes.Add(nb);
28 | }
29 | }
30 |
31 | public void SetMinValue(int boxindex, double min)
32 | {
33 | if (boxindex >=0 && boxindex < boxes.Count) boxes[boxindex].MinValue = min;
34 | }
35 |
36 | public void SetMaxValue(int boxindex, double max)
37 | {
38 | if (boxindex >= 0 && boxindex < boxes.Count) boxes[boxindex].MaxValue = max;
39 | }
40 |
41 | public event EventHandler ValueChanged;
42 |
43 | public double this [int index]
44 | {
45 | get { return index >= 0 && index < boxes.Count ? boxes[index].Value : double.NaN; }
46 | set { if (index >= 0 && index < boxes.Count) boxes[index].Value = value; }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/ExportSTLBinary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Windows.Media.Media3D;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | internal class ExportSTLBinary : Export
8 | {
9 | public ExportSTLBinary()
10 | {
11 | Extension = "stl";
12 | Description = "STL Binary";
13 | }
14 |
15 | public override void Save(MeshGeometry3D mesh, string filename)
16 | {
17 | using (BinaryWriter bw = new BinaryWriter(new FileStream(filename, FileMode.Create, FileAccess.Write)))
18 | {
19 | bw.Write(" STL Exported with SolidModelBrowser".PadRight(79));
20 |
21 | int q = mesh.TriangleIndices.Count / 3;
22 | bw.Write(q);
23 | int nq = mesh.Normals.Count;
24 |
25 | for (int c = 0; c < q; c++)
26 | {
27 | Point3D p1 = mesh.Positions[mesh.TriangleIndices[c * 3]];
28 | Point3D p2 = mesh.Positions[mesh.TriangleIndices[c * 3 + 1]];
29 | Point3D p3 = mesh.Positions[mesh.TriangleIndices[c * 3 + 2]];
30 |
31 | Vector3D n = c * 3 < nq ? mesh.Normals[c * 3] : Utils.GenerateNormal(p1, p2, p3);
32 | bw.Write((float)n.X);
33 | bw.Write((float)n.Y);
34 | bw.Write((float)n.Z);
35 |
36 | bw.Write((float)p1.X);
37 | bw.Write((float)p1.Y);
38 | bw.Write((float)p1.Z);
39 | bw.Write((float)p2.X);
40 | bw.Write((float)p2.Y);
41 | bw.Write((float)p2.Z);
42 | bw.Write((float)p3.X);
43 | bw.Write((float)p3.Y);
44 | bw.Write((float)p3.Z);
45 |
46 | bw.Write((UInt16)0);
47 | }
48 |
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Solid Model Browser
2 |
3 | This app is viewer and converter for STL, 3MF, OBJ, PLY, GCODE files.
4 | User can select files in left file panel and observe it's 3D content.
5 |
6 | 
7 |
8 | ## Requirements
9 |
10 | .NET Framework 4.8, WPF (Windows 7 ... Windows 11)
11 |
12 | ## Installation
13 |
14 | No installation needed, just copy executable to any empty folder. Settings file with defaults will be created after first startup.
15 |
16 | ## Build
17 |
18 | Build project with Visual Studio 2022
19 |
20 | ## Features
21 |
22 | - Open binary and ASCII STL files, load main 3Dmodel from 3MF files (CURA projects), partial support for OBJ files with triangle faces, PLY (ASCII and binary little-endian), experimental GCODE support with linear movements (Marlin compatible codes)
23 |
24 | - Export model to file formats STL (ASCII/Bin), 3MF (simplified), OBJ, PLY (ASCII/Bin)
25 |
26 | - Model slicing along Z axis (mostly to view GCODE layers) with vertical slider.
27 |
28 | 
29 |
30 | - Fly camera around model
31 |
32 | - Save current view from camera to PNG with selected DPI (in settings file)
33 |
34 | - Open current file with local installed application like slicer or 3D editor
35 |
36 | - Rotate model
37 |
38 | - Set up materials for model and scene lights
39 |
40 | - Perspective and orthographic camera modes, fish eye FOV model
41 |
42 | - Show XYZ axes and ground plane
43 |
44 | - Resolve normals problems, polygon vertex order
45 |
46 | - Show basic model info
47 |
48 | - Dark and Light app themes
49 |
50 | 
51 |
52 | ## Other Screenshots
53 |
54 | 
55 |
56 | 
57 |
--------------------------------------------------------------------------------
/SolidModelBrowser/PropertyInfoAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace SolidModelBrowser
7 | {
8 | public class PropertyInfoAttribute : Attribute
9 | {
10 | public string Name;
11 | public object Value;
12 | public object Object;
13 | public PropertyInfo Property;
14 |
15 | public string Category { get; set; }
16 | public string Description { get; set; }
17 | public string MenuLabel { get; set; }
18 | public string SortPrefix { get; set; }
19 | public double Min { get; set; } = 0.0;
20 | public double Max { get; set; } = double.MaxValue;
21 | public double Increment { get; set; } = 1.0;
22 |
23 | public static List GetPropertiesInfoList(object obj, bool sort)
24 | {
25 | var pi = new List();
26 | var props = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
27 | foreach (var p in props)
28 | {
29 | var attr = p.GetCustomAttribute();
30 | if (attr != null) pi.Add(new PropertyInfoAttribute()
31 | {
32 | Name = p.Name,
33 | Value = p.GetValue(obj, null),
34 | Object = obj,
35 | Property = p,
36 | Category = attr.Category ?? "All",
37 | Description = attr.Description,
38 | MenuLabel = attr.MenuLabel,
39 | SortPrefix = attr.SortPrefix,
40 | Min = attr.Min,
41 | Max = attr.Max,
42 | Increment = attr.Increment
43 | });
44 | }
45 | if (sort) pi = pi.OrderBy(p => p.Category + p.SortPrefix + p.Name).ToList();// pi.Sort((a, b) => string.Compare(a.Category + a.Name, b.Category + b.Name));
46 | return pi;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/SolidModelBrowser/SettingsWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Input;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | public enum SettingsWindowResult { None, ResetDefaults, OpenEditor };
8 |
9 | public partial class SettingsWindow : Window
10 | {
11 | public SettingsWindowResult WindowResult { get; set; } = SettingsWindowResult.None;
12 |
13 | public SettingsWindow()
14 | {
15 | InitializeComponent();
16 |
17 | ButtonClose.Click += (s, e) => Close();
18 | BorderHeader.MouseDown += (s, e) => { if (e.ChangedButton == MouseButton.Left) this.DragMove(); };
19 | ScrollViewerBase.PreviewMouseWheel += ScrollViewerBase_PreviewMouseWheel;
20 |
21 | ButtonLoadDefaults.Click += ButtonLoadDefaults_Click;
22 | ButtonOpenInTextEditor.Click += ButtonOpenInTextEditor_Click;
23 | }
24 |
25 | private void ButtonOpenInTextEditor_Click(object sender, RoutedEventArgs e)
26 | {
27 | if (Utils.MessageWindow("Close app and open settings.ini in default editor?\r\n(You have to associate ini files with Notepad++ or other text editor before using this option)", this, "YES,Cancel", Orientation.Horizontal) != "YES") return;
28 | WindowResult = SettingsWindowResult.OpenEditor;
29 | Close();
30 | }
31 |
32 | private void ButtonLoadDefaults_Click(object sender, RoutedEventArgs e)
33 | {
34 | if (Utils.MessageWindow("Are you sure you want to reset all settings to default values?", this, "YES,Cancel", Orientation.Horizontal) != "YES") return;
35 | WindowResult = SettingsWindowResult.ResetDefaults;
36 | Close();
37 | }
38 |
39 | private void ScrollViewerBase_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
40 | {
41 | ScrollViewer scv = (ScrollViewer)sender;
42 | scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
43 | e.Handled = true;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/ExportPLYAscii.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using System.Windows.Media.Media3D;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | internal class ExportPLYAscii : Export
8 | {
9 | public ExportPLYAscii()
10 | {
11 | Extension = "ply";
12 | Description = "PLY Ascii";
13 | }
14 |
15 | public override void Save(MeshGeometry3D mesh, string filename)
16 | {
17 | bool withnormals = mesh.Normals.Count == mesh.Positions.Count;
18 | int faces = mesh.TriangleIndices.Count / 3;
19 | int positions = mesh.Positions.Count;
20 |
21 | StringBuilder sb = new StringBuilder();
22 | sb.AppendLine("ply");
23 | sb.AppendLine("format ascii 1.0");
24 | sb.AppendLine("comment Exported from SolidModelBrowser");
25 | sb.AppendLine($"element vertex {positions}");
26 | sb.AppendLine("property float x");
27 | sb.AppendLine("property float y");
28 | sb.AppendLine("property float z");
29 | if (withnormals)
30 | {
31 | sb.AppendLine("property float nx");
32 | sb.AppendLine("property float ny");
33 | sb.AppendLine("property float nz");
34 | }
35 | sb.AppendLine($"element face {faces}");
36 | sb.AppendLine("property list uchar uint vertex_indices");
37 | sb.AppendLine("end_header");
38 |
39 | for(int c = 0; c < positions; c++)
40 | {
41 | var p = mesh.Positions[c];
42 | sb.Append($"{(float)p.X} {(float)p.Y} {(float)p.Z}");
43 | if (withnormals) sb.Append($" {(float)mesh.Normals[c].X} {(float)mesh.Normals[c].Y} {(float)mesh.Normals[c].Z}");
44 | sb.AppendLine();
45 | }
46 |
47 | for (int c = 0; c < faces; c++)
48 | {
49 | sb.AppendLine($"3 {mesh.TriangleIndices[c * 3]} {mesh.TriangleIndices[c * 3 + 1]} {mesh.TriangleIndices[c * 3 + 2]}");
50 | }
51 |
52 | File.WriteAllText(filename, sb.ToString());
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Solid Model Browser, change log
2 |
3 | ## v0.6.1 (23.04.2025)
4 |
5 | - Added settings to GCODE import (Layer height, Line width)
6 | - Added setting UseSystemWindowStyle (for compatibility issues if OS can't draw correct frameless windows)
7 |
8 | ## v0.6 (19.02.2025)
9 |
10 | - Export to STL, 3MF, OBJ, PLY formats added
11 | - Settings menu: reset to defaults and open in text editor buttons added
12 | - Rotation reworked (slower, but now it applied to model geometry data)
13 | - Axes length setting added
14 | - Ground settings added (size, color, checker size)
15 | - Ambient light and directional lights settings added
16 |
17 | ## v0.5 (28.01.2025)
18 |
19 | - Settings Menu added (instead of opening settings.ini file)
20 | - GCODE format import added (Marlin linear moves compatible, experimental)
21 | - Model Z-axis slicing (with slider)
22 | - Camera positioning at geometric/points_avg center buttons
23 | - Camera default positioning DefaultLookAtModelPointsAvgCenter setting added
24 | - Sort files by extensions setting added
25 | - Sort folders and files for all supported filesystems (fixed)
26 | - Open folder in filepanel and select file from command line argument (fixed)
27 |
28 | ## v0.4 (23.12.2024)
29 |
30 | - PLY format import added (ASCII and Binary little-endian)
31 | - Wireframe mode implemented
32 | - WireframeEdgeScale setting added
33 | - IgnoreOriginalNormals setting added, to skip normals if there are a lot files with broken or incorrect normals in user library
34 | - UnsmoothAfterLoading setting added, to load all models in flat mode
35 |
36 | ## v0.3.1 (08.12.2024)
37 |
38 | - 3MF import compatibility improvements
39 | - Different colors for files of different types in file panel
40 |
41 | ## v0.3 (25.10.2024)
42 |
43 | - 3MF with components import added (extended 3MF support)
44 | - Tool to reset meshes smooth structure to flat (button on tool panel)
45 | - Camera front, back, top, bottom, left, right view positions added (hotkeys)
46 | - Camera initial shift from model setting added
47 | - Model rotation angle 45/90 deg setting added
48 | - Ground plane added (tool panel)
49 | - [F1] help page added
50 | - 3MF import, no transform matrix for object bugfix
51 | - interface updates
52 |
53 | ## v0.2 (21.10.2024)
54 |
55 | - 3MF import class was remastered
56 | - model file name may be passed like command line argument to open with app
57 | - window max size calculation method was changed
58 | - window max size can be manually selected in settings (to solve problems with non-primary displays)
59 | - header double click expands window
60 | - camera FOV and FishEyeFOV angles settings were added
61 | - interface updates
62 |
63 | ## v0.1 initial release (17.10.2024)
64 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/ExportPLYBinary.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using System.Windows.Media.Media3D;
4 |
5 | namespace SolidModelBrowser
6 | {
7 | internal class ExportPLYBinary : Export
8 | {
9 | public ExportPLYBinary()
10 | {
11 | Extension = "ply";
12 | Description = "PLY Binary";
13 | }
14 |
15 | public override void Save(MeshGeometry3D mesh, string filename)
16 | {
17 | bool withnormals = mesh.Normals.Count == mesh.Positions.Count;
18 | int faces = mesh.TriangleIndices.Count / 3;
19 | int positions = mesh.Positions.Count;
20 |
21 | StringBuilder sb = new StringBuilder();
22 | sb.AppendLine("ply");
23 | sb.AppendLine("format binary_little_endian 1.0");
24 | sb.AppendLine("comment Exported from SolidModelBrowser");
25 | sb.AppendLine($"element vertex {positions}");
26 | sb.AppendLine("property float x");
27 | sb.AppendLine("property float y");
28 | sb.AppendLine("property float z");
29 | if (withnormals)
30 | {
31 | sb.AppendLine("property float nx");
32 | sb.AppendLine("property float ny");
33 | sb.AppendLine("property float nz");
34 | }
35 | sb.AppendLine($"element face {faces}");
36 | sb.AppendLine("property list uchar uint vertex_indices");
37 | sb.AppendLine("end_header");
38 |
39 | File.WriteAllText(filename, sb.ToString());
40 | using (BinaryWriter bw = new BinaryWriter(new FileStream(filename, FileMode.Append, FileAccess.Write)))
41 | {
42 | for (int c = 0; c < positions; c++)
43 | {
44 | var p = mesh.Positions[c];
45 | bw.Write((float)p.X);
46 | bw.Write((float)p.Y);
47 | bw.Write((float)p.Z);
48 |
49 | if (withnormals)
50 | {
51 | bw.Write((float)mesh.Normals[c].X);
52 | bw.Write((float)mesh.Normals[c].Y);
53 | bw.Write((float)mesh.Normals[c].Z);
54 | }
55 | }
56 |
57 | for (int c = 0; c < faces; c++)
58 | {
59 | bw.Write((byte)3);
60 | bw.Write(mesh.TriangleIndices[c * 3]);
61 | bw.Write(mesh.TriangleIndices[c * 3 + 1]);
62 | bw.Write(mesh.TriangleIndices[c * 3 + 2]);
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // Общие сведения об этой сборке предоставляются следующим набором
8 | // набор атрибутов. Измените значения этих атрибутов, чтобы изменить сведения,
9 | // связанные со сборкой.
10 | [assembly: AssemblyTitle("SolidModelBrowser")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("SolidModelBrowser")]
15 | [assembly: AssemblyCopyright("Copyright © 2022")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
20 | // для компонентов COM. Если необходимо обратиться к типу в этой сборке через
21 | // из модели COM, установите атрибут ComVisible для этого типа в значение true.
22 | [assembly: ComVisible(false)]
23 |
24 | //Чтобы начать создание локализуемых приложений, задайте
25 | //CultureYouAreCodingWith в файле .csproj
26 | //в . Например, при использовании английского (США)
27 | //в своих исходных файлах установите в en-US. Затем отмените преобразование в комментарий
28 | //атрибута NeutralResourceLanguage ниже. Обновите "en-US" в
29 | //строка внизу для обеспечения соответствия настройки UICulture в файле проекта.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //где расположены словари ресурсов по конкретным тематикам
36 | //(используется, если ресурс не найден на странице,
37 | // или в словарях ресурсов приложения)
38 | ResourceDictionaryLocation.SourceAssembly //где расположен словарь универсальных ресурсов
39 | //(используется, если ресурс не найден на странице,
40 | // в приложении или в каких-либо словарях ресурсов для конкретной темы)
41 | )]
42 |
43 |
44 | // Сведения о версии для сборки включают четыре следующих значения:
45 | //
46 | // Основной номер версии
47 | // Дополнительный номер версии
48 | // Номер сборки
49 | // Номер редакции
50 | //
51 | // Можно задать все значения или принять номера сборки и редакции по умолчанию
52 | // используя "*", как показано ниже:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/SolidModelBrowser/SettingsWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 | Settings
20 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Exports/Export3MF.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 | using System.Text;
4 | using System.Windows.Media.Media3D;
5 |
6 | namespace SolidModelBrowser
7 | {
8 | internal class Export3MF : Export
9 | {
10 | public Export3MF()
11 | {
12 | Extension = "3mf";
13 | Description = "3MF";
14 | }
15 |
16 | void addZipEntry(ZipArchive zip, string entryname, string content)
17 | {
18 | var ze = zip.CreateEntry(entryname);
19 | using (StreamWriter writer = new StreamWriter(ze.Open()))
20 | {
21 | writer.Write(content);
22 | }
23 | }
24 |
25 | public override void Save(MeshGeometry3D mesh, string filename)
26 | {
27 | using (var fs = new FileStream(filename, FileMode.Create))
28 | {
29 | using (var zip = new ZipArchive(fs, ZipArchiveMode.Create, true))
30 | {
31 | addZipEntry(zip, "[Content_Types].xml", " \r\n");
32 | addZipEntry(zip, "_rels/.rels", " \r\n- \r\n \r\n ");
33 |
34 | StringBuilder sb = new StringBuilder();
35 | sb.AppendLine("\r\n\r\n\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t \r\n\t\r\n\r\n");
41 |
42 | addZipEntry(zip, "3D/3dmodel.model", sb.ToString());
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Imports/Import.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Media3D;
8 |
9 | namespace SolidModelBrowser
10 | {
11 | internal abstract class Import
12 | {
13 | public static bool StopFlag = false;
14 | public static int Progress = 0;
15 | public static string ExceptionMessage;
16 |
17 | protected static Settings settings;
18 |
19 | // static buffers are common for all importers
20 | public static List Normals = new List();
21 | public static List Positions = new List();
22 | public static List Indices = new List();
23 |
24 | // extensions should be in lower case
25 | public List Extensions { get; protected set; } = new List();
26 | public Dictionary ExtensionsColors { get; protected set; } = new Dictionary();
27 | public Dictionary ExtensionsColorsLight { get; protected set; } = new Dictionary();
28 | public bool InitialXRotationNeeded { get; protected set; } = false;
29 |
30 | public abstract void Load(string filename);
31 |
32 | public void Initialize()
33 | {
34 | StopFlag = false;
35 | Progress = 0;
36 | ExceptionMessage = null;
37 |
38 | Normals.Clear();
39 | Positions.Clear();
40 | Indices.Clear();
41 | }
42 |
43 | public static void BindSettings(Settings s) => settings = s;
44 |
45 | public static List Imports = new List();
46 | public static Import CurrentImport = null;
47 |
48 | static Import()
49 | {
50 | Type thisT = MethodBase.GetCurrentMethod().DeclaringType;
51 | var importTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.BaseType != null && t.BaseType == thisT);
52 | foreach (var importType in importTypes) Imports.Add((Import)Activator.CreateInstance(importType));
53 | }
54 |
55 | public static bool SelectImporter(string filename)
56 | {
57 | string ext = Path.GetExtension(filename).Trim('.').ToLower();
58 | foreach (Import i in Imports)
59 | if (i.Extensions.Contains(ext))
60 | {
61 | CurrentImport = i;
62 | CurrentImport.Initialize();
63 | return true;
64 | }
65 | return false;
66 | }
67 |
68 | public static void FillColorsDictionary(Dictionary cd, bool lightTheme)
69 | {
70 | cd.Clear();
71 | foreach(var i in Imports)
72 | {
73 | var d = lightTheme ? i.ExtensionsColorsLight : i.ExtensionsColors;
74 | foreach (var c in d) cd.Add(c.Key, c.Value);
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Imports/ImportOBJ.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Windows.Media;
5 | using System.Windows.Media.Media3D;
6 |
7 | namespace SolidModelBrowser
8 | {
9 | internal class ImportOBJ : Import
10 | {
11 | public ImportOBJ()
12 | {
13 | Extensions = new List { "obj" };
14 | ExtensionsColors = new Dictionary { { "obj", new SolidColorBrush(Color.FromRgb(200, 200, 255)) } };
15 | ExtensionsColorsLight = new Dictionary { { "obj", new SolidColorBrush(Color.FromRgb(50, 50, 100)) } };
16 | InitialXRotationNeeded = true;
17 | }
18 |
19 | char[] separator = { ' ' };
20 |
21 | string[] getParts(string line, int linenum)
22 | {
23 | var p = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);
24 | if (p.Length < 4) throw new Exception($"OBJ parse failed at line {linenum}, '{line}' should have at least 3 values");
25 | return p;
26 | }
27 |
28 | public override void Load(string filename)
29 | {
30 | var lines = File.ReadAllLines(filename);
31 | int q = lines.Length;
32 |
33 | var pre_normals = new List(4096);
34 | var pre_positions = new List(4096);
35 |
36 | int index = 0;
37 |
38 | for (int c = 0; c < q; c++)
39 | {
40 | var line = lines[c].TrimStart().ToLower();
41 | if (line.StartsWith("v ")) // vertex
42 | {
43 | var p = getParts(line, c);
44 | pre_positions.Add(new Point3D(double.Parse(p[1]), double.Parse(p[2]), double.Parse(p[3])));
45 | }
46 | else if (line.StartsWith("vn ")) // normal
47 | {
48 | var p = getParts(line, c);
49 | pre_normals.Add(new Vector3D(double.Parse(p[1]), double.Parse(p[2]), double.Parse(p[3])));
50 | }
51 | else if (line.StartsWith("f ")) // face
52 | {
53 | var p = getParts(line, c);
54 | if (p.Length != 4) throw new Exception($"OBJ contains non triangle faces at line {c}, {line}");
55 | for (int i = 1; i < p.Length; i++)
56 | {
57 | var p2 = p[i].Split('/');
58 | int pos = int.Parse(p2[0]) - 1;
59 | Positions.Add(pre_positions[pos]);
60 | Indices.Add(index++);
61 |
62 | if (p2.Length == 3)
63 | {
64 | int n = int.Parse(p2[2]) - 1;
65 | //if (n >= 0 && pre_normals.Count > n) normals.Add(pre_normals[n]);
66 | }
67 | }
68 | }
69 |
70 | Progress = c * 100 / q;
71 | if (StopFlag) return;
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Этот код создан программой.
4 | // Исполняемая версия:4.0.30319.42000
5 | //
6 | // Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
7 | // повторной генерации кода.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace SolidModelBrowser.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д.
17 | ///
18 | // Этот класс создан автоматически классом StronglyTypedResourceBuilder
19 | // с помощью такого средства, как ResGen или Visual Studio.
20 | // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen
21 | // с параметром /str или перестройте свой проект VS.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SolidModelBrowser.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Перезаписывает свойство CurrentUICulture текущего потока для всех
51 | /// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/TextEncodedImage.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Media;
4 | using System.Windows.Media.Imaging;
5 |
6 | namespace SolidModelBrowser
7 | {
8 | public class TextEncodedImage : Image
9 | {
10 | WriteableBitmap stringToBitmap(SolidColorBrush pen, string s)
11 | {
12 | if (pen == null || s == null) return null;
13 | try
14 | {
15 | //var watch = System.Diagnostics.Stopwatch.StartNew();
16 | uint ppen = ((uint)pen.Color.A << 24) + ((uint)pen.Color.R << 16) + ((uint)pen.Color.G << 8) + pen.Color.B;
17 | int basecode = s[0];
18 | int w = s[1] - basecode;
19 | int h = s[2] - basecode;
20 | var bmp = new WriteableBitmap(w, h, 96, 96, PixelFormats.Bgra32, null);
21 |
22 | uint[,] data = new uint[h, w];
23 | for (int y = 0; y < h; y++)
24 | for (int x = 0; x < w; x++)
25 | if (((s[3 + (y * w + x) / 6] - basecode) & (1 << (y * w + x) % 6)) > 0) data[y, x] = ppen;
26 |
27 | // antialiasing
28 | uint phalf = ((uint)(pen.Color.A / 4) << 24) + ((uint)pen.Color.R << 16) + ((uint)pen.Color.G << 8) + pen.Color.B;
29 | for (int y = 1; y < h - 1; y++)
30 | for (int x = 1; x < w - 1; x++)
31 | {
32 | int q = 0;
33 | if (data[y - 1, x] == ppen) q++;
34 | if (data[y + 1, x] == ppen) q++;
35 | if (data[y, x - 1] == ppen) q++;
36 | if (data[y, x + 1] == ppen) q++;
37 | if ((q == 2 || q == 3) && data[y, x] == 0) data[y, x] = phalf;
38 | }
39 |
40 | bmp.WritePixels(new System.Windows.Int32Rect(0, 0, w, h), data, bmp.Format.BitsPerPixel * w / 8, 0);
41 |
42 | //watch.Stop();
43 | //MessageBox.Show(watch.ElapsedTicks.ToString());
44 | return bmp;
45 | }
46 | catch { return null; }
47 | }
48 |
49 | public static readonly DependencyProperty EncodedImageProperty = DependencyProperty.Register("EncodedImage", typeof(string), typeof(TextEncodedImage), new PropertyMetadata(null, propertyChangedCallback));
50 | public string EncodedImage
51 | {
52 | get { return (string)GetValue(EncodedImageProperty); }
53 | set { SetValue(EncodedImageProperty, value); }
54 | }
55 |
56 | public static readonly DependencyProperty EncodedImageColorProperty = DependencyProperty.Register("EncodedImageColor", typeof(SolidColorBrush), typeof(TextEncodedImage), new PropertyMetadata(null, propertyChangedCallback));
57 | public SolidColorBrush EncodedImageColor
58 | {
59 | get { return (SolidColorBrush)GetValue(EncodedImageColorProperty); }
60 | set { SetValue(EncodedImageColorProperty, value); }
61 | }
62 |
63 | private static void propertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
64 | {
65 | TextEncodedImage o = (TextEncodedImage)obj;
66 | o.Source = o.stringToBitmap((SolidColorBrush)o.GetValue(EncodedImageColorProperty) /*?? Brushes.White*/, (string)o.GetValue(EncodedImageProperty));
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/UCamera.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Controls;
2 | using System.Windows.Media.Media3D;
3 |
4 | namespace SolidModelBrowser
5 | {
6 | public class UCamera
7 | {
8 | Viewport3D viewport;
9 | PerspectiveCamera camPerspective = new PerspectiveCamera() { NearPlaneDistance = 0.1, FarPlaneDistance = 1000000.0 };
10 | OrthographicCamera camOrthographic = new OrthographicCamera() { NearPlaneDistance = 0.1, FarPlaneDistance = 1000000.0 };
11 |
12 |
13 | public UCamera(Viewport3D vp)
14 | {
15 | viewport = vp;
16 | viewport.Camera = camPerspective;
17 | }
18 |
19 | public void SelectType(bool orthographic)
20 | {
21 | if (orthographic) viewport.Camera = camOrthographic;
22 | else viewport.Camera = camPerspective;
23 | validateCamera();
24 | }
25 |
26 | public void DefaultPosition(double modelLen, double cameraInitialShift)
27 | {
28 | Position = new Point3D(lookCenter.X, lookCenter.Y - modelLen * cameraInitialShift, lookCenter.Z + modelLen * cameraInitialShift / 2);
29 | validateCamera();
30 | camOrthographic.Width = modelLen * cameraInitialShift;
31 | }
32 |
33 | public void RelativePosition(double modelLen, double dx, double dy, double dz)
34 | {
35 | Position = new Point3D(lookCenter.X + dx * modelLen, lookCenter.Y + dy * modelLen, lookCenter.Z + dz * modelLen);
36 | validateCamera();
37 | }
38 |
39 | public void TurnAt(Point3D pos)
40 | {
41 | lookCenter = pos;
42 | validateCamera();
43 | }
44 |
45 | void validateCamera()
46 | {
47 | camPerspective.LookDirection = lookCenter - camPerspective.Position;
48 | camPerspective.UpDirection = new Vector3D(0, 0, 1);
49 | camOrthographic.LookDirection = lookCenter - camOrthographic.Position;
50 | camOrthographic.UpDirection = new Vector3D(0, 0, 1);
51 | camOrthographic.Width = (Position - lookCenter).Length;
52 | }
53 |
54 | public void Scale(double mult)
55 | {
56 | Position = lookCenter + (Position - lookCenter) * mult;
57 | validateCamera();
58 | }
59 |
60 | public void Move(double dx, double dy)
61 | {
62 | var dv = Utils.GetShiftVectorInNormalSurface(Position - lookCenter, -dx, -dy);
63 | Position += dv;
64 | lookCenter += dv;
65 | validateCamera();
66 | }
67 |
68 | public void Orbit(double dx, double dy)
69 | {
70 | var nv1 = Utils.RotateVectorInParallel(Position - lookCenter, -dx / 50);
71 | var nv2 = Utils.RotateVectorInMeridian(nv1, -dy / 50);
72 | Position = lookCenter + nv2;
73 | validateCamera();
74 | }
75 |
76 | public void MoveFOV(double dx, double dy)
77 | {
78 | var f = (camPerspective.FieldOfView - dy - dx).MinMax(10, 160);
79 | camPerspective.FieldOfView = f;
80 | }
81 |
82 | public double FOV
83 | {
84 | get { return camPerspective.FieldOfView; }
85 | set { camPerspective.FieldOfView = value; }
86 | }
87 |
88 | Point3D lookCenter = new Point3D();
89 | public Point3D LookCenter
90 | {
91 | get { return lookCenter; }
92 | set
93 | {
94 | lookCenter = value;
95 | validateCamera();
96 | }
97 | }
98 |
99 | public Point3D Position
100 | {
101 | get { return camPerspective.Position; }
102 | set
103 | {
104 | camPerspective.Position = value;
105 | camOrthographic.Position = value;
106 | camOrthographic.Width = (Position - lookCenter).Length;
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/SolidModelBrowser/MainWindowSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Media;
4 | using System.Windows.Media.Media3D;
5 |
6 | namespace SolidModelBrowser
7 | {
8 | public partial class MainWindow : Window
9 | {
10 | void saveSettings()
11 | {
12 | if (this.WindowState == WindowState.Normal)
13 | {
14 | settings.LocationX = this.Left;
15 | settings.LocationY = this.Top;
16 | settings.Width = this.Width;
17 | settings.Height = this.Height;
18 | }
19 | settings.WindowState = this.WindowState;
20 | settings.FilePanelWidth = FilePanelColumn.Width.Value;
21 | settings.SelectedPath = filePanel.Path;
22 |
23 | settings.IsDiffuseEnabled = ButtonDiffuseMaterial.IsChecked.Value;
24 | settings.IsSpecularEnabled = ButtonSpecularMaterial.IsChecked.Value;
25 | settings.IsEmissiveEnabled = ButtonEmissiveMaterial.IsChecked.Value;
26 | settings.IsBackDiffuseEnabled = ButtonBacksideDiffuseMaterial.IsChecked.Value;
27 | settings.IsOrthoCameraEnabled = ButtonSelectCamera.IsChecked.Value;
28 | settings.IsFishEyeModeEnabled = ButtonFishEyeFOV.IsChecked.Value;
29 | settings.IsAxesEnabled = ButtonAxes.IsChecked.Value;
30 | settings.IsGroundEnabled = ButtonGround.IsChecked.Value;
31 |
32 | settings.Save();
33 | }
34 |
35 | void loadSettings()
36 | {
37 | settings.Load();
38 |
39 | Utils.SetWindowStyle(this, settings.UseSystemWindowStyle, StackPanelSystemButtons);
40 |
41 | if (settings.GetParseErrorsCount() > 0) Utils.MessageWindow("Settings loader warnings:\r\n\r\n" + String.Join("\r\n", settings.GetParseErrors().ToArray()));
42 | setAppTheme();
43 | this.Left = settings.LocationX;
44 | this.Top = settings.LocationY;
45 | this.Width = settings.Width;
46 | this.Height = settings.Height;
47 | this.WindowState = settings.WindowState;
48 | FilePanelColumn.Width = new GridLength(settings.FilePanelWidth);
49 | filePanel.SortFilesByExtensions = settings.SortFilesByExtensions;
50 | filePanel.Path = settings.SelectedPath;
51 | filePanel.Refresh();
52 |
53 | ButtonDiffuseMaterial.IsChecked = settings.IsDiffuseEnabled;
54 | ButtonSpecularMaterial.IsChecked = settings.IsSpecularEnabled;
55 | ButtonEmissiveMaterial.IsChecked = settings.IsEmissiveEnabled;
56 | ButtonBacksideDiffuseMaterial.IsChecked = settings.IsBackDiffuseEnabled;
57 | ButtonSelectCamera.IsChecked = settings.IsOrthoCameraEnabled;
58 | ButtonFishEyeFOV.IsChecked = settings.IsFishEyeModeEnabled;
59 | ButtonAxes.IsChecked = settings.IsAxesEnabled;
60 | ButtonGround.IsChecked = settings.IsGroundEnabled;
61 |
62 | settings.FOV = Utils.MinMax(settings.FOV, 5.0, 160.0);
63 | settings.FishEyeFOV = Utils.MinMax(settings.FishEyeFOV, 5.0, 160.0);
64 | settings.CameraInitialShift = Utils.MinMax(settings.CameraInitialShift, 0.5, 10.0);
65 | settings.ModelRotationAngle = Utils.MinMax(settings.ModelRotationAngle, 5.0, 180.0);
66 | settings.WireframeEdgeScale = Utils.MinMax(settings.WireframeEdgeScale, 0.001, 0.9);
67 |
68 | updateCameraModes();
69 | scene.IsAxesVisible = ButtonAxes.IsChecked.Value;
70 | scene.IsGroundVisible = ButtonGround.IsChecked.Value;
71 |
72 | scene.CreateMaterials(settings.DiffuseColor.Color, settings.SpecularColor.Color, settings.EmissiveColor.Color, settings.BackDiffuseColor.Color, settings.SpecularPower);
73 | applyMaterials();
74 |
75 | scene.RemoveAllLights();
76 | if (settings.IsAmbientLightEnabled) scene.CreateAmbientLight(settings.AmbientLightColor.Color);
77 | if (settings.IsDirectionalLight1Enabled) scene.CreateDirectionalLight(settings.DirectionalLight1Color.Color, settings.DirectionalLight1Dir);
78 | if (settings.IsDirectionalLight2Enabled) scene.CreateDirectionalLight(settings.DirectionalLight2Color.Color, settings.DirectionalLight2Dir);
79 | if (settings.IsDirectionalLight3Enabled) scene.CreateDirectionalLight(settings.DirectionalLight3Color.Color, settings.DirectionalLight3Dir);
80 |
81 | scene.CreateAxes(settings.AxesLength);
82 | scene.CreateGround(settings.GroundRectangle, settings.GroundCheckerSize, settings.GroundDiffuseColor.Color, settings.GroundEmissiveColor.Color);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Imports/ImportSTL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Windows.Media;
5 | using System.Windows.Media.Media3D;
6 |
7 | namespace SolidModelBrowser
8 | {
9 | internal class ImportSTL : Import
10 | {
11 | public ImportSTL()
12 | {
13 | Extensions = new List { "stl" };
14 | ExtensionsColors = new Dictionary { { "stl", new SolidColorBrush(Color.FromRgb(220, 220, 150)) } };
15 | ExtensionsColorsLight = new Dictionary { { "stl", new SolidColorBrush(Color.FromRgb(120, 120, 20)) } };
16 | }
17 |
18 | char[] separator = { ' ' };
19 |
20 | public bool isBinaryFormat(string filename)
21 | {
22 | using (var f = File.OpenRead(filename))
23 | {
24 | if (f.Length < 84) return false;
25 | f.Seek(80, SeekOrigin.Begin);
26 | int trianglesCount = f.ReadByte() + (f.ReadByte() << 8) + (f.ReadByte() << 16) + (f.ReadByte() << 24);
27 |
28 | return trianglesCount * 50 + 84 == f.Length;
29 | }
30 | }
31 |
32 | public override void Load(string filename)
33 | {
34 | if (isBinaryFormat(filename))
35 | {
36 | using (BinaryReader br = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
37 | {
38 | br.BaseStream.Seek(80, SeekOrigin.Begin);
39 | int q = br.ReadInt32();
40 |
41 | for (int c = 0; c < q; c++)
42 | {
43 | var n = new Vector3D(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
44 | Normals.Add(n);
45 | Normals.Add(n);
46 | Normals.Add(n);
47 | Positions.Add(new Point3D(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()));
48 | Positions.Add(new Point3D(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()));
49 | Positions.Add(new Point3D(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()));
50 | br.ReadUInt16();
51 | Indices.Add(c * 3);
52 | Indices.Add(c * 3 + 1);
53 | Indices.Add(c * 3 + 2);
54 |
55 | Progress = c * 100 / q;
56 | if (StopFlag) return;
57 | }
58 | }
59 | }
60 | else // ASCII format
61 | {
62 | var lines = File.ReadAllLines(filename);
63 | int q = lines.Length;
64 |
65 | if (q < 7 || !(lines[q - 1].Contains("endsolid") || lines[q - 2].Contains("endsolid"))) throw new Exception("Not valid STL format");
66 |
67 | int f = 0;
68 | for(int c = 0; c < q; c++)
69 | {
70 | var line = lines[c].ToLower();
71 | int i1 = line.IndexOf("facet normal");
72 | if (i1 >= 0)
73 | {
74 | var s = line.Substring(i1 + 12).Trim().Split(separator, StringSplitOptions.RemoveEmptyEntries);
75 | if (s.Length != 3) throw new Exception($"Bad format, line {c} '{line}' should have 3 values");
76 | var v = new Vector3D(double.Parse(s[0]), double.Parse(s[1]), double.Parse(s[2]));
77 | Normals.Add(v);
78 | Normals.Add(v);
79 | Normals.Add(v);
80 | Indices.Add(f * 3);
81 | Indices.Add(f * 3 + 1);
82 | Indices.Add(f * 3 + 2);
83 | f++;
84 | }
85 | else
86 | {
87 | int i2 = line.IndexOf("vertex");
88 | if (i2 >= 0)
89 | {
90 | var s = line.Substring(i2 + 6).Trim().Split(separator, StringSplitOptions.RemoveEmptyEntries);
91 | if (s.Length != 3) throw new Exception($"Bad format, line {c} '{line}' should have 3 values");
92 | var p = new Point3D(double.Parse(s[0]), double.Parse(s[1]), double.Parse(s[2]));
93 | Positions.Add(p);
94 | }
95 | }
96 |
97 | Progress = c * 100 / q;
98 | if (StopFlag) return;
99 | }
100 | }
101 |
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Imports/ImportGCODE.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Media3D;
7 |
8 | namespace SolidModelBrowser
9 | {
10 | internal class ImportGCODE : Import
11 | {
12 | public ImportGCODE()
13 | {
14 | Extensions = new List { "gcode" };
15 | ExtensionsColors = new Dictionary { { "gcode", new SolidColorBrush(Color.FromRgb(240, 240, 240)) } };
16 | ExtensionsColorsLight = new Dictionary { { "gcode", new SolidColorBrush(Color.FromRgb(40, 40, 40)) } };
17 | }
18 |
19 | struct GPosition
20 | {
21 | public double X, Y, Z, E, F;
22 | public bool fx, fy, fz, fe, ff;
23 | public void Add(GPosition p)
24 | {
25 | X += p.X; Y += p.Y; Z += p.Z; E += p.E; F += p.F;
26 | }
27 | }
28 |
29 | char[] separator = { ' ' };
30 |
31 | double parseValue(string value) => double.Parse(value.Substring(1));
32 |
33 | GPosition parseCmd(string[] parts) => parseCmd(parts, new GPosition());
34 | GPosition parseCmd(string[] parts, GPosition pos)
35 | {
36 | //GPosition pos = new GPosition();
37 | int q = parts.Length;
38 | for (int c = 1; c < q; c++)
39 | {
40 | if (parts[c].StartsWith(";")) c = q;
41 | else if (parts[c].StartsWith("X")) { pos.X = parseValue(parts[c]); pos.fx = true; }
42 | else if (parts[c].StartsWith("Y")) { pos.Y = parseValue(parts[c]); pos.fy = true; }
43 | else if (parts[c].StartsWith("Z")) { pos.Z = parseValue(parts[c]); pos.fz = true; }
44 | else if (parts[c].StartsWith("E")) { pos.E = parseValue(parts[c]); pos.fe = true; }
45 | else if (parts[c].StartsWith("F")) { pos.F = parseValue(parts[c]); pos.ff = true; }
46 | }
47 | return pos;
48 | }
49 |
50 |
51 | void addLine(GPosition src, GPosition dst)
52 | {
53 | Vector3D rz = new Vector3D(0.0, 0.0, settings.GCODELayerHeight);
54 | Vector3D a = new Vector3D(src.X, src.Y, src.Z);
55 | Vector3D b = new Vector3D(dst.X, dst.Y, dst.Z);
56 | var r = b - a;
57 | if (settings.GCODELineExtensions != 0.0)
58 | {
59 | var ext = r;
60 | ext.Normalize();
61 | ext *= settings.GCODELineExtensions;
62 | if ((ext * 2).Length < r.Length)
63 | {
64 | b += ext;
65 | a -= ext;
66 | }
67 | }
68 | var p = Vector3D.CrossProduct(r, rz);
69 | p.Normalize();
70 | p /= 2;
71 | p *= settings.GCODELineWidth; // line width
72 |
73 | int i = Positions.Count;
74 |
75 | Positions.Add((Point3D)(a + rz));
76 | Positions.Add((Point3D)(a + p));
77 | Positions.Add((Point3D)(b + p));
78 | Positions.Add((Point3D)(b + rz));
79 | Positions.Add((Point3D)(b - p));
80 | Positions.Add((Point3D)(a - p));
81 |
82 | Indices.Add(i);
83 | Indices.Add(i + 1);
84 | Indices.Add(i + 2);
85 |
86 | Indices.Add(i + 2);
87 | Indices.Add(i + 3);
88 | Indices.Add(i);
89 |
90 | Indices.Add(i);
91 | Indices.Add(i + 3);
92 | Indices.Add(i + 4);
93 |
94 | Indices.Add(i + 4);
95 | Indices.Add(i + 5);
96 | Indices.Add(i);
97 |
98 | //Indices.Add(i + 1);
99 | //Indices.Add(i + 5);
100 | //Indices.Add(i + 4);
101 |
102 | //Indices.Add(i + 4);
103 | //Indices.Add(i + 2);
104 | //Indices.Add(i + 1);
105 | }
106 |
107 | public override void Load(string filename)
108 | {
109 | bool absolutePos = true;
110 | GPosition pos = new GPosition();
111 |
112 | var strs = File.ReadAllLines(filename);
113 | int q = strs.Length;
114 | for (int c = 0; c < q; c++)
115 | {
116 | string s = strs[c].Trim().ToUpper();
117 | if (!string.IsNullOrWhiteSpace(s) && !s.StartsWith(";"))
118 | {
119 | var parts = s.Split(separator, StringSplitOptions.RemoveEmptyEntries);
120 | if (parts.Length > 0)
121 | {
122 | string cmd = parts[0];
123 | if (cmd == "G90") absolutePos = true;
124 | if (cmd == "G91") absolutePos = false;
125 | if (cmd == "G92") pos = parseCmd(parts, pos);
126 | if (cmd == "G0" || cmd == "G1")
127 | {
128 | var newpos = parseCmd(parts);
129 | if (absolutePos)
130 | {
131 | if (!newpos.fx) newpos.X = pos.X;
132 | if (!newpos.fy) newpos.Y = pos.Y;
133 | if (!newpos.fz) newpos.Z = pos.Z;
134 | if (!newpos.fe) newpos.E = pos.E;
135 | if (!newpos.ff) newpos.F = pos.F;
136 | }
137 | else newpos.Add(pos);
138 |
139 | if ((newpos.fx || newpos.fy) && newpos.E > pos.E) addLine(pos, newpos);
140 | pos = newpos;
141 | }
142 | }
143 | }
144 |
145 | Progress = c * 100 / q;
146 | if (StopFlag) return;
147 | }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/NumericBox.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Input;
5 |
6 | namespace SolidModelBrowser
7 | {
8 | public partial class NumericBox : UserControl
9 | {
10 | System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.InvariantCulture;
11 |
12 | bool mousePressed;
13 | Point mousePressedPos;
14 | double mousePressedInitialValue;
15 |
16 | public NumericBox()
17 | {
18 | InitializeComponent();
19 | textbox.LostFocus += (sender, e) => applyValue();
20 | textbox.KeyDown += textbox_KeyDown;
21 | //textbox.SelectionChanged += textbox_SelectionChanged;
22 |
23 | textbox.PreviewMouseDown += textbox_PreviewMouseDown;
24 | textbox.PreviewMouseMove += textbox_PreviewMouseMove;
25 | textbox.PreviewMouseUp += textbox_PreviewMouseUp;
26 |
27 | this.PreviewMouseDown += NumericBox_PreviewMouseDown;
28 | }
29 |
30 | void textbox_SelectionChanged(object sender, RoutedEventArgs e)
31 | {
32 | //textbox.SelectionLength = 0;
33 | //e.Handled = true;
34 | }
35 |
36 |
37 | void textbox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
38 | {
39 | if (e.ChangedButton == MouseButton.Left)
40 | {
41 | mousePressed = true;
42 | mousePressedPos = e.GetPosition(textbox);
43 | mousePressedInitialValue = val;
44 | }
45 | }
46 |
47 | void textbox_PreviewMouseUp(object sender, MouseButtonEventArgs e)
48 | {
49 | mousePressed = false;
50 | }
51 |
52 | void textbox_PreviewMouseMove(object sender, MouseEventArgs e)
53 | {
54 | double mult = 1.0;
55 | if (CtrlPressed()) mult = 10.0;
56 | if (ShiftPressed()) mult = 0.1;
57 | if (mousePressed)
58 | {
59 | Value = mousePressedInitialValue + (e.GetPosition(textbox) - mousePressedPos).X * mult;
60 | }
61 |
62 | }
63 |
64 |
65 | void NumericBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
66 | {
67 | applyValue();
68 | double posx = e.GetPosition(textbox).X;
69 | if (posx < 20) { val -= increment; updateValue(true); }
70 | if (posx > textbox.ActualWidth - 20) { val += increment; updateValue(true); }
71 | }
72 |
73 | void textbox_KeyDown(object sender, KeyEventArgs e)
74 | {
75 | if (!(e.Key >= Key.D0 && e.Key <= Key.D9) && (e.Key != Key.OemMinus) && (e.Key != Key.OemPeriod)) e.Handled = true;
76 | if (e.Key == Key.Enter) applyValue();
77 | }
78 |
79 | void applyValue()
80 | {
81 | {
82 | try
83 | {
84 | string s = textbox.Text;
85 | if (s == "" || s == "-") s = "0";
86 | double d = double.Parse(s, culture);
87 | val = d;
88 | updateValue(true);
89 | }
90 | catch
91 | {
92 | updateValue(true);
93 | }
94 | }
95 | }
96 |
97 | void updateValue(bool redraw)
98 | {
99 | double v = val;
100 | if (val < minvalue) val = minvalue;
101 | if (val > maxvalue) val = maxvalue;
102 |
103 | if (val != v) redraw = true;
104 |
105 | if (redraw)
106 | {
107 | int cp = textbox.CaretIndex;
108 | string s = val.ToString(culture);
109 |
110 | // validate fractional digits count
111 | int dotpos = s.LastIndexOf('.');
112 | if (dotpos >= 0)
113 | {
114 | int fracdq = s.Length - dotpos - 1;
115 | if (fracdq > rounddigits)
116 | {
117 | s = s.Substring(0, s.Length - (fracdq - rounddigits));
118 | }
119 | }
120 |
121 | textbox.Text = s;
122 |
123 | if (cp > 0) textbox.CaretIndex = cp;
124 |
125 | if (ValueChanged != null) ValueChanged(this, new EventArgs());
126 | }
127 | }
128 |
129 |
130 | public static bool CtrlPressed()
131 | {
132 | return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
133 | }
134 |
135 | public static bool ShiftPressed()
136 | {
137 | return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
138 | }
139 |
140 | public static bool AltPressed()
141 | {
142 | return Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt);
143 | }
144 |
145 |
146 | // --- Events ---
147 |
148 | public event EventHandler ValueChanged;
149 |
150 |
151 | // --- Properties ---
152 |
153 | double val = 0.0;
154 | public double Value
155 | {
156 | get { return val; }
157 | set
158 | {
159 | val = value;
160 | updateValue(true);
161 | }
162 | }
163 |
164 | double minvalue = double.MinValue;
165 | public double MinValue
166 | {
167 | get { return minvalue; }
168 | set
169 | {
170 | minvalue = value;
171 | updateValue(true);
172 | }
173 | }
174 |
175 | double maxvalue = double.MaxValue;
176 | public double MaxValue
177 | {
178 | get { return maxvalue; }
179 | set
180 | {
181 | maxvalue = value;
182 | updateValue(true);
183 | }
184 | }
185 |
186 | int rounddigits = 3;
187 | public int RoundDigits
188 | {
189 | get { return rounddigits; }
190 | set
191 | {
192 | rounddigits = value;
193 | updateValue(true);
194 | }
195 | }
196 |
197 | double increment = 1.0;
198 | public double Increment
199 | {
200 | get { return increment; }
201 | set { increment = value; }
202 | }
203 |
204 |
205 | //public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(NumericBox));
206 | //public string Title
207 | //{
208 | // get { return (string)GetValue(TitleProperty); }
209 | // set { SetValue(TitleProperty, value); }
210 | //}
211 |
212 | //public static readonly DependencyProperty TitleMarginProperty = DependencyProperty.Register("TitleMargin", typeof(Thickness), typeof(NumericBox));
213 | //public Thickness TitleMargin
214 | //{
215 | // get { return (Thickness)GetValue(TitleMarginProperty); }
216 | // set { SetValue(TitleMarginProperty, value); }
217 | //}
218 |
219 |
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Styles/Colors.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
62 |
63 |
64 |
65 |
66 |
67 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
123 |
124 |
125 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/PropertyPanel.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Media3D;
7 |
8 | namespace SolidModelBrowser
9 | {
10 | public partial class PropertyPanel : UserControl
11 | {
12 | public PropertyPanel()
13 | {
14 | InitializeComponent();
15 | }
16 |
17 | Thickness vmargin = new Thickness(0, 8, 0, 8);
18 | Thickness hmargin = new Thickness(8, 0, 8, 0);
19 | Thickness categoryMargin = new Thickness(0, 24, 0, 8);
20 | Thickness categoryPadding = new Thickness(8, 4, 0, 4);
21 | string lastCategory = "";
22 |
23 | void addProperty(PropertyInfoAttribute p)
24 | {
25 | var type = p.Property.PropertyType;
26 |
27 | if (p.Category != lastCategory)
28 | {
29 | Border b = new Border() { Margin = categoryMargin, Padding = categoryPadding, CornerRadius = new CornerRadius(4) };
30 | b.SetResourceReference(Border.BackgroundProperty, "PanelBack");
31 | TextBlock tb = new TextBlock() { Text = p.Category, FontWeight = FontWeights.Bold };
32 | b.Child = tb;
33 | panel.Children.Add(b);
34 |
35 | lastCategory = p.Category;
36 | }
37 |
38 | if (type == typeof(string))
39 | {
40 | TextBox tb = new TextBox() { Text = (string)p.Value };
41 | tb.TextChanged += valueChanged;
42 | tb.Tag = p;
43 |
44 | StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, Margin = vmargin };
45 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, ToolTip = p.Description });
46 | sp.Children.Add(tb);
47 | panel.Children.Add(sp);
48 | }
49 | if (type == typeof(bool))
50 | {
51 | CheckBox cb = new CheckBox() { IsChecked = (bool)p.Value };
52 | cb.Checked += valueChanged;
53 | cb.Unchecked += valueChanged;
54 | cb.Tag = p;
55 |
56 | StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, Margin = vmargin };
57 | sp.Children.Add(cb);
58 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, ToolTip = p.Description });
59 | panel.Children.Add(sp);
60 | }
61 | if (type == typeof(int))
62 | {
63 | NumericBox nb = new NumericBox() { Value = (int)p.Value, Width = 100, RoundDigits = 0, MinValue = p.Min, MaxValue = p.Max, Increment = p.Increment };
64 | nb.ValueChanged += valueChanged;
65 | nb.Tag = p;
66 |
67 | StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, Margin = vmargin };
68 | sp.Children.Add(nb);
69 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, Margin = hmargin, ToolTip = p.Description });
70 | panel.Children.Add(sp);
71 | }
72 | if (type == typeof(double))
73 | {
74 | NumericBox nb = new NumericBox() { Value = (double)p.Value, Width = 100, RoundDigits = 3, MinValue = p.Min, MaxValue = p.Max, Increment = p.Increment };
75 | nb.ValueChanged += valueChanged;
76 | nb.Tag = p;
77 |
78 | StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, Margin = vmargin };
79 | sp.Children.Add(nb);
80 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, Margin = hmargin, ToolTip = p.Description });
81 | panel.Children.Add(sp);
82 | }
83 | if (type == typeof(SColor))
84 | {
85 | ColorInput cs = new ColorInput() { ColorValue = ((SColor)p.Value).Color };
86 | cs.ValueChanged += valueChanged;
87 | cs.Tag = p;
88 |
89 | StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, Margin = vmargin };
90 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, ToolTip = p.Description });
91 | sp.Children.Add(cs);
92 | panel.Children.Add(sp);
93 | }
94 | if (type == typeof(Rect))
95 | {
96 | DoubleValuesInput dvi = new DoubleValuesInput(new string[] { "X", "Y", "W", "H" }, -10000.0, 10000, 0);
97 | var r = (Rect)p.Value;
98 | dvi[0] = r.X; dvi[1] = r.Y; dvi[2] = r.Width; dvi[3] = r.Height;
99 | dvi.SetMinValue(2, 10.0);
100 | dvi.SetMinValue(3, 10.0);
101 | dvi.ValueChanged += valueChanged;
102 | dvi.Tag = p;
103 |
104 | StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, Margin = vmargin };
105 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, ToolTip = p.Description });
106 | sp.Children.Add(dvi);
107 | panel.Children.Add(sp);
108 | }
109 | if (type == typeof(Vector3D))
110 | {
111 | DoubleValuesInput dvi = new DoubleValuesInput(new string[] { "X", "Y", "Z" }, -10000.0, 10000, 0);
112 | var r = (Vector3D)p.Value;
113 | dvi[0] = r.X; dvi[1] = r.Y; dvi[2] = r.Z;
114 | dvi.ValueChanged += valueChanged;
115 | dvi.Tag = p;
116 |
117 | StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, Margin = vmargin };
118 | sp.Children.Add(new TextBlock() { Text = p.MenuLabel, ToolTip = p.Description });
119 | sp.Children.Add(dvi);
120 | panel.Children.Add(sp);
121 | }
122 | }
123 |
124 | public void SetObject(object o)
125 | {
126 | try
127 | {
128 | panel.Children.Clear();
129 | var props = PropertyInfoAttribute.GetPropertiesInfoList(o, false).Where(p => p.MenuLabel != null).OrderBy(p => p.Category + p.SortPrefix + p.MenuLabel);
130 | foreach (var p in props) addProperty(p);
131 | }
132 | catch (Exception exc) { Utils.MessageWindow($"Settings enumeration failed with message\r\n{exc.Message}"); }
133 | }
134 |
135 | void valueChanged(object Sender, EventArgs e)
136 | {
137 | FrameworkElement fwe = Sender as FrameworkElement;
138 | if (fwe == null) return;
139 | var p = fwe.Tag as PropertyInfoAttribute;
140 | if (p == null) return;
141 | Type t = p.Property.PropertyType;
142 |
143 | try
144 | {
145 | if (t == typeof(string)) p.Property.SetValue(p.Object, (Sender as TextBox).Text);
146 | if (t == typeof(bool)) p.Property.SetValue(p.Object, (Sender as CheckBox).IsChecked);
147 | if (t == typeof(int)) p.Property.SetValue(p.Object, (int)((Sender as NumericBox).Value));
148 | if (t == typeof(double)) p.Property.SetValue(p.Object, (Sender as NumericBox).Value);
149 | if (t == typeof(SColor)) p.Property.SetValue(p.Object, new SColor((Sender as ColorInput).ColorValue));
150 | if (t == typeof(Rect)) p.Property.SetValue(p.Object, new Rect((Sender as DoubleValuesInput)[0], (Sender as DoubleValuesInput)[1], (Sender as DoubleValuesInput)[2], (Sender as DoubleValuesInput)[3]));
151 | if (t == typeof(Vector3D)) p.Property.SetValue(p.Object, new Vector3D((Sender as DoubleValuesInput)[0], (Sender as DoubleValuesInput)[1], (Sender as DoubleValuesInput)[2]));
152 | }
153 | catch (Exception exc) { Utils.MessageWindow($"Setting '{p.Name}' value change failed with message\r\n{exc.Message}"); }
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | *_nogit/
7 | *-nogit/
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | # Mono auto generated files
20 | mono_crash.*
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | [Ww][Ii][Nn]32/
30 | [Aa][Rr][Mm]/
31 | [Aa][Rr][Mm]64/
32 | bld/
33 | [Bb]in/
34 | [Oo]bj/
35 | [Oo]ut/
36 | [Ll]og/
37 | [Ll]ogs/
38 |
39 | # Visual Studio 2015/2017 cache/options directory
40 | .vs/
41 | # Uncomment if you have tasks that create the project's static files in wwwroot
42 | #wwwroot/
43 |
44 | # Visual Studio 2017 auto generated files
45 | Generated\ Files/
46 |
47 | # MSTest test Results
48 | [Tt]est[Rr]esult*/
49 | [Bb]uild[Ll]og.*
50 |
51 | # NUnit
52 | *.VisualState.xml
53 | TestResult.xml
54 | nunit-*.xml
55 |
56 | # Build Results of an ATL Project
57 | [Dd]ebugPS/
58 | [Rr]eleasePS/
59 | dlldata.c
60 |
61 | # Benchmark Results
62 | BenchmarkDotNet.Artifacts/
63 |
64 | # .NET Core
65 | project.lock.json
66 | project.fragment.lock.json
67 | artifacts/
68 |
69 | # ASP.NET Scaffolding
70 | ScaffoldingReadMe.txt
71 |
72 | # StyleCop
73 | StyleCopReport.xml
74 |
75 | # Files built by Visual Studio
76 | *_i.c
77 | *_p.c
78 | *_h.h
79 | *.ilk
80 | *.meta
81 | *.obj
82 | *.iobj
83 | *.pch
84 | *.pdb
85 | *.ipdb
86 | *.pgc
87 | *.pgd
88 | *.rsp
89 | *.sbr
90 | *.tlb
91 | *.tli
92 | *.tlh
93 | *.tmp
94 | *.tmp_proj
95 | *_wpftmp.csproj
96 | *.log
97 | *.vspscc
98 | *.vssscc
99 | .builds
100 | *.pidb
101 | *.svclog
102 | *.scc
103 |
104 | # Chutzpah Test files
105 | _Chutzpah*
106 |
107 | # Visual C++ cache files
108 | ipch/
109 | *.aps
110 | *.ncb
111 | *.opendb
112 | *.opensdf
113 | *.sdf
114 | *.cachefile
115 | *.VC.db
116 | *.VC.VC.opendb
117 |
118 | # Visual Studio profiler
119 | *.psess
120 | *.vsp
121 | *.vspx
122 | *.sap
123 |
124 | # Visual Studio Trace Files
125 | *.e2e
126 |
127 | # TFS 2012 Local Workspace
128 | $tf/
129 |
130 | # Guidance Automation Toolkit
131 | *.gpState
132 |
133 | # ReSharper is a .NET coding add-in
134 | _ReSharper*/
135 | *.[Rr]e[Ss]harper
136 | *.DotSettings.user
137 |
138 | # TeamCity is a build add-in
139 | _TeamCity*
140 |
141 | # DotCover is a Code Coverage Tool
142 | *.dotCover
143 |
144 | # AxoCover is a Code Coverage Tool
145 | .axoCover/*
146 | !.axoCover/settings.json
147 |
148 | # Coverlet is a free, cross platform Code Coverage Tool
149 | coverage*.json
150 | coverage*.xml
151 | coverage*.info
152 |
153 | # Visual Studio code coverage results
154 | *.coverage
155 | *.coveragexml
156 |
157 | # NCrunch
158 | _NCrunch_*
159 | .*crunch*.local.xml
160 | nCrunchTemp_*
161 |
162 | # MightyMoose
163 | *.mm.*
164 | AutoTest.Net/
165 |
166 | # Web workbench (sass)
167 | .sass-cache/
168 |
169 | # Installshield output folder
170 | [Ee]xpress/
171 |
172 | # DocProject is a documentation generator add-in
173 | DocProject/buildhelp/
174 | DocProject/Help/*.HxT
175 | DocProject/Help/*.HxC
176 | DocProject/Help/*.hhc
177 | DocProject/Help/*.hhk
178 | DocProject/Help/*.hhp
179 | DocProject/Help/Html2
180 | DocProject/Help/html
181 |
182 | # Click-Once directory
183 | publish/
184 |
185 | # Publish Web Output
186 | *.[Pp]ublish.xml
187 | *.azurePubxml
188 | # Note: Comment the next line if you want to checkin your web deploy settings,
189 | # but database connection strings (with potential passwords) will be unencrypted
190 | *.pubxml
191 | *.publishproj
192 |
193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
194 | # checkin your Azure Web App publish settings, but sensitive information contained
195 | # in these scripts will be unencrypted
196 | PublishScripts/
197 |
198 | # NuGet Packages
199 | *.nupkg
200 | # NuGet Symbol Packages
201 | *.snupkg
202 | # The packages folder can be ignored because of Package Restore
203 | **/[Pp]ackages/*
204 | # except build/, which is used as an MSBuild target.
205 | !**/[Pp]ackages/build/
206 | # Uncomment if necessary however generally it will be regenerated when needed
207 | #!**/[Pp]ackages/repositories.config
208 | # NuGet v3's project.json files produces more ignorable files
209 | *.nuget.props
210 | *.nuget.targets
211 |
212 | # Microsoft Azure Build Output
213 | csx/
214 | *.build.csdef
215 |
216 | # Microsoft Azure Emulator
217 | ecf/
218 | rcf/
219 |
220 | # Windows Store app package directories and files
221 | AppPackages/
222 | BundleArtifacts/
223 | Package.StoreAssociation.xml
224 | _pkginfo.txt
225 | *.appx
226 | *.appxbundle
227 | *.appxupload
228 |
229 | # Visual Studio cache files
230 | # files ending in .cache can be ignored
231 | *.[Cc]ache
232 | # but keep track of directories ending in .cache
233 | !?*.[Cc]ache/
234 |
235 | # Others
236 | ClientBin/
237 | ~$*
238 | *~
239 | *.dbmdl
240 | *.dbproj.schemaview
241 | *.jfm
242 | *.pfx
243 | *.publishsettings
244 | orleans.codegen.cs
245 |
246 | # Including strong name files can present a security risk
247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
248 | #*.snk
249 |
250 | # Since there are multiple workflows, uncomment next line to ignore bower_components
251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
252 | #bower_components/
253 |
254 | # RIA/Silverlight projects
255 | Generated_Code/
256 |
257 | # Backup & report files from converting an old project file
258 | # to a newer Visual Studio version. Backup files are not needed,
259 | # because we have git ;-)
260 | _UpgradeReport_Files/
261 | Backup*/
262 | UpgradeLog*.XML
263 | UpgradeLog*.htm
264 | ServiceFabricBackup/
265 | *.rptproj.bak
266 |
267 | # SQL Server files
268 | *.mdf
269 | *.ldf
270 | *.ndf
271 |
272 | # Business Intelligence projects
273 | *.rdl.data
274 | *.bim.layout
275 | *.bim_*.settings
276 | *.rptproj.rsuser
277 | *- [Bb]ackup.rdl
278 | *- [Bb]ackup ([0-9]).rdl
279 | *- [Bb]ackup ([0-9][0-9]).rdl
280 |
281 | # Microsoft Fakes
282 | FakesAssemblies/
283 |
284 | # GhostDoc plugin setting file
285 | *.GhostDoc.xml
286 |
287 | # Node.js Tools for Visual Studio
288 | .ntvs_analysis.dat
289 | node_modules/
290 |
291 | # Visual Studio 6 build log
292 | *.plg
293 |
294 | # Visual Studio 6 workspace options file
295 | *.opt
296 |
297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
298 | *.vbw
299 |
300 | # Visual Studio LightSwitch build output
301 | **/*.HTMLClient/GeneratedArtifacts
302 | **/*.DesktopClient/GeneratedArtifacts
303 | **/*.DesktopClient/ModelManifest.xml
304 | **/*.Server/GeneratedArtifacts
305 | **/*.Server/ModelManifest.xml
306 | _Pvt_Extensions
307 |
308 | # Paket dependency manager
309 | .paket/paket.exe
310 | paket-files/
311 |
312 | # FAKE - F# Make
313 | .fake/
314 |
315 | # CodeRush personal settings
316 | .cr/personal
317 |
318 | # Python Tools for Visual Studio (PTVS)
319 | __pycache__/
320 | *.pyc
321 |
322 | # Cake - Uncomment if you are using it
323 | # tools/**
324 | # !tools/packages.config
325 |
326 | # Tabs Studio
327 | *.tss
328 |
329 | # Telerik's JustMock configuration file
330 | *.jmconfig
331 |
332 | # BizTalk build output
333 | *.btp.cs
334 | *.btm.cs
335 | *.odx.cs
336 | *.xsd.cs
337 |
338 | # OpenCover UI analysis results
339 | OpenCover/
340 |
341 | # Azure Stream Analytics local run output
342 | ASALocalRun/
343 |
344 | # MSBuild Binary and Structured Log
345 | *.binlog
346 |
347 | # NVidia Nsight GPU debugger configuration file
348 | *.nvuser
349 |
350 | # MFractors (Xamarin productivity tool) working folder
351 | .mfractor/
352 |
353 | # Local History for Visual Studio
354 | .localhistory/
355 |
356 | # BeatPulse healthcheck temp database
357 | healthchecksdb
358 |
359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
360 | MigrationBackup/
361 |
362 | # Ionide (cross platform F# VS Code tools) working folder
363 | .ionide/
364 |
365 | # Fody - auto-generated XML schema
366 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/SolidModelBrowser/Styles/ColorsLight.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
134 |
135 |
136 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/SolidModelBrowser/SolidModelBrowser.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {CE656CE1-8F90-4831-B3DA-FAFFD375DDE6}
8 | WinExe
9 | SolidModelBrowser
10 | SolidModelBrowser
11 | v4.8
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 |
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 | false
28 |
29 |
30 | AnyCPU
31 | pdbonly
32 | true
33 | bin\Release\
34 | TRACE
35 | prompt
36 | 4
37 | false
38 |
39 |
40 | icon3a.ico
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 4.0
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | MSBuild:Compile
64 | Designer
65 |
66 |
67 | ColorInput.xaml
68 |
69 |
70 | DoubleValuesInput.xaml
71 |
72 |
73 | FileNavigationPanel.xaml
74 |
75 |
76 | NumericBox.xaml
77 |
78 |
79 | Scene.xaml
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | PropertyPanel.xaml
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | SettingsWindow.xaml
106 |
107 |
108 | Designer
109 | MSBuild:Compile
110 |
111 |
112 | Designer
113 | MSBuild:Compile
114 |
115 |
116 | Designer
117 | MSBuild:Compile
118 |
119 |
120 | Designer
121 | MSBuild:Compile
122 |
123 |
124 | Designer
125 | MSBuild:Compile
126 |
127 |
128 | MSBuild:Compile
129 | Designer
130 |
131 |
132 | MSBuild:Compile
133 | Designer
134 |
135 |
136 | MSBuild:Compile
137 | Designer
138 |
139 |
140 | MSBuild:Compile
141 | Designer
142 |
143 |
144 | App.xaml
145 | Code
146 |
147 |
148 | MainWindow.xaml
149 | Code
150 |
151 |
152 | MSBuild:Compile
153 | Designer
154 |
155 |
156 | Designer
157 | MSBuild:Compile
158 |
159 |
160 |
161 |
162 | Code
163 |
164 |
165 | True
166 | True
167 | Resources.resx
168 |
169 |
170 | True
171 | Settings.settings
172 | True
173 |
174 |
175 | ResXFileCodeGenerator
176 | Resources.Designer.cs
177 |
178 |
179 |
180 | SettingsSingleFileGenerator
181 | Settings.Designer.cs
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/Scene.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Media3D;
7 |
8 | namespace SolidModelBrowser
9 | {
10 | public partial class Scene : UserControl
11 | {
12 | GeometryModel3D geometry = new GeometryModel3D();
13 | MeshGeometry3D mesh = new MeshGeometry3D();
14 | UCamera camera;
15 |
16 | public Scene()
17 | {
18 | InitializeComponent();
19 | geometry.Geometry = mesh;
20 | geometry.Material = materials;
21 | modelgroup.Children.Add(geometry);
22 | modelgroup.Children.Add(lights);
23 |
24 | camera = new UCamera(viewport3D);
25 |
26 |
27 | }
28 |
29 |
30 | // mouse controllers
31 |
32 | Point mLastPos;
33 |
34 | private void Viewport_MouseWheel(object sender, MouseWheelEventArgs e)
35 | {
36 | camera.Scale(e.Delta > 0 ? 0.9 : 1.1);
37 | }
38 |
39 | private void Viewport_MouseMove(object sender, MouseEventArgs e)
40 | {
41 | if (e.LeftButton == MouseButtonState.Pressed || e.RightButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
42 | {
43 | Point pos = Mouse.GetPosition(viewport);
44 | Point actualPos = new Point(pos.X - viewport.ActualWidth / 2, viewport.ActualHeight / 2 - pos.Y);
45 | double dx = actualPos.X - mLastPos.X, dy = actualPos.Y - mLastPos.Y;
46 |
47 | if (e.LeftButton == MouseButtonState.Pressed) camera.Move(dx, dy);
48 | if (e.RightButton == MouseButtonState.Pressed) camera.Orbit(dx, dy);
49 |
50 | mLastPos = actualPos;
51 | }
52 | }
53 |
54 | private void Viewport_MouseDown(object sender, MouseButtonEventArgs e)
55 | {
56 | if (e.ChangedButton == MouseButton.Left && Keyboard.IsKeyDown(Key.LeftShift)) { ShiftAndDrag?.Invoke(this, EventArgs.Empty); return; }
57 |
58 | Point pos = Mouse.GetPosition(viewport);
59 | mLastPos = new Point(pos.X - viewport.ActualWidth / 2, viewport.ActualHeight / 2 - pos.Y);
60 | ((IInputElement)sender).CaptureMouse();
61 | }
62 |
63 | private void Viewport_MouseUp(object sender, MouseButtonEventArgs e)
64 | {
65 | ((IInputElement)sender).ReleaseMouseCapture();
66 | }
67 |
68 | // materials
69 |
70 | DiffuseMaterial matDiffuse;
71 | SpecularMaterial matSpecular;
72 | EmissiveMaterial matEmissive;
73 | DiffuseMaterial matBackDiffuse;
74 |
75 | public void CreateMaterials(Color diffuse, Color specular, Color emissive, Color backDiffuse, double specularPower)
76 | {
77 | matDiffuse = new DiffuseMaterial(new SolidColorBrush(diffuse));
78 | //matDiffuse.AmbientColor = Color.FromArgb(255, 0, 255, 0);
79 | matSpecular = new SpecularMaterial(new SolidColorBrush(specular), specularPower);
80 | matEmissive = new EmissiveMaterial(new SolidColorBrush(emissive));
81 | matBackDiffuse = new DiffuseMaterial(new SolidColorBrush(backDiffuse));
82 | }
83 |
84 | MaterialGroup materials = new MaterialGroup();
85 |
86 | public void ApplyMaterials(bool diffuse, bool specular, bool emissive, bool backDiffuse)
87 | {
88 | materials.Children.Clear();
89 | if (diffuse) materials.Children.Add(matDiffuse);
90 | if (specular) materials.Children.Add(matSpecular);
91 | if (emissive) materials.Children.Add(matEmissive);
92 | geometry.Material = materials;
93 |
94 | geometry.BackMaterial = backDiffuse ? matBackDiffuse : null;
95 | }
96 |
97 | // lights
98 |
99 | Model3DGroup lights = new Model3DGroup();
100 |
101 | public void RemoveAllLights() => lights.Children.Clear();
102 | public void CreateAmbientLight(Color c) => lights.Children.Add(new AmbientLight(c));
103 | public void CreateDirectionalLight(Color c, Vector3D dir) => lights.Children.Add(new DirectionalLight(c, dir));
104 |
105 | // axes
106 |
107 | enum Axis { X, Y, Z }
108 | GeometryModel3D createAxis(Axis axis, double len, Color color)
109 | {
110 | Point3DCollection p = null;
111 | if (axis == Axis.X) p = new Point3DCollection() { new Point3D(0, 0, 0), new Point3D(len, 0, 0), new Point3D(0, 0, 1), new Point3D(len, 0, 1) };
112 | if (axis == Axis.Y) p = new Point3DCollection() { new Point3D(0, 0, 0), new Point3D(0, len, 0), new Point3D(0, 0, 1), new Point3D(0, len, 1) };
113 | if (axis == Axis.Z) p = new Point3DCollection() { new Point3D(0, 0, 1), new Point3D(0, 0, len), new Point3D(1, 0, 1), new Point3D(1, 0, len) };
114 | var i = new Int32Collection() { 0, 1, 2, 1, 3, 2 };
115 | var mesh = new MeshGeometry3D() { Positions = p, TriangleIndices = i };
116 | var mat = new DiffuseMaterial(new SolidColorBrush(color));
117 | return new GeometryModel3D() { Geometry = mesh, Material = mat, BackMaterial = mat };
118 | }
119 |
120 | Model3DGroup axes = new Model3DGroup();
121 | public void CreateAxes(double len)
122 | {
123 | axes.Children.Clear();
124 | axes.Children.Add(createAxis(Axis.X, len, Color.FromArgb(160, 255, 0, 0)));
125 | axes.Children.Add(createAxis(Axis.Y, len, Color.FromArgb(160, 0, 255, 0)));
126 | axes.Children.Add(createAxis(Axis.Z, len, Color.FromArgb(160, 0, 0, 255)));
127 | }
128 |
129 | // ground
130 |
131 | GeometryModel3D ground = new GeometryModel3D();
132 | public void CreateGround(Rect r, double checkerSize, Color diffuseColor, Color emissiveColor)
133 | {
134 | if (r.Width == 0 || r.Height == 0 || checkerSize == 0) return;
135 |
136 | var p = new Point3DCollection() { new Point3D(r.Left, r.Top, 0), new Point3D(r.Right, r.Top, 0), new Point3D(r.Left, r.Bottom, 0), new Point3D(r.Right, r.Bottom, 0) };
137 | var texcoord = new PointCollection() { new Point(0, 0), new Point(1, 0), new Point(0, 1), new Point(1, 1) };
138 | var i = new Int32Collection() { 0, 1, 2, 1, 3, 2 };
139 | var mesh = new MeshGeometry3D { Positions = p, TextureCoordinates = texcoord, TriangleIndices = i };
140 |
141 | var geomcoll = new GeometryCollection() { new RectangleGeometry(new Rect(0, 0, 50, 50)), new RectangleGeometry(new Rect(50, 50, 50, 50)) };
142 | var coloredDrawing = new GeometryDrawing() { Brush = new SolidColorBrush(diffuseColor), Geometry = new GeometryGroup() { Children = geomcoll } };
143 | var drawingBrush = new DrawingBrush { Viewport = new Rect(0, 0, 2 * checkerSize / r.Width, 2 * checkerSize / r.Height), TileMode = TileMode.Tile, Drawing = coloredDrawing };
144 | var diffuseMaterial = new DiffuseMaterial() { Brush = drawingBrush };
145 | var specularMaterial = new SpecularMaterial() { Brush = new SolidColorBrush(Color.FromArgb(0xFF, 0xD0, 0xD0, 0xD0)) };
146 | var emissiveMaterial = new EmissiveMaterial() { Brush = new SolidColorBrush(emissiveColor) };
147 | var materialGroup = new MaterialGroup();
148 | materialGroup.Children.Add(diffuseMaterial);
149 | materialGroup.Children.Add(specularMaterial);
150 | materialGroup.Children.Add(emissiveMaterial);
151 | var backMaterial = new DiffuseMaterial() { Brush = new SolidColorBrush(Color.FromArgb(0x40, 0xFF, 0xA0, 0xA0)) };
152 |
153 | ground.Geometry = mesh;
154 | ground.Material = materialGroup;
155 | ground.BackMaterial = backMaterial;
156 | }
157 |
158 |
159 | // events and props
160 |
161 | public event EventHandler ShiftAndDrag;
162 |
163 | public MeshGeometry3D Mesh => mesh;
164 | public UCamera Camera => camera;
165 | public Viewport3D Viewport3D => viewport3D;
166 |
167 | bool isAxesVisible;
168 | public bool IsAxesVisible
169 | {
170 | get { return isAxesVisible; }
171 | set
172 | {
173 | isAxesVisible = value;
174 | if (modelgroup.Children.Contains(axes)) modelgroup.Children.Remove(axes);
175 | if (isAxesVisible) modelgroup.Children.Add(axes);
176 | }
177 | }
178 |
179 | bool isGroundVisible;
180 | public bool IsGroundVisible
181 | {
182 | get { return isGroundVisible; }
183 | set
184 | {
185 | isGroundVisible = value;
186 | if (modelgroup.Children.Contains(ground)) modelgroup.Children.Remove(ground);
187 | if (isGroundVisible) modelgroup.Children.Add(ground);
188 | }
189 | }
190 |
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Imports/Import3MF.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 | using System.Windows.Media;
9 | using System.Windows.Media.Media3D;
10 |
11 | namespace SolidModelBrowser
12 | {
13 | internal class Import3MF : Import
14 | {
15 | public Import3MF()
16 | {
17 | Extensions = new List { "3mf" };
18 | ExtensionsColors = new Dictionary { { "3mf", new SolidColorBrush(Color.FromRgb(255, 150, 150)) } };
19 | ExtensionsColorsLight = new Dictionary { { "3mf", new SolidColorBrush(Color.FromRgb(200, 50, 50)) } };
20 | InitialXRotationNeeded = false;
21 | }
22 |
23 | string readZipEntry(ZipArchive zfile, string entryName)
24 | {
25 | var zentry = zfile.Entries.First(e => e.FullName.ToLower() == entryName);
26 | using (var sr = new StreamReader(zentry.Open(), Encoding.UTF8))
27 | return sr.ReadToEnd();
28 | }
29 |
30 | string getTag(string s, string tag, bool shorttail, ref int pos)
31 | {
32 | string tail = shorttail ? "/>" : $"{tag}>";
33 | int ps = s.IndexOf("<" + tag, pos, StringComparison.Ordinal);
34 | if (ps < 0) return null;
35 | int pe = s.IndexOf(tail, ps, StringComparison.Ordinal);
36 | if (pe < 0) return null;
37 | pos = pe + tail.Length;
38 | return s.Substring(ps, pe - ps + tail.Length);
39 | }
40 |
41 | string getTag(string s, string tag, bool shorttail)
42 | {
43 | int pos = 0;
44 | return getTag(s, tag, shorttail, ref pos);
45 | }
46 |
47 | List getTags(string s, string tag, bool shorttail)
48 | {
49 | string tail = shorttail ? "/>" : $"{tag}>";
50 | int pos = 0;
51 | var tags = new List();
52 | string t;
53 | while((t = getTag(s, tag, shorttail, ref pos)) != null) tags.Add(t);
54 | return tags;
55 | }
56 |
57 | string getParam(string s, string param)
58 | {
59 | return Regex.Match(s, $"{param}=\"(.*?)\"", RegexOptions.IgnoreCase).Groups[1].Value;
60 | }
61 |
62 | Matrix3D buildMatrix(string s)
63 | {
64 | if (string.IsNullOrWhiteSpace(s)) return new Matrix3D();
65 | var p = s.Split(' ');
66 | var m = new Matrix3D();
67 | m.M11 = double.Parse(p[0]);
68 | m.M12 = double.Parse(p[1]);
69 | m.M13 = double.Parse(p[2]);
70 | m.M21 = double.Parse(p[3]);
71 | m.M22 = double.Parse(p[4]);
72 | m.M23 = double.Parse(p[5]);
73 | m.M31 = double.Parse(p[6]);
74 | m.M32 = double.Parse(p[7]);
75 | m.M33 = double.Parse(p[8]);
76 | m.OffsetX = double.Parse(p[9]);
77 | m.OffsetY = double.Parse(p[10]);
78 | m.OffsetZ = double.Parse(p[11]);
79 | m.M44 = 1D;
80 | return m;
81 | }
82 |
83 | Vector3D? getVertex(string s, ref int pos)
84 | {
85 | Vector3D v = new Vector3D();
86 | int p1 = s.IndexOf("> componentCache = new Dictionary>();
190 |
191 | void loadComponent(string comp, string model, ZipArchive zfile, Matrix3D basemtr)
192 | {
193 | string id = getParam(comp, "objectid");
194 | string path = getParam(comp, "p:path").TrimStart('/').ToLower();
195 | string tr = getParam(comp, "transform");
196 | Matrix3D mtr = buildMatrix(tr) * basemtr;
197 |
198 | string resources;
199 | List objects;
200 | if (!string.IsNullOrWhiteSpace(path))
201 | {
202 | if (componentCache.ContainsKey(path)) objects = componentCache[path];
203 | else
204 | {
205 | model = readZipEntry(zfile, path);
206 | resources = getTag(model, "resources", false);
207 | objects = getTags(resources, "object", false);
208 | componentCache.Add(path, objects);
209 | }
210 | }
211 | else // if no path specified use local model
212 | {
213 | resources = getTag(model, "resources", false);
214 | objects = getTags(resources, "object", false);
215 | }
216 |
217 | string obj = objects.Where(o => o.Contains($" id=\"{id}\"")).FirstOrDefault();
218 |
219 | appendProgress(obj);
220 |
221 | parseMesh(obj, mtr);
222 | }
223 |
224 | public override void Load(string filename)
225 | {
226 | basepos = 0;
227 | string model;
228 | using (var zfile = ZipFile.OpenRead(filename))
229 | {
230 | model = readZipEntry(zfile, "3d/3dmodel.model");
231 |
232 | string resources = getTag(model, "resources", false);
233 | var objects = getTags(resources, "object", false);
234 |
235 | string build = getTag(model, "build", false);
236 | var items = getTags(build, "item", true);
237 |
238 | initProgress(model);
239 |
240 | foreach (string item in items)
241 | {
242 | string id = getParam(item, "objectid");
243 | string tr = getParam(item, "transform");
244 | Matrix3D mtr = buildMatrix(tr);
245 |
246 | string obj = objects.Where(o => o.Contains($" id=\"{id}\"")).FirstOrDefault();
247 |
248 | //if (obj.Contains("")) ExceptionMessage = "3MF mesh with external components is not supported yet";
249 | string components = getTag(obj, "components", false);
250 | if (components != null)
251 | {
252 | var comps = getTags(components, "component", true);
253 | foreach (var comp in comps) loadComponent(comp, model, zfile, mtr);
254 | }
255 |
256 | parseMesh(obj, mtr);
257 | if (StopFlag) return;
258 | }
259 | }
260 | }
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Imports/ImportPLY.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Media3D;
7 |
8 | namespace SolidModelBrowser
9 | {
10 | internal class ImportPLY : Import
11 | {
12 | public ImportPLY()
13 | {
14 | Extensions = new List { "ply" };
15 | ExtensionsColors = new Dictionary { { "ply", new SolidColorBrush(Color.FromRgb(220, 150, 220)) } };
16 | ExtensionsColorsLight = new Dictionary { { "ply", new SolidColorBrush(Color.FromRgb(120, 20, 120)) } };
17 | }
18 |
19 | enum PLYFormat { Undef, ASCII, BinaryLE, BinaryBE }
20 | PLYFormat plyFormat = PLYFormat.Undef;
21 | int vertices = 0;
22 | int faces = 0;
23 | bool withnormals = false;
24 |
25 | List vertexTypes = new List();
26 | List vertexValues = new List();
27 |
28 | int headerLinesCount = 0;
29 |
30 | char[] separator = new char[] { ' ' };
31 |
32 | void initialize()
33 | {
34 | plyFormat = PLYFormat.Undef;
35 | vertices = 0;
36 | faces = 0;
37 | vertexTypes.Clear();
38 | vertexValues.Clear();
39 | headerLinesCount = 0;
40 | withnormals = false;
41 | }
42 |
43 | void loadHeader(string filename)
44 | {
45 | headerLinesCount = 0;
46 | using (var fileStream = File.OpenRead(filename))
47 | using (var streamReader = new StreamReader(fileStream, Encoding.ASCII, true, 1024))
48 | {
49 | string s;
50 | string currElement = "";
51 | while ((s = streamReader.ReadLine()) != null && !s.Trim().ToLower().StartsWith("end_header"))
52 | {
53 | string t = s.Trim().ToLower();
54 | string[] parts = t.Split(separator, StringSplitOptions.RemoveEmptyEntries);
55 | int tq = parts.Length - 1;
56 | if (parts[0] == "format")
57 | {
58 | if (parts[1] == "ascii") plyFormat = PLYFormat.ASCII;
59 | if (parts[1] == "binary_little_endian") plyFormat |= PLYFormat.BinaryLE;
60 | if (parts[1] == "binary_big_endian") plyFormat |= PLYFormat.BinaryBE;
61 | }
62 | if (parts[0] == "element")
63 | {
64 | if ((parts[1] == "vertex")) { currElement = "v"; vertices = int.Parse(parts[2]); }
65 | else if ((parts[1] == "face")) { currElement = "f"; faces = int.Parse(parts[2]); }
66 | else if (vertices == 0 || faces == 0) throw new Exception("Not supported PLY structure (elements order)");
67 | }
68 | if (parts[0] == "property")
69 | {
70 | if (currElement == "v")
71 | {
72 | vertexTypes.Add(parts[1]);
73 | vertexValues.Add(parts[2]);
74 | }
75 | if (currElement == "f")
76 | {
77 | if (!(parts[1] == "list" && (parts[2] == "char" || parts[2] == "uchar") && (parts[3] == "int" || parts[3] == "uint"))) throw new Exception("Not supported PLY structure (faces property description)");
78 | }
79 | }
80 | headerLinesCount++;
81 | }
82 | headerLinesCount++;
83 | }
84 |
85 | if (vertexValues.Contains("nx") && vertexValues.Contains("ny") && vertexValues.Contains("nz")) withnormals = true;
86 | }
87 |
88 | public override void Load(string filename)
89 | {
90 | initialize();
91 | loadHeader(filename);
92 | int vq = vertexValues.Count;
93 |
94 | if (plyFormat == PLYFormat.ASCII)
95 | {
96 | string[] lines = File.ReadAllLines(filename);
97 | int linescount = lines.Length;
98 | for (int c = 0; c < vertices; c++)
99 | {
100 | int linenum = headerLinesCount + c;
101 | string[] parts = lines[linenum].Split(separator, StringSplitOptions.RemoveEmptyEntries);
102 | double x = 0.0, y = 0.0, z = 0.0;
103 | double nx = 0.0, ny = 0.0, nz = 0.0;
104 | for (int v = 0; v < vq; v++)
105 | {
106 | var vv = vertexValues[v];
107 | if (vv == "x") x = double.Parse(parts[v]);
108 | else if (vv == "y") y = double.Parse(parts[v]);
109 | else if (vv == "z") z = double.Parse(parts[v]);
110 | else if (vv == "nx") nx = double.Parse(parts[v]);
111 | else if (vv == "ny") ny = double.Parse(parts[v]);
112 | else if (vv == "nz") nz = double.Parse(parts[v]);
113 | }
114 | Positions.Add(new Point3D(x, y, z));
115 | if (withnormals) Normals.Add(new Vector3D(nx, ny, nz));
116 |
117 | Progress = linenum * 100 / linescount;
118 | if (StopFlag) return;
119 | }
120 | for (int c = 0; c < faces; c++)
121 | {
122 | int linenum = headerLinesCount + vertices + c;
123 | string[] parts = lines[linenum].Split(separator, StringSplitOptions.RemoveEmptyEntries);
124 |
125 | int q = int.Parse(parts[0]);
126 | if (q < 3) throw new Exception("Not supported PLY structure (faces should have at leat 3 vertices)");
127 |
128 | int i1 = int.Parse(parts[1]);
129 | int i2 = int.Parse(parts[2]);
130 | for (int i = 0; i < q - 2; i++)
131 | {
132 | int i3 = int.Parse(parts[i + 3]);
133 | Indices.Add(i1);
134 | Indices.Add(i2);
135 | Indices.Add(i3);
136 | i2 = i3;
137 | }
138 |
139 | Progress = linenum * 100 / linescount;
140 | if (StopFlag) return;
141 | }
142 | }
143 | else if (plyFormat == PLYFormat.BinaryLE)
144 | {
145 | using (var fileStream = File.OpenRead(filename))
146 | using (var br = new BinaryReader(fileStream))
147 | {
148 | string endh = "end_header";
149 | int cc = 0;
150 | int cq = endh.Length;
151 | while (cc < cq) cc = char.ToLower(br.ReadChar()) == endh[cc] ? cc + 1 : 0;
152 | while (br.PeekChar() == 0x0D || br.PeekChar() == 0x0A) br.ReadChar();
153 |
154 | int datacount = vertices + faces;
155 | for (int c = 0; c < vertices; c++)
156 | {
157 | double x = 0.0, y = 0.0, z = 0.0;
158 | double nx = 0.0, ny = 0.0, nz = 0.0;
159 | for (int v = 0; v < vq; v++)
160 | {
161 | if (vertexTypes[v] == "float")
162 | {
163 | float t = br.ReadSingle();
164 | string vv = vertexValues[v];
165 | if (vv == "x") x = t;
166 | else if (vv == "y") y = t;
167 | else if (vv == "z") z = t;
168 | else if (vv == "nx") nx = t;
169 | else if (vv == "ny") ny = t;
170 | else if (vv == "nz") nz = t;
171 | }
172 | else if (vertexTypes[v] == "double")
173 | {
174 | double t = br.ReadDouble();
175 | string vv = vertexValues[v];
176 | if (vv == "x") x = t;
177 | else if (vv == "y") y = t;
178 | else if (vv == "z") z = t;
179 | else if (vv == "nx") nx = t;
180 | else if (vv == "ny") ny = t;
181 | else if (vv == "nz") nz = t;
182 | }
183 | else if (vertexTypes[v] == "char" || vertexTypes[v] == "uchar") br.ReadByte();
184 | else if (vertexTypes[v] == "short" || vertexTypes[v] == "ushort") br.ReadUInt16();
185 | else if (vertexTypes[v] == "int" || vertexTypes[v] == "uint") br.ReadUInt32();
186 | else throw new Exception("Not supported PLY structure (unknown data type)");
187 | }
188 | Positions.Add(new Point3D(x, y, z));
189 | if (withnormals) Normals.Add(new Vector3D(nx, ny, nz));
190 |
191 | Progress = c * 100 / datacount;
192 | if (StopFlag) return;
193 | }
194 |
195 | for (int c = 0; c < faces; c++)
196 | {
197 | int q = br.ReadByte();
198 |
199 | if (q < 3) throw new Exception("Not supported PLY structure (faces should have at leat 3 vertices)");
200 |
201 | int i1 = br.ReadInt32();
202 | int i2 = br.ReadInt32();
203 | for (int i = 0; i < q - 2; i++)
204 | {
205 | int i3 = br.ReadInt32();
206 | Indices.Add(i1);
207 | Indices.Add(i2);
208 | Indices.Add(i3);
209 | i2 = i3;
210 | }
211 |
212 | Progress = (vertices + c) * 100 / datacount;
213 | if (StopFlag) return;
214 | }
215 | }
216 | }
217 | else if (plyFormat == PLYFormat.BinaryBE) throw new Exception("Not supported PLY structure (Big Endian Binary PLY is not supported)");
218 | else throw new Exception("Not supported PLY structure (Unknown format)");
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Controls/FileNavigationPanel.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Input;
7 | using System.Windows.Media;
8 |
9 | namespace SolidModelBrowser
10 | {
11 | public partial class FileNavigationPanel : UserControl
12 | {
13 | public event SelectionChangedEventHandler SelectionChanged;
14 |
15 | public delegate void FileNavigationEventHandler(object Sender, string filename);
16 | public event FileNavigationEventHandler FileNavigated;
17 |
18 |
19 | public FileNavigationPanel()
20 | {
21 | InitializeComponent();
22 |
23 | listBoxFiles.SelectionChanged += ListBoxFiles_SelectionChanged;
24 | listBoxFiles.PreviewMouseDoubleClick += ListBoxFiles_PreviewMouseDoubleClick;
25 | listBoxFiles.PreviewKeyDown += ListBoxFiles_PreviewKeyDown;
26 |
27 | fill();
28 | foreach (DriveInfo d in DriveInfo.GetDrives())
29 | {
30 | RadioButton rb = new RadioButton();
31 | rb.Content = d.Name[0].ToString().ToUpper();
32 | //rb.Margin = new Thickness(4);
33 | rb.GroupName = "drivesgroup";
34 | rb.Checked += (sender, args) => { path = rb.Content as string + @":\"; fill(); };
35 | rb.BorderThickness = new Thickness(0);
36 | rb.MinWidth = 30;
37 | rb.MinHeight = 30;
38 | drivesWrapPanel.Children.Add(rb);
39 | }
40 |
41 | Path = @"c:\";
42 | watchForDirectory();
43 | }
44 |
45 | ~FileNavigationPanel()
46 | {
47 | watcher?.Dispose();
48 | }
49 |
50 | private string getSelectedItemPath()
51 | {
52 | if (listBoxFiles.SelectedItem == null) return null;
53 | TextBlock t = listBoxFiles.SelectedItem as TextBlock;
54 | if (t == null) return null;
55 | return t.Tag as string;
56 | }
57 |
58 | private void ListBoxFiles_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
59 | {
60 | DependencyObject temp = (DependencyObject)e.OriginalSource;
61 | while ((temp = VisualTreeHelper.GetParent(temp)) != null)
62 | if (temp is ListBoxItem)
63 | {
64 | TryEnterSelectedFolder();
65 | e.Handled = true;
66 | }
67 | }
68 |
69 | bool dontRaiseSelectionChanged = false;
70 | private void ListBoxFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
71 | {
72 | if (dontRaiseSelectionChanged) return;
73 | SelectionChanged?.Invoke(this, e);
74 |
75 | string p = getSelectedItemPath();
76 | if (p == null) return;
77 | string ext = System.IO.Path.GetExtension(p).Trim('.').ToLower();
78 | if (!UseExtensionFilter || extensions.Contains(ext)) FileNavigated?.Invoke(this, p);
79 | }
80 |
81 | private void ListBoxFiles_PreviewKeyDown(object sender, KeyEventArgs e)
82 | {
83 | if (e.Key == Key.Enter) TryEnterSelectedFolder();
84 | if (e.Key == Key.Back && listBoxFiles.Items.Count > 0)
85 | {
86 | if ((listBoxFiles.Items[0] as TextBlock)?.Text == "..")
87 | {
88 | listBoxFiles.SelectedIndex = 0;
89 | TryEnterSelectedFolder();
90 | }
91 | }
92 |
93 | if (IsIgnoringLetterKeysNavigation && e.Key >= Key.A && e.Key <= Key.Z) e.Handled = true;
94 | }
95 |
96 | private void TryEnterSelectedFolder()
97 | {
98 | try
99 | {
100 | TextBlock t = listBoxFiles.SelectedItem as TextBlock;
101 | if (t != null)
102 | {
103 | if (t.Text == "..")
104 | {
105 | DirectoryInfo dir = new DirectoryInfo(path);
106 | string dirname = dir.Name;
107 | path = dir.Parent.FullName;
108 | fill();
109 |
110 | foreach (var li in listBoxFiles.Items) if ((li as TextBlock)?.Text == dirname) listBoxFiles.SelectedItem = li;
111 | }
112 | else
113 | {
114 | string testpath = System.IO.Path.Combine(path, t.Text);
115 | if (Directory.Exists(testpath))
116 | {
117 | path = testpath;
118 | fill();
119 | }
120 | }
121 | }
122 | if (listBoxFiles.Items.Count > 0)
123 | {
124 | if (listBoxFiles.SelectedItem == null) listBoxFiles.SelectedIndex = 0;
125 | listBoxFiles.UpdateLayout();
126 | listBoxFiles.ScrollIntoView(listBoxFiles.SelectedItem);
127 | ListBoxItem temp = (listBoxFiles.ItemContainerGenerator.ContainerFromItem(listBoxFiles.SelectedItem) as ListBoxItem);
128 | if (temp != null) temp.Focus();
129 | }
130 | }
131 | catch { }
132 | }
133 |
134 |
135 | //SolidColorBrush folderBrush = new SolidColorBrush(Color.FromArgb(255, 200, 200, 240));
136 |
137 | private void fill() => fill(false);
138 | private void fill(bool keepSelectedItem)
139 | {
140 | if (!Directory.Exists(path)) return;
141 |
142 | string si = getSelectedItemPath();
143 |
144 | listBoxFiles.Items.Clear();
145 |
146 | if ((new DirectoryInfo(path)).Parent != null) listBoxFiles.Items.Add(new TextBlock() { Text = ".." });
147 |
148 | string[] dirs = Directory.GetDirectories(path).OrderBy(d => d).ToArray();
149 | for (int c = 0; c < dirs.Length; c++)
150 | {
151 | TextBlock t = new TextBlock();
152 | t.Text = System.IO.Path.GetFileName(dirs[c]);
153 | t.Tag = dirs[c];
154 | t.FontWeight = FontWeights.Bold;
155 | //t.Foreground = folderBrush;
156 | listBoxFiles.Items.Add(t);
157 | }
158 |
159 | string[] files = Directory.GetFiles(path);
160 | if (SortFilesByExtensions) files = files.OrderBy(f => System.IO.Path.GetExtension(f) + System.IO.Path.GetFileNameWithoutExtension(f)).ToArray();
161 | else files = files.OrderBy(f => f).ToArray();
162 | for (int c = 0; c < files.Length; c++)
163 | {
164 | string ext = System.IO.Path.GetExtension(files[c]).Trim('.').ToLower();
165 | if (extensions.Contains(ext) || !UseExtensionFilter)
166 | {
167 | TextBlock t = new TextBlock();
168 | if (extensionsColors.ContainsKey(ext)) t.Foreground = extensionsColors[ext];
169 | t.Text = System.IO.Path.GetFileName(files[c]);
170 | t.Tag = files[c];
171 | listBoxFiles.Items.Add(t);
172 | }
173 | }
174 |
175 | if (keepSelectedItem)
176 | {
177 | var li = listBoxFiles.Items.OfType().FirstOrDefault(t => t.Tag?.ToString() == si);
178 | listBoxFiles.SelectedItem = li;
179 | }
180 | }
181 |
182 | public void SelectFile(string filepath, bool focusControl)
183 | {
184 | if (!File.Exists(filepath)) return;
185 | Path = System.IO.Path.GetDirectoryName(filepath);
186 | foreach (var f in listBoxFiles.Items)
187 | if (f is TextBlock t && (t.Tag as string) == filepath)
188 | {
189 | listBoxFiles.SelectedItem = f;
190 | if (focusControl)
191 | {
192 | listBoxFiles.UpdateLayout();
193 | var listBoxItem = (ListBoxItem)listBoxFiles.ItemContainerGenerator.ContainerFromItem(listBoxFiles.SelectedItem);
194 | listBoxItem?.Focus();
195 | }
196 | }
197 | }
198 |
199 | public void Reselect()
200 | {
201 | if (listBoxFiles.SelectedItem != null)
202 | {
203 | object sel = listBoxFiles.SelectedItem;
204 | listBoxFiles.SelectedItem = null;
205 | listBoxFiles.SelectedItem = sel;
206 | }
207 | }
208 |
209 | public void Refresh()
210 | {
211 | dontRaiseSelectionChanged = true;
212 | fill(true);
213 | dontRaiseSelectionChanged = false;
214 | if (listBoxFiles.SelectedItem == null) ListBoxFiles_SelectionChanged(this, null);
215 | }
216 |
217 | FileSystemWatcher watcher = new FileSystemWatcher();
218 | void watchForDirectory()
219 | {
220 | watcher.Path = path;
221 | watcher.Filter = "*.*";
222 | watcher.Changed += (s, e) => Application.Current.Dispatcher.Invoke(() => Refresh());
223 | watcher.Created += (s, e) => Application.Current.Dispatcher.Invoke(() => Refresh());
224 | watcher.Deleted += (s, e) => Application.Current.Dispatcher.Invoke(() => Refresh());
225 | watcher.Renamed += (s, e) => Application.Current.Dispatcher.Invoke(() => Refresh());
226 | watcher.EnableRaisingEvents = true;
227 | }
228 |
229 | string path = @"c:\";
230 | public string Path
231 | {
232 | get
233 | {
234 | return path;
235 | }
236 | set
237 | {
238 | if (value == path) return;
239 | if (!Directory.Exists(value)) return;
240 |
241 | string drv = value.Substring(0, 1).ToUpper();
242 | var rbutton = drivesWrapPanel.Children.OfType().FirstOrDefault(rb => (rb.Content as string).StartsWith(drv));
243 | if (rbutton != null) rbutton.IsChecked = true;
244 |
245 | path = value;
246 | fill();
247 | watcher.Path = path;
248 | }
249 | }
250 |
251 | public string FullName
252 | {
253 | get
254 | {
255 | string fname = (listBoxFiles.SelectedItem as TextBlock)?.Text;
256 | if (fname != null && fname != "..") return System.IO.Path.Combine(path, fname);
257 | return null;
258 | }
259 | }
260 |
261 | public bool SortFilesByExtensions { get; set; } = false;
262 | public bool UseExtensionFilter { get; set; } = true;
263 |
264 | List extensions = new List();
265 | public List Extensions
266 | {
267 | get
268 | {
269 | return extensions;
270 | }
271 | set
272 | {
273 | extensions = value;
274 | fill(true);
275 | }
276 | }
277 |
278 | Dictionary extensionsColors = new Dictionary();
279 | public Dictionary ExtensionsColors
280 | {
281 | get
282 | {
283 | return extensionsColors;
284 | }
285 | set
286 | {
287 | extensionsColors = value;
288 | fill(true);
289 | }
290 | }
291 |
292 | public bool IsIgnoringLetterKeysNavigation { get; set; }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/SolidModelBrowser/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
29 |
32 |
35 |
36 |
39 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
65 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
82 |
85 |
88 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
102 |
103 |
104 |
105 |
106 |
109 |
110 |
111 |
114 |
117 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/SolidModelBrowser/Settings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Windows;
7 | using System.Windows.Media;
8 | using System.Windows.Media.Media3D;
9 |
10 | namespace SolidModelBrowser
11 | {
12 | internal class Settings
13 | {
14 | [PropertyInfo(Category = "Interface", Description = "Use application light theme")]
15 | public bool LightTheme { get; set; } = false;
16 |
17 | [PropertyInfo(Category = "Main Window", Description = "Window state")]
18 | public WindowState WindowState { get; set; } = WindowState.Normal;
19 |
20 | [PropertyInfo(Category = "Main Window", Description = "Window X location")]
21 | public double LocationX { get; set; } = 100;
22 |
23 | [PropertyInfo(Category = "Main Window", Description = "Window Y location")]
24 | public double LocationY { get; set; } = 150;
25 |
26 | [PropertyInfo(Category = "Main Window", Description = "Window width")]
27 | public double Width { get; set; } = 800;
28 |
29 | [PropertyInfo(Category = "Main Window", Description = "Window height")]
30 | public double Height { get; set; } = 600;
31 |
32 | [PropertyInfo(Category = "Main Window", Description = "Max Window width, set 0 for screen limit")]
33 | public double MaxWidth { get; set; } = 0;
34 |
35 | [PropertyInfo(Category = "Main Window", Description = "Max Window height, set 0 for screen limit\r\nthis option is to manually limit window on non primary displays")]
36 | public double MaxHeight { get; set; } = 0;
37 |
38 | [PropertyInfo(Category = "Main Window", Description = "Width of file panel")]
39 | public double FilePanelWidth { get; set; } = 200;
40 |
41 | [PropertyInfo(Category = "Main Window", Description = "Opacity of file panel when it is not in use", MenuLabel = "File panel idle opacity", Min = 0.0, Max = 1.0, Increment = 0.1)]
42 | public double FilePanelIdleOpacity { get; set; } = 0.2;
43 |
44 |
45 | [PropertyInfo(Category = "Utils", Description = "External application to open current viewed file", MenuLabel = "External Application")]
46 | public string ExternalApp { get; set; } = "";
47 |
48 | [PropertyInfo(Category = "Utils", Description = "External application command line arguments\r\nVariable $file$ = current filename", MenuLabel = "External Application Arguments")]
49 | public string ExternalAppArguments { get; set; } = "$file$";
50 |
51 | [PropertyInfo(Category = "Utils", Description = "Last selected path in file panel")]
52 | public string SelectedPath { get; set; } = @"c:\";
53 |
54 | [PropertyInfo(Category = "Utils", Description = "DPI to render image before save to file", MenuLabel = "Save image DPI")]
55 | public int SaveImageDPI { get; set; } = 600;
56 |
57 |
58 | [PropertyInfo(Category = "Materials", Description = "Model diffuse color ARGB", MenuLabel = "Diffuse color", SortPrefix = "00")]
59 | public SColor DiffuseColor { get; set; } = new SColor("#FFFFFF00");
60 |
61 | [PropertyInfo(Category = "Materials", Description = "Model specular color ARGB", MenuLabel = "Specular color", SortPrefix = "01")]
62 | public SColor SpecularColor { get; set; } = new SColor("#FFDCDCDC");
63 |
64 | [PropertyInfo(Category = "Materials", Description = "Model emissive color ARGB", MenuLabel = "Emissive color", SortPrefix = "02")]
65 | public SColor EmissiveColor { get; set; } = new SColor("#FF000040");
66 |
67 | [PropertyInfo(Category = "Materials", Description = "Model backside diffuse color ARGB", MenuLabel = "Inner diffuse color", SortPrefix = "03")]
68 | public SColor BackDiffuseColor { get; set; } = new SColor("#FFFF0000");
69 |
70 | [PropertyInfo(Category = "Materials", Description = "Value that specifies the degree to which a material applied to a 3-D model reflects the lighting model as shine", MenuLabel = "Specular power", Min = 0.0, Max = 10000.0)]
71 | public double SpecularPower { get; set; } = 40.0;
72 |
73 | [PropertyInfo(Category = "Materials", Description = "Diffuse material is enabled")]
74 | public bool IsDiffuseEnabled { get; set; } = true;
75 |
76 | [PropertyInfo(Category = "Materials", Description = "Specular material is enabled")]
77 | public bool IsSpecularEnabled { get; set; } = true;
78 |
79 | [PropertyInfo(Category = "Materials", Description = "Emissive material is enabled")]
80 | public bool IsEmissiveEnabled { get; set; } = true;
81 |
82 | [PropertyInfo(Category = "Materials", Description = "Backside diffuse material is enabled")]
83 | public bool IsBackDiffuseEnabled { get; set; } = true;
84 |
85 |
86 | [PropertyInfo(Category = "Interface", Description = "Orthographic camera mode is enabled")]
87 | public bool IsOrthoCameraEnabled { get; set; } = false;
88 |
89 | [PropertyInfo(Category = "Interface", Description = "Wide angle camera mode is enabled")]
90 | public bool IsFishEyeModeEnabled { get; set; } = false;
91 |
92 | [PropertyInfo(Category = "Interface", Description = "Show XYZ axes lines")]
93 | public bool IsAxesEnabled { get; set; } = false;
94 |
95 | [PropertyInfo(Category = "Interface", Description = "Show ground plane")]
96 | public bool IsGroundEnabled { get; set; } = false;
97 |
98 | [PropertyInfo(Category = "Interface", Description = "Use different colors for files in panel depending on file extensions", MenuLabel = "Different colors for files")]
99 | public bool ColorizeFiles { get; set; } = true;
100 |
101 | [PropertyInfo(Category = "Interface", Description = "Sort files by extensions in filepanel", MenuLabel = "Sort files by extensions")]
102 | public bool SortFilesByExtensions { get; set; } = true;
103 |
104 | [PropertyInfo(Category = "Interface", Description = "Use system style for app windows", MenuLabel = "Use system style for app windows")]
105 | public bool UseSystemWindowStyle { get; set; } = false;
106 |
107 |
108 |
109 | [PropertyInfo(Category = "Camera", Description = "Camera field of view angle", MenuLabel = "Normal FOV", Min = 5.0, Max = 160.0)]
110 | public double FOV { get; set; } = 45.0;
111 |
112 | [PropertyInfo(Category = "Camera", Description = "Camera field of view angle in fish eye mode", MenuLabel = "FOV in fish eye mode", Min = 5.0, Max = 160.0)]
113 | public double FishEyeFOV { get; set; } = 120.0;
114 |
115 | [PropertyInfo(Category = "Camera", Description = "Camera basic shift from model in units relative to model overall size", MenuLabel = "Initial shift", Min = 0.5, Max = 10.0, Increment = 0.1)]
116 | public double CameraInitialShift { get; set; } = 1.5;
117 |
118 | [PropertyInfo(Category = "Camera", Description = "Default camera look at model points average or geometric center", MenuLabel = "Default camera look at points average center")]
119 | public bool DefaultLookAtModelPointsAvgCenter { get; set; } = true;
120 |
121 |
122 |
123 | [PropertyInfo(Category = "Model", Description = "Model rotation angle 5...180 deg", MenuLabel = "Rotation angle", Min = 5.0, Max = 180.0)]
124 | public double ModelRotationAngle { get; set; } = 90.0;
125 |
126 | [PropertyInfo(Category = "Model", Description = "Wireframe mode edge scaling factor 0.001...0.9", MenuLabel = "Wireframe edge scale", Min = 0.001, Max = 0.9, Increment = 0.01)]
127 | public double WireframeEdgeScale { get; set; } = 0.03;
128 |
129 | [PropertyInfo(Category = "Model", Description = "Skip loading models original normals", MenuLabel = "Skip load normals")]
130 | public bool IgnoreOriginalNormals { get; set; } = false;
131 |
132 | [PropertyInfo(Category = "Model", Description = "Convert smoothed models to flat automatically", MenuLabel = "Unsmooth after loading")]
133 | public bool UnsmoothAfterLoading { get; set; } = false;
134 |
135 |
136 |
137 | [PropertyInfo(Category = "Scene", Description = "Axes length (X, Y, Z)", MenuLabel = "Axes length", Min = 10.0, Max = 10000.0)]
138 | public double AxesLength { get; set; } = 1000.0;
139 |
140 | [PropertyInfo(Category = "Scene", Description = "Ground rectangle (left, bottom, width and height)", MenuLabel = "Ground rectangle")]
141 | public Rect GroundRectangle { get; set; } = new Rect(-500, -500, 1000, 1000);
142 |
143 | [PropertyInfo(Category = "Scene", Description = "Ground checker size", MenuLabel = "Ground checker size", Min = 10.0, Max = 1000.0)]
144 | public double GroundCheckerSize { get; set; } = 50.0;
145 |
146 | [PropertyInfo(Category = "Scene", Description = "Ground diffuse color ARGB", MenuLabel = "Ground diffuse color")]
147 | public SColor GroundDiffuseColor { get; set; } = new SColor("#40FFFFFF");
148 |
149 | [PropertyInfo(Category = "Scene", Description = "Ground emissive color ARGB", MenuLabel = "Ground emissive color")]
150 | public SColor GroundEmissiveColor { get; set; } = new SColor("#FF000040");
151 |
152 |
153 | [PropertyInfo(Category = "Scene Lights", Description = "Ambient light enabled", MenuLabel = "Ambient light enabled", SortPrefix = "00")]
154 | public bool IsAmbientLightEnabled { get; set; } = true;
155 |
156 | [PropertyInfo(Category = "Scene Lights", Description = "Ambient light color ARGB", MenuLabel = "Ambient light color", SortPrefix = "01")]
157 | public SColor AmbientLightColor { get; set; } = new SColor("#FF202020");
158 |
159 |
160 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 1 enabled", MenuLabel = "Directional light 1 enabled", SortPrefix = "02")]
161 | public bool IsDirectionalLight1Enabled { get; set; } = true;
162 |
163 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 1 color ARGB", MenuLabel = "Directional light 1 color", SortPrefix = "03")]
164 | public SColor DirectionalLight1Color { get; set; } = new SColor("#FF0000FF");
165 |
166 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 1 direction", MenuLabel = "Directional light 1 direction", SortPrefix = "04")]
167 | public Vector3D DirectionalLight1Dir { get; set; } = new Vector3D(2, 5, -5);
168 |
169 |
170 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 2 enabled", MenuLabel = "Directional light 2 enabled", SortPrefix = "05")]
171 | public bool IsDirectionalLight2Enabled { get; set; } = true;
172 |
173 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 2 color ARGB", MenuLabel = "Directional light 2 color", SortPrefix = "06")]
174 | public SColor DirectionalLight2Color { get; set; } = new SColor("#FFD0D0D0");
175 |
176 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 2 direction", MenuLabel = "Directional light 2 direction", SortPrefix = "07")]
177 | public Vector3D DirectionalLight2Dir { get; set; } = new Vector3D(-2, 5, -5);
178 |
179 |
180 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 3 enabled", MenuLabel = "Directional light 3 enabled", SortPrefix = "08")]
181 | public bool IsDirectionalLight3Enabled { get; set; } = true;
182 |
183 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 3 color ARGB", MenuLabel = "Directional light 3 color", SortPrefix = "09")]
184 | public SColor DirectionalLight3Color { get; set; } = new SColor("#FFFF8080");
185 |
186 | [PropertyInfo(Category = "Scene Lights", Description = "Directional light 3 direction", MenuLabel = "Directional light 3 direction", SortPrefix = "10")]
187 | public Vector3D DirectionalLight3Dir { get; set; } = new Vector3D(1, -5, 1);
188 |
189 |
190 | [PropertyInfo(Category = "Import GCODE", Description = "GCODE default layer height", MenuLabel = "Layer height (reload model to see changes)", Min = 0.01, Max = 10.0, Increment = 0.1)]
191 | public double GCODELayerHeight { get; set; } = 0.2;
192 |
193 | [PropertyInfo(Category = "Import GCODE", Description = "GCODE default line width", MenuLabel = "Line width (reload model to see changes)", Min = 0.01, Max = 10.0, Increment = 0.1)]
194 | public double GCODELineWidth { get; set; } = 0.5;
195 |
196 | [PropertyInfo(Category = "Import GCODE", Description = "GCODE line ends extensions", MenuLabel = "GCODE line ends extensions (reload model to see changes)", Min = -10.0, Max = 10.0, Increment = 0.1)]
197 | public double GCODELineExtensions { get; set; } = 0.0;
198 |
199 |
200 | string defPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
201 |
202 |
203 |
204 |
205 | // --- Save ---
206 |
207 | public void Save() => Save(defPath, true);
208 | public void Save(string filename, bool writeDescriptions)
209 | {
210 | var pi = PropertyInfoAttribute.GetPropertiesInfoList(this, true);
211 | List lines = new List();
212 |
213 | string currentCatName = null;
214 | foreach (var p in pi)
215 | {
216 | if (p.Category != currentCatName)
217 | {
218 | if (lines.Count > 0) lines.Add("");
219 | lines.Add($"[{p.Category}]");
220 | currentCatName = p.Category;
221 | }
222 | if (p.Value != null)
223 | {
224 | if (writeDescriptions && p.Description != null) lines.Add("# " + p.Description.Replace("\r\n", "\r\n# "));
225 | lines.Add($"{p.Name}={escape(p.Value.ToString())}");
226 | }
227 | }
228 | File.WriteAllLines(filename, lines);
229 | }
230 |
231 |
232 | // --- Load ---
233 |
234 | List parseErrors = new List();
235 | public List GetParseErrors() => parseErrors;
236 | public int GetParseErrorsCount() => parseErrors.Count;
237 |
238 | public void Load() => Load(defPath);
239 | public void Load(string filename)
240 | {
241 | if (!File.Exists(filename))
242 | {
243 | parseErrors.Add($"No settings file {filename}, default one is created");
244 | Save();
245 | return;
246 | }
247 | var lines = File.ReadAllLines(filename);
248 | Load(lines);
249 | }
250 |
251 | public void Load(string[] lines)
252 | {
253 | parseErrors.Clear();
254 | var t = this.GetType();
255 | foreach (string line in lines)
256 | {
257 | if (line.Length > 0 && char.IsLetter(line[0]))
258 | {
259 | int pos = line.IndexOf('=');
260 | string name = pos >= 0 ? line.Substring(0, pos) : line;
261 | string value = pos >= 0 ? unescape(line.Substring(pos + 1)) : "";
262 | var p = t.GetProperty(name);
263 | if (p != null)
264 | {
265 | Type pt = p.PropertyType;
266 | object v = null;
267 | try
268 | {
269 | if (pt == typeof(string)) v = value;
270 | else if (pt.IsEnum) v = Enum.Parse(pt, value);
271 | else
272 | {
273 | var mi = pt.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null);
274 | if (mi != null) v = mi.Invoke(null, new object[] { value });
275 | else if (p.GetValue(this, null) != null)
276 | {
277 | mi = pt.GetMethod("Parse", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);
278 | if (mi != null) mi.Invoke(p.GetValue(this, null), new object[] { value });
279 | else parseErrors.Add($"No parser for {pt.Name}");
280 | }
281 | }
282 |
283 | if (v != null) p.SetValue(this, v, null);
284 | }
285 | catch { parseErrors.Add($"Parsing failed for {name}={value}"); }
286 | }
287 | else parseErrors.Add($"No property in object with name {name}");
288 | }
289 | }
290 | }
291 |
292 | string escape(string s) => s.Replace("\r", "").Replace("\n", "");
293 | string unescape(string s) => s.Replace("", "\r").Replace("", "\n");
294 |
295 | public void StartProcess() => System.Diagnostics.Process.Start(defPath);
296 | }
297 |
298 |
299 | public class SColor
300 | {
301 | public Color Color { get; set; }
302 | public SColor(string s) => this.Parse(s);
303 | public SColor(Color c) => Color = c;
304 | public override string ToString() => Color.ToString();
305 | public void Parse(string s) => Color = (Color)ColorConverter.ConvertFromString(s);
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/SolidModelBrowser/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Effects;
11 | using System.Windows.Media.Media3D;
12 | using System.Windows.Shell;
13 | using System.Windows.Threading;
14 |
15 | namespace SolidModelBrowser
16 | {
17 | public partial class MainWindow : Window
18 | {
19 | const string version = "0.6.1";
20 |
21 | MeshGeometry3D meshBase, meshWireframe;
22 | Point3D modelCenter = new Point3D();
23 | Point3D modelPointsAvgCenter = new Point3D();
24 | Vector3D modelSize = new Vector3D();
25 |
26 | string lastFilename;
27 |
28 | DispatcherTimer loadingTimer = new DispatcherTimer() { Interval = new TimeSpan(0, 0, 0, 0, 100), IsEnabled = false };
29 |
30 | Settings settings = new Settings();
31 |
32 | public MainWindow()
33 | {
34 | InitializeComponent();
35 | System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; // to avoid "." "," mess while parsing
36 |
37 | ButtonReloadModel.Click += (s, e) => { clearView(); loadFile(lastFilename); };
38 | ButtonSaveImage.Click += (s, e) => Utils.SaveImagePNG(scene.Viewport3D, settings.SaveImageDPI);
39 | ButtonOpenExtApp.Click += (s, e) => Utils.RunExternalApp(settings.ExternalApp, settings.ExternalAppArguments, lastFilename);
40 | ButtonRotateX.Click += (s, e) => addRotation(settings.ModelRotationAngle, 0, 0);
41 | ButtonRotateY.Click += (s, e) => addRotation(0, settings.ModelRotationAngle, 0);
42 | ButtonRotateZ.Click += (s, e) => addRotation(0, 0, settings.ModelRotationAngle);
43 | ButtonDiffuseMaterial.Click += (s, e) => applyMaterials();
44 | ButtonSpecularMaterial.Click += (s, e) => applyMaterials();
45 | ButtonEmissiveMaterial.Click += (s, e) => applyMaterials();
46 | ButtonBacksideDiffuseMaterial.Click += (s, e) => applyMaterials();
47 | ButtonSelectCamera.Click += (s, e) => updateCameraModes();
48 | ButtonCenterCameraAtModelGC.Click += (s, e) => scene.Camera.TurnAt(modelCenter);
49 | ButtonCenterCameraAtModelMC.Click += (s, e) => scene.Camera.TurnAt(modelPointsAvgCenter);
50 | ButtonFishEyeFOV.Click += (s, e) => updateCameraModes();
51 | ButtonAxes.Click += (s, e) => scene.IsAxesVisible = ButtonAxes.IsChecked.Value;
52 | ButtonGround.Click += (s, e) => scene.IsGroundVisible = ButtonGround.IsChecked.Value;
53 | ButtonSwapNormals.Click += (s, e) => { Utils.InvertNormals(meshBase); updateMeshes(true); };
54 | ButtonRemoveNormals.Click += (s, e) => { meshBase.Normals.Clear(); updateMeshes(true); };
55 | ButtonChangeVerticesOrder.Click += (s, e) => { Utils.InvertVertexOrder(meshBase); updateMeshes(true); };
56 | ButtonRecreateUnsmoothed.Click += (s, e) => { meshBase = Utils.UnsmoothMesh(meshBase); updateMeshes(true); };
57 | ButtonWireframe.Click += (s, e) => updateMeshes(false);
58 | ButtonShowModelInfo.Click += (s, e) => updateModelInfo();
59 | ButtonSettings.Click += (s, e) => runSettings();
60 | ButtonExport.Click += (s, e) => export();
61 |
62 | ButtonChangeTheme.Click += (s, e) => { settings.LightTheme = !settings.LightTheme; setAppTheme(); };
63 | ButtonMinimize.Click += (s, e) => WindowState = WindowState.Minimized;
64 | ButtonMaximize.Click += (s, e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
65 | ButtonClose.Click += (s, e) => Close();
66 |
67 | scene.ShiftAndDrag += (s, e) => this.DragMove();
68 | sliderSliceHeight.ValueChanged += (s, e) => slice();
69 |
70 | filePanel.IsIgnoringLetterKeysNavigation = true;
71 | filePanel.Extensions = new List();
72 | foreach (Import importer in Import.Imports) filePanel.Extensions.AddRange(importer.Extensions);
73 | filePanel.MouseEnter += (s, e) => filePanel.Opacity = 1.0;
74 | filePanel.MouseLeave += (s, e) => filePanel.Opacity = settings.FilePanelIdleOpacity;
75 | filePanel.SelectionChanged += (s, e) => clearView();
76 | filePanel.FileNavigated += (s, filename) => loadFile(filename);
77 |
78 | this.PreviewKeyDown += MainWindow_PreviewKeyDown;
79 |
80 | loadingTimer.Tick += LoadingTimer_Tick;
81 |
82 | loadSettings();
83 | this.Closing += (s, e) => saveSettings();
84 | Import.BindSettings(settings);
85 |
86 | // set max window size to avoid taskbar overlay
87 | var scrsize = Utils.GetCurrentScreenSize(this);
88 | MaxWidth = settings.MaxWidth < 1 ? scrsize.Width : Math.Max(settings.MaxWidth, MinWidth);
89 | MaxHeight = settings.MaxHeight < 1 ? scrsize.Height : Math.Max(settings.MaxHeight, MinHeight);
90 |
91 | showInfo($"Solid Model Browser v {version}\r\nSTL, OBJ, 3MF, PLY, GCODE formats supported\r\n\r\nquestfulcat 2025 (C)\r\nhttps://github.com/questfulcat/solidmodelbrowser\r\nDistributed under MIT License\r\n\r\nF1 - keymap help");
92 | this.ContentRendered += windowRendered;
93 | }
94 |
95 | void windowRendered(object s, EventArgs a)
96 | {
97 | var args = Environment.GetCommandLineArgs();
98 | if (args.Length == 2 && File.Exists(args[1]) && Import.SelectImporter(args[1])) filePanel.SelectFile(args[1], true);
99 | this.ContentRendered -= windowRendered;
100 | }
101 |
102 |
103 | void showInfo(string info) { textBlockInfo.Text = info; infoContainer.Visibility = Visibility.Visible; }
104 | void hideInfo() => infoContainer.Visibility = Visibility.Collapsed;
105 | void showModelInfo() => showInfo($"File: {lastFilename}\r\n\r\nVertices: {scene.Mesh.Positions.Count}\r\nTriangles: {scene.Mesh.TriangleIndices.Count / 3}\r\nNormals: {scene.Mesh.Normals.Count}\r\n\r\nSize X:{modelSize.X.ToString("0.00")}, Y:{modelSize.Y.ToString("0.00")}, Z:{modelSize.Z.ToString("0.00")}\r\nGeometric Center X:{modelCenter.X.ToString("0.00")}, Y:{modelCenter.Y.ToString("0.00")}, Z:{modelCenter.Z.ToString("0.00")}\r\nPoints Average Center X:{modelPointsAvgCenter.X.ToString("0.00")}, Y:{modelPointsAvgCenter.Y.ToString("0.00")}, Z:{modelPointsAvgCenter.Z.ToString("0.00")}");
106 |
107 |
108 | private void LoadingTimer_Tick(object sender, EventArgs e)
109 | {
110 | try
111 | {
112 | //textBlockProgress.Text = Import.Progress.ToString();
113 | borderProgressContainer.Visibility = Visibility.Visible;
114 | borderProgress.Width = Import.Progress < 0 ? 0 : (Import.Progress > 100 ? 100 : Import.Progress);
115 |
116 | if (currentTask?.Status == TaskStatus.Running) return;
117 | loadingTimer.Stop();
118 | borderProgressContainer.Visibility = Visibility.Hidden;
119 |
120 | if (Import.ExceptionMessage != null)
121 | {
122 | clearView();
123 | showInfo(Import.ExceptionMessage);
124 | return;
125 | }
126 |
127 | if (settings.IgnoreOriginalNormals) Import.Normals.Clear();
128 | meshBase = Utils.FillMeshFromImport();
129 | meshWireframe = null;
130 | if (settings.UnsmoothAfterLoading) meshBase = Utils.UnsmoothMesh(meshBase);
131 | Utils.FillMesh(meshBase, scene.Mesh);
132 | Utils.GetModelCenterAndSize(meshBase, out modelCenter, out modelPointsAvgCenter, out modelSize);
133 | scene.Camera.LookCenter = settings.DefaultLookAtModelPointsAvgCenter ? modelPointsAvgCenter : modelCenter;
134 | scene.Camera.DefaultPosition(modelSize.Length, settings.CameraInitialShift);
135 |
136 | if (Import.CurrentImport.InitialXRotationNeeded) addRotation(90.0, 0, 0);
137 | if (ButtonShowModelInfo.IsChecked.Value) showModelInfo();
138 | else hideInfo();
139 |
140 | initSlicer();
141 | }
142 | catch (Exception exc)
143 | {
144 | loadingTimer.Stop();
145 | clearView();
146 | showInfo(exc.Message);
147 | }
148 | }
149 |
150 |
151 | Task currentTask = null;
152 | void loadFile(string filename)
153 | {
154 | if (!File.Exists(filename)) return;
155 | lastFilename = null;
156 | try
157 | {
158 | if (currentTask?.Status == TaskStatus.Running)
159 | {
160 | Import.StopFlag = true;
161 | currentTask.Wait(3000);
162 | }
163 | currentTask?.Dispose();
164 |
165 | if (!Import.SelectImporter(filename)) { clearView(); return; }
166 |
167 | currentTask = new Task(() =>
168 | {
169 | try
170 | {
171 | Import.CurrentImport.Load(filename);
172 | }
173 | catch (Exception exc) { Import.ExceptionMessage = exc.Message; }
174 | });
175 |
176 | lastFilename = filename;
177 | loadingTimer.Start();
178 | showInfo("loading...");
179 |
180 | currentTask.Start();
181 | }
182 | catch (Exception exc)
183 | {
184 | showInfo(exc.Message);
185 | }
186 | }
187 |
188 | void addRotation(double ax, double ay, double az)
189 | {
190 | Utils.RotateMesh(meshBase, settings.DefaultLookAtModelPointsAvgCenter ? modelPointsAvgCenter : modelCenter, ax, ay, az);
191 | Utils.GetModelCenterAndSize(meshBase, out modelCenter, out modelPointsAvgCenter, out modelSize);
192 | initSlicer();
193 | updateMeshes(true);
194 | }
195 |
196 | private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
197 | {
198 | string CAS = Utils.GetKeyboardModifiers();
199 |
200 | if (CAS == "") // no modifier keys
201 | {
202 | double ra = settings.ModelRotationAngle;
203 | if (e.Key == Key.A) addRotation(0, 0, ra);
204 | if (e.Key == Key.D) addRotation(0, 0, -ra);
205 | if (e.Key == Key.S) addRotation(ra, 0, 0);
206 | if (e.Key == Key.W) addRotation(-ra, 0, 0);
207 | if (e.Key == Key.E) addRotation(0, ra, 0);
208 | if (e.Key == Key.Q) addRotation(0, -ra, 0);
209 |
210 | double cis = settings.CameraInitialShift;
211 | if (e.Key == Key.G) scene.Camera.RelativePosition(modelSize.Length, 0, -cis, 0);
212 | if (e.Key == Key.R) scene.Camera.RelativePosition(modelSize.Length, 0, cis, 0);
213 | if (e.Key == Key.F) scene.Camera.RelativePosition(modelSize.Length, -cis, 0, 0);
214 | if (e.Key == Key.H) scene.Camera.RelativePosition(modelSize.Length, cis, 0, 0);
215 | if (e.Key == Key.T) scene.Camera.RelativePosition(modelSize.Length, 0, -0.0001, cis);
216 | if (e.Key == Key.B) scene.Camera.RelativePosition(modelSize.Length, 0, -0.0001, -cis);
217 |
218 | if (e.Key == Key.F1)
219 | {
220 | ButtonShowModelInfo.IsChecked = false;
221 | if (textBlockInfo.Text == help && infoContainer.Visibility == Visibility.Visible) hideInfo();
222 | else showInfo(help);
223 | }
224 | if (e.Key == Key.F2) { ButtonDiffuseMaterial.Toggle(); applyMaterials(); }
225 | if (e.Key == Key.F3) { ButtonSpecularMaterial.Toggle(); applyMaterials(); }
226 | if (e.Key == Key.F4) { ButtonEmissiveMaterial.Toggle(); applyMaterials(); }
227 | if (e.Key == Key.F5) { clearView(); loadFile(lastFilename); }
228 | if (e.Key == Key.F6) Utils.SaveImagePNG(scene.Viewport3D, settings.SaveImageDPI);
229 | if (e.Key == Key.F7) Utils.RunExternalApp(settings.ExternalApp, settings.ExternalAppArguments, lastFilename);
230 | if (e.Key == Key.F8) { ButtonSelectCamera.Toggle(); scene.Camera.SelectType(ButtonSelectCamera.IsChecked.Value); }
231 | if (e.Key == Key.C) scene.Camera.TurnAt(modelCenter);
232 | if (e.Key == Key.M) scene.Camera.TurnAt(modelPointsAvgCenter);
233 | if (e.Key == Key.O) { ButtonAxes.Toggle(); scene.IsAxesVisible = ButtonAxes.IsChecked.Value; }
234 | if (e.Key == Key.P) { ButtonGround.Toggle(); scene.IsGroundVisible = ButtonGround.IsChecked.Value; }
235 | if (e.Key == Key.I) { ButtonShowModelInfo.Toggle(); updateModelInfo(); }
236 | }
237 |
238 | if (CAS == "C") // CTRL
239 | {
240 | if (e.Key == Key.F) { meshBase = Utils.UnsmoothMesh(meshBase); updateMeshes(true); }
241 | if (e.Key == Key.W) { ButtonWireframe.Toggle(); updateMeshes(false); }
242 | if (e.Key == Key.S) export();
243 | }
244 | }
245 |
246 | void clearView()
247 | {
248 | if (meshBase == null) return;
249 | meshBase.TriangleIndices.Clear();
250 | meshBase.Positions.Clear();
251 | meshBase.Normals.Clear();
252 | ButtonWireframe.IsChecked = false;
253 | updateMeshes(false);
254 | hideInfo();
255 | }
256 |
257 | void applyMaterials()
258 | {
259 | if (!(ButtonDiffuseMaterial.IsChecked.Value || ButtonSpecularMaterial.IsChecked.Value || ButtonEmissiveMaterial.IsChecked.Value)) ButtonDiffuseMaterial.IsChecked = true;
260 | scene.ApplyMaterials(ButtonDiffuseMaterial.IsChecked.Value, ButtonSpecularMaterial.IsChecked.Value, ButtonEmissiveMaterial.IsChecked.Value, ButtonBacksideDiffuseMaterial.IsChecked.Value);
261 | }
262 |
263 | void updateCameraModes()
264 | {
265 | scene.Camera.SelectType(ButtonSelectCamera.IsChecked.Value);
266 | scene.Camera.FOV = ButtonFishEyeFOV.IsChecked.Value ? settings.FishEyeFOV : settings.FOV;
267 | }
268 |
269 |
270 |
271 | private void Handler_MouseDown(object sender, MouseButtonEventArgs e)
272 | {
273 | if (e.ClickCount == 2) { WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; return; }
274 | if (e.ChangedButton == MouseButton.Left) this.DragMove();
275 | }
276 |
277 | void setAppTheme()
278 | {
279 | if (settings.LightTheme) App.SetTheme("ColorsLight.xaml");
280 | else App.SetTheme("Colors.xaml");
281 | filePanel.ExtensionsColors.Clear();
282 | if (settings.ColorizeFiles) { Import.FillColorsDictionary(filePanel.ExtensionsColors, settings.LightTheme); filePanel.Refresh(); }
283 | }
284 |
285 |
286 | void updateModelInfo()
287 | {
288 | if (ButtonShowModelInfo.IsChecked.Value) showModelInfo();
289 | else hideInfo();
290 | }
291 |
292 | void runSettings()
293 | {
294 | var settingsWindow = new SettingsWindow();
295 | settingsWindow.Owner = this;
296 | Utils.SetWindowStyle(settingsWindow, settings.UseSystemWindowStyle, settingsWindow.ButtonClose);
297 | settingsWindow.propertyPanel.SetObject(settings);
298 | this.Effect = new BlurEffect() { Radius = 4.0 };
299 | settingsWindow.ShowDialog();
300 | this.Effect = null;
301 | if (settingsWindow.WindowResult == SettingsWindowResult.ResetDefaults) { settings = new Settings(); }
302 | saveSettings();
303 | loadSettings();
304 | if (settingsWindow.WindowResult == SettingsWindowResult.OpenEditor) { settings.StartProcess(); Close(); }
305 | }
306 |
307 | void updateMeshes(bool forceUpdateWireframe)
308 | {
309 | var wfmode = ButtonWireframe.IsChecked.Value;
310 | if (wfmode)
311 | {
312 | if (meshWireframe == null || forceUpdateWireframe)
313 | meshWireframe = Utils.ConvertToWireframe(meshBase, settings.WireframeEdgeScale);
314 | }
315 | else meshWireframe = null;
316 | Utils.FillMesh(wfmode ? meshWireframe : meshBase, scene.Mesh);
317 | slice();
318 | }
319 |
320 | void initSlicer()
321 | {
322 | skipslice = true;
323 | sliderSliceHeight.Maximum = modelCenter.Z + modelSize.Z / 2;
324 | sliderSliceHeight.Minimum = modelCenter.Z - modelSize.Z / 2;
325 | sliderSliceHeight.Value = sliderSliceHeight.Maximum;
326 | sliderSliceHeight.LargeChange = 0.2;
327 | skipslice = false;
328 | }
329 |
330 | //int slicescount = 0;
331 | bool skipslice = false;
332 | void slice()
333 | {
334 | if (skipslice) return;
335 | Utils.SliceMesh(ButtonWireframe.IsChecked.Value ? meshWireframe : meshBase, scene.Mesh, sliderSliceHeight.Value);
336 | if (scene.Mesh.TriangleIndices.Count == 0) { scene.Mesh.TriangleIndices.Add(0); }
337 | //showInfo($"ms: {Utils.sw.ElapsedMilliseconds}\r\nticks: {Utils.sw.ElapsedTicks}\r\nheight: {sliderSliceHeight.Value}, slicescount {++slicescount}, tris: {mesh.TriangleIndices.Count}");
338 | }
339 |
340 | void export()
341 | {
342 | string exporters = string.Join(",", Export.Exports.Select(e => e.Description).OrderBy(e => e));
343 | string r = Utils.MessageWindow("Select export format", this, exporters, Orientation.Vertical);
344 | var exp = Export.Exports.FirstOrDefault(e => e.Description == r);
345 | if (exp == null) return;
346 | var sfd = new System.Windows.Forms.SaveFileDialog() { DefaultExt = exp.Extension, Filter = $"{exp.Description}|*.{exp.Extension}" };
347 | if (sfd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
348 | try
349 | {
350 | MeshGeometry3D m = scene.Mesh;
351 | if (exp.InitialXRotationNeeded) { m = scene.Mesh.Clone(); Utils.RotateMesh(m, settings.DefaultLookAtModelPointsAvgCenter ? modelPointsAvgCenter : modelCenter, -90, 0, 0); }
352 | exp.Save(m, sfd.FileName);
353 | showInfo($"Exported to file {sfd.FileName}");
354 | }
355 | catch (Exception exc) { Utils.MessageWindow($"Export failed with message\r\n{exc.Message}"); }
356 | }
357 | }
358 | }
359 |
--------------------------------------------------------------------------------