.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Forgot to Dispose? Actually, No.

14 REPLIES 14
Reply
Message 1 of 15
Anonymous
2696 Views, 14 Replies

Forgot to Dispose? Actually, No.

I have a very odd problem taking place and I'm at wits end after trying to debug it for hours. Basically, I am getting the messages:

Forgot to call Dispose? (Autodesk.AutoCAD.DatabaseServices.LayerTableRecord): DisposableWrapper
Forgot to call Dispose? (Autodesk.AutoCAD.DatabaseServices.LinetypeTableRecord): DisposableWrapper

The problem is that I didn't forget to call dispose. In fact, I'm probably calling dispose more than I should after getting this problem. I get both of these messages when I close AutoCAD after running a particular function. The function calls many other functions so it's especially difficult to debug (and too long to really post here). Here is what the function basically does:

1. The sub is called setlay and the argument is a string that represents a layer. I look through the LayerTable for that named layer and, if found, I dispose of everything, call a sub (setactlay) that sets that layer to active, then exit the sub.
If the layer is not present, then it connects to an Access db using Ole and fills a DataSet, which is then transferred to a DataTable. I then look through the table using DataRows to find the named layer; if found, I get the color and linetype information in variables, dispose of everything, then call two subs.

2. The first sub (loadlay) creates a new layer using the passed arguments from the db. There is a function (chkltype: returns DatabaseServices.LinetypeTableRecord) call inside there to check if the linetype is valid, and if not then another function (loadltype: returns boolean) call is placed to load the linetype from a .lin file. If still not found at that point, it returns false and the default linetype is used instead.
If it is found in the .lin, it loads the linetype, disposes of everything, returns true and I make a recursive chkltype call to return the LinetypeTableRecord.

3. The second sub (setactlay, also called earlier) finds the layer in the LayerTable then unfreezes and turns it on if necessary. I set DWG.CLayer equal to the layer's objectID, then dispose of everything.

4. Back in setlay, I exit the sub after having run loadlay and setactlay. If the layer was not found in the db, I just dispose of everything and end the sub (no output).

I disposed of everything that has the dispose method along the entire logic path. I committed and disposed of every transaction before making a function call. The only thing that I can possibly think of is that either the recursive call is preventing something from being disposed of properly. I have F8'd through the entire logic path and those messages appeared when the sub that called setlay ended. The program works perfectly except for those pesky messages, but I know that if I just let it go then only bad things can happen.

Please, somebody help me... if you need further clarification or more details I'm more than willing to share. Thanks.
14 REPLIES 14
Message 2 of 15
rdswords
in reply to: Anonymous

I'm afraid you'll likely have to post at least part of your code here. You don't necessarily just want to call dispose on everything that has the method. It sounds like you probably have just a small error somewhere, and that is a bit difficult to pinpoint without seeing what is written.
Message 3 of 15
Anonymous
in reply to: Anonymous

Sorry, nothing more than a verbose description of what
your code does isn't going to get you much help.


--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2009
Supporting AutoCAD 2000 through 2009

http://www.acadxtabs.com

Introducing AcadXTabs 2010:
http://www.caddzone.com/acadxtabs/AcadXTabs2010.htm

wrote in message news:5960131@discussion.autodesk.com...
I have a very odd problem taking place and I'm at wits end after trying to debug it for hours. Basically, I am getting the messages:

Forgot to call Dispose? (Autodesk.AutoCAD.DatabaseServices.LayerTableRecord): DisposableWrapper
Forgot to call Dispose? (Autodesk.AutoCAD.DatabaseServices.LinetypeTableRecord): DisposableWrapper

The problem is that I didn't forget to call dispose. In fact, I'm probably calling dispose more than I should after getting this problem. I get both of these messages when I close AutoCAD after running a particular function. The function calls many other functions so it's especially difficult to debug (and too long to really post here). Here is what the function basically does:

1. The sub is called setlay and the argument is a string that represents a layer. I look through the LayerTable for that named layer and, if found, I dispose of everything, call a sub (setactlay) that sets that layer to active, then exit the sub.
If the layer is not present, then it connects to an Access db using Ole and fills a DataSet, which is then transferred to a DataTable. I then look through the table using DataRows to find the named layer; if found, I get the color and linetype information in variables, dispose of everything, then call two subs.

