VB.NET - Slice Solid3D multiple times

VB.NET - Slice Solid3D multiple times

surfer96
Advocate Advocate
5,447 Views
25 Replies
Message 1 of 26

VB.NET - Slice Solid3D multiple times

surfer96
Advocate
Advocate

I do have  a "100-100-1" 3d solid and want to slice and reslice it/them  multiple (e. g. 5) times  by random planes.

The first slicing generates 2 solids, the second 4 solids etc., which will result in a set of seperated 3d solids, whose volumes could be read out.

 

What I need is some selection set to collect all 3d solids in model space and some "loop"- or "repeat"-function for the slicing.Unbenannt.JPG

 

This is what I got so far...

 

<CommandMethod("SliceABox")>
        Public Sub SliceABox()
            '' Get the current document and database, and start a transaction
            Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
            Dim acCurDb As Database = acDoc.Database

            Using acTrans As Transaction = acCurDb.TransactionManager.StartTransaction()
                '' Open the Block table for read
                Dim acBlkTbl As BlockTable
                acBlkTbl = acTrans.GetObject(acCurDb.BlockTableId,
                                             OpenMode.ForRead)

                '' Open the Block table record Model space for write
                Dim acBlkTblRec As BlockTableRecord
                acBlkTblRec = acTrans.GetObject(acBlkTbl(BlockTableRecord.ModelSpace),
                                                OpenMode.ForWrite)

                '' Create a 3D solid box
                Using acSol3D As Solid3d = New Solid3d()
                    Dim width = 100
                    acSol3D.CreateBox(width, width, 1)
                    acSol3D.ColorIndex = 7

                    '' Position the center of the 3D solid at (width / 2, width / 2, 0.5) 
                    acSol3D.TransformBy(Matrix3d.Displacement(New Point3d(width / 2, width / 2, 0.5) -
                                                              Point3d.Origin))

                    '' Add the new object to the block table record and the transaction
                    acBlkTblRec.AppendEntity(acSol3D)
                    acTrans.AddNewlyCreatedDBObject(acSol3D, True)

                    '' Define the mirror plane
                    Dim r As New Random()
                    Dim xr1 = width * r.NextDouble()
                    Dim yr1 = width * r.NextDouble()
                    Dim xr2 = width * r.NextDouble()
                    Dim yr2 = width * r.NextDouble()

                    Dim acPlane As Plane = New Plane(New Point3d(xr1, yr1, 0),
                                           New Point3d(xr1, yr1, 1),
                                           New Point3d(xr2, yr2, 1))

                    Dim acSol3DSlice As Solid3d = acSol3D.Slice(acPlane, True)
                    acSol3DSlice.ColorIndex = 1

                    '' Add the new object to the block table record and the transaction
                    acBlkTblRec.AppendEntity(acSol3DSlice)
                    acTrans.AddNewlyCreatedDBObject(acSol3DSlice, True)
                End Using

                '' Save the new objects to the database
                acTrans.Commit()
            End Using
        End Sub

 

0 Likes
Accepted solutions (2)
5,448 Views
25 Replies
Replies (25)
Message 2 of 26

_gile
Consultant
Consultant

Hi,

 

Here's an example. As the random play may not slice some solids, we have to wrap it in a try/catch statement.

