Slicing a solid with a planar surface

Slicing a solid with a planar surface

R.Gerritsen4967
Advocate Advocate
4,239 Views
16 Replies
Message 1 of 17

Slicing a solid with a planar surface

R.Gerritsen4967
Advocate
Advocate

The slice function only works with a plane or a surface.

But now I'm trying to slice a solid with a planar surface, and unfortunately I'm having no succes.

 

According to the AutoCAD help, the function needs a plane or a surface as input. It's also possible to enter an optional boolean value to keep the negative half of the sliced solid. Although my Visual Studio gives me an error if I omit the boolean value.

 

I got it working with a plane I defined, but this plane slices the solid in some unwanted places.

 

Dim newSolid = sol.Slice(plane, True)

 

 

So I tried to draw a planar surface which is exactly the crossection of the solid. (a circle for instance).

The slice function does not work with a planar surface, so I used:

 

srf.CopyFrom(plnsrf)
srf = TryCast(srf, Autodesk.AutoCAD.DatabaseServices.Surface)

 

This converts the planar surface to a normal surface. (Added to the database and checked objecttype. This conversion is working)

But now the slice function crashes AutoCAD and I can't figure out why.

 

Using the normal slice command in AutoCAD enables me to choose a planar object (surface or even region) to slice the solid.

 

Does anyone know what I'm doing wrong?

0 Likes
4,240 Views
16 Replies
Replies (16)
Message 2 of 17

_gile
Consultant
Consultant

Hi,

 

You did not show relevant code to illustrate your issue.

 

here's an example of slicing a solid (cylinder) with a Plane.

 

        [CommandMethod("TEST")]
        public static void Test()
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            var db = doc.Database;
            using (var tr = db.TransactionManager.StartTransaction())
            {
                using (var solid = new Solid3d())
                {
                    var model = (BlockTableRecord)tr.GetObject(
                        SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
                    solid.CreateFrustum(10.0, 2.0, 2.0, 2.0);
                    model.AppendEntity(solid);
                    tr.AddNewlyCreatedDBObject(solid, true);
                    var plane = new Plane(Point3d.Origin, new Vector3d(1.0, 0.0, -1.0));
                    solid.Slice(plane);
                }
                tr.Commit();
            }
        }

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 17

R.Gerritsen4967
Advocate
Advocate

Hi @_gile ,

Thanks for your answer.

I did not post much code because it works OK when I use a plane to slice the solid. So I know that part of the code is good. By the way I'm using an example you posted somewhere on the forum 'SliceaBox'.

 

My problem is that I want to slice the solid with a (planar) surface.

I can generate the planar surface, convert it to a surface and add them both to the drawing to check if they are the way I want them to be. This all works OK.If I just use the AutoCAD slice command and use the newly generated surface, everything works out fine. Even if I explode the surface and use the region for slicing it is like I want it to be.

But when I want to use the surface instead of the plane to slice the solid with the .net method, AutoCAD crashes.

 

So I must be doing something wrong. And unfortunately I can't find examples online of slicing with a surface.

 

0 Likes
Message 4 of 17

_gile
Consultant
Consultant

OK, you're right, it seems ther's a bug with the Solid3d.Slice(Autodesk.AutoCAD.DatabaseServices.Surface surface) method.

So, you can either use the Surface.GetPlane() method.