2. The first sub (loadlay) creates a new layer using the passed arguments from the db. There is a function (chkltype: returns DatabaseServices.LinetypeTableRecord) call inside there to check if the linetype is valid, and if not then another function (loadltype: returns boolean) call is placed to load the linetype from a .lin file. If still not found at that point, it returns false and the default linetype is used instead.
If it is found in the .lin, it loads the linetype, disposes of everything, returns true and I make a recursive chkltype call to return the LinetypeTableRecord.

3. The second sub (setactlay, also called earlier) finds the layer in the LayerTable then unfreezes and turns it on if necessary. I set DWG.CLayer equal to the layer's objectID, then dispose of everything.

4. Back in setlay, I exit the sub after having run loadlay and setactlay. If the layer was not found in the db, I just dispose of everything and end the sub (no output).

I disposed of everything that has the dispose method along the entire logic path. I committed and disposed of every transaction before making a function call. The only thing that I can possibly think of is that either the recursive call is preventing something from being disposed of properly. I have F8'd through the entire logic path and those messages appeared when the sub that called setlay ended. The program works perfectly except for those pesky messages, but I know that if I just let it go then only bad things can happen.

Please, somebody help me... if you need further clarification or more details I'm more than willing to share. Thanks.
Message 4 of 15
Anonymous
in reply to: Anonymous

Understandable... at least there is a full description posted above. Here is the code:

SETLAY
Public Sub setlay(ByVal new_lay As String)
Dim myDWG As ApplicationServices.Document
Dim myDB As DatabaseServices.Database
Dim myTransMan As DatabaseServices.TransactionManager
Dim myTrans As DatabaseServices.Transaction
Dim myLT As DatabaseServices.LayerTable
Dim myLayer As DatabaseServices.LayerTableRecord
Dim mySTE As DatabaseServices.SymbolTableEnumerator
Dim myLayerDB As New OleDb.OleDbConnection("File name=C:\Program Files\AutoCAD 2007\db\layers.udl")
Dim myLayerDA As New OleDb.OleDbDataAdapter("Select Lay_name, Lay_color, lay_ltype, Lay_grp from layers", myLayerDB)
Dim myLayerDS As New Data.DataSet
Dim myLayerDT As Data.DataTable
Dim myLayerDR As Data.DataRow
Dim lay_name As String
Dim lay_group As String
Dim lay_color As Double
Dim lay_ltype As String


myDWG = ApplicationServices.Application.DocumentManager.MdiActiveDocument
myDB = myDWG.Database
myTransMan = myDWG.TransactionManager
myTrans = myTransMan.StartTransaction

myLayerDA.Fill(myLayerDS)
myLayerDT = myLayerDS.Tables(0)

myLT = myDB.LayerTableId.GetObject(DatabaseServices.OpenMode.ForWrite)
mySTE = myLT.GetEnumerator
While mySTE.MoveNext
myLayer = mySTE.Current.GetObject(OpenMode.ForWrite)
If myLayer.Name = new_lay Then
myTrans.Commit()
myLayerDB.Close()
myLayerDB.Dispose()
myLayerDA.Dispose()
myLayer.Dispose()
myTrans.Dispose()
myTransMan.Dispose()
setactlay(new_lay)
Exit Sub
End If
End While



For Each myLayerDR In myLayerDT.Rows
lay_name = myLayerDR("Lay_name")
lay_color = myLayerDR("Lay_color")
lay_ltype = myLayerDR("lay_ltype")
lay_group = myLayerDR("Lay_grp")
If lay_name = new_lay Then
myTrans.Commit()
myLayerDB.Close()
myLayerDB.Dispose()
myLayerDA.Dispose()
myTrans.Dispose()
myTransMan.Dispose()
loadlay(lay_name, Colors.Color.FromColorIndex(Colors.ColorMethod.ByAci, CShort(lay_color)), lay_ltype)
setactlay(lay_name)
Exit Sub
End If
Next

myTrans.Commit()
myLayerDB.Close()
myLayerDB.Dispose()
myLayerDA.Dispose()
myTrans.Dispose()
myTransMan.Dispose()

End Sub