For each solid slicing, we have to store the newly created solid (if any) in a temporay list and add these solids to the main list.

 

        [CommandMethod("SliceABox")]
        public static void SliceABox()
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;

            using (var tr = db.TransactionManager.StartTransaction())
            {
                var model = (BlockTableRecord)tr.GetObject(
                    SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
                var box = new Solid3d();
                box.CreateBox(100.0, 100.0, 1.0);
                box.TransformBy(Matrix3d.Displacement(new Vector3d(50.0, 50.0, 0.5)));
                model.AppendEntity(box);
                tr.AddNewlyCreatedDBObject(box, true);
                var solids = new List<Solid3d> { box };
                var random = new Random();
                // repeat the operation 8 times
                for (int i = 0; i < 8; i++)
                {
                    // randomly define the plane origin in [(0, 0, 0)..(100, 100, 0)] range
                    var origin = new Point3d(random.NextDouble() * 100.0, random.NextDouble() * 100.0, 0.0);

                    // randomly define the plane normal in [(-1, -1, 0)..(1, 1, 0)] range
                    var normal = new Vector3d(random.NextDouble() * 2.0 - 1.0, random.NextDouble() * 2.0 - 1.0, 0.0);
                    ed.WriteMessage($"\nOrigin: {origin}, normal: {normal}");

                    // create an empty list for the newly created solids
                    var tempList = new List<Solid3d>();
                    // try to slice each solid with the plane
                    foreach (var solid in solids)
                    {
                        try
                        {
                            var newSolid = solid.Slice(new Plane(origin, normal), true);
                            model.AppendEntity(newSolid);
                            tr.AddNewlyCreatedDBObject(newSolid, true);
                            // if slicing succeeded, add the newly created solide to the temporari list
                            tempList.Add(newSolid);
                        }
                        catch
                        {
                            ed.WriteMessage("\nFailed to slice.");
                        }
                    }
                    // add the new created solids to the main list
                    solids.AddRange(tempList);
                }
                tr.Commit();
            }
        }


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 3 of 26

surfer96
Advocate
Advocate

Although not knowing anything about C# I pasted your code into Autodesk's C# wizard for dotNET and that's what it said:

 

Unbenannt.JPG

 

I would simply like to get the code running, generate a list of volumes, sort the list and define a conditional "wrapping" loop to garantuee all volumes will be within a range from 10 to 1000 (e. g.).

 

Here's similar code in LISP to illustrate the logic behind it.

I hope that .NET will be faster für these purposes than LISP...

0 Likes
Message 4 of 26

kerry_w_brown
Mentor
Mentor

@surfer96 wrote:

Although not knowing anything about C# I pasted your code into Autodesk's C# wizard for dotNET and ....

 

Try a converter like http://converter.telerik.com/

 

or

https://www.google.com/search?q=convert+c%23+to+vb.net&rlz=1C1CHBF_en-GBAU700AU700&oq=cnvert+C%23+&a...


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
Message 5 of 26

_gile
Consultant
Consultant
Accepted solution

You should have been able to convert the C# code (maybe with the help of a converter as @kerry_w_brown purposed) because both are quite closed , but you'd rather learn C# instead of VB because there're much more examples using C#.

 

Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.Runtime

Public Class Commands

    <CommandMethod("SliceABox")>
    Public Shared Sub SliceABox()
        Dim doc = Application.DocumentManager.MdiActiveDocument
        Dim db = doc.Database
        Dim ed = doc.Editor

        Using tr = db.TransactionManager.StartTransaction()
            Dim model = CType(tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite), BlockTableRecord)
            Dim box = New Solid3d()
            box.CreateBox(100.0, 100.0, 1.0)
            box.TransformBy(Matrix3d.Displacement(New Vector3d(50.0, 50.0, 0.5)))
            model.AppendEntity(box)
            tr.AddNewlyCreatedDBObject(box, True)
            Dim solids = New List(Of Solid3d)
            solids.Add(box)
            Dim random = New Random()
            'repeat the operation 6 times
            For i As Integer = 0 To 6 - 1

                'randomly define the plane origin in [(0, 0, 0)..(100, 100, 0)] range
                Dim origin = New Point3d(random.NextDouble() * 100.0, random.NextDouble() * 100.0, 0.0)

                'randomly define the plane normal in [(-1, -1, 0)..(1, 1, 0)] range
                Dim normal = New Vector3d(random.NextDouble() * 2.0 - 1.0, random.NextDouble() * 2.0 - 1.0, 0.0)
                ed.WriteMessage(vbLf & "\nOrigin: " & origin.ToString() & ", normal: " & normal.ToString())

                'create an empty list for the newly created solids
                Dim tempList = New List(Of Solid3d)()
                'try to slice each solid with the plane
                For Each solid In solids
                    Try
                        Dim newSolid = solid.Slice(New Plane(origin, normal), True)
                        model.AppendEntity(newSolid)
                        tr.AddNewlyCreatedDBObject(newSolid, True)
                        'if slicing succeeded, add the newly created solid to the temporary list
                        tempList.Add(newSolid)
                    Catch
                        ed.WriteMessage(vbLf & "Failed to slice.")
                    End Try
                Next

                solids.AddRange(tempList)
            Next

            tr.Commit()
        End Using
    End Sub

