deprecated vertexsource
This commit is contained in:
parent
6e3c4be07c
commit
9e2aad8651
11 changed files with 74 additions and 110 deletions
|
|
@ -195,7 +195,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (selectedItem is PathObject3D pathObject)
|
||||
{
|
||||
var vertexStorage = pathObject.VertexSource as VertexStorage;
|
||||
var vertexStorage = pathObject.VertexStorage;
|
||||
|
||||
activePoints = vertexStorage.Vertices();
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
public double SliceHeight { get; set; } = 10;
|
||||
|
||||
public override IVertexSource VertexSource { get; set; } = new VertexStorage();
|
||||
|
||||
|
||||
private double cutMargin = .01;
|
||||
|
||||
public (Mesh mesh, Polygons polygons) Cut(IObject3D item)
|
||||
|
|
@ -84,7 +83,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
var newPathObject = new PathObject3D()
|
||||
{
|
||||
VertexSource = new VertexStorage(this.VertexSource)
|
||||
VertexStorage = new VertexStorage(this.GetVertexSource())
|
||||
};
|
||||
|
||||
base.Apply(undoBuffer, new IObject3D[] { newPathObject });
|
||||
|
|
@ -151,7 +150,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var polygons = new Polygons();
|
||||
VertexSource = polygons.PolygonToPathStorage();
|
||||
VertexStorage = polygons.PolygonToPathStorage();
|
||||
|
||||
var newChildren = new List<Object3D>();
|
||||
foreach (var sourceItem in SourceContainer.VisibleMeshes())
|
||||
|
|
@ -169,7 +168,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
newChildren.Add(newMesh);
|
||||
}
|
||||
|
||||
VertexSource = polygons.PolygonToPathStorage();
|
||||
VertexStorage = polygons.PolygonToPathStorage();
|
||||
|
||||
RemoveAllButSource();
|
||||
SourceContainer.Visible = false;
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ either expressed or implied, of the FreeBSD Project.
|
|||
/*********************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ClipperLib;
|
||||
|
|
@ -179,8 +177,6 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
[Slider(0, 1)]
|
||||
public DoubleOrExpression RangeEnd { get; set; } = 1;
|
||||
|
||||
public override IVertexSource VertexSource { get; set; } = new VertexStorage();
|
||||
|
||||
private IThresholdFunction ThresholdFunction
|
||||
{
|
||||
get
|
||||
|
|
@ -281,7 +277,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
affine *= Affine.NewTranslation(-aabb.XSize / 2, -aabb.YSize / 2);
|
||||
|
||||
rawVectorShape.transform(affine);
|
||||
this.VertexSource = rawVectorShape;
|
||||
this.VertexStorage = rawVectorShape;
|
||||
|
||||
progressReporter?.Invoke(1, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,12 +172,12 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
|
||||
public static void FlattenToPathObject(this IObject3D item, UndoBuffer undoBuffer)
|
||||
{
|
||||
if (item.VertexSource != null)
|
||||
if (item.GetVertexSource() != null)
|
||||
{
|
||||
using (item.RebuildLock())
|
||||
{
|
||||
var newPathObject = new PathObject3D();
|
||||
newPathObject.VertexSource = new VertexStorage(item.VertexSource);
|
||||
newPathObject.VertexStorage = new VertexStorage(item.GetVertexSource());
|
||||
|
||||
// and replace us with the children
|
||||
var replaceCommand = new ReplaceCommand(new[] { item }, new[] { newPathObject });
|
||||
|
|
@ -254,13 +254,13 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
if (item.VertexSource != null)
|
||||
if (item.GetVertexSource() != null)
|
||||
{
|
||||
var lastPosition = Vector2.Zero;
|
||||
var maxXYZ = item.GetAxisAlignedBoundingBox().MaxXYZ;
|
||||
maxXYZ = maxXYZ.Transform(item.Matrix.Inverted);
|
||||
|
||||
foreach (var vertex in item.VertexSource.Vertices())
|
||||
foreach (var vertex in item.GetVertexSource().Vertices())
|
||||
{
|
||||
var position = vertex.position;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
Name = name;
|
||||
}
|
||||
|
||||
public override IVertexSource VertexSource { get; set; } = new VertexStorage();
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
|
|
@ -102,7 +100,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
|
||||
// set the mesh to show the path
|
||||
this.Mesh = this.VertexSource.Extrude(Constants.PathPolygonsHeight);
|
||||
this.Mesh = this.GetVertexSource().Extrude(Constants.PathPolygonsHeight);
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
|
|
@ -135,7 +133,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
|
||||
var first = participants.First();
|
||||
var resultsVertexSource = first.VertexSource.Transform(first.Matrix);
|
||||
var resultsVertexSource = first.GetVertexSource().Transform(first.Matrix);
|
||||
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
|
|
@ -145,9 +143,9 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
foreach (var item in participants)
|
||||
{
|
||||
if (item != first
|
||||
&& item.VertexSource != null)
|
||||
&& item.GetVertexSource() != null)
|
||||
{
|
||||
var itemVertexSource = item.VertexSource.Transform(item.Matrix);
|
||||
var itemVertexSource = item.GetVertexSource().Transform(item.Matrix);
|
||||
|
||||
resultsVertexSource = resultsVertexSource.MergePaths(itemVertexSource, clipType);
|
||||
|
||||
|
|
@ -157,7 +155,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
}
|
||||
|
||||
this.VertexSource = resultsVertexSource;
|
||||
this.VertexStorage = new VertexStorage(resultsVertexSource);
|
||||
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,15 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
public class RevolveObject3D : Object3D, IEditorDraw
|
||||
{
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(0, 360, snapDistance: 1)]
|
||||
public DoubleOrExpression Rotation { get; set; } = 0;
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(-30, 30, snapDistance: 1)]
|
||||
public DoubleOrExpression AxisPosition { get; set; } = 0;
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(3, 360, snapDistance: 1)]
|
||||
[Slider(0, 360, snapDistance: 1)]
|
||||
public DoubleOrExpression StartingAngle { get; set; } = 0;
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
|
|
@ -131,7 +136,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var path = this.CombinedVisibleChildrenPaths();
|
||||
if (path == null)
|
||||
if (path != null)
|
||||
{
|
||||
var (start, end) = GetStartEnd(this, path);
|
||||
layer.World.Render3DLine(start, end, Color.Red, true);
|
||||
|
|
@ -162,7 +167,8 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
this.DebugDepth("Rebuild");
|
||||
bool valuesChanged = false;
|
||||
|
||||
var startingAngle = StartingAngle.ClampIfNotCalculated(this, 0, 360 - .01, ref valuesChanged);
|
||||
var rotation = MathHelper.DegreesToRadians(Rotation.ClampIfNotCalculated(this, 0, 360, ref valuesChanged));
|
||||
var startingAngle = StartingAngle.ClampIfNotCalculated(this, 0, 360 - .01, ref valuesChanged);
|
||||
var endingAngle = EndingAngle.ClampIfNotCalculated(this, startingAngle + .01, 360, ref valuesChanged);
|
||||
var sides = Sides.Value(this);
|
||||
var axisPosition = AxisPosition.Value(this);
|
||||
|
|
@ -187,7 +193,8 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
|
||||
var vertexSource = this.CombinedVisibleChildrenPaths();
|
||||
var pathBounds = vertexSource.GetBounds();
|
||||
vertexSource = vertexSource.Rotate(rotation);
|
||||
var pathBounds = vertexSource.GetBounds();
|
||||
vertexSource = vertexSource.Translate(-pathBounds.Left - axisPosition, 0);
|
||||
Mesh mesh = VertexSourceToMesh.Revolve(vertexSource,
|
||||
sides,
|
||||
|
|
@ -195,8 +202,9 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
MathHelper.DegreesToRadians(360 - startingAngle),
|
||||
false);
|
||||
|
||||
var transform = Matrix4X4.CreateTranslation(pathBounds.Left + axisPosition, 0, 0) * Matrix4X4.CreateRotationZ(-rotation);
|
||||
// take the axis offset out
|
||||
mesh.Transform(Matrix4X4.CreateTranslation(pathBounds.Left + axisPosition, 0, 0));
|
||||
mesh.Transform(transform);
|
||||
|
||||
if (mesh.Vertices.Count == 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -130,7 +130,27 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
base.Cancel(undoBuffer);
|
||||
}
|
||||
|
||||
private (IVertexSource vertexSource, double height) meshVertexCache;
|
||||
private double cacheHeight;
|
||||
|
||||
public override IVertexSource GetVertexSource()
|
||||
{
|
||||
var paths = this.CombinedVisibleChildrenPaths();
|
||||
if (paths == null)
|
||||
{
|
||||
var calculationHeight = CalculationHeight.Value(this);
|
||||
if (VertexStorage == null || cacheHeight != calculationHeight)
|
||||
{
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
var cutPlane = new Plane(Vector3.UnitZ, new Vector3(0, 0, aabb.MinXYZ.Z + calculationHeight));
|
||||
VertexStorage = new VertexStorage(GetSlicePaths(this, cutPlane));
|
||||
cacheHeight = calculationHeight;
|
||||
}
|
||||
|
||||
return VertexStorage;
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
public static IVertexSource GetSlicePaths(IObject3D source, Plane plane)
|
||||
{
|
||||
|
|
@ -146,50 +166,6 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
return totalSlice.CreateVertexStorage();
|
||||
}
|
||||
|
||||
private bool OutlineIsFromMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
var vertexSource = this.Descendants<IObject3D>().FirstOrDefault(i => i.VertexSource != null)?.VertexSource;
|
||||
var hasMesh = this.Descendants<IObject3D>().Where(m => m.Mesh != null).Any();
|
||||
|
||||
return vertexSource == null && hasMesh;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override IVertexSource VertexSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OutlineIsFromMesh)
|
||||
{
|
||||
var calculationHeight = CalculationHeight.Value(this);
|
||||
if (meshVertexCache.vertexSource == null || meshVertexCache.height != calculationHeight)
|
||||
{
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
var cutPlane = new Plane(Vector3.UnitZ, new Vector3(0, 0, aabb.MinXYZ.Z + calculationHeight));
|
||||
meshVertexCache.vertexSource = GetSlicePaths(this, cutPlane);
|
||||
meshVertexCache.height = calculationHeight;
|
||||
}
|
||||
|
||||
return meshVertexCache.vertexSource;
|
||||
}
|
||||
|
||||
var vertexSource = this.Descendants<IObject3D>().FirstOrDefault((i) => i.VertexSource != null)?.VertexSource;
|
||||
return vertexSource;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
var pathObject = this.Children.FirstOrDefault(i => i.VertexSource != null);
|
||||
if (pathObject != null)
|
||||
{
|
||||
pathObject.VertexSource = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<BaseObject3D> Create()
|
||||
{
|
||||
var item = new BaseObject3D();
|
||||
|
|
@ -207,7 +183,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
&& !RebuildLocked)
|
||||
{
|
||||
// make sure we clear our cache
|
||||
meshVertexCache.vertexSource = null;
|
||||
VertexStorage = null;
|
||||
await Rebuild();
|
||||
}
|
||||
else if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this))
|
||||
|
|
@ -248,7 +224,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
});
|
||||
|
||||
// and create the base
|
||||
var vertexSource = this.VertexSource;
|
||||
var vertexSource = GetVertexSource();
|
||||
|
||||
// Convert VertexSource into expected Polygons
|
||||
Polygons polygonShape = (vertexSource == null) ? null : vertexSource.CreatePolygons();
|
||||
|
|
@ -317,9 +293,10 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
}
|
||||
else
|
||||
{
|
||||
var outsidePolygons = new List<List<IntPoint>>();
|
||||
var outsidePolygons = new Polygons();
|
||||
// remove all holes from the polygons so we only center the major outlines
|
||||
var polygons = VertexSource.CreatePolygons();
|
||||
var polygons = GetVertexSource().CreatePolygons();
|
||||
polygons = polygons.GetCorrectedWinding();
|
||||
|
||||
foreach (var polygon in polygons)
|
||||
{
|
||||
|
|
@ -438,7 +415,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
changeSet.Add(nameof(Centering), BaseType == BaseTypes.Circle);
|
||||
changeSet.Add(nameof(ExtrusionHeight), BaseType != BaseTypes.None);
|
||||
|
||||
var vertexSource = this.Descendants<IObject3D>().FirstOrDefault((i) => i.VertexSource != null)?.VertexSource;
|
||||
var vertexSource = GetVertexSource();
|
||||
var meshSource = this.Descendants<IObject3D>().Where((i) => i.Mesh != null);
|
||||
|
||||
changeSet.Add(nameof(CalculationHeight), vertexSource == null && meshSource.Where(m => m.Mesh != null).Any());
|
||||
|
|
@ -464,9 +441,9 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
if (OutlineIsFromMesh)
|
||||
if (GetVertexSource() != null)
|
||||
{
|
||||
layer.World.RenderPathOutline(CalcTransform(), VertexSource, Agg.Color.Red, 5);
|
||||
layer.World.RenderPathOutline(CalcTransform(), GetVertexSource(), Agg.Color.Red, 5);
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
|
|
@ -475,10 +452,10 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
if (OutlineIsFromMesh)
|
||||
if (GetVertexSource() != null)
|
||||
{
|
||||
// TODO: Untested.
|
||||
return layer.World.GetWorldspaceAabbOfRenderPathOutline(CalcTransform(), VertexSource, 5);
|
||||
return layer.World.GetWorldspaceAabbOfRenderPathOutline(CalcTransform(), GetVertexSource(), 5);
|
||||
}
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ of the authors and should not be interpreted as representing official policies,
|
|||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.Agg.VertexSource;
|
||||
|
|
@ -35,10 +34,8 @@ using MatterHackers.DataConverters3D;
|
|||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
|
|
@ -52,21 +49,6 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
public override string ThumbnailName => "Box";
|
||||
|
||||
[JsonIgnore]
|
||||
private IVertexSource _vertexSource = new VertexStorage();
|
||||
|
||||
public override IVertexSource VertexSource
|
||||
{
|
||||
get => _vertexSource;
|
||||
|
||||
set
|
||||
{
|
||||
_vertexSource = value;
|
||||
// set the mesh to show the path
|
||||
this.Mesh = this.VertexSource.Extrude(Constants.PathPolygonsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
|
|
@ -128,9 +110,12 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
using (new CenterAndHeightMaintainer(this))
|
||||
{
|
||||
var width = Width.Value(this);
|
||||
var depth = Depth.Value(this);
|
||||
VertexSource = new RoundedRect(-width / 2, -depth / 2, width / 2, depth / 2, 0);
|
||||
bool valuesChanged = false;
|
||||
var width = Width.ClampIfNotCalculated(this, .01, 10000, ref valuesChanged);
|
||||
var depth = Depth.ClampIfNotCalculated(this, .01, 10000, ref valuesChanged);
|
||||
VertexStorage = new VertexStorage(new RoundedRect(-width / 2, -depth / 2, width / 2, depth / 2, 0));
|
||||
|
||||
this.Mesh = VertexStorage.Extrude(Constants.PathPolygonsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
[DisplayName("Part(s) to Subtract")]
|
||||
public SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
|
||||
|
||||
public override IVertexSource VertexSource { get; set; } = new VertexStorage();
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
|
|
@ -125,7 +123,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
}
|
||||
|
||||
// set the mesh to show the path
|
||||
var extrudeMesh = this.VertexSource.Extrude(Constants.PathPolygonsHeight);
|
||||
var extrudeMesh = this.GetVertexSource().Extrude(Constants.PathPolygonsHeight);
|
||||
if(extrudeMesh.Vertices.Count() > 5)
|
||||
{
|
||||
this.Mesh = extrudeMesh;
|
||||
|
|
@ -198,11 +196,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
bool first = true;
|
||||
foreach (var keep in keepVisibleItems)
|
||||
{
|
||||
var resultsVertexSource = keep.VertexSource.Transform(keep.Matrix);
|
||||
var resultsVertexSource = keep.GetVertexSource().Transform(keep.Matrix);
|
||||
|
||||
foreach (var remove in removeVisibleItems)
|
||||
{
|
||||
resultsVertexSource = resultsVertexSource.MergePaths(remove.VertexSource.Transform(remove.Matrix), ClipperLib.ClipType.ctDifference);
|
||||
resultsVertexSource = resultsVertexSource.MergePaths(remove.GetVertexSource().Transform(remove.Matrix), ClipperLib.ClipType.ctDifference);
|
||||
|
||||
// report our progress
|
||||
ratioCompleted += amountPerOperation;
|
||||
|
|
@ -212,12 +210,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
|
||||
if (first)
|
||||
{
|
||||
this.VertexSource = resultsVertexSource;
|
||||
this.VertexStorage = new VertexStorage(resultsVertexSource);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.VertexSource.MergePaths(resultsVertexSource, ClipperLib.ClipType.ctUnion);
|
||||
this.GetVertexSource().MergePaths(resultsVertexSource, ClipperLib.ClipType.ctUnion);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4258,6 +4258,9 @@ Translated:Rotate: Right Mouse Button | Ctrl + Left Mouse Button | Arrow Keys
|
|||
English:Rotate: Right Mouse Button, Ctrl + Left Mouse Button, Arrow Keys
|
||||
Translated:Rotate: Right Mouse Button, Ctrl + Left Mouse Button, Arrow Keys
|
||||
|
||||
English:Rotation
|
||||
Translated:Rotation
|
||||
|
||||
English:Rotation Distance
|
||||
Translated:Rotation Distance
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit d061bb620e8fa595aa2d2509dc33bb7874140278
|
||||
Subproject commit 837ccc782954724ae26d080a9b063112a70a527b
|
||||
Loading…
Add table
Add a link
Reference in a new issue