LOADLAY
Public Sub loadlay(ByVal layName As String, ByVal layCol As Colors.Color, ByVal layLType As String)
Dim myDWG As ApplicationServices.Document
Dim myDB As DatabaseServices.Database
Dim myTransMan As DatabaseServices.TransactionManager
Dim myTrans As DatabaseServices.Transaction
Dim myLineType As DatabaseServices.LinetypeTableRecord

myDWG = ApplicationServices.Application.DocumentManager.MdiActiveDocument
myDB = myDWG.Database

myLineType = chkltype(layLType)
myTransMan = myDWG.TransactionManager
myTrans = myTransMan.StartTransaction


Dim myLT As DatabaseServices.LayerTable
Dim myLayer As New DatabaseServices.LayerTableRecord

myLT = myDB.LayerTableId.GetObject(DatabaseServices.OpenMode.ForWrite)
myLayer.Name = layName
myLayer.Color = layCol
If myLineType.Name <> "NOTFOUND" Then
myLayer.LinetypeObjectId = myLineType.ObjectId
End If
myLT.Add(myLayer)


myTrans.AddNewlyCreatedDBObject(myLayer, True)

myTrans.Commit()
myLayer.Dispose()
myLineType.Dispose()
myTrans.Dispose()
myTransMan.Dispose()
End Sub

CHKLTYPE
Public Function chkltype(ByVal lay_ltype As String) As DatabaseServices.LinetypeTableRecord
Dim myDWG As ApplicationServices.Document
Dim myDB As DatabaseServices.Database
Dim myTransMan As DatabaseServices.TransactionManager
Dim myTrans As DatabaseServices.Transaction
Dim foundLineType As Boolean

Dim myLT As DatabaseServices.LinetypeTable
Dim myLineType As New DatabaseServices.LinetypeTableRecord
Dim mySTE As DatabaseServices.SymbolTableEnumerator

myDWG = ApplicationServices.Application.DocumentManager.MdiActiveDocument
myDB = myDWG.Database
myTransMan = myDWG.TransactionManager
myTrans = myTransMan.StartTransaction
foundLineType = False

myLT = myDB.LinetypeTableId.GetObject(DatabaseServices.OpenMode.ForRead)
mySTE = myLT.GetEnumerator
While mySTE.MoveNext
myLineType = mySTE.Current.GetObject(OpenMode.ForRead)
If UCase(myLineType.Name) = UCase(lay_ltype) Then
foundLineType = True
Exit While
End If
End While

myTrans.Commit()

myTrans.Dispose()
myTransMan.Dispose()

If foundLineType Then
Return myLineType
Else
If loadltype(lay_ltype) Then
myLineType = chkltype(lay_ltype)
Return myLineType
Else
myLineType = New DatabaseServices.LinetypeTableRecord
myLineType.Name = "NOTFOUND"
Return myLineType
End If
End If

End Function

LOADLTYPE
Public Function loadltype(ByVal layltype As String) As Boolean
Dim myDWG As ApplicationServices.Document
Dim myDB As DatabaseServices.Database
Dim myTransMan As DatabaseServices.TransactionManager
Dim myTrans As DatabaseServices.Transaction
Dim success As Boolean

myDWG = ApplicationServices.Application.DocumentManager.MdiActiveDocument
myDB = myDWG.Database
myTransMan = myDWG.TransactionManager
myTrans = myTransMan.StartTransaction

Dim myLT As DatabaseServices.LinetypeTable
myLT = myDB.LinetypeTableId.GetObject(DatabaseServices.OpenMode.ForWrite)
Try
myLT.Database.LoadLineTypeFile(layltype, "lt.lin")
success = True
Catch
MsgBox("The linetype you requested cannot be found. The default linetype will be used.")
success = False
Finally
myLT.Dispose()
End Try
myTrans.Commit()
myTrans.Dispose()
myTransMan.Dispose()
Return success
End Function

SETACTLAY
Public Sub setactlay(ByVal lay_name As String)
Dim myDWG As ApplicationServices.Document
Dim myDB As DatabaseServices.Database
Dim myTransMan As DatabaseServices.TransactionManager
Dim myTrans As DatabaseServices.Transaction
Dim foundLayer As Boolean

myDWG = ApplicationServices.Application.DocumentManager.MdiActiveDocument
myDB = myDWG.Database
myTransMan = myDWG.TransactionManager
myTrans = myTransMan.StartTransaction
foundLayer = False