End Class


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 6 of 26

surfer96
Advocate
Advocate

@_gile 

Your code works fine.

Unfortunately I don't have enough time right now. But I will go on with it in a couple of weeks.

 

Merci so far !

Unbenannt.JPG

 

0 Likes
Message 7 of 26

surfer96
Advocate
Advocate

I tried to extract the solids' volumes to a list and compare the "volume list's" minimum and maximum values with fitting inside a conditional range (e.g. from 50 to 2500).

 

Thus the smallest volume has to be larger than 50, and the biggest volume smaller than 2500.

 

The dll is built, but the looping condition is not met. May this be be caused by "Failed to slice" interrupting the loop?

Or is there further errors in my extensions to the initial code?

 

 

 

Namespace Slice_Solids

    Public Class MyCommands

        <CommandMethod("Slice_Solids")>
        Public Shared Sub Slice_Solids()
            Dim doc = Application.DocumentManager.MdiActiveDocument
            Dim db = doc.Database
            Dim ed = doc.Editor

            Using tr = db.TransactionManager.StartTransaction()
                Dim model = CType(tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite), BlockTableRecord)
                Dim box = New Solid3d()
                box.CreateBox(100.0, 100.0, 1.0)
                box.TransformBy(Matrix3d.Displacement(New Vector3d(50.0, 50.0, 0.5)))
                model.AppendEntity(box)
                tr.AddNewlyCreatedDBObject(box, True)
                Dim solids = New List(Of Solid3d)
                solids.Add(box)

                'list of solid volumes
                Dim solids_vol = New List(Of Double)
                solids_vol.Add(box.MassProperties.Volume)

                'conditional range for solid volumes
                While solids_vol.Min < 50 And solids_vol.Max > 2500

                    Dim random = New Random()
                    'repeat the operation 8 times
                    For i As Integer = 0 To 7 - 1

                        'randomly define the plane origin in [(0, 0, 0)..(100, 100, 0)] range
                        Dim origin = New Point3d(random.NextDouble() * 100.0, random.NextDouble() * 100.0, 0.0)

                        'randomly define the plane normal in [(-1, -1, 0)..(1, 1, 0)] range
                        Dim normal = New Vector3d(random.NextDouble() * 2.0 - 1.0, random.NextDouble() * 2.0 - 1.0, 0.0)
                        ed.WriteMessage(vbLf & "\nOrigin: " & origin.ToString() & ", normal: " & normal.ToString())

                        'create an empty list for the newly created solids
                        Dim tempList = New List(Of Solid3d)()
                        'create an empty list for the newly created solids' volumes
                        Dim tempList_vol = New List(Of Double)

                        'try to slice each solid with the plane
                        For Each solid In solids
                            Try
                                Dim newSolid = solid.Slice(New Plane(origin, normal), True)
                                model.AppendEntity(newSolid)
                                tr.AddNewlyCreatedDBObject(newSolid, True)
                                'if slicing succeeded, add the newly created solid to the temporary list
                                tempList.Add(newSolid)

                                'if slicing succeeded, add the newly created solid's volume to the temporary volume list
                                tempList_vol.Add(newSolid.MassProperties.Volume)

                            Catch
                                ed.WriteMessage(vbLf & "Failed to slice.")
                            End Try
                        Next

                        solids.AddRange(tempList)
                        solids_vol.AddRange(tempList_vol)

                    Next

                End While

                tr.Commit()
            End Using
        End Sub
    End Class
