I think the main performance issue is due to the using of Solid3d.
I tried something with a geometric object (see Polygon2d class below) which is a non database resident object (as Point2d or Vector2d). The Polygon2d type have a TrySlice method which try to slice the polygon with a Line2d (unbounded line) and returns true if slicing succeeded; false other wise. This method also have an 'out parameter' to get the generated polygons. This is much more cheaper than slicing Solid3d entities
The Polygon2d class
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;
namespace Autodesk.AutoCAD.Geometry
{
/// <summary>
/// Provides methods and properties for a polygon
/// </summary>
class Polygon2d
{
/// <summary>
/// Creates a new instance of Polygon2d
/// </summary>
/// <param name="segments">Segments collection</param>
public Polygon2d(LineSegment2d[] segments) : base()
{
Segments = segments.ToArray();
Vertices = segments.Select(s => s.StartPoint).ToArray();
NumberOfVertices = Vertices.Length;
Initialize();
}
/// <summary>
/// Get the area of the polygon
/// </summary>
public double Area { get; private set; }
/// <summary>
/// Gets the centroid of the polygon
/// </summary>
public Point2d Centroid { get; private set; }
/// <summary>
/// Gets a value indicating if the vertices turn clockwise
/// </summary>
public bool IsClockwise { get; private set; }
/// <summary>
/// Gets the number of vertices
/// </summary>
public int NumberOfVertices { get; }
/// <summary>
/// Gets the segments
/// </summary>
public LineSegment2d[] Segments { get; }
/// <summary>
/// Gets the polybon vertices
/// </summary>
public Point2d[] Vertices { get; }
/// <summary>
/// Creates a new instance of Polygon2d
/// </summary>
/// <param name="vertices">Vertices collection</param>
/// <returns>The newly created Polygon2d</returns>
public static Polygon2d Create(Point2d[] vertices)
{
int nbSegments = vertices.Count();
var segments = new LineSegment2d[nbSegments];
for (int i = 0; i < nbSegments; i++)
{
segments[i] = new LineSegment2d(vertices[i], vertices[(i + 1) % nbSegments]);
}
return new Polygon2d(segments);
}
/// <summary>
/// Try to slice the polygon with a line
/// </summary>
/// <param name="line">Line used to slice the polygon</param>
/// <param name="polygons">Polygons generated by the slicing operation</param>
/// <returns>true, if the slicing operation succeeded; false, otherwise</returns>
public bool TrySlice(Line2d line, out Polygon2d[] polygons)
{
polygons = new[] { this };
var intersections = new List<Intersection>();
for (int i = 0; i < Segments.Length; i++)
{
var segment = Segments[i];
var intersPts = segment
.IntersectWith(line)?
.Where(pt => !Vertices.Contains(pt))
.ToArray();
if (intersPts != null)
{
intersections.Add(new Intersection() { Point = intersPts[0], Index = i });
}
}
if (intersections.Count == 2)
{
var index0 = intersections[0].Index;
int index1 = intersections[1].Index;
polygons = new Polygon2d[2];
var vertices = new Point2d[index0 + 2 + NumberOfVertices - index1];
int i = 0;
for (; i < index0 + 1; i++)
{
vertices[i] = Vertices[i];
}
vertices[i++] = intersections[0].Point;
vertices[i++] = intersections[1].Point;
for (int j = index1 + 1; j < NumberOfVertices; j++)
{
vertices[i++] = Vertices[j];
}
polygons[0] = Create(vertices);
vertices = new Point2d[index1 - index0 + 2];
i = 0;
vertices[i++] = intersections[0].Point;
for (int j = index0 + 1; j < index1 + 1; j++)
{
vertices[i++] = Vertices[j];
}
vertices[i] = intersections[1].Point;
polygons[1] = Create(vertices);
return true;
}
return false;
}
/// <summary>
/// Converts the polygon into a closed Polyline
/// </summary>
/// <returns>The newly created polyline</returns>
public Polyline ToPolyline()
{
var pline = new Polyline();
for (int i = 0; i < NumberOfVertices; i++)
{
pline.AddVertexAt(i, Vertices[i], 0.0, 0.0, 0.0);
}
pline.Closed = true;
return pline;
}
/// <summary>
/// Initializes the Area, Centroid and IsClockwise properties
/// </summary>
private void Initialize()
{
Point2d cen = new Point2d();
Point2d triangleCenter;
double triangleArea;
double area = 0.0;
int last = NumberOfVertices - 1;
Point2d p0 = Vertices[0];
for (int i = 1; i < last; i++)
{
var p1 = Vertices[i];
var p2 = Vertices[i + 1];
triangleArea = ((p1.X - p0.X) * (p2.Y - p0.Y)) - ((p2.X - p0.X) * (p1.Y - p0.Y));
triangleCenter = (p0 + p1.GetAsVector() + p2.GetAsVector()) / 3.0;
area += triangleArea;
cen += (triangleCenter * triangleArea).GetAsVector();
}
Area = Math.Abs(area / 2.0);
Centroid = cen.DivideBy(area);
IsClockwise = area < 0.0;
}
/// <summary>
/// Stores the intersection data
/// </summary>
struct Intersection
{
/// <summary>
/// Gets or sets the intersection point
/// </summary>
public Point2d Point { get; set; }
/// <summary>
/// Gets or sets the index of the segment
/// </summary>
public int Index { get; set; }
}
}
}
The algorithm is the same as before, I just add a loop counter in the while statement so that we can restart from scratch (the original square) if the counter exceeds some number (200 in the example).
The following testing command creates a Polyline for each polygon. It would be easy to create a Region from each polyline and extrude it to get a Solid3d.
[CommandMethod("SliceAPline")]
public static void SliceAPolyline()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// creates the base polygon
var square = Polygon2d.Create(new[]
{
Point2d.Origin,
new Point2d(100.0, 0.0),
new Point2d(100.0, 100.0),
new Point2d(0.0, 100.0)
});
var polygons = new List<Polygon2d> { square };
var random = new Random();
int cnt = 0;
// loop while there is a polygon whose area is greater than 1000
while (polygons.Any(p => p.Area > 1000))
{
// if the number of loops exceeds 200 re-start from the square
if (200 < cnt)
{
cnt = 0;
polygons = new List<Polygon2d> { square };
ed.WriteMessage("\nRe-started");
}
// create a random line to slice the polygons
var line = new Line2d(
new Point2d(random.NextDouble() * 100.0, random.NextDouble() * 100.0),
new Vector2d(random.NextDouble() * 2.0 - 1.0, random.NextDouble() * 2.0 - 1.0));
var toAdd = new List<Polygon2d>(); // polygons to be added to the collection
var toRemove = new List<int>(); // polygons to be removed from the collection
// try to slice each polygon
for (int i = 0; i < polygons.Count; i++)
{
// if slicing succeeded,
if (polygons[i].TrySlice(line, out Polygon2d[] sliced))
{
// if the area of one of the generated polygon is minor than 100,
if (sliced[0].Area < 100 || sliced[1].Area < 100)
{
// clear the 'toAdd' list and break the 'for' loop
toAdd.Clear();
break;
}
// else, update the toAdd and toRemove lists
else
toAdd.AddRange(sliced);
toRemove.Add(i);
}
}
// if slicing succeeded (i.e. none genrated polygon with an area minor than 100)
if (toAdd.Count > 0)
{
// remove the sliced polygons
for (int i = toRemove.Count - 1; i >= 0; i--)
{
polygons.RemoveAt(toRemove[i]);
}
// add the generated polygons
polygons.AddRange(toAdd);
}
cnt++;
}
ed.WriteMessage($"\nCreated {polygons.Count} polygons in {cnt} loops (areas: {string.Join(", ", polygons.Select(p => p.Area.ToString("0.00")))})");
// convert the polygons into polylines
using (var tr = db.TransactionManager.StartTransaction())
{
var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
foreach (Polygon2d polygon in polygons)
{
var pline = polygon.ToPolyline();
curSpace.AppendEntity(pline);
tr.AddNewlyCreatedDBObject(pline, true);
}
tr.Commit();
}
}