Dim myLT As DatabaseServices.LayerTable
Dim myLayer As New DatabaseServices.LayerTableRecord
Dim mySTE As DatabaseServices.SymbolTableEnumerator

myLT = myDB.LayerTableId.GetObject(DatabaseServices.OpenMode.ForRead)
mySTE = myLT.GetEnumerator
While mySTE.MoveNext
myLayer = mySTE.Current.GetObject(OpenMode.ForWrite)
If myLayer.Name = lay_name Then
If myLayer.IsFrozen Then myLayer.IsFrozen = False
If myLayer.IsOff Then myLayer.IsOff = False
foundLayer = True
Exit While
End If
End While



If foundLayer Then
myDB.Clayer = myLayer.ObjectId
End If


myTrans.Commit()
myLayer.Dispose()
myTrans.Dispose()
myTransMan.Dispose()
End Sub
Message 5 of 15
rdswords
in reply to: Anonymous

I code in C# so a lot of this is difficult for me to follow. Firstly, and Tony can confirm whether I am mistaken here, I'm pretty sure you don't want to dispose myTransMan. Secondly, it appears to me that you are disposing of some things twice. Also, you don't need to dispose of every little variable. Try just disposing of transactions and see if the code works.
Message 6 of 15
Anonymous
in reply to: Anonymous

A lot of the disposals were added after I encountered this problem; most of the time I was just disposing of myDB (in setlay), myTrans and myTransMan. The book "VB.NET Programming for AutoCAD Customization" by Jerry Winters is where I learned to dispose of the Transaction Manager... it was in every piece of sample code he had pretty much. I don't know whether that means it's right or wrong, I'm just citing where I got that idea.

I originally called setlay 4 times in my test sub... when I did that, I would get 6 of those errors after running, then 2 more when AutoCAD was closed. It seems like the errors are thrown whenever the inserted layer, which immediately became active, lost focus and was no longer the active layer. Maybe I'm missing something when I switch active layers?

The other thing that I find especially vexing is that I didn't have this problem until I involved the Access db and the lt.lin file... and even though I keep getting this problem every time, the order in which I receive these warnings is always changing.

Thanks for your time in this matter, it is very much appreciated.
Message 7 of 15
Anonymous
in reply to: Anonymous

Just an update: I commented out all the dispose calls from myTransMan and got rid of all the superfluous dispose calls from debugging... still no luck. I'm considering restructuring the code; instead of starting and stopping explicitly, I may see if the Using method will work better since it automatically handles the dispose method. I'm really hoping to avoid that though.

Ugh.
Message 8 of 15
Anonymous
in reply to: Anonymous

>> The book "VB.NET Programming for AutoCAD Customization" by Jerry Winters is where I learned to dispose of the Transaction Manager... it was in every piece of sample code he had pretty much. I don't know whether that means it's right or wrong, I'm just citing where I got that idea. <<

Sorry to have to say it, but Jerry Winters has never written a single line of C++/Native ObjectARX code, and barely understands the underlying ObjectARX API. In case anyone didn't notice, this isn't about the VB.NET programming language, this is about the ObjectARX API.

The TransactionManager object wraps an instance of the native AcDbTransactionManager class, and there is one and only one instance of it for each AcDbDatabase, and that instance cannot/should not be destroyed by any consumer that uses it.

In this case, you didn't create the TransactionManager, you only obtained a reference to it via the TransactionManager property of the Database. Hence, you do not 'own' it, nor are you responsible for managing its lifetime (the owning Database is), so even though it incorrectly has a Dispose method (yes, that is a design flaw - see below), you should never call it.

The reason that many API objects support IDisposable and have Dispose() methods, is largely coincidental, by way of the fact that they derive from a base type called 'DisposableWrapper'. DisposableWrapper provides a generic means of implementing a managed type that 'wraps' a native object (which could be just about anything).

In some cases, when a DisposableWrapper-based type is garbage collected (either because its Dispose() method was called, or because it is no longer referenced and the garbage collector reclaimed it), the underlying or 'wrapped' native object is also destroyed, usually because the consumer that is using the DisposableWrapper-based type also owns it, and is responsible for managing its lifetime. An example of that, is when you create a new DBObject-based type, but do not add it to a Database. In that case, you own that object and you are responsible for managing its life, and so you must destroy it when you're done using it, and that's done by calling its Dispose() method.