End Namespace

 

 

 

0 Likes
Message 8 of 26

surfer96
Advocate
Advocate

I think there's a logical error within the looping condition:

 

It probably has to be

While solids_vol.Min < 50 Or solids_vol.Max > 2500

 

instead of

While solids_vol.Min < 50 And solids_vol.Max > 2500

 

arrgh...

0 Likes
Message 9 of 26

_gile
Consultant
Consultant

"Failed to slice" occurs when the slicing plane does not cut the solid and does not interupt the process (it's just a warning).

If you want to only have solids which volumes are in a [50..2500] range, you cannot do this a posteriori as you do because slicing always creates minor volumes so, if a slicing operation generates a solid which volume is minor than 50, there's none rollback.

One way to do this should be looping while the final list contains any solid greater than 2500 and, within the loop, for each slicing operation, check if any of the resulting solid is minor than 50 and if so, rollback the operation.

 

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices.Core;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(RandomSliceBox.Commands))]

namespace RandomSliceBox
{
    public class Commands
    {
        [CommandMethod("SliceABox")]
        public static void SliceABox()
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;

            using (var tr = db.TransactionManager.StartTransaction())
            {
                var model = (BlockTableRecord)tr.GetObject(
                    SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
                var box = new Solid3d();
                box.CreateBox(100.0, 100.0, 1.0);
                box.TransformBy(Matrix3d.Displacement(new Vector3d(50.0, 50.0, 0.5)));
                model.AppendEntity(box);
                tr.AddNewlyCreatedDBObject(box, true);
                var solids = new List<Solid3d> { box };
                var random = new Random();

                // repeat the operation while the list contains any solid which volume is greater than 2500
                while (solids.Any(s => s.MassProperties.Volume > 2500))
                {
                    // randomly define the plane origin in [(0, 0, 0)..(100, 100, 0)] range
                    var origin = new Point3d(random.NextDouble() * 100.0, random.NextDouble() * 100.0, 0.0);

                    // randomly define the plane normal in [(-1, -1, 0)..(1, 1, 0)] range
                    var normal = new Vector3d(random.NextDouble() * 2.0 - 1.0, random.NextDouble() * 2.0 - 1.0, 0.0);

                    // start a nested transaction to make rollback easier
                    using (var nestedTr = db.TransactionManager.StartTransaction())
                    {
                        // create an empty list for the newly created solids
                        var tempList = new List<Solid3d>();
                        bool rollback = false;

                        // try to slice each solid with the plane
                        foreach (var solid in solids)
                        {
                            try
                            {
                                var newSolid = solid.Slice(new Plane(origin, normal), true);
                                // if slicing genrated a solid which volume is minor than 50, set 'rollback' to true and break the for each loop
                                if (solid.MassProperties.Volume < 50 || newSolid.MassProperties.Volume < 50)
                                {
                                    rollback = true;
                                    break;
                                }
                                // if slicing succeeded, add the newly created solid to the temporay list
                                model.AppendEntity(newSolid);
                                nestedTr.AddNewlyCreatedDBObject(newSolid, true);
                                tempList.Add(newSolid);
                            }
                            catch { }
                        }

                        // if rollbak is false, (if it's true, the nested transaction won't be commited, i.e.aborted)
                        if (!rollback)
                        {
                            //  add the newly created solids to the main list
                            solids.AddRange(tempList);
                            // and commit the nested transaction
                            nestedTr.Commit();
                        }
                    }
                }
                tr.Commit();

                // print results to the text screen
                ed.WriteMessage("\n" +
                    solids.Count +
                    " solids created (volumes: " +
                    string.Join(", ", solids.Select(s => s.MassProperties.Volume.ToString("0"))) +
                    ")");
            }
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 10 of 26

surfer96
Advocate
Advocate

Thanks for your code which basically worked for a range from 50 - 2000 or similar.

 

However if the volumes' conditional boundaries are tightened to e.g. 100 - 1000, computation may either take very long or not lead to any result within a reasonable time span.

 

The logic in the code reminds me of a mandelbrot fractal, just proceeding in one direction with no possibility to step back in case the direction might not have been a good idea. Volume evaluation takes place step by step, which means solid by solid. I may be wrong, but as I see it, a new "slice-solid" meeeting the condition will stay for ever and never be removed even if its position might turnout to be a disadvantage 5 rounds later?

 

Maybe the approach should be like in the LISP-file:

Generate all volumes "at once", check their volumes in a sorted list and then restart from scratch if the bounding conditions are not met.

 

Is it possible to sort the volumes in the output list in ascending order?

Could there be a second "while condition" as to a minimum number of resulting solids, something like:

 

while (solids.Any(s => s.MassProperties.Volume > 2500)) OR solids.count < 10

 

Mandelbrot_set_with_coloured_environment.png

 

0 Likes
Message 11 of 26

_gile
Consultant
Consultant

Due to the fact the slicing plane is randomly defined, the execution time span is unpredictible.

The only condition which can be check at each slicing operation is the genration of a too small solid. This means that, at least, this operation have to be rolled back but, at this step, it's impossible to predict if re-start from scratch would be faster then only roll back this operation.

 

 

while (solids.Count < 10 || solids.Any(s => s.MassProperties.Volume > 2500))

 

 

PS: there's none random generation in the Mandelbrot fractal.

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 12 of 26

surfer96
Advocate
Advocate

Thanks a lot for your extensive and substantial support.

 

One last thought about it:

Does .NET offer the possibility to slice all solids in model space "at once" like in the LISP code below?

 

Something like "solids.Any(Function(s) s.Slice(New Plane(origin, normal), True)"?

 

(repeat NoS
    (setq r1 (* (LM:rand) 100)			; Define the section plane with three points
	  r2 (* (LM:rand) 100)
	  r3 (* (LM:rand) 100)
	  r4 (* (LM:rand) 100)
	  slicePt1 (vlax-3d-point r1 r2 0)
	  slicePt2 (vlax-3d-point r1 r2 1)
          slicePt3 (vlax-3d-point r3 r4 0)
	  )
  (setq ss (ssget "_X" '((0 . "3DSOLID"))))
  (if ss
      (vlax-for obj (vla-get-activeselectionset c_doc)
	(setq sliceObj (vla-SliceSolid obj slicePt1 slicePt2 slicePt3 :vlax-true))
	);end_for
  );end_if
  );end_repeat

 

I have to work on a Revit project for the coming weeks, but will give the .NET challenge another try in march or april...

0 Likes
Message 13 of 26

surfer96
Advocate
Advocate

Just saw that

 

foreach (var solid in solids)
                    {
                        try
                        {
                            var newSolid = solid.Slice(new Plane(origin, normal), true);

 

basically selects any solid previously added to the "solids" list for slicing...

0 Likes
Message 14 of 26

_gile
Consultant
Consultant

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();
            }
        }


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 15 of 26

surfer96
Advocate
Advocate

Sorry, but I do not know how to make the new Polygon2d class accessible.

 

Do I have to "add" the new class to my "VB project" or to "Autodesk.AutoCAD.Geometry" or...?

 

0 Likes
Message 16 of 26

_gile
Consultant
Consultant

You can simply add a new class to your project a new file, you could also also define the class in the same file as your command class or as a private class within the main class.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 17 of 26

_gile
Consultant
Consultant

It looks like you're missing some .NET and OOP basics, you can read more about Classes ans Namespaces here:

C#: Classes and Namespaces

VB: Classes and Namespaces



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 18 of 26

surfer96
Advocate
Advocate

I tried to implement your ideas but had no success. Maybe the new ploygon2d class was not generated correctly...

 

There were no errors indicated in Visual Studio 2017, the dll was built and the command could be  executed in AutoCAD prompting the following message: \nCreated 1 polygons in 0 loops (areas: 0.00)

It seems as if looping and slicing did not start at all.

 

Here is what I tried:

 

mycommands.vb

 

Imports System
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.EditorInput
Imports AutoCAD_VB_plug_in1.Autodesk.AutoCAD.Geometry
Imports System.Linq

' This line is not mandatory, but improves loading performances
<Assembly: CommandClass(GetType(AutoCAD_VB_plug_in1.MyCommands))>

Namespace AutoCAD_VB_plug_in1
    Public Class MyCommands
        <CommandMethod("SliceAPline")>
        Public Shared Sub SliceAPolyline()
            Dim doc = Application.DocumentManager.MdiActiveDocument
            Dim db = doc.Database
            Dim ed = doc.Editor
            Dim square = Polygon2d.Create({Point2d.Origin, New Point2d(100.0, 0.0), New Point2d(100.0, 100.0), New Point2d(0.0, 100.0)})
            Dim polygons = New List(Of Polygon2d) From {
                square
            }
            Dim random = New Random()
            Dim cnt As Integer = 0
            Dim sliced As Polygon2d() = Nothing

            While polygons.Any(Function(p) p.Area > 1000)

                If 200 < cnt Then
                    cnt = 0
                    polygons = New List(Of Polygon2d) From {
                        square
                    }
                    ed.WriteMessage(vbLf & "Re-started")
                End If

                Dim 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))
                Dim toAdd = New List(Of Polygon2d)()
                Dim toRemove = New List(Of Integer)()

                For i As Integer = 0 To polygons.Count - 1

                    If polygons(i).TrySlice(line, sliced) Then

                        If sliced(0).Area < 100 OrElse sliced(1).Area < 100 Then
                            toAdd.Clear()
                            Exit For
                        Else
                            toAdd.AddRange(sliced)
                        End If

                        toRemove.Add(i)
                    End If
                Next

                If toAdd.Count > 0 Then

                    For i As Integer = toRemove.Count - 1 To 0
                        polygons.RemoveAt(toRemove(i))
                    Next

                    polygons.AddRange(toAdd)
                End If

                cnt += 1
            End While

            ed.WriteMessage($"\nCreated {polygons.Count} polygons in {cnt} loops (areas: {String.Join(", ", polygons.[Select](Function(p) p.Area.ToString("0.00")))})")

            Using tr = db.TransactionManager.StartTransaction()
                Dim curSpace = CType(tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite), BlockTableRecord)

