Author Topic: .NET XREF Routines  (Read 23334 times)

0 Members and 1 Guest are viewing this topic.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
.NET XREF Routines
« on: January 27, 2010, 03:31:12 AM »
LIBRARY THREAD for  AutoCAD XREFS
 Members are encouraged to post any functions, methods, snips regarding
AutoCAD XREFS in .NET : C# ,  VB , F# , Python , etc

Feel free to include comments, descriptive notes, limitations,  and images to document your post.

Please post questions in a regular thread.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Chuck Gabriel

  • Guest
Re: .NET XREF Routines
« Reply #1 on: January 27, 2010, 10:00:33 AM »
Command line tools for xrefs:
AttachXrefs
BindXrefs
DetachXrefs
OpenXrefs
ReloadXrefs
ReloadAllXrefs
UnloadXrefs


Code: [Select]
using System;
using System.IO;
using System.Text;

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.ApplicationServices;

using ofdFlags = Autodesk.AutoCAD.Windows.OpenFileDialog.OpenFileDialogFlags;

[assembly: ExtensionApplication(typeof(cgabriel.XrefTools))]
[assembly: CommandClass(typeof(cgabriel.XrefTools))]

namespace cgabriel
{
    public class XrefTools : IExtensionApplication
    {
        #region ExtensionAppImplementation

        public void Initialize() { }
        public void Terminate() { }

        #endregion

        #region Helpers

        public delegate void ProcessSingleXref(BlockTableRecord btr);

        public delegate void ProcessMultipleXrefs(ObjectIdCollection xrefIds);

        public static void detachXref(BlockTableRecord btr)
        {
            Application.DocumentManager.MdiActiveDocument.Database.DetachXref(btr.ObjectId);
        }

        public static void openXref(BlockTableRecord btr)
        {
            string xrefPath = btr.PathName;
            if (xrefPath.Contains(".\\"))
            {
                string hostPath =
                    Application.DocumentManager.MdiActiveDocument.Database.Filename;
                Directory.SetCurrentDirectory(Path.GetDirectoryName(hostPath));
                xrefPath = Path.GetFullPath(xrefPath);
            }
            if (!File.Exists(xrefPath)) return;
            Document doc = Application.DocumentManager.Open(xrefPath, false);
            if (doc.IsReadOnly)
            {
                System.Windows.Forms.MessageBox.Show(
                    doc.Name + " opened in read-only mode.",
                    "OpenXrefs",
                    System.Windows.Forms.MessageBoxButtons.OK,
                    System.Windows.Forms.MessageBoxIcon.Warning);
            }
        }

        public static void bindXrefs(ObjectIdCollection xrefIds)
        {
            Application.DocumentManager.MdiActiveDocument.Database.BindXrefs(xrefIds, false);
        }

        public static void reloadXrefs(ObjectIdCollection xrefIds)
        {
            Application.DocumentManager.MdiActiveDocument.Database.ReloadXrefs(xrefIds);
        }

        public static void unloadXrefs(ObjectIdCollection xrefIds)
        {
            Application.DocumentManager.MdiActiveDocument.Database.UnloadXrefs(xrefIds);
        }

