796 lines
21 KiB
C#
796 lines
21 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using MeshExplorer.Controls;
|
|
using MeshExplorer.IO;
|
|
using TriangleNet;
|
|
using TriangleNet.Geometry;
|
|
using TriangleNet.Meshing;
|
|
using TriangleNet.Meshing.Algorithm;
|
|
using TriangleNet.Rendering;
|
|
using TriangleNet.Smoothing;
|
|
using TriangleNet.Voronoi;
|
|
|
|
namespace MeshExplorer
|
|
{
|
|
public partial class FormMain : Form
|
|
{
|
|
Settings settings;
|
|
|
|
Mesh mesh;
|
|
IPolygon input;
|
|
VoronoiBase voronoi;
|
|
|
|
FormLog frmLog;
|
|
FormGenerator frmGenerator;
|
|
|
|
RenderManager renderManager;
|
|
|
|
public FormMain()
|
|
{
|
|
InitializeComponent();
|
|
|
|
ToolStripManager.Renderer = new DarkToolStripRenderer();
|
|
}
|
|
|
|
private void Form1_Load(object sender, EventArgs e)
|
|
{
|
|
oldClientSize = this.ClientSize;
|
|
|
|
settings = new Settings();
|
|
|
|
renderManager = new RenderManager();
|
|
|
|
IRenderControl control = new TriangleNet.Rendering.GDI.RenderControl();
|
|
|
|
/*
|
|
if (!renderManager.TryCreateControl("Triangle.Rendering.SharpGL.dll",
|
|
new string[] { "SharpGL.dll" }, out control))
|
|
{
|
|
control = new TriangleNet.Rendering.GDI.RenderControl();
|
|
|
|
if (frmLog == null)
|
|
{
|
|
frmLog = new FormLog();
|
|
}
|
|
|
|
frmLog.AddItem("Failed to initialize OpenGL.", true);
|
|
}
|
|
//*/
|
|
|
|
if (control != null)
|
|
{
|
|
InitializeRenderControl((Control)control);
|
|
renderManager.Initialize(control);
|
|
}
|
|
else
|
|
{
|
|
DarkMessageBox.Show("Ooops ...", "Failed to initialize renderer.");
|
|
}
|
|
}
|
|
|
|
private void InitializeRenderControl(Control control)
|
|
{
|
|
this.splitContainer.SuspendLayout();
|
|
this.splitContainer.Panel2.Controls.Add(control);
|
|
|
|
var size = this.splitContainer.Panel2.ClientRectangle;
|
|
|
|
// Initialize control
|
|
control.BackColor = Color.Black;
|
|
control.Dock = DockStyle.Fill;
|
|
control.Font = new Font("Consolas", 8.25F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
|
|
control.Location = new System.Drawing.Point(0, 0);
|
|
control.Name = "renderControl1";
|
|
control.Size = new Size(size.Width, size.Height);
|
|
control.TabIndex = 0;
|
|
control.Text = "renderControl1";
|
|
|
|
this.splitContainer.ResumeLayout();
|
|
}
|
|
|
|
protected override void OnMouseWheel(MouseEventArgs e)
|
|
{
|
|
if (splitContainer.Panel2.Bounds.Contains(e.Location))
|
|
{
|
|
var control = renderManager.Control as Control;
|
|
|
|
// Set focus on the render control.
|
|
if (control != null && !control.Focused)
|
|
{
|
|
control.Focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Form1_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
switch (e.KeyCode)
|
|
{
|
|
case Keys.F3:
|
|
OpenWithDialog();
|
|
break;
|
|
case Keys.F4:
|
|
Save();
|
|
break;
|
|
case Keys.F5:
|
|
Reload();
|
|
break;
|
|
case Keys.F8:
|
|
TriangulateOrRefine();
|
|
break;
|
|
case Keys.F9:
|
|
Smooth();
|
|
break;
|
|
case Keys.F12:
|
|
ShowLog();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void frmGenerator_InputGenerated(object sender, EventArgs e)
|
|
{
|
|
this.input = sender as IPolygon;
|
|
|
|
if (input != null)
|
|
{
|
|
settings.CurrentFile = "tmp-" + DateTime.Now.ToString("HH-mm-ss");
|
|
HandleNewInput();
|
|
}
|
|
}
|
|
|
|
private void btnMesh_Click(object sender, EventArgs e)
|
|
{
|
|
TriangulateOrRefine();
|
|
}
|
|
|
|
private void btnSmooth_Click(object sender, EventArgs e)
|
|
{
|
|
Smooth();
|
|
}
|
|
|
|
#region Drag and drop
|
|
|
|
private void frmDragDrop(object sender, DragEventArgs e)
|
|
{
|
|
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
|
{
|
|
string[] args = (string[])e.Data.GetData(DataFormats.FileDrop);
|
|
|
|
Open(args[0]);
|
|
}
|
|
}
|
|
|
|
private void frmDragOver(object sender, DragEventArgs e)
|
|
{
|
|
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
|
{
|
|
string[] args = (string[])e.Data.GetData(DataFormats.FileDrop);
|
|
|
|
if (args.Length > 1)
|
|
{
|
|
e.Effect = DragDropEffects.None;
|
|
return;
|
|
}
|
|
|
|
string file = args[0].ToLower();
|
|
|
|
// Check if file extension is known
|
|
if (FileProcessor.CanHandleFile(file))
|
|
{
|
|
e.Effect = DragDropEffects.Copy;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Effect = DragDropEffects.None;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Resize event handler
|
|
|
|
bool isResizing = false;
|
|
Size oldClientSize;
|
|
|
|
private void ResizeHandler(object sender, EventArgs e)
|
|
{
|
|
// Handle window minimize and maximize
|
|
if (!isResizing)
|
|
{
|
|
renderManager.Resize();
|
|
}
|
|
}
|
|
|
|
private void ResizeEndHandler(object sender, EventArgs e)
|
|
{
|
|
isResizing = false;
|
|
|
|
if (this.ClientSize != this.oldClientSize)
|
|
{
|
|
this.oldClientSize = this.ClientSize;
|
|
renderManager.Resize();
|
|
}
|
|
}
|
|
|
|
private void ResizeBeginHandler(object sender, EventArgs e)
|
|
{
|
|
isResizing = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region State changes
|
|
|
|
private void LockOnException()
|
|
{
|
|
btnMesh.Enabled = false;
|
|
btnSmooth.Enabled = false;
|
|
|
|
//menuFileSave.Enabled = false;
|
|
//menuFileExport.Enabled = false;
|
|
menuViewVoronoi.Enabled = false;
|
|
menuToolsCheck.Enabled = false;
|
|
menuToolsRcm.Enabled = false;
|
|
|
|
settings.ExceptionThrown = true;
|
|
}
|
|
|
|
private void HandleNewInput()
|
|
{
|
|
// Reset mesh
|
|
mesh = null;
|
|
voronoi = null;
|
|
|
|
// Reset state
|
|
settings.RefineMode = false;
|
|
settings.ExceptionThrown = false;
|
|
|
|
// Reset buttons
|
|
btnMesh.Enabled = true;
|
|
btnMesh.Text = "Triangulate";
|
|
btnSmooth.Enabled = false;
|
|
|
|
// Update Statistic view
|
|
statisticView.HandleNewInput(input);
|
|
|
|
// Clear voronoi
|
|
menuViewVoronoi.Checked = false;
|
|
|
|
// Disable menu items
|
|
menuFileSave.Enabled = false;
|
|
menuFileExport.Enabled = false;
|
|
menuViewVoronoi.Enabled = false;
|
|
menuToolsCheck.Enabled = false;
|
|
menuToolsRcm.Enabled = false;
|
|
|
|
// Render input
|
|
renderManager.Set(input);
|
|
|
|
// Update window caption
|
|
this.Text = "Triangle.NET - Mesh Explorer - " + settings.CurrentFile;
|
|
}
|
|
|
|
private void HandleMeshImport()
|
|
{
|
|
voronoi = null;
|
|
|
|
// Render mesh
|
|
renderManager.Set(mesh, true);
|
|
|
|
// Update window caption
|
|
this.Text = "Triangle.NET - Mesh Explorer - " + settings.CurrentFile;
|
|
|
|
// Update Statistic view
|
|
statisticView.HandleMeshImport(input, mesh);
|
|
|
|
// Set refine mode
|
|
btnMesh.Enabled = true;
|
|
btnMesh.Text = "Refine";
|
|
|
|
settings.RefineMode = true;
|
|
|
|
HandleMeshChange();
|
|
}
|
|
|
|
private void HandleMeshUpdate()
|
|
{
|
|
// Render mesh
|
|
renderManager.Set(mesh, false);
|
|
|
|
// Update Statistic view
|
|
statisticView.HandleMeshUpdate(mesh);
|
|
|
|
HandleMeshChange();
|
|
}
|
|
|
|
private void HandleMeshChange()
|
|
{
|
|
// Update Statistic view
|
|
statisticView.HandleMeshChange(mesh);
|
|
|
|
// TODO: Should the Voronoi diagram automatically update?
|
|
voronoi = null;
|
|
menuViewVoronoi.Checked = false;
|
|
|
|
// Enable menu items
|
|
menuFileSave.Enabled = true;
|
|
menuFileExport.Enabled = true;
|
|
menuViewVoronoi.Enabled = true;
|
|
menuToolsCheck.Enabled = true;
|
|
menuToolsRcm.Enabled = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Commands
|
|
|
|
private void OpenWithDialog()
|
|
{
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
|
|
|
ofd.Filter = settings.OfdFilter;
|
|
ofd.FilterIndex = settings.OfdFilterIndex;
|
|
ofd.InitialDirectory = settings.OfdDirectory;
|
|
ofd.FileName = "";
|
|
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
if (Open(ofd.FileName))
|
|
{
|
|
// Update folder settings
|
|
settings.OfdFilterIndex = ofd.FilterIndex;
|
|
settings.OfdDirectory = Path.GetDirectoryName(ofd.FileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool Open(string filename)
|
|
{
|
|
if (!FileProcessor.CanHandleFile(filename))
|
|
{
|
|
// TODO: show message.
|
|
}
|
|
else
|
|
{
|
|
if (FileProcessor.ContainsMeshData(filename))
|
|
{
|
|
if (filename.EndsWith(".ele") || DarkMessageBox.Show("Import mesh", Settings.ImportString,
|
|
"Do you want to import the mesh?", MessageBoxButtons.YesNo) == DialogResult.OK)
|
|
{
|
|
input = null;
|
|
|
|
try
|
|
{
|
|
mesh = FileProcessor.Import(filename);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DarkMessageBox.Show("Import mesh error", e.Message, MessageBoxButtons.OK);
|
|
return false;
|
|
}
|
|
|
|
if (mesh != null)
|
|
{
|
|
statisticView.UpdateStatistic(mesh);
|
|
|
|
// Update settings
|
|
settings.CurrentFile = Path.GetFileName(filename);
|
|
|
|
HandleMeshImport();
|
|
btnSmooth.Enabled = true; // TODO: Remove
|
|
}
|
|
// else Message
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
input = FileProcessor.Read(filename);
|
|
}
|
|
|
|
if (input != null)
|
|
{
|
|
// Update settings
|
|
settings.CurrentFile = Path.GetFileName(filename);
|
|
|
|
HandleNewInput();
|
|
}
|
|
// else Message
|
|
|
|
return true;
|
|
}
|
|
|
|
private void Save()
|
|
{
|
|
SaveFileDialog sfd = new SaveFileDialog();
|
|
|
|
sfd.Filter = settings.SfdFilter;
|
|
sfd.FilterIndex = settings.SfdFilterIndex;
|
|
sfd.InitialDirectory = settings.SfdDirectory;
|
|
sfd.FileName = "";
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
FileProcessor.Save(sfd.FileName, mesh);
|
|
}
|
|
}
|
|
|
|
private void Reload()
|
|
{
|
|
if (input != null)
|
|
{
|
|
mesh = null;
|
|
settings.RefineMode = false;
|
|
settings.ExceptionThrown = false;
|
|
|
|
HandleNewInput();
|
|
}
|
|
}
|
|
|
|
private void TriangulateOrRefine()
|
|
{
|
|
if ((input == null && !settings.RefineMode) || settings.ExceptionThrown)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (settings.RefineMode == false)
|
|
{
|
|
Triangulate();
|
|
|
|
if (meshControlView.ParamQualityChecked)
|
|
{
|
|
btnMesh.Text = "Refine";
|
|
btnSmooth.Enabled = mesh.IsPolygon;
|
|
}
|
|
}
|
|
else if (meshControlView.ParamQualityChecked)
|
|
{
|
|
Refine();
|
|
}
|
|
}
|
|
|
|
private void Triangulate()
|
|
{
|
|
if (input == null) return;
|
|
|
|
var options = new ConstraintOptions();
|
|
var quality = new QualityOptions();
|
|
|
|
if (meshControlView.ParamConformDelChecked)
|
|
{
|
|
options.ConformingDelaunay = true;
|
|
}
|
|
|
|
if (meshControlView.ParamQualityChecked)
|
|
{
|
|
quality.MinimumAngle = meshControlView.ParamMinAngleValue;
|
|
|
|
double maxAngle = meshControlView.ParamMaxAngleValue;
|
|
|
|
if (maxAngle < 180)
|
|
{
|
|
quality.MaximumAngle = maxAngle;
|
|
}
|
|
|
|
// Ignore area constraints on initial triangulation.
|
|
|
|
//double area = slMaxArea.Value * 0.01;
|
|
//if (area > 0 && area < 1)
|
|
//{
|
|
// var size = input.Bounds;
|
|
// double min = Math.Min(size.Width, size.Height);
|
|
// mesh.SetOption(Options.MaxArea, area * min);
|
|
//}
|
|
}
|
|
|
|
if (meshControlView.ParamConvexChecked)
|
|
{
|
|
options.Convex = true;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (meshControlView.ParamSweeplineChecked)
|
|
{
|
|
mesh = (Mesh)input.Triangulate(options, quality, new SweepLine());
|
|
}
|
|
else
|
|
{
|
|
mesh = (Mesh)input.Triangulate(options, quality);
|
|
}
|
|
|
|
statisticView.UpdateStatistic(mesh);
|
|
|
|
HandleMeshUpdate();
|
|
|
|
if (meshControlView.ParamQualityChecked)
|
|
{
|
|
settings.RefineMode = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LockOnException();
|
|
DarkMessageBox.Show("Exception - Triangulate", ex.Message, MessageBoxButtons.OK);
|
|
}
|
|
|
|
UpdateLog();
|
|
}
|
|
|
|
private void Refine()
|
|
{
|
|
if (mesh == null) return;
|
|
|
|
double area = meshControlView.ParamMaxAreaValue;
|
|
|
|
var quality = new QualityOptions();
|
|
|
|
if (area > 0 && area < 1)
|
|
{
|
|
quality.MaximumArea = area * statisticView.Statistic.LargestArea;
|
|
}
|
|
|
|
quality.MinimumAngle = meshControlView.ParamMinAngleValue;
|
|
|
|
double maxAngle = meshControlView.ParamMaxAngleValue;
|
|
|
|
if (maxAngle < 180)
|
|
{
|
|
quality.MaximumAngle = maxAngle;
|
|
}
|
|
|
|
try
|
|
{
|
|
mesh.Refine(quality, meshControlView.ParamConformDelChecked);
|
|
|
|
statisticView.UpdateStatistic(mesh);
|
|
|
|
HandleMeshUpdate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LockOnException();
|
|
DarkMessageBox.Show("Exception - Refine", ex.Message, MessageBoxButtons.OK);
|
|
}
|
|
|
|
UpdateLog();
|
|
}
|
|
|
|
private void Renumber()
|
|
{
|
|
if (mesh == null || settings.ExceptionThrown) return;
|
|
|
|
bool tmp = Log.Verbose;
|
|
Log.Verbose = true;
|
|
|
|
mesh.Renumber(NodeNumbering.CuthillMcKee);
|
|
ShowLog();
|
|
|
|
Log.Verbose = tmp;
|
|
}
|
|
|
|
private void Smooth()
|
|
{
|
|
if (mesh == null || settings.ExceptionThrown) return;
|
|
|
|
if (!mesh.IsPolygon)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var smoother = new SimpleSmoother();
|
|
|
|
try
|
|
{
|
|
smoother.Smooth(this.mesh);
|
|
|
|
statisticView.UpdateStatistic(mesh);
|
|
|
|
HandleMeshUpdate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LockOnException();
|
|
DarkMessageBox.Show("Exception - Smooth", ex.Message, MessageBoxButtons.OK);
|
|
}
|
|
|
|
UpdateLog();
|
|
}
|
|
|
|
private bool CreateVoronoi()
|
|
{
|
|
if (mesh == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (mesh.IsPolygon)
|
|
{
|
|
try
|
|
{
|
|
this.voronoi = new BoundedVoronoi(mesh);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (!meshControlView.ParamConformDelChecked)
|
|
{
|
|
DarkMessageBox.Show("Exception - Bounded Voronoi", Settings.VoronoiString, MessageBoxButtons.OK);
|
|
}
|
|
else
|
|
{
|
|
DarkMessageBox.Show("Exception - Bounded Voronoi", ex.Message, MessageBoxButtons.OK);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.voronoi = new StandardVoronoi(mesh);
|
|
}
|
|
|
|
// HACK: List<Vertex> -> ICollection<Point> ? Nope, no way.
|
|
// Vertex[] -> ICollection<Point> ? Well, ok.
|
|
renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void ShowLog()
|
|
{
|
|
if (frmLog == null)
|
|
{
|
|
frmLog = new FormLog();
|
|
}
|
|
|
|
UpdateLog();
|
|
|
|
if (!frmLog.Visible)
|
|
{
|
|
frmLog.Show(this);
|
|
}
|
|
}
|
|
|
|
private void UpdateLog()
|
|
{
|
|
if (frmLog != null)
|
|
{
|
|
frmLog.UpdateItems();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Menu Handler
|
|
|
|
private void menuFileOpen_Click(object sender, EventArgs e)
|
|
{
|
|
OpenWithDialog();
|
|
}
|
|
|
|
private void menuFileSave_Click(object sender, EventArgs ev)
|
|
{
|
|
if (mesh != null)
|
|
{
|
|
Save();
|
|
}
|
|
}
|
|
|
|
private void menuFileExport_Click(object sender, EventArgs e)
|
|
{
|
|
if (mesh != null)
|
|
{
|
|
FormExport export = new FormExport();
|
|
|
|
string file = settings.OfdDirectory;
|
|
|
|
if (!file.EndsWith("\\"))
|
|
{
|
|
file += "\\";
|
|
}
|
|
|
|
file += settings.CurrentFile;
|
|
|
|
export.ImageName = Path.ChangeExtension(file, ".png");
|
|
|
|
if (export.ShowDialog() == DialogResult.OK)
|
|
{
|
|
int format = export.ImageFormat;
|
|
int size = export.ImageSize;
|
|
bool compress = export.UseCompression;
|
|
|
|
var writer = new ImageWriter();
|
|
|
|
writer.Export(this.mesh, export.ImageName, format, size, compress);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void menuFileQuit_Click(object sender, EventArgs e)
|
|
{
|
|
this.Close();
|
|
}
|
|
|
|
private void menuViewVoronoi_Click(object sender, EventArgs e)
|
|
{
|
|
if (this.voronoi == null)
|
|
{
|
|
menuViewVoronoi.Checked = CreateVoronoi();
|
|
}
|
|
else
|
|
{
|
|
bool visible = menuViewVoronoi.Checked;
|
|
|
|
renderManager.Enable(4, !visible);
|
|
menuViewVoronoi.Checked = !visible;
|
|
}
|
|
}
|
|
|
|
private void menuViewLog_Click(object sender, EventArgs e)
|
|
{
|
|
ShowLog();
|
|
}
|
|
|
|
private void menuToolsGenerator_Click(object sender, EventArgs e)
|
|
{
|
|
if (frmGenerator == null || frmGenerator.IsDisposed)
|
|
{
|
|
frmGenerator = new FormGenerator();
|
|
frmGenerator.InputGenerated += new EventHandler(frmGenerator_InputGenerated);
|
|
}
|
|
|
|
if (!frmGenerator.Visible)
|
|
{
|
|
frmGenerator.Show();
|
|
}
|
|
else
|
|
{
|
|
frmGenerator.Activate();
|
|
}
|
|
}
|
|
|
|
private void menuToolsCheck_Click(object sender, EventArgs e)
|
|
{
|
|
if (mesh != null)
|
|
{
|
|
bool save = Log.Verbose;
|
|
|
|
Log.Verbose = true;
|
|
|
|
bool isConsistent = MeshValidator.IsConsistent(mesh);
|
|
bool isDelaunay = MeshValidator.IsDelaunay(mesh);
|
|
|
|
Log.Verbose = save;
|
|
|
|
if (isConsistent)
|
|
{
|
|
Log.Instance.Info("Mesh topology appears to be consistent.");
|
|
}
|
|
|
|
if (isDelaunay)
|
|
{
|
|
Log.Instance.Info("Mesh is (conforming) Delaunay.");
|
|
}
|
|
|
|
ShowLog();
|
|
}
|
|
}
|
|
|
|
private void menuToolsTopology_Click(object sender, EventArgs e)
|
|
{
|
|
(new FormTopology()).ShowDialog(this);
|
|
}
|
|
|
|
private void menuToolsRcm_Click(object sender, EventArgs e)
|
|
{
|
|
Renumber();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|