Author Topic: How to add breaklines to Civil-3D surface in C#.NET?  (Read 6391 times)

0 Members and 1 Guest are viewing this topic.

sinc

  • Guest
How to add breaklines to Civil-3D surface in C#.NET?
« on: March 10, 2007, 02:29:51 PM »
How do you add a breakline to a surface?  I've managed to get an existing TIN surface, and find the Breaklines collection, which has the following method:

Code: [Select]
HRESULT AddStandardBreakline(
[in] VARIANT pBreaklineEntities,
[in] BSTR Description,
[in] double MidOrdinateDistance,
[out, retval] IAeccSurfaceBreakline ** pBreakline
);

But I can't figure out how to use this method.  No matter what I try, I get "Value does not fall within expected range" exceptions.  What does this method expect for the "pBreaklineEnitites" argument?  The help says this:

Code: [Select]
pBreaklineEntities  VARIANT which contains a zero based safe array of object ids or BreaklineEntity instances. 

...but what exactly does that mean?  What object ids can be used, or how do I create BreaklineEntity instances?

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: How to add breaklines to Civil-3D surface in C#.NET?
« Reply #1 on: March 10, 2007, 09:40:46 PM »
Hi Sinc,
My brain is too fried to attempt this in C# tonight, but with a little bit of fooling around in in VBA I was able to add breaklines. I hope you can convert what I did in VBA to C#.....
Code: [Select]
    Const sAppName = "AeccXUiLand.AeccApplication.4.0"
    Dim oApp As AcadApplication
    Dim oSurf As AeccTinSurface
     
    Set oApp = ThisDrawing.Application
    Set g_oCivilApp = oApp.GetInterfaceObject(sAppName)
    Set g_oAeccDoc = g_oCivilApp.ActiveDocument
    Set oSurf = g_oAeccDoc.Surfaces.Item("XGND")
    Dim opoly As AcadEntity
    Dim vpoint As Variant
    Dim oBrklines As AeccSurfaceBreaklines
    Dim polys(0) As Object
    ThisDrawing.Utility.GetEntity opoly, vpoint, "Select polyline: "
    Set oBrklines = oSurf.Breaklines
   
    Set polys(0) = opoly
    oBrklines.AddStandardBreakline polys, "My polys", 1#

sinc

  • Guest
Re: How to add breaklines to Civil-3D surface in C#.NET?
« Reply #2 on: March 11, 2007, 08:37:49 AM »
I'm not having much luck.

First, I can't seem to get access to the AeccDoc object.  I think it might have something to do with all those warnings that appear when trying to load Autodesk.AEC.Interop.UIBase and Autodesk.AECC.Interop.UiLand.  I'm beginning to wonder if the complete Civil-3D API is even functional in C#...  Do you know of anyone else who has actually been trying to customize C3D with C#?

Anyway, since I can't seem to use the AeccDoc object, I tried going through the AeccDb object.  But Autodesk also seems to have left out the Item() method on the AeccDb.Surfaces collection.  So I had to do the following to get the surface:
Code: [Select]
public IAeccSurface GetSurfaceWithName(string surfaceName)
{
foreach (IAeccSurface surface in aeccDb.Surfaces) {
if (surface.Name == surfaceName)
return surface;
}
return null;
}

I then tried converting your VBA, and ended up with the following nonfunctional code:
Code: [Select]
[CommandMethod("BRK")]
public void CreateBreaklinesBySelection()
{
c3dUtil = new C3DUtil();
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
try {
PromptEntityOptions pops = new PromptEntityOptions("\nSelect polyline:");
PromptEntityResult pres = ed.GetEntity(pops);
if (pres.Status == PromptStatus.OK) {
ObjectId[] idArray = new ObjectId[] {pres.ObjectId};
IAeccTinSurface surface = (IAeccTinSurface)c3dUtil.GetSurfaceWithName("EG1");
surface.Breaklines.AddStandardBreakline(idArray, "Test", 1.0);
}
}
catch (System.Exception e) {
ed.WriteMessage("\n" + e.Message);
}
c3dUtil = null;
}

This throws the following exception on the AddStandardBreakline method:

Quote
The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))
« Last Edit: March 11, 2007, 08:48:11 AM by sinc »

sinc

  • Guest
Re: How to add breaklines to Civil-3D surface in C#.NET?
« Reply #3 on: March 15, 2007, 12:24:13 AM »
D'oh!  It seems in C#, collections don't use Item() methods.  You just use an index, like with a normal array.  So you can get the surface using something like this:

Code: [Select]
IAeccTinSurface surface = (IAeccTinSurface)aeccDb.Surfaces["MySurfaceName"];
It works with either an integer index or the surface name as the index.  I take it that this is supposed to be a basic C# thing that anyone working in C# is supposed to just know, so they don't bother putting it in the documentation, or something like that...   :-D

