982 lines
No EOL
29 KiB
C#
982 lines
No EOL
29 KiB
C#
/*
|
|
Copyright (c) 2014, Lars Brubaker
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
The views and conclusions contained in the software and documentation are those
|
|
of the authors and should not be interpreted as representing official policies,
|
|
either expressed or implied, of the FreeBSD Project.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MatterHackers.Agg;
|
|
using MatterHackers.Agg.Image;
|
|
using MatterHackers.Agg.OpenGlGui;
|
|
using MatterHackers.Agg.UI;
|
|
using MatterHackers.DataConverters3D;
|
|
using MatterHackers.MatterControl;
|
|
using MatterHackers.MatterControl.DesignTools.EditableTypes;
|
|
using MatterHackers.MatterControl.DesignTools.Operations;
|
|
using MatterHackers.MatterControl.PartPreviewWindow;
|
|
using MatterHackers.MatterControl.PartPreviewWindow.View3D;
|
|
using MatterHackers.PolygonMesh;
|
|
using MatterHackers.RenderOpenGl;
|
|
using MatterHackers.RenderOpenGl.OpenGl;
|
|
using MatterHackers.VectorMath;
|
|
using static MatterHackers.Agg.Easing;
|
|
|
|
namespace MatterHackers.MeshVisualizer
|
|
{
|
|
public enum BedShape { Rectangular, Circular };
|
|
|
|
public class DrawGlContentEventArgs : DrawEventArgs
|
|
{
|
|
public bool ZBuffered { get; }
|
|
|
|
public DrawGlContentEventArgs(bool zBuffered, DrawEventArgs e)
|
|
: base(e.Graphics2D)
|
|
{
|
|
ZBuffered = zBuffered;
|
|
}
|
|
}
|
|
|
|
public static class MaterialRendering
|
|
{
|
|
public static void RenderCylinderOutline(this WorldView world, Matrix4X4 worldMatrix, Vector3 center, double Diameter, double Height, int sides, Color color, double lineWidth = 1, double extendLineLength = 0)
|
|
{
|
|
GLHelper.PrepareFor3DLineRender(true);
|
|
Frustum frustum = world.GetClippingFrustum();
|
|
for (int i = 0; i < sides; i++)
|
|
{
|
|
var rotatedPoint = new Vector3(Math.Cos(MathHelper.Tau * i / sides), Math.Sin(MathHelper.Tau * i / sides), 0) * Diameter / 2;
|
|
var sideTop = Vector3.Transform(center + rotatedPoint + new Vector3(0, 0, Height / 2), worldMatrix);
|
|
var sideBottom = Vector3.Transform(center + rotatedPoint + new Vector3(0, 0, -Height / 2), worldMatrix);
|
|
var rotated2Point = new Vector3(Math.Cos(MathHelper.Tau * (i + 1) / sides), Math.Sin(MathHelper.Tau * (i + 1) / sides), 0) * Diameter / 2;
|
|
var topStart = sideTop;
|
|
var topEnd = Vector3.Transform(center + rotated2Point + new Vector3(0, 0, Height / 2), worldMatrix);
|
|
var bottomStart = sideBottom;
|
|
var bottomEnd = Vector3.Transform(center + rotated2Point + new Vector3(0, 0, -Height / 2), worldMatrix);
|
|
|
|
if (extendLineLength > 0)
|
|
{
|
|
GLHelper.ExtendLineEnds(ref sideTop, ref sideBottom, extendLineLength);
|
|
}
|
|
|
|
world.Render3DLineNoPrep(frustum, sideTop, sideBottom, color, lineWidth);
|
|
world.Render3DLineNoPrep(frustum, topStart, topEnd, color, lineWidth);
|
|
world.Render3DLineNoPrep(frustum, bottomStart, bottomEnd, color, lineWidth);
|
|
}
|
|
|
|
// turn the lighting back on
|
|
GL.Enable(EnableCap.Lighting);
|
|
}
|
|
|
|
public static void RenderAabb(this WorldView world, AxisAlignedBoundingBox bounds, Matrix4X4 matrix, Color color, double width, double extendLineLength = 0)
|
|
{
|
|
GLHelper.PrepareFor3DLineRender(true);
|
|
|
|
Frustum frustum = world.GetClippingFrustum();
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
Vector3 sideStartPosition = Vector3.Transform(bounds.GetBottomCorner(i), matrix);
|
|
Vector3 sideEndPosition = Vector3.Transform(bounds.GetTopCorner(i), matrix);
|
|
|
|
Vector3 bottomStartPosition = sideStartPosition;
|
|
Vector3 bottomEndPosition = Vector3.Transform(bounds.GetBottomCorner((i + 1) % 4), matrix);
|
|
|
|
Vector3 topStartPosition = sideEndPosition;
|
|
Vector3 topEndPosition = Vector3.Transform(bounds.GetTopCorner((i + 1) % 4), matrix);
|
|
|
|
if (extendLineLength > 0)
|
|
{
|
|
GLHelper.ExtendLineEnds(ref sideStartPosition, ref sideEndPosition, extendLineLength);
|
|
GLHelper.ExtendLineEnds(ref topStartPosition, ref topEndPosition, extendLineLength);
|
|
GLHelper.ExtendLineEnds(ref bottomStartPosition, ref bottomEndPosition, extendLineLength);
|
|
}
|
|
|
|
// draw each of the edge lines (4) and their touching top and bottom lines (2 each)
|
|
world.Render3DLineNoPrep(frustum, sideStartPosition, sideEndPosition, color, width);
|
|
world.Render3DLineNoPrep(frustum, topStartPosition, topEndPosition, color, width);
|
|
world.Render3DLineNoPrep(frustum, bottomStartPosition, bottomEndPosition, color, width);
|
|
}
|
|
|
|
GL.Enable(EnableCap.Lighting);
|
|
}
|
|
|
|
public static void RenderAxis(this WorldView world, Vector3 position, Matrix4X4 matrix, double size, double lineWidth)
|
|
{
|
|
GLHelper.PrepareFor3DLineRender(true);
|
|
|
|
Frustum frustum = world.GetClippingFrustum();
|
|
Vector3 length = Vector3.One * size;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
var min = position;
|
|
min[i] -= length[i];
|
|
Vector3 start = Vector3.Transform(min, matrix);
|
|
|
|
var max = position;
|
|
max[i] += length[i];
|
|
Vector3 end = Vector3.Transform(max, matrix);
|
|
|
|
var color = Agg.Color.Red;
|
|
switch (i)
|
|
{
|
|
case 1:
|
|
color = Agg.Color.Green;
|
|
break;
|
|
|
|
case 2:
|
|
color = Agg.Color.Blue;
|
|
break;
|
|
}
|
|
|
|
// draw each of the edge lines (4) and their touching top and bottom lines (2 each)
|
|
world.Render3DLineNoPrep(frustum, start, end, color, lineWidth);
|
|
}
|
|
|
|
GL.Enable(EnableCap.Lighting);
|
|
}
|
|
|
|
public static void RenderDirectionAxis(this WorldView world, DirectionAxis axis, Matrix4X4 matrix, double size)
|
|
{
|
|
GLHelper.PrepareFor3DLineRender(true);
|
|
|
|
Frustum frustum = world.GetClippingFrustum();
|
|
Vector3 length = axis.Normal * size;
|
|
var color = Agg.Color.Red;
|
|
|
|
// draw center line
|
|
{
|
|
var min = axis.Origin - length;
|
|
Vector3 start = Vector3.Transform(min, matrix);
|
|
|
|
var max = axis.Origin + length;
|
|
Vector3 end = Vector3.Transform(max, matrix);
|
|
|
|
world.Render3DLineNoPrep(frustum, start, end, color, 1);
|
|
}
|
|
|
|
var perpendicular = Vector3.GetPerpendicular(axis.Normal, Vector3.Zero).GetNormal();
|
|
// draw some lines to mark the rotation plane
|
|
int count = 20;
|
|
bool first = true;
|
|
var firstEnd = Vector3.Zero;
|
|
var lastEnd = Vector3.Zero;
|
|
var center = Vector3.Transform(axis.Origin, matrix);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var rotation = size/4 * Vector3.Transform(perpendicular, Matrix4X4.CreateRotation(axis.Normal, MathHelper.Tau * i / count));
|
|
// draw center line
|
|
var max = axis.Origin + rotation;
|
|
Vector3 end = Vector3.Transform(max, matrix);
|
|
|
|
world.Render3DLineNoPrep(frustum, center, end, color, 1);
|
|
if (!first)
|
|
{
|
|
world.Render3DLineNoPrep(frustum, end, lastEnd, color, 1);
|
|
}
|
|
else
|
|
{
|
|
firstEnd = end;
|
|
}
|
|
lastEnd = end;
|
|
first = false;
|
|
}
|
|
world.Render3DLineNoPrep(frustum, firstEnd, lastEnd, color, 1);
|
|
|
|
GL.Enable(EnableCap.Lighting);
|
|
}
|
|
|
|
public static Color Color(int materialIndex)
|
|
{
|
|
return ColorF.FromHSL(Math.Max(materialIndex, 0) / 10.0, .99, .49).ToColor();
|
|
}
|
|
|
|
public static bool InsideBuildVolume(this IObject3D item, PrinterConfig printerConfig)
|
|
{
|
|
if(item.Mesh == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var worldMatrix = item.WorldMatrix();
|
|
// probably need , true (require precision)
|
|
var aabb = item.Mesh.GetAxisAlignedBoundingBox(worldMatrix);
|
|
|
|
var bed = printerConfig.Bed;
|
|
|
|
if (bed.BuildHeight > 0
|
|
&& (aabb.maxXYZ.Z <= 0
|
|
|| aabb.maxXYZ.Z >= bed.BuildHeight))
|
|
{
|
|
// object completely below the bed or any part above the build volume
|
|
return false;
|
|
}
|
|
|
|
switch(bed.BedShape)
|
|
{
|
|
case BedShape.Rectangular:
|
|
if(aabb.minXYZ.X < bed.BedCenter.X - bed.ViewerVolume.X/2
|
|
|| aabb.maxXYZ.X > bed.BedCenter.X + bed.ViewerVolume.X / 2
|
|
|| aabb.minXYZ.Y < bed.BedCenter.Y - bed.ViewerVolume.Y / 2
|
|
|| aabb.maxXYZ.Y > bed.BedCenter.Y + bed.ViewerVolume.Y / 2)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case BedShape.Circular:
|
|
// This could be much better if it checked the actual vertext data of the mesh against the cylinder
|
|
// first check if any of it is outside the bed rect
|
|
if (aabb.minXYZ.X < bed.BedCenter.X - bed.ViewerVolume.X / 2
|
|
|| aabb.maxXYZ.X > bed.BedCenter.X + bed.ViewerVolume.X / 2
|
|
|| aabb.minXYZ.Y < bed.BedCenter.Y - bed.ViewerVolume.Y / 2
|
|
|| aabb.maxXYZ.Y > bed.BedCenter.Y + bed.ViewerVolume.Y / 2)
|
|
{
|
|
// TODO: then check if all of it is outside the bed circle
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public class MeshViewerWidget : GuiWidget
|
|
{
|
|
static ImageBuffer ViewOnlyTexture;
|
|
|
|
private Color lightWireframe = new Color("#aaa4");
|
|
private Color darkWireframe = new Color("#3334");
|
|
private Color gCodeMeshColor;
|
|
|
|
// TODO: Need to be instance based for multi-printer
|
|
public GuiWidget ParentSurface { get; set; }
|
|
|
|
private InteractionLayer interactionLayer;
|
|
|
|
private BedConfig sceneContext;
|
|
|
|
private double selectionHighlightWidth = 5;
|
|
|
|
private Color debugBorderColor = Color.Green;
|
|
|
|
public MeshViewerWidget(BedConfig sceneContext, InteractionLayer interactionLayer, string startingTextMessage = "", EditorType editorType = EditorType.Part)
|
|
{
|
|
this.EditorMode = editorType;
|
|
this.scene = sceneContext.Scene;
|
|
this.sceneContext = sceneContext;
|
|
this.interactionLayer = interactionLayer;
|
|
this.World = interactionLayer.World;
|
|
|
|
var theme = ApplicationController.Instance.Theme;
|
|
|
|
gCodeMeshColor = new Color(theme.Colors.PrimaryAccentColor, 35);
|
|
|
|
scene.SelectionChanged += (sender, e) =>
|
|
{
|
|
Invalidate();
|
|
lastSelectionChangedMs = UiThread.CurrentTimerMs;
|
|
};
|
|
|
|
BedColor = new ColorF(.8, .8, .8, .7).ToColor();
|
|
BuildVolumeColor = new ColorF(.2, .8, .3, .2).ToColor();
|
|
|
|
this.interactionLayer.DrawGlTransparentContent += Draw_GlTransparentContent;
|
|
|
|
if (ViewOnlyTexture == null)
|
|
{
|
|
UiThread.RunOnIdle(() =>
|
|
{
|
|
ViewOnlyTexture = new ImageBuffer(32, 32, 32);
|
|
var graphics2D = ViewOnlyTexture.NewGraphics2D();
|
|
graphics2D.Clear(Color.White);
|
|
graphics2D.FillRectangle(0, 0, ViewOnlyTexture.Width / 2, ViewOnlyTexture.Height, Color.LightGray);
|
|
// request the texture so we can set it to repeat
|
|
var plugin = ImageGlPlugin.GetImageGlPlugin(ViewOnlyTexture, true, true, false);
|
|
});
|
|
}
|
|
}
|
|
|
|
public override void OnParentChanged(EventArgs e)
|
|
{
|
|
this.ParentSurface = this.Parent;
|
|
base.OnParentChanged(e);
|
|
}
|
|
|
|
public WorldView World { get; }
|
|
|
|
public event EventHandler LoadDone;
|
|
|
|
public bool AllowBedRenderingWhenEmpty { get; set; }
|
|
public Color BedColor { get; set; }
|
|
|
|
public Color BuildVolumeColor { get; set; }
|
|
|
|
public override void OnLoad(EventArgs args)
|
|
{
|
|
// some debug code to be able to click on parts
|
|
if (false)
|
|
{
|
|
AfterDraw += (sender, e) =>
|
|
{
|
|
foreach (var child in scene.Children)
|
|
{
|
|
this.World.RenderDebugAABB(e.Graphics2D, child.TraceData().GetAxisAlignedBoundingBox());
|
|
this.World.RenderDebugAABB(e.Graphics2D, child.GetAxisAlignedBoundingBox(Matrix4X4.Identity));
|
|
}
|
|
};
|
|
}
|
|
|
|
base.OnLoad(args);
|
|
}
|
|
|
|
public override void FindNamedChildrenRecursive(string nameToSearchFor, List<WidgetAndPosition> foundChildren, RectangleDouble touchingBounds, SearchType seachType, bool allowInvalidItems = true)
|
|
{
|
|
foreach (InteractionVolume child in interactionLayer.InteractionVolumes)
|
|
{
|
|
string object3DName = child.Name;
|
|
|
|
bool nameFound = false;
|
|
|
|
if (seachType == SearchType.Exact)
|
|
{
|
|
if (object3DName == nameToSearchFor)
|
|
{
|
|
nameFound = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nameToSearchFor == ""
|
|
|| object3DName.Contains(nameToSearchFor))
|
|
{
|
|
nameFound = true;
|
|
}
|
|
}
|
|
|
|
if (nameFound
|
|
&& child.CollisionVolume != null)
|
|
{
|
|
AxisAlignedBoundingBox bounds = child.CollisionVolume.GetAxisAlignedBoundingBox();
|
|
bounds = bounds.NewTransformed(child.TotalTransform);
|
|
|
|
RectangleDouble screenBoundsOfObject3D = RectangleDouble.ZeroIntersection;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
screenBoundsOfObject3D.ExpandToInclude(this.World.GetScreenPosition(bounds.GetTopCorner(i)));
|
|
screenBoundsOfObject3D.ExpandToInclude(this.World.GetScreenPosition(bounds.GetBottomCorner(i)));
|
|
}
|
|
|
|
if (touchingBounds.IsTouching(screenBoundsOfObject3D))
|
|
{
|
|
Vector3 renderPosition = bounds.Center;
|
|
Vector2 objectCenterScreenSpace = this.World.GetScreenPosition(renderPosition);
|
|
Point2D screenPositionOfObject3D = new Point2D((int)objectCenterScreenSpace.X, (int)objectCenterScreenSpace.Y);
|
|
|
|
foundChildren.Add(new WidgetAndPosition(this, screenPositionOfObject3D, object3DName, child));
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var child in scene.Children)
|
|
{
|
|
string object3DName = child.Name;
|
|
if (object3DName == null && child.MeshPath != null)
|
|
{
|
|
object3DName = Path.GetFileName(child.MeshPath);
|
|
}
|
|
|
|
bool nameFound = false;
|
|
|
|
if (seachType == SearchType.Exact)
|
|
{
|
|
if (object3DName == nameToSearchFor)
|
|
{
|
|
nameFound = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nameToSearchFor == ""
|
|
|| object3DName.Contains(nameToSearchFor))
|
|
{
|
|
nameFound = true;
|
|
}
|
|
}
|
|
|
|
if (nameFound)
|
|
{
|
|
AxisAlignedBoundingBox bounds = child.TraceData().GetAxisAlignedBoundingBox();
|
|
|
|
RectangleDouble screenBoundsOfObject3D = RectangleDouble.ZeroIntersection;
|
|
for(int i=0; i<4; i++)
|
|
{
|
|
screenBoundsOfObject3D.ExpandToInclude(this.World.GetScreenPosition(bounds.GetTopCorner(i)));
|
|
screenBoundsOfObject3D.ExpandToInclude(this.World.GetScreenPosition(bounds.GetBottomCorner(i)));
|
|
}
|
|
|
|
if (touchingBounds.IsTouching(screenBoundsOfObject3D))
|
|
{
|
|
Vector3 renderPosition = bounds.Center;
|
|
Vector2 objectCenterScreenSpace = this.World.GetScreenPosition(renderPosition);
|
|
Point2D screenPositionOfObject3D = new Point2D((int)objectCenterScreenSpace.X, (int)objectCenterScreenSpace.Y);
|
|
|
|
foundChildren.Add(new WidgetAndPosition(this, screenPositionOfObject3D, object3DName, child));
|
|
}
|
|
}
|
|
}
|
|
|
|
base.FindNamedChildrenRecursive(nameToSearchFor, foundChildren, touchingBounds, seachType, allowInvalidItems);
|
|
}
|
|
|
|
protected InteractiveScene scene { get; }
|
|
|
|
public static void AssertDebugNotDefined()
|
|
{
|
|
#if DEBUG
|
|
throw new Exception("DEBUG is defined and should not be!");
|
|
#endif
|
|
}
|
|
|
|
public static Color GetExtruderColor(int extruderIndex)
|
|
{
|
|
return MaterialRendering.Color(extruderIndex);
|
|
}
|
|
|
|
public void CreateGlDataObject(IObject3D item)
|
|
{
|
|
if(item.Mesh != null)
|
|
{
|
|
GLMeshTrianglePlugin.Get(item.Mesh);
|
|
}
|
|
|
|
foreach (IObject3D child in item.Children.Where(o => o.Mesh != null))
|
|
{
|
|
GLMeshTrianglePlugin.Get(child.Mesh);
|
|
}
|
|
}
|
|
|
|
public bool SuppressUiVolumes { get; set; } = false;
|
|
|
|
private CancellationTokenSource fileLoadCancellationTokenSource;
|
|
|
|
public async Task LoadItemIntoScene(string itemPath, Vector2 bedCenter = new Vector2(), string itemName = null)
|
|
{
|
|
if (File.Exists(itemPath))
|
|
{
|
|
fileLoadCancellationTokenSource = new CancellationTokenSource();
|
|
|
|
// TODO: How to we handle mesh load errors? How do we report success?
|
|
IObject3D loadedItem = await Task.Run(() => Object3D.Load(itemPath, fileLoadCancellationTokenSource.Token));
|
|
if (loadedItem != null)
|
|
{
|
|
if (itemName != null)
|
|
{
|
|
loadedItem.Name = itemName;
|
|
}
|
|
|
|
// SetMeshAfterLoad
|
|
scene.Children.Modify(list =>
|
|
{
|
|
if (loadedItem.Mesh != null)
|
|
{
|
|
// STLs currently load directly into the mesh rather than as a group like AMF
|
|
list.Add(loadedItem);
|
|
}
|
|
else
|
|
{
|
|
list.AddRange(loadedItem.Children);
|
|
}
|
|
});
|
|
|
|
CreateGlDataObject(loadedItem);
|
|
}
|
|
else
|
|
{
|
|
// TODO: Error message container moved to Interaction Layer - how could we support this type of error for a loaded scene item?
|
|
//partProcessingInfo.centeredInfoText.Text = string.Format("Sorry! No 3D view available\nfor this file.");
|
|
}
|
|
|
|
// Invoke LoadDone event
|
|
LoadDone?.Invoke(this, null);
|
|
}
|
|
else
|
|
{
|
|
// TODO: Error message container moved to Interaction Layer - how could we support this type of error for a loaded scene item?
|
|
//partProcessingInfo.centeredInfoText.Text = string.Format("{0}\n'{1}'", "File not found on disk.", Path.GetFileName(itemPath));
|
|
}
|
|
|
|
fileLoadCancellationTokenSource = null;
|
|
}
|
|
|
|
public override void OnClosed(ClosedEventArgs e)
|
|
{
|
|
fileLoadCancellationTokenSource?.Cancel();
|
|
base.OnClosed(e);
|
|
}
|
|
|
|
private void DrawObject(IObject3D object3D, List<Object3DView> transparentMeshes, bool parentSelected, DrawEventArgs e)
|
|
{
|
|
foreach (var item in object3D.VisibleMeshes())
|
|
{
|
|
// check for correct persistable rendering
|
|
if(MeshViewerWidget.ViewOnlyTexture != null
|
|
&& item.Mesh.Faces.Count > 0)
|
|
{
|
|
ImageBuffer faceTexture = null;
|
|
item.Mesh.FaceTexture.TryGetValue((item.Mesh.Faces[0], 0), out faceTexture);
|
|
bool hasPersistableTexture = faceTexture == MeshViewerWidget.ViewOnlyTexture;
|
|
|
|
if (item.WorldPersistable())
|
|
{
|
|
if (hasPersistableTexture)
|
|
{
|
|
// make sure it does not have the view only texture
|
|
item.Mesh.RemoveTexture(ViewOnlyTexture, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!hasPersistableTexture)
|
|
{
|
|
// make sure it does have the view only texture
|
|
var aabb = item.Mesh.GetAxisAlignedBoundingBox();
|
|
var matrix = Matrix4X4.CreateScale(.5, .5, 1);
|
|
matrix *= Matrix4X4.CreateRotationZ(MathHelper.Tau / 8);
|
|
item.Mesh.PlaceTexture(ViewOnlyTexture, matrix);
|
|
}
|
|
}
|
|
}
|
|
|
|
Color drawColor = GetItemColor(item);
|
|
|
|
bool isDebugItem = (item == scene.DebugItem);
|
|
|
|
if (!sceneContext.ViewState.ModelView)
|
|
{
|
|
if (modelRenderStyle == ModelRenderStyle.WireframeAndSolid)
|
|
{
|
|
drawColor = gCodeMeshColor;
|
|
}
|
|
else if (modelRenderStyle == ModelRenderStyle.None)
|
|
{
|
|
drawColor = Color.Transparent;
|
|
}
|
|
}
|
|
|
|
if ((drawColor.alpha == 255
|
|
&& !item.Mesh.FaceTexture.Where((ft) => ft.Value.HasTransparency).Any())
|
|
|| isDebugItem)
|
|
{
|
|
// Render as solid
|
|
GLHelper.Render(item.Mesh, drawColor, item.WorldMatrix(), sceneContext.ViewState.RenderType, item.WorldMatrix() * World.ModelviewMatrix, darkWireframe);
|
|
}
|
|
else if (drawColor != Color.Transparent)
|
|
{
|
|
// Queue for transparency
|
|
transparentMeshes.Add(new Object3DView(item, drawColor));
|
|
}
|
|
|
|
var selectedItem = scene.SelectedItem;
|
|
bool isSelected = selectedItem != null
|
|
&& (selectedItem.DescendantsAndSelf().Any((i) => i == item)
|
|
|| selectedItem.Parents<ModifiedMeshObject3D>().Any((mw) => mw == item));
|
|
|
|
if (isSelected && scene.DrawSelection)
|
|
{
|
|
var frustum = World.GetClippingFrustum();
|
|
|
|
var selectionColor = Color.White;
|
|
double secondsSinceSelectionChanged = (UiThread.CurrentTimerMs - lastSelectionChangedMs) / 1000.0;
|
|
if (secondsSinceSelectionChanged < .5)
|
|
{
|
|
//var accentColor = ApplicationController.Instance.Theme.Colors.PrimaryAccentColor;
|
|
var accentColor = Color.LightGray;
|
|
if (secondsSinceSelectionChanged < .25)
|
|
{
|
|
selectionColor = Color.White.Blend(accentColor, Quadratic.InOut(secondsSinceSelectionChanged * 4));
|
|
}
|
|
else
|
|
{
|
|
selectionColor = accentColor.Blend(Color.White, Quadratic.InOut((secondsSinceSelectionChanged - .25) * 4));
|
|
}
|
|
Invalidate();
|
|
}
|
|
|
|
RenderSelection(item, frustum, selectionColor);
|
|
}
|
|
|
|
#if DEBUG
|
|
if (isDebugItem)
|
|
{
|
|
var frustum = World.GetClippingFrustum();
|
|
|
|
var aabb = object3D.GetAxisAlignedBoundingBox(Matrix4X4.Identity);
|
|
|
|
World.RenderAabb(aabb, Matrix4X4.Identity, debugBorderColor, 1);
|
|
|
|
if (item.Mesh != null)
|
|
{
|
|
GLHelper.Render(item.Mesh, debugBorderColor, item.WorldMatrix(),
|
|
RenderTypes.Wireframe, item.WorldMatrix() * World.ModelviewMatrix);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// RenderNormals(renderData);
|
|
|
|
// turn lighting back on after rendering selection outlines
|
|
GL.Enable(EnableCap.Lighting);
|
|
}
|
|
}
|
|
|
|
private Color GetItemColor(IObject3D item)
|
|
{
|
|
Color drawColor = item.WorldColor();
|
|
if (item.WorldOutputType() == PrintOutputTypes.Support)
|
|
{
|
|
drawColor = new Color(Color.Yellow, 120);
|
|
}
|
|
|
|
// If there is a printer - check if the object is within the bed volume (has no AABB outside the bed volume)
|
|
if (sceneContext.Printer != null)
|
|
{
|
|
if (!item.InsideBuildVolume(sceneContext.Printer))
|
|
{
|
|
drawColor = new Color(drawColor, 65);
|
|
}
|
|
}
|
|
|
|
// check if we should be rendering materials (this overrides the other colors)
|
|
if (sceneContext.ViewState.RenderType == RenderTypes.Materials)
|
|
{
|
|
drawColor = MaterialRendering.Color(item.WorldMaterialIndex());
|
|
}
|
|
|
|
if(drawColor.alpha != 255
|
|
&& item is Object3D item3D)
|
|
{
|
|
item3D.EnsureTransparentSorting();
|
|
}
|
|
|
|
return drawColor;
|
|
}
|
|
|
|
private void RenderNormals(IObject3D renderData)
|
|
{
|
|
var frustum = World.GetClippingFrustum();
|
|
|
|
foreach (var face in renderData.Mesh.Faces)
|
|
{
|
|
int vertexCount = 0;
|
|
Vector3 faceCenter = Vector3.Zero;
|
|
foreach (var vertex in face.Vertices())
|
|
{
|
|
faceCenter += vertex.Position;
|
|
vertexCount++;
|
|
}
|
|
faceCenter /= vertexCount;
|
|
|
|
var transformed1 = Vector3.Transform(faceCenter, renderData.Matrix);
|
|
var normal = Vector3.TransformNormal(face.Normal, renderData.Matrix).GetNormal();
|
|
|
|
World.Render3DLineNoPrep(frustum, transformed1, transformed1 + normal, Color.Red, 2);
|
|
}
|
|
}
|
|
|
|
private void RenderSelection(IObject3D item, Frustum frustum, Color selectionColor)
|
|
{
|
|
if (item.Mesh == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Turn off lighting
|
|
GL.Disable(EnableCap.Lighting);
|
|
// Only render back faces
|
|
GL.CullFace(CullFaceMode.Front);
|
|
// Expand the object
|
|
var worldMatrix = item.WorldMatrix();
|
|
var worldBounds = item.Mesh.GetAxisAlignedBoundingBox(worldMatrix);
|
|
var worldCenter = worldBounds.Center;
|
|
double distBetweenPixelsWorldSpace = World.GetWorldUnitsPerScreenPixelAtPosition(worldCenter);
|
|
var pixelsAccross = worldBounds.Size / distBetweenPixelsWorldSpace;
|
|
var pixelsWant = pixelsAccross + Vector3.One * 4;
|
|
|
|
var wantMm = pixelsWant * distBetweenPixelsWorldSpace;
|
|
|
|
var scaleMatrix = worldMatrix.ApplyAtPosition(worldCenter, Matrix4X4.CreateScale(
|
|
wantMm.X / worldBounds.XSize,
|
|
wantMm.Y / worldBounds.YSize,
|
|
wantMm.Z / worldBounds.ZSize));
|
|
|
|
GLHelper.Render(item.Mesh,
|
|
selectionColor,
|
|
scaleMatrix, RenderTypes.Shaded,
|
|
null,
|
|
darkWireframe);
|
|
|
|
// restore settings
|
|
GL.CullFace(CullFaceMode.Back);
|
|
GL.Enable(EnableCap.Lighting);
|
|
}
|
|
|
|
public enum EditorType { Printer, Part }
|
|
|
|
public EditorType EditorMode { get; set; } = EditorType.Part;
|
|
|
|
private int BackToFrontXY(Object3DView a, Object3DView b)
|
|
{
|
|
var meshA = a.Object3D.Mesh;
|
|
var meshB = b.Object3D.Mesh;
|
|
|
|
if (meshA == null)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (meshB == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
var aCenterWorld = Vector3.Transform(meshA.GetAxisAlignedBoundingBox().Center, a.Object3D.Matrix);
|
|
aCenterWorld.Z = 0; // we only want to look at the distance on xy in world space
|
|
var aCenterInViewSpace = Vector3.Transform(aCenterWorld, World.ModelviewMatrix);
|
|
|
|
var bCenterWorld = Vector3.Transform(meshB.GetAxisAlignedBoundingBox().Center, b.Object3D.Matrix);
|
|
bCenterWorld.Z = 0; // we only want to look at the distance on xy in world space
|
|
var bCenterInViewSpace = Vector3.Transform(bCenterWorld, World.ModelviewMatrix);
|
|
|
|
return bCenterInViewSpace.LengthSquared.CompareTo(aCenterInViewSpace.LengthSquared);
|
|
}
|
|
|
|
private void Draw_GlTransparentContent(object sender, DrawEventArgs e)
|
|
{
|
|
var gcodeOptions = sceneContext.RendererOptions;
|
|
|
|
switch(gcodeOptions.GCodeModelView)
|
|
{
|
|
case "Wireframe":
|
|
modelRenderStyle = ModelRenderStyle.Wireframe;
|
|
break;
|
|
|
|
case "Semi-Transparent":
|
|
modelRenderStyle = ModelRenderStyle.WireframeAndSolid;
|
|
break;
|
|
|
|
default:
|
|
modelRenderStyle = ModelRenderStyle.None;
|
|
break;
|
|
}
|
|
|
|
// Draw solid objects, extract transparent
|
|
var transparentMeshes = new List<Object3DView>();
|
|
foreach (var object3D in scene.Children)
|
|
{
|
|
if (object3D.Visible)
|
|
{
|
|
DrawObject(object3D, transparentMeshes, false, e);
|
|
}
|
|
}
|
|
|
|
transparentMeshes.Sort(BackToFrontXY);
|
|
|
|
var bedNormalInViewSpace = Vector3.TransformNormal(Vector3.UnitZ, World.ModelviewMatrix).GetNormal();
|
|
var pointOnBedInViewSpace = Vector3.Transform(new Vector3(10, 10, 0), World.ModelviewMatrix);
|
|
var lookingDownOnBed = Vector3.Dot(bedNormalInViewSpace, pointOnBedInViewSpace) < 0;
|
|
|
|
if (lookingDownOnBed)
|
|
{
|
|
RenderBedMesh(lookingDownOnBed);
|
|
}
|
|
|
|
// Draw transparent objects
|
|
foreach (var item in transparentMeshes)
|
|
{
|
|
var object3D = item.Object3D;
|
|
GLHelper.Render(
|
|
object3D.Mesh,
|
|
item.Color,
|
|
object3D.WorldMatrix(),
|
|
RenderTypes.Outlines,
|
|
object3D.WorldMatrix() * World.ModelviewMatrix,
|
|
(modelRenderStyle == ModelRenderStyle.Solid) ? Color.Transparent : lightWireframe);
|
|
}
|
|
|
|
if (!lookingDownOnBed)
|
|
{
|
|
RenderBedMesh(lookingDownOnBed);
|
|
}
|
|
|
|
// we don't want to render the bed or build volume before we load a model.
|
|
if (scene.HasChildren() || AllowBedRenderingWhenEmpty)
|
|
{
|
|
if (false) // this is code to draw a small axis indicator
|
|
{
|
|
double big = 10;
|
|
double small = 1;
|
|
Mesh xAxis = PlatonicSolids.CreateCube(big, small, small);
|
|
GLHelper.Render(xAxis, Color.Red);
|
|
Mesh yAxis = PlatonicSolids.CreateCube(small, big, small);
|
|
GLHelper.Render(yAxis, Color.Green);
|
|
Mesh zAxis = PlatonicSolids.CreateCube(small, small, big);
|
|
GLHelper.Render(zAxis, Color.Blue);
|
|
}
|
|
}
|
|
|
|
DrawInteractionVolumes(e);
|
|
|
|
if (scene.DebugItem?.Mesh != null)
|
|
{
|
|
var debugItem = scene.DebugItem;
|
|
GLHelper.Render(debugItem.Mesh, debugBorderColor, debugItem.WorldMatrix(),
|
|
RenderTypes.Wireframe, debugItem.WorldMatrix() * World.ModelviewMatrix);
|
|
}
|
|
}
|
|
|
|
private void RenderBedMesh(bool lookingDownOnBed)
|
|
{
|
|
if (this.EditorMode == EditorType.Printer)
|
|
{
|
|
// only render if we are above the bed
|
|
if (sceneContext.RendererOptions.RenderBed)
|
|
{
|
|
var bedColor = this.BedColor;
|
|
if (!lookingDownOnBed)
|
|
{
|
|
bedColor = new Color(this.BedColor, this.BedColor.alpha / 4);
|
|
}
|
|
|
|
GLHelper.Render(sceneContext.Mesh, bedColor, RenderTypes.Shaded, World.ModelviewMatrix);
|
|
|
|
if (sceneContext.PrinterShape != null)
|
|
{
|
|
GLHelper.Render(sceneContext.PrinterShape, bedColor, RenderTypes.Shaded, World.ModelviewMatrix);
|
|
}
|
|
}
|
|
|
|
if (sceneContext.BuildVolumeMesh != null && sceneContext.RendererOptions.RenderBuildVolume)
|
|
{
|
|
GLHelper.Render(sceneContext.BuildVolumeMesh, this.BuildVolumeColor, RenderTypes.Shaded, World.ModelviewMatrix);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GL.Disable(EnableCap.Texture2D);
|
|
GL.Disable(EnableCap.Blend);
|
|
|
|
int width = 600;
|
|
|
|
GL.Begin(BeginMode.Lines);
|
|
{
|
|
for (int i = -width; i <= width; i += 50)
|
|
{
|
|
GL.Color4(240, 240, 240, 255);
|
|
GL.Vertex3(i, width, 0);
|
|
GL.Vertex3(i, -width, 0);
|
|
|
|
GL.Vertex3(width, i, 0);
|
|
GL.Vertex3(-width, i, 0);
|
|
}
|
|
|
|
GL.Color4(255, 0, 0, 255);
|
|
GL.Vertex3(width, 0, 0);
|
|
GL.Vertex3(-width, 0, 0);
|
|
|
|
GL.Color4(0, 255, 0, 255);
|
|
GL.Vertex3(0, width, 0);
|
|
GL.Vertex3(0, -width, 0);
|
|
|
|
GL.Color4(0, 0, 255, 255);
|
|
GL.Vertex3(0, 0, 10);
|
|
GL.Vertex3(0, 0, -10);
|
|
}
|
|
GL.End();
|
|
}
|
|
}
|
|
|
|
private void DrawInteractionVolumes(DrawEventArgs e)
|
|
{
|
|
if(SuppressUiVolumes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// draw on top of anything that is already drawn
|
|
foreach (InteractionVolume interactionVolume in interactionLayer.InteractionVolumes)
|
|
{
|
|
if (interactionVolume.DrawOnTop)
|
|
{
|
|
GL.Disable(EnableCap.DepthTest);
|
|
interactionVolume.DrawGlContent(new DrawGlContentEventArgs(false, e));
|
|
GL.Enable(EnableCap.DepthTest);
|
|
}
|
|
}
|
|
|
|
// Draw again setting the depth buffer and ensuring that all the interaction objects are sorted as well as we can
|
|
foreach (InteractionVolume interactionVolume in interactionLayer.InteractionVolumes)
|
|
{
|
|
interactionVolume.DrawGlContent(new DrawGlContentEventArgs(true, e));
|
|
}
|
|
}
|
|
|
|
public enum ModelRenderStyle
|
|
{
|
|
Solid,
|
|
Wireframe,
|
|
WireframeAndSolid,
|
|
None
|
|
}
|
|
|
|
private ModelRenderStyle modelRenderStyle = MeshViewerWidget.ModelRenderStyle.Wireframe;
|
|
private long lastSelectionChangedMs;
|
|
|
|
private class Object3DView
|
|
{
|
|
public Color Color { get; set; }
|
|
|
|
public IObject3D Object3D { get; }
|
|
|
|
public Object3DView(IObject3D source, Color color)
|
|
{
|
|
this.Object3D = source;
|
|
this.Color = color;
|
|
|
|
if (source is Object3D object3D
|
|
&& color != source.Color
|
|
&& color.alpha != 255)
|
|
{
|
|
object3D.EnsureTransparentSorting();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|