In other cases (like TransactionManager) the underlying native object is not destroyed when the wrapper is, because the native object is 'shared' by many consumers and its lifetime is tied to and/or managed by another 'owner' object, which in the case of TransactionManager, is the owning Database. So, it is Autodesk's generic and widespread re-use of the DisposableWrapper type that is the source of the confusion for many.

Personally, I view the fact that Autodesk used this base type even when the object should not be disposed by consumers, and/or when the implementation of Dispose() does absolutely nothing, as horrendously poor API design, and one of the major contributing factors of the confusion surrounding the Dispose() method and its use, that so many new .NET programmers and learners (like Jerry Winters) experience, a fact that is clearly evidenced by just reading this newsgroup.

The bottom line with regards to the debug messages that you see, is that if the message is displayed for a DBObject that you obtained from a transaction, the message is itself bogus, and you can safely ignore it. That's because you can safely avoid calling Dispose() on any DBObject that you obtained from a Transaction.

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2009
Supporting AutoCAD 2000 through 2009

http://www.acadxtabs.com

Introducing AcadXTabs 2010:
http://www.caddzone.com/acadxtabs/AcadXTabs2010.htm

wrote in message news:5960259@discussion.autodesk.com...
A lot of the disposals were added after I encountered this problem; most of the time I was just disposing of myDB (in setlay), myTrans and myTransMan.
I originally called setlay 4 times in my test sub... when I did that, I would get 6 of those errors after running, then 2 more when AutoCAD was closed. It seems like the errors are thrown whenever the inserted layer, which immediately became active, lost focus and was no longer the active layer. Maybe I'm missing something when I switch active layers?

The other thing that I find especially vexing is that I didn't have this problem until I involved the Access db and the lt.lin file... and even though I keep getting this problem every time, the order in which I receive these warnings is always changing.

Thanks for your time in this matter, it is very much appreciated.
Message 9 of 15
Anonymous
in reply to: Anonymous

Wow... I had no idea about that, thank you Tony, it makes perfect sense when you actually understand the underlying mechanics of the ObjectARX API. I am definitely a new .NET AutoCAD programmer but I luckily have a heavy programming background so I can grasp this new stuff pretty easily. Seems I'll be going through my code and taking those out then!

Also, I finally figured out exactly what was causing those messages; in CHKLTYPE and SETACTLAY I declared my LineTypeTableRecord and LayerTableRecord (respectively) as "New"... when I remove the New keyword then VS gives me a warning about not assigning those variables values before referencing them (I guess since they are in a conditional loop). So instead I just set the layer equal to LayerZero after declaration; the linetype was a little more complicated, I called a mySTE.MoveNext before the while loop, set the linetype to the mySTE.Current.GetObject, then called mySTE.Reset before starting the loop. The warnings are gone and so are the messages!

Thank you everyone for the assistance, I'll figure this all out at some point and then maybe I can be of help on here as well.
Message 10 of 15
Anonymous
in reply to: Anonymous

While the TransactionManager class does derive from DisposableWrapper, it does not override the DeleteUnmanagedObject() method, which is the underlying method that destroys the unmanaged object. Thus calling it’s dispose does nothing .

According to the ObjectARX docs (Memory Management and Dispose Pattern) you must actively call dispose. In this case I do understand Mr. Winters teaching as per the API documentation, He has too , even if the call does nothing. It would be nice if the docs would go into a little more depth on this subject, especially when it comes to DBObjects.

Does Jerry’s book talk about the Dispose pattern? If so what does it say?
Message 11 of 15
Anonymous
in reply to: Anonymous

He actually doesn't say anything specific about Dispose or why you should be disposing any objects... it's just there in all of his code, commented as 'Dispose of the Transaction Objects.

So I guess it really doesn't do anything to have it there or not, it's just an empty call... interesting. If I never had problems with transactions, I would never look it up either... the code works with or without, so I'm thinking Jerry's off the hook.

It's a good book though, lots of sample code (he has a ton of snippets included on the CD) and it outlines all the basics of working with blocks, dynamic blocks, linetypes, layers, layouts, cloning, selection sets, VB forms, extracting from DB/DWG, writing to DB/XLS/XML... there's even a chapter about construction geometry. All I knew prior to reading it was a small amount of VBA AutoCAD coding, so I'm definitely a happy customer.
Message 12 of 15
Anonymous
in reply to: Anonymous