Still no progress on the breaklines, though.  And still getting 68 "warnings" on compile (I think I got rid of one somehow - seems like I used to get 69).

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: How to add breaklines to Civil-3D surface in C#.NET?
« Reply #4 on: March 26, 2007, 01:01:39 PM »
Success!!!! Yep, thanks to Kean Walmsley pointing me in the right direction, I was able to get the C# code to add a standard breakline. From Kean:
Quote
As Civil 3D has a COM API, it’s clearly expecting an array of COM objects. So rather than creating an array of objects of type Entity (which is a managed entity), try using AcadObject or AcadEntity as the contained type.
Also, thanks to Kerry for suggesting I quiz Kean about this.

Need to add this in the beginning section of the class:
Code: [Select]
using Autodesk.AutoCAD.Interop.Common;
then the code to get the pline must be revised to this:
Code: [Select]
                    using (Transaction tr = Application.DocumentManager.MdiActiveDocument.TransactionManager.StartTransaction())
                    {
                        Entity oEnt = tr.GetObject(pres.ObjectId, OpenMode.ForRead, false) as Entity;
                        AcadObject[] idArray = { (AcadObject)oEnt.AcadObject };
                        AeccTinSurface surface = (AeccTinSurface)c3dUtil.AeccDb.Surfaces["EG1"];
                        double midOrd = 1.0;
                        surface.Breaklines.AddStandardBreakline(idArray, "Test", midOrd);
                        surface.Update();
Yep, it was definitely our being relative noob's to C# (as I suspected) that was the problem.

sinc

  • Guest
Re: How to add breaklines to Civil-3D surface in C#.NET?
« Reply #5 on: March 26, 2007, 09:43:37 PM »
Yep, it was definitely our being relative noob's to C# (as I suspected) that was the problem.

Great!

But I don't think it's our noobishness (is that a word?) that's the main problem...  It strikes me as being a problem with the API.  Why mix COM and .NET like that?

Well, I know why, it's because Autocad is primarily legacy code and they're sticking a .NET wrapper around the thing, but still, this strikes me as hole in the wrapper.  As such, it would be nice to have it documented, rather than expect all .NET programmers to be fluent in COM...   ;-)

I agree, it's something that we probably would've eventually figured out.  I already suspected this, because I had looked at various Breaklines collections in the debugger and noticed that they were COM objects, but I had no idea how to deal with it in C#.  But hopefully, now that we're hammering Autodesk with complaints, they'll either document the usage, or better yet, fix the wrapper...   :-D