                For Each polygon As Polygon2d In polygons
                    Dim pline = polygon.ToPolyline()
                    curSpace.AppendEntity(pline)
                    tr.AddNewlyCreatedDBObject(pline, True)
                Next

                tr.Commit()
            End Using
        End Sub
    End Class
End Namespace

 

 

Class polygon2d.vb

 

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports Autodesk.AutoCAD.DatabaseServices
Imports System.Runtime.InteropServices
Imports Autodesk.AutoCAD.Geometry

Namespace Autodesk.AutoCAD.Geometry
    Public Class Polygon2d
        Public Sub New(ByVal segments As LineSegment2d())
            MyBase.New()
            segments = segments.ToArray()
            Vertices = segments.[Select](Function(s) s.StartPoint).ToArray()
            NumberOfVertices = Vertices.Length
            Initialize()
        End Sub

        Public Property Area As Double
        Public Property Centroid As Point2d
        Public Property IsClockwise As Boolean
        Public ReadOnly Property NumberOfVertices As Integer
        Public ReadOnly Property Segments As LineSegment2d()
        Public ReadOnly Property Vertices As Point2d()

        Public Shared Function Create(ByVal vertices As Point2d()) As Polygon2d
            Dim nbSegments As Integer = vertices.Count()
            Dim segments = New LineSegment2d(nbSegments - 1) {}