>> In this case I do understand Mr. Winters teaching as per the API documentation, He has too , even if the call does nothing. <<

Hate to disagree.

public class Class1()
{
[CommandMethod( "KABOOM" )]
public static void Kaboom()
{
Document doc = Autodesk.AutoCAD.ApplicationServices.
Application.DocumentManager.MdiActiveDocument;

doc.Dispose();
}
}


--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2009
Supporting AutoCAD 2000 through 2009

http://www.acadxtabs.com

Introducing AcadXTabs 2010:
http://www.caddzone.com/acadxtabs/AcadXTabs2010.htm

wrote in message news:5961352@discussion.autodesk.com...
While the TransactionManager class does derive from DisposableWrapper, it does not override the DeleteUnmanagedObject() method, which is the underlying method that destroys the unmanaged object. Thus calling it’s dispose does nothing .

According to the ObjectARX docs (Memory Management and Dispose Pattern) you must actively call dispose. In this case I do understand Mr. Winters teaching as per the API documentation, He has too , even if the call does nothing. It would be nice if the docs would go into a little more depth on this subject, especially when it comes to DBObjects.

Does Jerry’s book talk about the Dispose pattern? If so what does it say?
Message 13 of 15
Anonymous
in reply to: Anonymous

I stand corrected.
Incase others haven’t seen this, Kean has written about this topic.
http://through-the-interface.typepad.com/through_the_interface/2008/06/cleaning-up-aft.html
Message 14 of 15
Anonymous
in reply to: Anonymous

I wouldn't expect Autodesk's documentation to explain
or apologize for bad API design.

If you ask me, types should support IDisposable when
there is a reason for it. Saving a few bucks by having a
single reusable base type for wrapping native objects,
when the result is massive confusion on the part of the
API user, and user code that is needlessly littered with
superfluous calls to Dispose() or using(), certainly is an
example of what I would consider bad API design.

The last sentence in the docs you reference ("Do not rely
on .NET garbage collection to free the memory used by
unmanaged resources"), is true for managed wrappers that
wrap native types having destructors that execute code
that's dependent on thread-local state, because that state
cannot be accessed from the OS thread the finalizer runs
in. But, that's a more general problem, and not limited to
IDisposable or destructors of native types.

The basic problem is that to date, Microsoft has not been
able to figure out how to offload scheduling/management
of managed threads (including the one the finalizer runs
in) to a managed CLR host.

This distilled example shows what happens when an
object tries to access AutoCAD APIs from a foreign or
OS thread (in this case, the finalizer's thread):

public class BuggyDisposable : IDisposable
{
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}

void Dispose(bool disposing)
{
Application.DocumentManager.MdiActiveDocument
.Editor.WriteMessage( "\n->>> Goodbye" );
}

~BuggyDisposable()
{
Dispose( false );
}

[CommandMethod( "TEST_BUGGY_DISPOSABLE" )]
public static void TestBuggyDisposable()
{
BuggyDisposable disposable = new BuggyDisposable();
disposable = null;
GC.Collect();
}
}


Running the above code should crash AutoCAD.

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2009
Supporting AutoCAD 2000 through 2009

http://www.acadxtabs.com

Introducing AcadXTabs 2010:
http://www.caddzone.com/acadxtabs/AcadXTabs2010.htm

--
http://www.caddzone.com

AcadXTabs: MDI Document Tabs for AutoCAD 2009
Supporting AutoCAD 2000 through 2009

http://www.acadxtabs.com

Introducing AcadXTabs 2010:
http://www.caddzone.com/acadxtabs/AcadXTabs2010.htm

wrote in message news:5962164@discussion.autodesk.com...
I stand corrected.
Incase others haven’t seen this, Kean has written about this topic.
http://through-the-interface.typepad.com/through_the_interface/2008/06/cleaning-up-aft.html
Message 15 of 15
Anonymous
in reply to: Anonymous

Tony, I can either search all your posts here, then compile them into a book of wisdom on AutoCAD.Net, or you could write one/two/three... Either way, the resulting book would net $,$$$,$$$.$$ from those of us just trying to keep up.

jvj

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Forma Design Contest


Autodesk Design & Make Report