Still, now I can get back to re-writing Land Desktop's "Breakline By Points" routine for Civil-3D, since Autodesk decided we didn't need that functionality any more...   (Yeah, I know it's relatively easy to draw 3D-Polylines and then add them to a surface, but it's annoying trying to keep track of which polylines have been turned into breaklines and which ones haven't, make sure things are on the right layer, yadda yadda.)

sinc

  • Guest
Re: How to add breaklines to Civil-3D surface in C#.NET?
« Reply #6 on: April 01, 2007, 09:05:06 PM »
All right, I finally got it.  I think I might be able to simplify this a bit, and add an UNDO command, but it seems to be working pretty well.

Here's the "interesting part" of the code.  The complete project can be downloaded from our website.

This routine took an awful lot longer to write than I thought it would take, primarily due to the documentation.  That COM thing was the biggest beast, but there were others.  I had to do a lot of trial-and-error to figure out how things worked, primarily due to poor documentation and the lack of examples.  But it also involved the most-extensive piece of C# and .NET programming I've done so far, so I learned a lot about C# the .NET API in the process.  This piece of code illustrates a number of things that are vague, missing, or difficult to find in the docs, so it might serve as a useful resource for others.

I still don't understand why certain pieces of the API expect COM objects, while the rest of it expects managed objects.  But it's not so bad now that I know how to identify and deal with the issue.  Instead, it's become just another annoyance, like those messy TypedValue arrays...   ;-)

Code: [Select]
[CommandMethod("BRKPT")]
public void CreateBreaklinesByPoints()
{
c3dUtil = new C3DUtil();
DocumentLock docLock = Application.DocumentManager.MdiActiveDocument.LockDocument();
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
db = Application.DocumentManager.MdiActiveDocument.Database;
BreaklineSettings settings = new BreaklineSettings();
checkSettings(settings);
if (settings.SurfaceName.Equals("")) {
ed.WriteMessage("\nNo surfaces in current drawing!");
}
else {
Transaction tr;
db.TileMode = true; // make sure we're in modelspace
ed.WriteMessage("\nSurface: {0}    Layer: {1}", settings.SurfaceName, settings.LayerName);
PromptPointOptions newLineProps = new PromptPointOptions("\nSelect start point:");
newLineProps.Keywords.Add("Settings");
newLineProps.AllowNone = true;
newLineProps.UseBasePoint = false;
PromptPointOptions nextPointProps = new PromptPointOptions("\nSelect next point:");
nextPointProps.AllowNone = true;
nextPointProps.UseBasePoint = true;
PromptPointResult newLinePromptResult, nextPointResult;
bool keepGoing = true;
do {
do {
newLinePromptResult = ed.GetPoint(newLineProps);
if (newLinePromptResult.Status == PromptStatus.Keyword) {
showSettingsDialog();
settings.ReadSettings();
}
} while (newLinePromptResult.Status == PromptStatus.Keyword);
if (newLinePromptResult.Status == PromptStatus.OK) {
ObjectId brklineId;
nextPointProps.BasePoint = newLinePromptResult.Value;
nextPointResult = ed.GetPoint(nextPointProps);
if (nextPointResult.Status == PromptStatus.OK) {
using (tr = db.TransactionManager.StartTransaction()) {
BlockTableRecord curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
Point3dCollection collec = new Point3dCollection();
collec.Add(newLinePromptResult.Value);
collec.Add(nextPointResult.Value);
Polyline3d brkline = new Polyline3d(Poly3dType.SimplePoly, collec, false);
brkline.Layer = settings.LayerName;
brklineId = curSpace.AppendEntity(brkline);
tr.AddNewlyCreatedDBObject(brkline, true);
tr.Commit();
}
do {
nextPointProps.BasePoint = nextPointResult.Value;
nextPointResult = ed.GetPoint(nextPointProps);
if (nextPointResult.Status == PromptStatus.OK) {
using (tr = db.TransactionManager.StartTransaction()) {
Polyline3d brkline = (Polyline3d)tr.GetObject(brklineId, OpenMode.ForWrite);
PolylineVertex3d pv = new PolylineVertex3d(nextPointResult.Value);
brkline.AppendVertex(pv);
tr.AddNewlyCreatedDBObject(pv, true);
tr.Commit();
}
}
} while (nextPointResult.Status == PromptStatus.OK);
if (nextPointResult.Status == PromptStatus.None) {
// user right-clicked, add the breakline to the surface
addBreakline(brklineId, settings.SurfaceName);
}
else {
// user probably hit cancel, erase the polyline
using (tr = db.TransactionManager.StartTransaction()) {
Polyline3d brkline = (Polyline3d)tr.GetObject(brklineId, OpenMode.ForWrite);
brkline.Erase();
tr.Commit();
}
}
}
if (nextPointResult.Status == PromptStatus.Cancel)
keepGoing = false;
}
} while (keepGoing && (newLinePromptResult.Status == PromptStatus.OK));
}
db.Dispose();
docLock.Dispose();
c3dUtil = null;
}

private void addBreakline(ObjectId breaklineId, String surfaceName)
{
try {
using (Transaction tr = Application.DocumentManager.MdiActiveDocument.TransactionManager.StartTransaction()) {
Entity ent = tr.GetObject(breaklineId, OpenMode.ForRead, false) as Entity;
AcadObject[] idArray = new AcadObject[] { (AcadObject)ent.AcadObject };
IAeccTinSurface surface = (IAeccTinSurface)c3dUtil.AeccDb.Surfaces[surfaceName];
surface.Breaklines.AddStandardBreakline(idArray, "BreaklineByPoints", midordinate);
surface.Update();
tr.Commit();
}
}
catch (System.Exception e) {
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\n" + e.Message);
}
}

And then there's this part, which gets the Thawed Layer names in a list that's sorted in the same order as the Layers toolbar:

Code: [Select]
public static ArrayList GetThawedLayerNames()
{
ArrayList layerNames = new ArrayList();
Database db = Application.DocumentManager.MdiActiveDocument.Database;
using (Transaction tr = db.TransactionManager.StartTransaction()) {
LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
foreach (ObjectId layerId in lt) {
LayerTableRecord layer = (LayerTableRecord)tr.GetObject(layerId, OpenMode.ForRead);
if (!layer.IsFrozen) layerNames.Add(layer.Name);
}
layerNames.Sort(new LayerNameComparer());
tr.Abort();
}
return layerNames;
}

// Default comparer ignores leading characters like dashes.  This comparer causes layer
// names to list in an order more like the one in the Layers toolbar and Layer Manager.
public class LayerNameComparer : IComparer
{
int IComparer.Compare(Object x, Object y)
{
char[] xchar = ((String)x).ToUpper().ToCharArray();
char[] ychar = ((String)y).ToUpper().ToCharArray();
int count = xchar.Length;
if (ychar.Length < count)
count = ychar.Length;
int i=0;
--count;
while ((i<count) && xchar[i].Equals(ychar[i])) {i++;}
int result = xchar[i].CompareTo(ychar[i]);
if (result == 0) {
result = xchar.Length.CompareTo(ychar.Length);
}
return result;
}
}