        public static void processXrefs(string promptMessage, ProcessSingleXref process)
        {
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
            TypedValue[] filterList = { new TypedValue(0, "INSERT") };
            ed.WriteMessage(promptMessage);
            PromptSelectionResult result = ed.GetSelection(new SelectionFilter(filterList));
            if (result.Status != PromptStatus.OK) return;

            ObjectId[] ids = result.Value.GetObjectIds();
            Database db = Application.DocumentManager.MdiActiveDocument.Database;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                ObjectIdCollection xrefIds = new ObjectIdCollection();
                foreach (ObjectId id in ids)
                {
                    BlockReference blockRef = (BlockReference)tr.GetObject(id, OpenMode.ForRead, false, true);
                    ObjectId bId = blockRef.BlockTableRecord;
                    if (!xrefIds.Contains(bId))
                    {
                        xrefIds.Add(bId);
                        BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bId, OpenMode.ForRead);
                        if (btr.IsFromExternalReference)
                            process(btr);
                    }
                }
                tr.Commit();
            }
        }

        public static void processXrefs(string promptMessage, ProcessMultipleXrefs process)
        {
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
            TypedValue[] filterList = { new TypedValue(0, "INSERT") };
            ed.WriteMessage(promptMessage);
            PromptSelectionResult result = ed.GetSelection(new SelectionFilter(filterList));
            if (result.Status != PromptStatus.OK) return;

            ObjectId[] ids = result.Value.GetObjectIds();
            Database db = Application.DocumentManager.MdiActiveDocument.Database;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                ObjectIdCollection blockIds = new ObjectIdCollection();
                foreach (ObjectId id in ids)
                {
                    BlockReference blockRef = (BlockReference)tr.GetObject(id, OpenMode.ForRead, false, true);
                    blockIds.Add(blockRef.BlockTableRecord);
                }
                ObjectIdCollection xrefIds = filterXrefIds(blockIds);
                if (xrefIds.Count != 0)
                    process(xrefIds);
                tr.Commit();
            }
        }

        public static void attachXrefs(string[] fileNames)
        {
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
            Array.Sort(fileNames);
            Database db = Application.DocumentManager.MdiActiveDocument.Database;
            double dimScale = db.Dimscale;
            foreach (string fileName in fileNames)
            {
                PromptPointOptions options = new PromptPointOptions("Pick insertion point for " + fileName + ": ");
                options.AllowNone = false;
                PromptPointResult pt = ed.GetPoint(options);
                if (pt.Status != PromptStatus.OK) continue;

                double xrefScale = getDwgScale(fileName);
                double scaleFactor = dimScale / xrefScale;
                using (Transaction tr = Application.DocumentManager.MdiActiveDocument.TransactionManager.StartTransaction())
                {
                    ObjectId xrefId = db.AttachXref(fileName, Path.GetFileNameWithoutExtension(fileName));
                    BlockReference blockRef = new BlockReference(pt.Value, xrefId);
                    BlockTableRecord layoutBlock = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                    blockRef.ScaleFactors = new Scale3d(scaleFactor, scaleFactor, scaleFactor);
                    blockRef.Layer = "0";
                    layoutBlock.AppendEntity(blockRef);
                    tr.AddNewlyCreatedDBObject(blockRef, true);
                    tr.Commit();
                }
            }
        }

        public static double getDwgScale(string fileName)
        {
            using (Database db = new Database(false, true))
            {
                db.ReadDwgFile(fileName, FileOpenMode.OpenForReadAndAllShare, false, string.Empty);
                return db.Dimscale;
            }
        }

        public static ObjectIdCollection filterXrefIds(ObjectIdCollection blockIds)
        {
            ObjectIdCollection xrefIds = new ObjectIdCollection();
            foreach (ObjectId bId in blockIds)
            {
                if (!xrefIds.Contains(bId))
                {
                    BlockTableRecord btr = (BlockTableRecord)bId.GetObject(OpenMode.ForRead);
                    if (btr.IsFromExternalReference)
                        xrefIds.Add(bId);
                }
            }
            return xrefIds;
        }

        #endregion

        #region Commands

        [CommandMethod("XrefTools", "AttachXrefs", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void XrefAttach()
        {
            string initFolder = Application.DocumentManager.MdiActiveDocument.Database.Filename.ToUpper();
            if (initFolder.Contains("PLOT"))
            {
                initFolder = initFolder.Replace("-PLOT.DWG", "");
                initFolder = initFolder.Replace("PLOT\\", "");
                initFolder = initFolder.Replace("PLOTS\\", "");
                if (!Directory.Exists(initFolder))
                    initFolder = Application.DocumentManager.MdiActiveDocument.Database.Filename;
            }

            ofdFlags flags = ofdFlags.DefaultIsFolder | ofdFlags.AllowMultiple;
            OpenFileDialog dlg = new OpenFileDialog("Select Drawings to Attach", initFolder, "dwg", "Select Xrefs", flags);
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                attachXrefs(dlg.GetFilenames());
        }

        [CommandMethod("XrefTools", "BindXrefs", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void XrefBind()
        {
            processXrefs("\nSelect xrefs to bind: ", XrefTools.bindXrefs);
        }

        [CommandMethod("XrefTools", "DetachXrefs", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void XrefDetach()
        {
            processXrefs("\nSelect xrefs to detach: ", XrefTools.detachXref);
        }

        [CommandMethod("XrefTools", "OpenXrefs", CommandFlags.Session)]
        public static void XrefOpen()
        {
            processXrefs("\nSelect xrefs to open: ", XrefTools.openXref);
        }

        [CommandMethod("XrefTools", "ReloadXrefs", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void XrefReload()
        {
            processXrefs("\nSelect xrefs to reload: ", XrefTools.reloadXrefs);
        }

        [CommandMethod("XrefTools", "ReloadAllXrefs", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void XrefReloadAll()
        {
            Database db = Application.DocumentManager.MdiActiveDocument.Database;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable blockTbl = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                ObjectIdCollection blockIds = new ObjectIdCollection();
                foreach (ObjectId bId in blockTbl)
                    blockIds.Add(bId);
                ObjectIdCollection xrefIds = filterXrefIds(blockIds);
                if (xrefIds.Count != 0)
                    db.ReloadXrefs(xrefIds);
                tr.Commit();
            }
        }

        [CommandMethod("XrefTools", "UnloadXrefs", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void XrefUnload()
        {
            processXrefs("\nSelect xrefs to unload: ", XrefTools.unloadXrefs);
        }

        #endregion

    }
}

T.Willey

  • Needs a day job
  • Posts: 5251
Re: .NET XREF Routines
« Reply #2 on: January 28, 2010, 11:03:39 AM »
Here is my code to batch repath/rename xrefs.

[ http://www.theswamp.org/index.php?topic=22517.0 ]
Tim

I don't want to ' end-up ', I want to ' become '. - Me

Please think about donating if this post helped you.

T.Willey

  • Needs a day job
  • Posts: 5251
Re: .NET XREF Routines
« Reply #3 on: January 28, 2010, 11:06:03 AM »
Here is my class that hold my xref information, that I use in other codes.
Code: [Select]
public class MyXrefInformation {
    private string XrName;
    private string XrPath;
    private string XrFndAtPath = string.Empty;
    private string NewXrName = "";
    private string NewXrPath = "";
    private string[] InsertedAt;
    private string[] _OwnerNames = new string[0];
    private string[] _ChildrenNames = new string[0];
    private Autodesk.AutoCAD.DatabaseServices.XrefStatus XrStatus;
    private string DwgPath;
    private bool Overlay;
    private bool Nested;
   
    public MyXrefInformation() {}
   
    public string Name {
        get { return XrName; }
        set { XrName = value; }
    }
    public string Path {
        get { return XrPath; }
        set { XrPath = value; }
    }
    public string FoundAtPath {
        get { return XrFndAtPath; }
        set { XrFndAtPath = value; }
    }
    public string[] InsertedWhere {
        get { return InsertedAt; }
        set { InsertedAt = value; }
    }
    public Autodesk.AutoCAD.DatabaseServices.XrefStatus Status {
        get { return XrStatus; }
        set { XrStatus = value; }
    }
    public string DrawingPath {
        get { return DwgPath; }
        set { DwgPath = value; }
    }
    public bool IsOverlay {
        get { return Overlay; }
        set { Overlay = value; }
    }
    public bool IsNested {
        get { return Nested; }
        set { Nested = value; }
    }
    public string NewName {
        get { return NewXrName; }
        set { NewXrName = value; }
    }
    public string NewPath {
        get { return NewXrPath; }
        set { NewXrPath = value; }
    }
    public string[] OwnerNames {
        get { return _OwnerNames; }
        set { _OwnerNames = value; }
    }
    public string[] ChildrenNames {
        get { return _ChildrenNames; }
        set { _ChildrenNames = value; }
    }
}

Here is an example of how I use it.
Code: [Select]
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.IO;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;

public static MyXrefInformation[] FindXrefs (Database db) {
    MyXrefInformation[] XrefArray;
    string[] tempStrArray;
    int tempCnt;
    using (Transaction Trans = db.TransactionManager.StartTransaction()) {
        BlockTable BlkTbl = (BlockTable)Trans.GetObject(db.BlockTableId, OpenMode.ForRead);
        db.ResolveXrefs(false, true);
        XrefGraph XrGph = db.GetHostDwgXrefGraph(false);
        XrefArray = new MyXrefInformation[XrGph.NumNodes - 1];
        for (int i = 1; i < XrGph.NumNodes; ++i) {
            XrefGraphNode XrNode = XrGph.GetXrefNode(i);
            BlockTableRecord btr = (BlockTableRecord)Trans.GetObject(XrNode.BlockTableRecordId, OpenMode.ForRead);
            MyXrefInformation XrInfo = new MyXrefInformation();
            XrInfo.Name = XrNode.Name;
            XrInfo.Path = btr.PathName;
            XrInfo.DrawingPath = db.Filename;
            string FoundAt = WillLoad(btr.PathName, db);
            if (XrNode.XrefStatus == XrefStatus.Unresolved) {
                if ( string.IsNullOrEmpty( FoundAt ) )
                    XrInfo.Status = XrefStatus.Unresolved;
                else
                    XrInfo.Status = XrefStatus.Resolved;
            }
            else
                XrInfo.Status = XrNode.XrefStatus;
            if ( XrInfo.Status == XrefStatus.Resolved ) {
                    XrInfo.FoundAtPath = FoundAt;
            }
            XrInfo.IsNested = XrNode.IsNested;
            XrInfo.IsOverlay = btr.IsFromOverlayReference;
            ObjectIdCollection ObjIdCol = (ObjectIdCollection)btr.GetBlockReferenceIds(true, true);
            string[] InsertedAtArray = new string[ObjIdCol.Count];
            for (int j = 0; j < ObjIdCol.Count; ++j) {
                ObjectId ObjId = ObjIdCol[j];
                BlockReference BlkRef = (BlockReference)Trans.GetObject(ObjId, OpenMode.ForRead);
                BlockTableRecord tempbtr = (BlockTableRecord)Trans.GetObject(BlkRef.OwnerId, OpenMode.ForRead);
                if (tempbtr.IsLayout) {
                    Layout templo = (Layout)Trans.GetObject(tempbtr.LayoutId, OpenMode.ForRead);
                    InsertedAtArray[j] = "Layout: " + templo.LayoutName;
                }
                else InsertedAtArray[j] = "Block: " + tempbtr.Name;
            }
            XrInfo.InsertedWhere = InsertedAtArray;
            if ( !XrNode.NumIn.Equals(0) ) {
                tempStrArray = new string[XrNode.NumIn];
                tempCnt = 0;
                for (int j = 0; j < XrNode.NumIn; j++) {
                    int tempInt = FindGraphLocation( XrNode.In( j ) );
                    if ( tempInt.Equals( -1 ) )
                        continue;
                    tempStrArray[tempCnt] = XrGph.GetXrefNode( tempInt ).Name;
                    tempCnt++;
                }
                XrInfo.OwnerNames = tempStrArray;
            }
            if ( !XrNode.NumOut.Equals(0) ) {
                tempStrArray = new string[XrNode.NumOut];
                tempCnt = 0;
                for (int j = 0; j < XrNode.NumOut; j++) {
                    int tempInt = FindGraphLocation( XrNode.Out( j ) );
                    if ( tempInt.Equals( -1 ) )
                        continue;
                    tempStrArray[tempCnt] = XrGph.GetXrefNode( tempInt ).Name;
                    tempCnt++;
                }
                XrInfo.ChildrenNames = tempStrArray;
            }
            XrefArray[i - 1] = XrInfo;
        }
    }
    return XrefArray;
}
Tim

I don't want to ' end-up ', I want to ' become '. - Me

Please think about donating if this post helped you.

huiz

  • Swamp Rat
  • Posts: 913
  • Certified Prof C3D
Re: .NET XREF Routines
« Reply #4 on: June 29, 2010, 03:08:20 PM »
Function in VB.NET to insert a Raster Image (that is an external reference) in AutoCAD. It includes options to manager transparency and the layer.

Code: [Select]
  Public Function Add_Raster(ByRef ThisDoc As Document, ByVal RasterFile As String, ByVal InsPoint As Geometry.Point3d, ByVal Scale As Double, ByVal ImageTransp As Boolean, ByVal ImageLayer As String) As RasterImage
    Dim RasterEnt As RasterImage, RasterDef As RasterImageDef
    Dim ImageDicID, ImageDefID As ObjectId, ImageDic As DBDictionary
    Dim Matrix As Geometry.Matrix3d
    Dim btr As BlockTableRecord
    Dim RasterName As String = ""

    RasterName = RasterFile.Substring(RasterFile.LastIndexOf("\") + 1)
    RasterName = RasterName.Substring(0, RasterName.IndexOf("."))

    Add_Raster = Nothing
    If ThisDoc Is Nothing Then Exit Function

    Try

      Using Trans As Transaction = ThisDoc.TransactionManager.StartTransaction()

        RasterEnt = New RasterImage
        RasterEnt.Dispose() ' force loading of RasterImage.dbx module (needed for 2009 and earlier)

        RasterDef = New RasterImageDef
        RasterDef.SourceFileName = RasterFile
        RasterDef.ActiveFileName = RasterFile
        RasterDef.Load()

        ImageDicID = RasterImageDef.GetImageDictionary(ThisDoc.Database)
        If ImageDicID.IsNull Then
          RasterImageDef.CreateImageDictionary(ThisDoc.Database)
          ImageDicID = RasterImageDef.GetImageDictionary(ThisDoc.Database)
        End If
        If ImageDicID.IsNull Then
          MsgBox("Could not create image dictionary", MsgBoxStyle.Critical, "Failed")
          'Exit Try
        End If
        ImageDic = Trans.GetObject(ImageDicID, DatabaseServices.OpenMode.ForWrite)
        If ImageDic Is Nothing Then
          MsgBox("Could not open image dictionary", MsgBoxStyle.Critical, "Failed")
        Else
          If ImageDic.Contains(RasterName) Then
            MsgBox("That image name is already in use", MsgBoxStyle.Critical, "Failed")
          Else
            ImageDic.UpgradeOpen()
            ImageDefID = ImageDic.SetAt(RasterName, RasterDef)
            Trans.AddNewlyCreatedDBObject(RasterDef, True)
            RasterEnt = New RasterImage
            RasterEnt.SetDatabaseDefaults(ThisDoc.Database)
            Matrix = New Geometry.Matrix3d
            Matrix = Geometry.Matrix3d.Scaling(Scale / RasterDef.Size.X, New Geometry.Point3d(0, 0, 0))
            RasterEnt.TransformBy(Matrix)
            Matrix = Geometry.Matrix3d.Displacement(New Geometry.Vector3d(InsPoint.X, InsPoint.Y, InsPoint.Z))
            RasterEnt.TransformBy(Matrix)
            RasterEnt.ImageDefId = ImageDefID
            If ImageTransp = True Then
              RasterEnt.ImageTransparency = True
            End If
            RasterEnt.Layer = ImageLayer
            btr = Trans.GetObject(ThisDoc.Database.CurrentSpaceId, DatabaseServices.OpenMode.ForWrite)
            btr.AppendEntity(RasterEnt)
            Trans.AddNewlyCreatedDBObject(RasterEnt, True)
            RasterEnt.AssociateRasterDef(RasterDef)
            Trans.Commit()
          End If
        End If

      End Using

      Add_Raster = RasterEnt

    Catch ex As Exception

      MsgBox("Error inserting image " & RasterName, MsgBoxStyle.Critical, "Failed." & ex.ToString)

    End Try

  End Function


Original code from GTVic  with a little addition. Original idea: Tony Tanzillo (who coded this in C#).
The conclusion is justified that the initialization of the development of critical subsystem optimizes the probability of success to the development of the technical behavior over a given period.

Jeff H

  • Needs a day job
  • Posts: 6144
Re: .NET XREF Routines
« Reply #5 on: December 01, 2010, 02:25:59 AM »
Has not been tested thoroughly and basic and simple
but did for someone else to insert a xref that is a closed drawing into a closed drawing

C#
Code: [Select]

 [CommandMethod("AttachXref")]
        public void AttachXref()
        {
            string xrefPath = "C:\\Test\\XrefTest.dwg";
            string hostPath = "C:\\Test\\HostTest.dwg";
            Database xrefDb = new Database(false, false);
            xrefDb.ReadDwgFile(hostPath, FileShare.ReadWrite, false, "");

            using (Transaction trx = xrefDb.TransactionManager.StartTransaction())
            {

                BlockTable xrefBt = xrefDb.BlockTableId.GetObject(OpenMode.ForRead) as BlockTable;
                BlockTableRecord btrMs = xrefBt[BlockTableRecord.ModelSpace].GetObject(OpenMode.ForWrite) as BlockTableRecord;

                ObjectId xrefObjId = xrefDb.AttachXref(xrefPath, Path.GetFileNameWithoutExtension(xrefPath));

                BlockReference bref = new BlockReference(Point3d.Origin, xrefObjId);

                btrMs.AppendEntity(bref);
                trx.AddNewlyCreatedDBObject(bref, true);

                xrefDb.CloseInput(true);
                xrefDb.SaveAs(hostPath, DwgVersion.Current);

                trx.Commit();

            }
        }

VB
Code: [Select]
<CommandMethod("AttachXref")> _
    Public Sub AttachXref()
        Dim xrefPath As String = "C:\Test\XrefTest.dwg"
        Dim hostPath As String = "C:\Test\HostTest.dwg"
        Dim xrefDb As Database = New Database(False, False)
        xrefDb.ReadDwgFile(hostPath, FileShare.ReadWrite, False, "")

        Using trx As Transaction = xrefDb.TransactionManager.StartTransaction()

            Dim xrefBt As BlockTable = xrefDb.BlockTableId.GetObject(OpenMode.ForRead)
            Dim btrMs As BlockTableRecord = xrefBt(BlockTableRecord.ModelSpace).GetObject(OpenMode.ForWrite)

            Dim xrefObjId As ObjectId = xrefDb.AttachXref(xrefPath, Path.GetFileNameWithoutExtension(xrefPath))

            Dim bref As New BlockReference(Point3d.Origin, xrefObjId)

            btrMs.AppendEntity(bref)
            trx.AddNewlyCreatedDBObject(bref, True)

            xrefDb.CloseInput(True)
            xrefDb.SaveAs(hostPath, DwgVersion.Current)

            trx.Commit()

        End Using
    End Sub

Seth007

  • Mosquito
  • Posts: 5
Re: .NET XREF Routines
« Reply #6 on: December 01, 2010, 06:55:51 AM »
Quote
T.Willey wrote:

Code: [Select]
string FoundAt = WillLoad(btr.PathName, db);
Code: [Select]
   int tempInt = FindGraphLocation( XrNode.Out( j ) );

Could you please explain what these functions do?  :?

edit:
SOLVED!
For anyone with same problem, utility file is posted here by T.Willey http://www.theswamp.org/index.php?topic=24075.0
« Last Edit: December 02, 2010, 04:52:39 AM by Seth007 »