I'm trying to draw a solid 3D shape in AutoCAD programmatically. I can get the shape to draw as a wireframe. Here is what my code currently draws
The object is a bunch of AutoCAD lines grouped together into a Group object. What I want is a method that takes a Group as an argument and turns that group into a solid blockreference. Here is my attempt at it.
public static void DefineBlockFromGroup(Group groupToCreateBlockFrom, string blockName, Point3d origin, bool replaceChosenEntities = false) { List<ObjectId> idsOfEntitiesInGroup = groupToCreateBlockFrom.GetAllEntityIds().ToList(); //Getting the entities that will be used to create the block CreateBlockDefinition(idsOfEntitiesInGroup, blockName, origin, replaceChosenEntities); } public static void CreateBlockDefinition(List<ObjectId> entities, string blockName, Point3d origin, bool replaceChosenEntities = false) { BlockTableRecord record = new BlockTableRecord(); //Will become record of new block type ObjectId blockId = ObjectId.Null; //Will become id of new block type using (Transaction tr = _database.TransactionManager.StartTransaction()) using (DocumentLock docLock = _activeDocument.LockDocument()) { BlockTable bt = (BlockTable)tr.GetObject(_database.BlockTableId, OpenMode.ForRead); //Getting block table to put new record in bt.UpgradeOpen(); record = new BlockTableRecord(); //New record will contain all entities which will make up the block record.Name = blockName; //Setting name of the block record.Origin = origin; //Setting origin point of the block bt.Add(record); tr.AddNewlyCreatedDBObject(record, true); blockId = bt[record.Name]; tr.Commit(); } //copy the select entities to block by using deepclone. ObjectIdCollection collection = new ObjectIdCollection(entities.ToArray()); IdMapping mapping = new IdMapping(); _database.DeepCloneObjects(collection, blockId, mapping, false); if (replaceChosenEntities) { using (Transaction tr = _database.TransactionManager.StartTransaction()) using (DocumentLock docLock = _activeDocument.LockDocument()) { Entity baseEntity = tr.GetObject(entities[0], OpenMode.ForWrite) as Entity; foreach (ObjectId id in entities) { EraseSingleObjectFromDrawingWithId(id); } DrawBlockFacingAPoint(record.Name, record.Origin, record.Origin, baseEntity.Layer, null); tr.Commit(); } } } public static void EraseSingleObjectFromDrawingWithId(ObjectId idOfObjectToErase) { // Start a transaction using (Transaction tr = _database.TransactionManager.StartTransaction()) { DBObject objToErase = tr.GetObject(idOfObjectToErase, OpenMode.ForWrite); // Check to make sure a valid SelectedObject object was returned if (objToErase != null) { objToErase.Erase(); } tr.Commit(); } } public static BlockReference DrawBlockFacingAPoint(string name, Point3d position, Point3d direction, string layerToInsertOn, List<string> attributeValues = null, Distance xScale = null, Distance yScale = null, Distance zScale = null) { LastPositionPoint = position; LastDirectionPoint = direction; //Creating default distances if null is passed for the scales if (xScale == null) { xScale = new Distance(DistanceType.Inch, 1); } if (yScale == null) { yScale = new Distance(DistanceType.Inch, 1); } if (zScale == null) { zScale = new Distance(DistanceType.Inch, 1); } ObjectId blkRecId = _generateBlockRecordId(name); //Generating ID for the block BlockReference blkRef = null; //Reference of the block that will be inserted using (Transaction tr = _database.TransactionManager.StartTransaction()) //Starting the transaction to insert the block into the drawing using (DocumentLock docLock = _activeDocument.LockDocument()) { blkRef = new BlockReference(position, blkRecId); //Creating the block reference blkRef.SetDatabaseDefaults(); blkRef.ScaleFactors = new Scale3d(xScale.Inches, yScale.Inches, zScale.Inches); //Changing the scales to what the programmer specifies blkRef.Layer = layerToInsertOn; //Assigning layer to draw the block on Point start = Point.MakePointWithInches(position.X, position.Y, position.Z); //Generating first AutoCAD point Point end = Point.MakePointWithInches(direction.X, direction.Y, direction.Z); //Generating second AutoCAD point GeometryClassLibrary.Line line = new GeometryClassLibrary.Line(start, end); //Creating a line to mirror a block over if necessary Angle a = new Angle(AngleType.Radian, 0); //Angle to rotate the block by //Assigning angle based on direction point if (position.Y != direction.Y) { a = line.AngleBetween(GeometryClassLibrary.Line.XAxis); } if (direction.Y < position.Y) { blkRef.Rotation = a.Negate().GetValue(AngleType.Radian); //Allowing for rotations beyond 180 degrees } else { blkRef.Rotation = a.GetValue(AngleType.Radian); } BlockTableRecord blkTblRec = tr.GetObject(_database.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; blkTblRec.AppendEntity(blkRef); //Adding block referece to the block table of the drawing tr.AddNewlyCreatedDBObject(blkRef, true); //Adding block to the drawing //Assigning attributes of the block BlockTableRecord btr = (BlockTableRecord)tr.GetObject(blkRef.BlockTableRecord, OpenMode.ForRead); int attCounter = 0; //Counter to iterate through attributes foreach (ObjectId objId in btr) //Checking each item in the BlockReference's records { DBObject obj = objId.GetObject(OpenMode.ForRead); if (obj is AttributeDefinition) //If the object is an attribute, update it. { AttributeDefinition ad = obj as AttributeDefinition; AttributeReference ar = new AttributeReference(); ar.SetAttributeFromBlock(ad, blkRef.BlockTransform); ar.Position = ad.Position.TransformBy(blkRef.BlockTransform); try { ar.TextString = attributeValues.ElementAt(attCounter); } catch (ArgumentNullException) { ar.TextString = ""; } catch (ArgumentOutOfRangeException) { ar.TextString = ""; } attCounter++; blkRef.AttributeCollection.AppendAttribute(ar); tr.AddNewlyCreatedDBObject(ar, true); } } tr.Commit(); return blkRef; } } private static ObjectId _generateBlockRecordId(string passedBlockName) { Transaction tr = _database.TransactionManager.StartTransaction(); DocumentLock docLock = _activeDocument.LockDocument(); using (tr) using (docLock) { BlockTable blkTbl = tr.GetObject(_database.BlockTableId, OpenMode.ForRead) as BlockTable; ObjectId blkRecId = ObjectId.Null; if (blkTbl.Has(passedBlockName)) //Checking if the block exists { blkRecId = blkTbl[passedBlockName]; //If it does, getting the current id } else //If it doesn't exist create one { Database blkDb = new Database(false, true); blkDb.ReadDwgFile(passedBlockName + ".dwg", System.IO.FileShare.Read, true, ""); //Reading block source file _database.Insert(passedBlockName, blkDb, true); blkRecId = blkTbl[passedBlockName]; } return blkRecId; } }
Sorry there's so many methods involved. I think the problem lies in actually drawing the block but I included all the involved methods just in case. Anyways, the methods succeed in creating a block definition and drawing an instance of that block in place of the group of lines. Problem is the block is still a wireframe. If anyone knows how to change this approach to draw solid blocks I will be wicked grateful!
Solved! Go to Solution.
Solved by Keith.Brown. Go to Solution.
I believe that the problem you are going to have is that you cannot create a solid from a group of lines. None of the constructors for a solid3d take a line or a group of lines as a parameter. You can grab the endpoints of your lines and create a solid as a box and plug in the coordinates of the endpoints. That might get you what you are after but you will need to do some work to determine the points to use.
Here is a link to the help file that shows how to create a 3d solid as a wedge. You should be able to use it to create a solid as a box also.
http://help.autodesk.com/view/ACD/2016/ENU/?guid=GUID-A5DEE210-347C-4FB7-A0D7-C48F3B949225
Thanks for the link! I got solid3d objects drawing now thanks to this method
public static ObjectId DrawPolyhedron(Polyhedron polyhedronToDraw, string layerToInsertOn = "0")
{
Solid3d polyhedronToReturn = new Solid3d();
using (Transaction tr = _database.TransactionManager.StartTransaction())
{
// Open the Block table record for read
BlockTable acBlkTbl = tr.GetObject(_database.BlockTableId, OpenMode.ForRead) as BlockTable;
// Open the Block table record Model space for write
BlockTableRecord acBlkTblRec = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
polyhedronToReturn.CreateBox(polyhedronToDraw.FindLength().Inches, polyhedronToDraw.FindWidth().Inches, polyhedronToDraw.FindHeight().Inches);
// Add the new object to the block table record and the transaction
acBlkTblRec.AppendEntity(polyhedronToReturn);
tr.AddNewlyCreatedDBObject(polyhedronToReturn, true);
_editor.Regen();
tr.Commit();
return polyhedronToReturn.Id;
}
}
The problem is that it draws them at the origin. How can I specify where I want the location of the solid to be?
get a displacement matrix and move your solid, maybe (not tested) - hth:
var location = polyhedronLocation - Point3d.Origin;
var displacement = Matrix3d.Displacement(location);
solid.TransformBy(displacement);
No need to get fancy with getting the displacement vector. Just do this.
polyhedronToReturn.TransformBy(polyhedronToReturn.CenterPoint.GetAsVector())
You should be able to calculate the solid center point from the line points. I believe that the box is centered upon the origin when created so you only need to transform it to the center point of the original box defined by lines.
I see, so there's no way to create a solid object (even say just a solid rectangle) from a list of points or faces?
If you notice in the solid.CreateBox method it asks for a length, width, and height. No where does it ask for a location. This is because it will always create it centered on the origin.
You are free of course to wrap the CreateBox method into one of your own methods where you pass the location in and it will automatically transform the box. Something like this.
Public Solid3d CreateBoxWithLocation(double Length, double, Width, double Height, Point3d Location) { var solid = New Solid3d() solid.CreateBox(Length, Width, Height) solid.TransformBy(Matrix3d.Displacement(location.GetAsVector())) return solid }
I wrote that off the top of my head so the syntax might be incorrect but hopefully you get the point.
p.s. You will notice in the post above when I was showing how to tranform the solid that I forgot to add the Matrix3d. This post shows the correct method. I am writing this from my phone so not easy to double check code syntax.
Here is an extension method that I use from my library. It overloads the CreateBox method with one that also accepts a point3d for the location.
namespace KAB.Tools.GeometryExtensions { using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; using AcRx = Autodesk.AutoCAD.Runtime; public static class Solid3DExtensions { private const double Epsilon = 0.005; public static Solid3d CreateBox(this Solid3d solid, double length, double width, double height, Point3d location) { if (solid == null) { throw new AcRx.Exception(ErrorStatus.NullEntityPointer, "The solid cannot be null!"); } if ((length < Epsilon) || (width < Epsilon) || (height < Epsilon)) { throw new AcRx.Exception(ErrorStatus.OutOfRange, "The parameter is too small!"); } solid.CreateBox(length, width, height); solid.TransformBy(Matrix3d.Displacement(location.GetAsVector())); return solid; } } }
Something else to consider when dealing with long regular objects (pipe, ducting, many steel shapes) is you would normally have the cross-sectional shape and a length. From that you can construct an outline in the appropriate plane and location then extrude it to the required solid.