            For i As Integer = 0 To nbSegments - 1
                segments(i) = New LineSegment2d(vertices(i), vertices((i + 1) Mod nbSegments))
            Next

            Return New Polygon2d(segments)
        End Function

        Public Function TrySlice(ByVal line As Line2d, <Out> ByRef polygons As Polygon2d()) As Boolean
            polygons = {Me}
            Dim intersections = New List(Of Intersection)()

            For i As Integer = 0 To Segments.Length - 1
                Dim segment = Segments(i)
                Dim intersPts = segment.IntersectWith(line)?.Where(Function(pt) Not Vertices.Contains(pt)).ToArray()

                If intersPts IsNot Nothing Then
                    intersections.Add(New Intersection() With {
                        .Point = intersPts(0),
                        .Index = i
                    })
                End If
            Next

            If intersections.Count = 2 Then
                Dim index0 = intersections(0).Index
                Dim index1 As Integer = intersections(1).Index
                polygons = New Polygon2d(1) {}
                Dim vertices = New Point2d(index0 + 2 + NumberOfVertices - index1 - 1) {}
                Dim i As Integer = 0

                While i < index0 + 1
                    vertices(i) = vertices(i)
                    i += 1
                End While

                vertices(Math.Min(System.Threading.Interlocked.Increment(i), i - 1)) = intersections(0).Point
                vertices(Math.Min(System.Threading.Interlocked.Increment(i), i - 1)) = intersections(1).Point