public static void Test()
{
    var doc = Application.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    using (var tr = db.TransactionManager.StartTransaction())
    using (var solid = new Solid3d())
    {
        var model = (BlockTableRecord)tr.GetObject(
            SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
        solid.CreateFrustum(10.0, 2.0, 2.0, 2.0);
        model.AppendEntity(solid);
        tr.AddNewlyCreatedDBObject(solid, true);

        using (var circle = new Circle(Point3d.Origin, new Vector3d(1.0, 0.0, -1.0), 3.0))
        {
            var curves = new DBObjectCollection();
            curves.Add(circle);
            var regions = Region.CreateFromCurves(curves);
            using (var region = (Region)regions[0])
            {
                var surface = new PlaneSurface();
                surface.CreateFromRegion(region);
                model.AppendEntity(surface);
                tr.AddNewlyCreatedDBObject(surface, true);

                solid.Slice(surface.GetPlane());
            }
        }
        tr.Commit();
    }
}

or the overload with the negativeHalfToo argument

public static void Test()
{
    var doc = Application.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    using (var tr = db.TransactionManager.StartTransaction())
    using (var solid = new Solid3d())
    {
        var model = (BlockTableRecord)tr.GetObject(
            SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);
        solid.CreateFrustum(10.0, 2.0, 2.0, 2.0);
        model.AppendEntity(solid);
        tr.AddNewlyCreatedDBObject(solid, true);

        using (var circle = new Circle(Point3d.Origin, new Vector3d(1.0, 0.0, -1.0), 3.0))
        {
            var curves = new DBObjectCollection();
            curves.Add(circle);
            var regions = Region.CreateFromCurves(curves);
            using (var region = (Region)regions[0])
            {
                var surface = new PlaneSurface();
                surface.CreateFromRegion(region);
                model.AppendEntity(surface);
                tr.AddNewlyCreatedDBObject(surface, true);

                var solid2 = solid.Slice(surface, true);
                model.AppendEntity(solid2);
                tr.AddNewlyCreatedDBObject(solid2, true);
            }
        }
        tr.Commit();
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 5 of 17

R.Gerritsen4967
Advocate
Advocate

@_gile,

 

Thanks for checking this!

Unfortunately I can't use your solution with the Surface.GetPlane(), because the plane slices the solid in more places.

I need it to slice at jut one place.

 

For instance a pipe with a more than 90° bend in it. If I want to slice the pipe at the start and end of the bend, I can't use the plane because this will slice the pipe in other places.

 

Time for another approach. Now I sweep the pipe/other structure along a polyline, so I will try to explode the polyline and do a sweep for every part. So a new challenge arises: fixing the sweep in way of the corners (mitre angles) in the polyline...

0 Likes
Message 6 of 17

_gile
Consultant
Consultant

So, a workaround should be calling the overloaded with the negativeHalfToo  argument and immediately dispose of the newly created solid.

solid.Slice(surface, true).Dispose();


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 7 of 17

R.Gerritsen4967
Advocate
Advocate

@_gile,

I'm not sure what you mean. Because when the plane slices the solid at more than one point there are already more than 2 resulting solids from the slice. If I dispose some of them it leaves me with a solid with gaps...

0 Likes
Message 8 of 17

_gile
Consultant
Consultant

I meant using the plane surface object itself (not its plane) with the Slice(Autodesk.AutoCAD.DatabaseSevices.Surface surface, bool negativeHalfToo) overload which does not crash AutoCAD and dispose of the 'negative half solid'.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 9 of 17

R.Gerritsen4967
Advocate
Advocate

@_gile,

 

Unfortunately this also crashes AutoCAD. Just using the surface crashes AutoCAD. Using the plane of the surface works but is not a solution for me.

Omitting the boolean value gives me a visual studio error:

afbeelding.png

 

I'm using AutoCAD 2020 and Visual studio 2017. DLL is compiled for .Net framework 4.7.

Referencefiles are ObjectARX2020 versions.

 

This is my vb.net code:

 

 

 

 

   <CommandMethod("slicetest", CommandFlags.Modal)> Public Shared Sub SliceTest()
        Dim doc As Document = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument
        Dim ed As Editor = doc.Editor
        Dim db As Database = doc.Database

        Using trx As Transaction = db.TransactionManager.StartTransaction()
            Dim btr As BlockTableRecord = TryCast(trx.GetObject(db.CurrentSpaceId, OpenMode.ForWrite), BlockTableRecord)
            Dim peo As New PromptEntityOptions(vbLf & "Select polyline: ")
            peo.SetRejectMessage(vbLf & "Select POLYLINE!!!")
            peo.AddAllowedClass(GetType(Autodesk.AutoCAD.DatabaseServices.Polyline), True)
            Dim per As PromptEntityResult = ed.GetEntity(peo)
            If per.Status <> PromptStatus.OK Then Return
            Dim plPath As Autodesk.AutoCAD.DatabaseServices.Polyline = TryCast(per.ObjectId.GetObject(OpenMode.ForWrite), Autodesk.AutoCAD.DatabaseServices.Polyline)

            Dim Dia As Double = 100
            Dim circ As New Circle(plPath.StartPoint, New Vector3d(0, 0, 1), Dia / 2)
            Dim sol As New Solid3d With {
                .RecordHistory = True
            }
            Dim sob As New SweepOptionsBuilder With {
                .Align = SweepOptionsAlignOption.AlignSweepEntityToPath
            }

            sol.CreateSweptSolid(circ, plPath, sob.ToSweepOptions())
            btr.AppendEntity(sol)
            trx.AddNewlyCreatedDBObject(sol, True)

            Dim solids = New List(Of Solid3d) From {
                sol
            }

            For i As Integer = 1 To plPath.NumberOfVertices - 2

                Dim tempList = New List(Of Solid3d)()
                Dim vec As New Vector3d
                Dim plnsrf As New PlaneSurface
                Dim srf As New Autodesk.AutoCAD.DatabaseServices.Surface

                If plPath.GetSegmentType(i) = SegmentType.Arc Then
                    vec = plPath.GetPointAtParameter(i).GetVectorTo(plPath.GetPointAtParameter(i - 1))

                    Dim acDBObjColl As New DBObjectCollection()
                    acDBObjColl.Add(New Circle(plPath.GetPointAtParameter(i), vec, (Dia / 2)))
                    Dim circleregion As New DBObjectCollection()
                    circleregion = Region.CreateFromCurves(acDBObjColl)
                    plnsrf.CreateFromRegion(circleregion(0))
                    srf.CopyFrom(plnsrf)
                    srf = TryCast(srf, Autodesk.AutoCAD.DatabaseServices.Surface)
                    btr.AppendEntity(srf)
                    trx.AddNewlyCreatedDBObject(srf, True)

                End If

                If plPath.GetSegmentType(i - 1) = SegmentType.Arc Then
                    vec = plPath.GetPointAtParameter(i).GetVectorTo(plPath.GetPointAtParameter(i + 1))

                    Dim acDBObjColl As New DBObjectCollection()
                    acDBObjColl.Add(New Circle(plPath.GetPointAtParameter(i), vec, (Dia / 2)))
                    Dim circleregion As New DBObjectCollection()
                    circleregion = Region.CreateFromCurves(acDBObjColl)
                    plnsrf.CreateFromRegion(circleregion(0))
                    srf.CopyFrom(plnsrf)
                    srf = TryCast(srf, Autodesk.AutoCAD.DatabaseServices.Surface)
                    btr.AppendEntity(srf)
                    trx.AddNewlyCreatedDBObject(srf, True)

                End If

                For Each sol In solids
                    Try
                        'Dim newSolid = sol.Slice(srf, True) 'Slicing with surface crashes AutoCAD
                        'sol.Slice(srf, True).Dispose() 'Slicing with surface crashes AutoCAD

                        Dim newSolid = sol.Slice(srf.GetPlane, True) 'Slicing with plane works, but no solution to my problem
                        'sol.Slice(srf.GetPlane, True).Dispose()

                        btr.AppendEntity(newSolid)
                        trx.AddNewlyCreatedDBObject(newSolid, True)
                        tempList.Add(newSolid)
                    Catch
                        ed.WriteMessage(vbLf & "Failed to slice.")
                    End Try
                Next

                solids.AddRange(tempList)
            Next
            trx.Commit()
        End Using
    End Sub

 

 

 

 

 

In the last part (The for next loop) I have commented out some of the options to play around.

This code makes a solid pipe with (diameter 100). At this stage I only want to deal with arcsegments in the polyline.

See below for a screenshot of a polyline I'm using.

afbeelding.png

 

 

0 Likes
Message 10 of 17

_gile
Consultant
Consultant

Here're two examples of testing commands.

Both prompt the user to select a solid and a surface.

TEST1 slices the solid with the selected surface and dispose of the 'negative half' solid.

TEST2 slices the solid with the selected surface and keep both solids (i.e. add the newly created one instead of disposing it)

 

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

    var options = new PromptEntityOptions("\nSelect solid: ");
    options.SetRejectMessage("\nInvalid selection.");
    options.AddAllowedClass(typeof(Solid3d), true);
    var result = ed.GetEntity(options);
    if (result.Status != PromptStatus.OK) 
        return;
    var solidId = result.ObjectId;

    options.Message = "\nSelect surface: ";
    options.RemoveAllowedClass(typeof(Solid3d));
    options.AddAllowedClass(typeof(AcDb.Surface), false);
    result = ed.GetEntity(options);
    if (result.Status != PromptStatus.OK)
        return;
    var surfaceId = result.ObjectId;

    using (var tr = db.TransactionManager.StartTransaction())
    {
        var solid = (Solid3d)tr.GetObject(solidId, OpenMode.ForWrite);
        var surface = (AcDb.Surface)tr.GetObject(surfaceId, OpenMode.ForRead);
        solid.Slice(surface, true).Dispose();
        tr.Commit();
    }
}

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

    var options = new PromptEntityOptions("\nSelect solid: ");
    options.SetRejectMessage("\nInvalid selection.");
    options.AddAllowedClass(typeof(Solid3d), true);
    var result = ed.GetEntity(options);
    if (result.Status != PromptStatus.OK)
        return;
    var solidId = result.ObjectId;

    options.Message = "\nSelect surface: ";
    options.RemoveAllowedClass(typeof(Solid3d));
    options.AddAllowedClass(typeof(AcDb.Surface), false);
    result = ed.GetEntity(options);
    if (result.Status != PromptStatus.OK)
        return;
    var surfaceId = result.ObjectId;

    using (var tr = db.TransactionManager.StartTransaction())
    {
        var solid = (Solid3d)tr.GetObject(solidId, OpenMode.ForWrite);
        var surface = (AcDb.Surface)tr.GetObject(surfaceId, OpenMode.ForRead);
        var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
        var solid2 = solid.Slice(surface, true);
        curSpace.AppendEntity(solid2);
        tr.AddNewlyCreatedDBObject(solid2, true);
        tr.Commit();
    }
}

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 11 of 17

R.Gerritsen4967
Advocate
Advocate

Thanks@_gile for taking the time to make an example!

The test2 command does what I want. Even with the surfaces I created with my own routine.

So I will try to find out if I can incorporate your solution into mine.

 

0 Likes
Message 12 of 17

Izhar_Azati
Advocate
Advocate
		/// <summary>
		/// Slice solid with planer polygon (XY plane)
		/// (without the call to Sleep(), autocad crash)
		/// </summary>
		[_AcTrx.CommandMethod( "TEST3" )]
		public static void Test3() {
			var doc = _AcApCore.Application.DocumentManager.MdiActiveDocument;
			var db = doc.Database;
			var ed = doc.Editor;
#if TEST_WITH_SLEEP
			var sleep = Math.Abs( db.Useri4 );
#endif

			var options = new _AcEd.PromptEntityOptions( "\nSelect solid: " );
			options.SetRejectMessage( "\nInvalid selection." );
			options.AddAllowedClass( typeof( _AcDb.Solid3d ), true );
			var result = ed.GetEntity( options );
			if( result.Status != _AcEd.PromptStatus.OK )
				return;
			var solidId = result.ObjectId;

			options.RemoveAllowedClass( typeof( _AcDb.Solid3d ) );

			options.Message = "\nSelect Polyline: ";
			options.AddAllowedClass( typeof( _AcDb.Polyline ), false );
			result = ed.GetEntity( options );
			if( result.Status != _AcEd.PromptStatus.OK )
				return;

			var polylineId = result.ObjectId;
			var textLayerId = GetLayer( "MyVertexLayer", db );

			using( var tr = db.TransactionManager.StartTransaction() ) {
				if( tr.GetObject( _AcDb.SymbolUtilityServices.GetBlockModelSpaceId( db ), _AcDb.OpenMode.ForWrite ) is _AcDb.BlockTableRecord ms ) {
					if( tr.GetObject( polylineId, _AcDb.OpenMode.ForRead ) is _AcDb.Polyline polyline ) {
						if( tr.GetObject( solidId, _AcDb.OpenMode.ForRead ) is _AcDb.Solid3d solid ) {
							try {
								var zMin = solid.MassProperties.Extents.MinPoint.Z - 10.0;
								var zMax = solid.MassProperties.Extents.MaxPoint.Z;
								var sweepSize = zMax - zMin + 10.0;
								var pointNumber = 0;

								//	create temporary polyline for creating the temporary vertical cutting surface
								var polylineTemp = polyline.Clone() as _AcDb.Polyline;
								if( polylineTemp != null ) {
									try {
										polylineTemp.Elevation = zMin;
										var vec = new _AcGe.Vector3d( 0.0, 0.0, sweepSize );
										var surface = new _AcDb.ExtrudedSurface();
										var swo = new _AcDb.SweepOptions();
										surface.CreateExtrudedSurface( polylineTemp, vec, swo );
										//var surfaceId = ms.AppendEntity( surface );
										//tr.AddNewlyCreatedDBObject( surface, true );
#if TEST_WITH_SLEEP
										Thread.Sleep( sleep );
#endif
										try {
											var solidDup = solid.Clone() as _AcDb.Solid3d;
											if( solidDup != null ) {
												ms.AppendEntity( solidDup );
												tr.AddNewlyCreatedDBObject( solidDup, true );
#if TEST_WITH_SLEEP
												Thread.Sleep( sleep );
#endif
												try {
													solidDup.Slice( surface );
#if TEST_WITH_SLEEP
													Thread.Sleep( sleep );
#endif
													if( !solidDup.IsNull && solidDup.Area > 0.001 ) {
														solidDup.ColorIndex = 5;
														using( var brep = new Brep( solidDup ) ) {
															foreach( var vertex in brep.Vertices ) {
																var p = vertex.Point;
																pointNumber++;
																var txt = new _AcDb.DBText();
																txt.Position = p;
																txt.TextString = pointNumber.ToString();
																txt.LayerId = textLayerId;
																ms.AppendEntity( txt );
																tr.AddNewlyCreatedDBObject( txt, true );
															}
														}
													}
												} catch {
													solidDup.Erase();
												}
											}
										} catch( System.Exception ex2 ) {
											ed.WriteMessage( "\nex2: " + ex2.Message );
										}
									} catch( System.Exception ex3 ) {
										ed.WriteMessage( "\nex3: " + ex3.Message );
									}
								}
							} catch( System.Exception ex4 ) {
								ed.WriteMessage( "\nex4: " + ex4.Message );
							}
						}
					}
				}
				tr.Commit();
			}
		}


		public static _AcDb.ObjectId GetLayer( string layerName, _AcDb.Database db ) {
			_AcDb.ObjectId ltId;
			using( var tr = db.TransactionManager.StartTransaction() ) {
				var lt = (_AcDb.LayerTable)tr.GetObject( db.LayerTableId, _AcDb.OpenMode.ForRead );
				if( lt.Has( layerName ) ) {
					ltId = lt[layerName];
					if( tr.GetObject( ltId, _AcDb.OpenMode.ForRead ) is _AcDb.LayerTableRecord lyr && (lyr.IsLocked || lyr.IsFrozen || lyr.IsOff) ) {
						lyr.UpgradeOpen();
						lyr.IsLocked = false;
						lyr.IsFrozen = false;
						lyr.IsOff = false;
						lyr.DowngradeOpen();
					}
				} else {
					var ltr = new _AcDb.LayerTableRecord { Name = layerName };
					lt.UpgradeOpen();
					ltId = lt.Add( ltr );
					tr.AddNewlyCreatedDBObject( ltr, true );
					lt.DowngradeOpen();
				}
				tr.Commit();
			}
			return ltId;
		}

 

Screenshot 2022-02-02 174853.png

Message 13 of 17

izzycamerlinckx
Observer
Observer

Hi, I had the same problem and this seems to fix it. I don't have any understanding of coding though, could you tell me how I can create this command in autocad, or what I have to google? I found something about lisp files and adding them via loading application under the Manage tab, but this seems to be a different language, and needs to be a different file? Thanks!

0 Likes
Message 14 of 17

Izhar_Azati
Advocate
Advocate

Hello @izzycamerlinckx 

 

In the attached files you can find:
DWG file for test.
ZIP file of the code (C#).
ZIP file of DLL with function to cut SOLID by POLYLINE.

 

The DLL file must be copied to a directory that ACAD trusts, and from ACAD load it with the command: NETLOAD
To run the command, write CutByPolyline or CBP for short.

The cut is made perpendicular to the XY plane.
The original body does not change, a copy is created and it is cut, leaving only one side.
DLL files can be dangerous, always check with antivirus software!

Test on AutoCAD 2021, 2024.

0 Likes
Message 15 of 17

Izhar_Azati
Advocate
Advocate

Second attempt for the files I wanted to attach

0 Likes
Message 16 of 17

izzycamerlinckx
Observer
Observer

Thank you very much for the command and explaining how to load it!

0 Likes
Message 17 of 17

doanphung
Participant
Participant

I had faced the above problem. If boolen == True, return a solid3D and vice as.

Solid3d OutputSolid1 = null;
// Determine the side based on the dot product
if (dotProduct > 0)
{
solid.Slice(slicingPlane, false);
OutputSolid1 = solid;
}
else if (dotProduct < 0)
{
OutputSolid1 = solid.Slice(slicingPlane, true);
}
slicingPlane.Dispose();
return OutputSolid1;

0 Likes