                For j As Integer = index1 + 1 To NumberOfVertices - 1
                    vertices(Math.Min(System.Threading.Interlocked.Increment(i), i - 1)) = vertices(j)
                Next

                polygons(0) = Create(vertices)
                vertices = New Point2d(index1 - index0 + 2 - 1) {}
                i = 0
                vertices(Math.Min(System.Threading.Interlocked.Increment(i), i - 1)) = intersections(0).Point

                For j As Integer = index0 + 1 To index1 + 1 - 1
                    vertices(Math.Min(System.Threading.Interlocked.Increment(i), i - 1)) = vertices(j)
                Next

                vertices(i) = intersections(1).Point
                polygons(1) = Create(vertices)
                Return True
            End If

            Return False
        End Function

        Public Function ToPolyline() As Polyline
            Dim pline = New Polyline()

            For i As Integer = 0 To NumberOfVertices - 1
                pline.AddVertexAt(i, Vertices(i), 0.0, 0.0, 0.0)
            Next

            pline.Closed = True
            Return pline
        End Function

        Private Sub Initialize()
            Dim cen As Point2d = New Point2d()
            Dim triangleCenter As Point2d
            Dim triangleArea As Double
            Dim area As Double = 0.0
            Dim last As Integer = NumberOfVertices - 1
            Dim p0 As Point2d = Vertices(0)

            For i As Integer = 1 To last - 1
                Dim p1 = Vertices(i)
                Dim 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()
            Next

            area = Math.Abs(area / 2.0)
            Centroid = cen.DivideBy(area)
            IsClockwise = area < 0.0
        End Sub

        Structure Intersection
            Public Property Point As Point2d
            Public Property Index As Integer
        End Structure
    End Class
End Namespace

 

 

0 Likes
Message 19 of 26

surfer96
Advocate
Advocate

Here is a picture to illustrate what I'm actually after:

 

Unbenannt.JPG

 

An outer solid defines a geometrical boundary, an inner solid will be substracted from the outer solid so the thickness of the remaining shell will be 1.

 

Then a series of random planes should slice the shell into separated solids, with all their volumes being compared to a restrictive volume condition within a while loop.

 

The thickness of all resulting volumes remaining 1, all volumes will equal the facade elements' surfaces.

 

I've tried this with Revit/Dynamo/Python and other visual programinng solutions which basically worked but was far too slow. LISP was faster but still not fast enough.

 

And now I would like to know wether .NET might be a solution to the speed problem...

0 Likes
Message 20 of 26

surfer96
Advocate
Advocate

A picture of the Solution Explorer with the new "Polygon2d" class:

 

Unbenannt.JPG

 

0 Likes