Author Topic: AutoCAD - First Plug-in Training  (Read 7904 times)

0 Members and 1 Guest are viewing this topic.

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: AutoCAD - First Plug-in Training
« Reply #30 on: December 29, 2011, 09:22:39 am »
<...>
I think F# is a GREAT idea. We probably won't get many new dotNet AutoCAD customisers usinf F#, but it may help them to become familiar with reading it ... thus helping with understanding some of the samples floatong around.

So, I give a try.
This is certainly not the ultimate F# translation because I'm quite newby with this language, but it was a funny exercise.
Any advice from some F# expert as kaefer (are there others over there ?) is welcome.

I tried to keep the comments in the code when there's no major change so that it should be easier to compare.

KeepAttributesHorizontal - Lesson 1
The major change here is the using of an 'object expression' instead of implementing a new type. I thaught interesting to show this F# ability.
As the overrule instance is created, to check if the overrule is already registered we use a 'global' mutable boolean 'variable' to avoid registering it twice (in F# binding values are immutables by default).

Code: [Select]
module HorizontalAttributes

open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.Geometry
open Autodesk.AutoCAD.Runtime

// Mutable value that indicates whether the overrule is registered
let mutable isRegistered = false

// No need to create a new type derived from TransformOverrule,
// just create an 'object expression' based on the TransformOverrule type
// and override the TransformBy method.
let keepStraightOverrule = { new TransformOverrule() with
    override this.TransformBy(entity, transform) =

        // Call the normal TransformBy function for the attribute
        // reference we're overruling.
        base.TransformBy(entity, transform)
        // We know entity must be an AttributeReference because
        // that is the only entity we registered the overrule for.
        let attRef = entity :?> AttributeReference
        // Set rotation of attribute reference to 0 (horizontal)
        attRef.Rotation <- 0.}

[<CommandMethod("KeepStraight")>]
let implementOverrule() =

    // We only want to register our overrule instance once,
    // so we check if it is not already registered
    // (i.e. this may be the 2nd time we've run the command)
    if not isRegistered then
        // Register the overrule
        Overrule.AddOverrule(
            RXClass.GetClass(typeof<AttributeReference>),
            keepStraightOverrule, false)
        isRegistered <- true
    // Make sure overruling is turned on so our overrule works
    Overrule.Overruling <- true


ObjectOrientedSample
'this' is not a F# keyword and could have been replaced by any other word (i.e. x.SetLocation ...)
Notice that with F# the assignation operator for 'variables' (or properties) is : <-

Code: [Select]
module ObjectOrientedSampleFs

open Autodesk.AutoCAD.Runtime

type Point() =
    let mutable xCoord = 0
    let mutable yCoord = 0

    member this.SetLocation x y =
        xCoord <- x
        yCoord <- y

    member this.X
        with get() = xCoord
        and set(value) = xCoord <- value

    member this.Y
        with get() = yCoord
        and set(value) = yCoord <- value

type NewPoint() =
    inherit Point()

    let mutable zCoord = 0

    member this.Z
        with get() = zCoord
        and set(value) = if value >= 0 then zCoord <- value

[<CommandMethod("RunTest")>]
let runTest() =
    let pt1 = new Point()
    // USe Step Into on the next line to step into SetLocation
    pt1.SetLocation 10 10
    let xVal1 = pt1.X
    pt1.X <- 9
    let xVal2 = pt1.X

    let pt2 = new NewPoint()
    // SetLocation, X and Y are inherited from Point class
    pt2.SetLocation 20 20
    let xVal3 = pt2.X
    pt2.X <- 9
    // Z is new to the NewPoint class
    pt2.Z <- 12
    let pt3 = pt2 :> Point
    //  pt3 is variable of type Point, but holds an object of type NewPoint
    ()


KeepAttributesHorizontal - Lesson 5
Using of a comprehension (sequence expression) to convert the ObjectIdCollection into an ObjectId array (this avoid the ugly array resizing requiered with the AttributeCollection.CopyTo() method).

Code: [Select]
module HorizontalAttributes

open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Geometry
open Autodesk.AutoCAD.Runtime

// Mutable value that indicates whether the overrule is registered
let mutable isRegistered = false

// No need to create a new type derived from TransformOverrule,
// just create an 'object expression' based on the TransformOverrule type
// and override the TransformBy method.
let keepStraightOverrule = { new TransformOverrule() with
    override this.TransformBy(entity, transform) =

        // Call the normal TransformBy function for the attribute
        // reference we're overruling.
        base.TransformBy(entity, transform)
        // We know entity must be an AttributeReference because
        // that is the only entity we registered the overrule for.
        let attRef = entity :?> AttributeReference
        // Set rotation of attribute reference to 0 (horizontal)
        attRef.Rotation <- 0.}

[<CommandMethod("KeepStraight")>]
let implementOverrule() =

    let doc = Application.DocumentManager.MdiActiveDocument
    let ed = doc.Editor

    // Select a block reference

    let opts = new PromptEntityOptions("\nSelect a block reference: ")
    opts.SetRejectMessage("\nMust be block reference...")
    opts.AddAllowedClass(typeof<BlockReference>, true)

    let res = ed.GetEntity(opts)

    if res.Status = PromptStatus.OK then
        let db = doc.Database
        use trans = db.TransactionManager.StartTransaction()

        // Open the BlockReference for read.
        // We know its a BlockReference because we set a filter in
        // our PromptEntityOptions
        let blkRef = trans.GetObject(res.ObjectId, OpenMode.ForRead) :?> BlockReference
        let attRefCol = blkRef.AttributeCollection

        // Using a 'comprehension' to convert the ObjectIdCollection into an ObjectId array
        let objIds = [| for id in attRefCol -> id |]

        // We only want to register our overrule instance once,
        // so we check if it is not already registered
        // (i.e. this may be the 2nd time we've run the command)
        if not isRegistered then
            Overrule.AddOverrule(
                RXClass.GetClass(typeof<AttributeReference>),
                keepStraightOverrule, false)
            isRegistered <- true

        // Specify which Attributes will be overruled
        keepStraightOverrule.SetIdFilter(objIds)

        // Make sure overruling is turned on so our overrule works
        Overrule.Overruling <- true


KeepAttributesHorizontal - Lesson 6
We can see here the use of the pipeline operator (|>) and the Seq module specific functions to iterate through the attributes (the same thing can be done with C# or VB using Linq but it is the 'natural' way with F#). The F# sequence type (seq<'T>) is similar to IEnumerable<T> (IEnumerable (Of T) in VB)
Notice the need to 'ignore' the value returned by SymbolTable.Add() method.
Notice that functions have to be defined in the code prior they're called.

Code: [Select]
module HorizontalAttributes

open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Geometry
open Autodesk.AutoCAD.Runtime

// Mutable value that indicates whether the overrule is registered
let mutable isRegistered = false

// Registered Application Id for Xdata
let regAppName = "ADSK_ATTRIBUTE_ZERO_OVERRULE"

// No need to create a new type derived from TransformOverrule,
// just create an 'object expression' based on the TransformOverrule type
// and override the TransformBy method.
let keepStraightOverrule = { new TransformOverrule() with
    override this.TransformBy(entity, transform) =

        // Call the normal TransformBy function for the attribute
        // reference we're overruling.
        base.TransformBy(entity, transform)
        // We know entity must be an AttributeReference because
        // that is the only entity we registered the overrule for.
        let attRef = entity :?> AttributeReference
        // Set rotation of attribute reference to 0 (horizontal)
        attRef.Rotation <- 0.}


let addRegAppId (db:Database) =
    use trans = db.TransactionManager.StartTransaction()

    // First create our RegAppId (if it doesn't already exist)
    let appTbl = trans.GetObject(db.RegAppTableId, OpenMode.ForRead) :?> RegAppTable

    if not (appTbl.Has(regAppName)) then
        let appTblRec = new RegAppTableRecord()
        appTbl.UpgradeOpen()
        appTblRec.Name <- regAppName
        appTbl.Add(appTblRec) |> ignore
        trans.AddNewlyCreatedDBObject(appTblRec, true)
        trans.Commit()


[<CommandMethod("ActivateOverrule")>]
let activateOverrule() =
   
    // We only want to register our overrule instance once,
    // so we check if it is not already registered
    // (i.e. this may be the 2nd time we've run the command)
    if not isRegistered then
        // Register the overrule
        Overrule.AddOverrule(
            RXClass.GetClass(typeof<AttributeReference>),
            keepStraightOverrule, false)
        isRegistered <- true

    // Specify which Attributes will be overruled
    keepStraightOverrule.SetXDataFilter(regAppName)
    // Make sure overruling is turned on so our overrule works
    Overrule.Overruling <- true


[<CommandMethod("KeepStraight")>]
let implementOverrule() =

    let doc = Application.DocumentManager.MdiActiveDocument
    let ed = doc.Editor

    // Select a block reference

    let opts = new PromptEntityOptions("\nSelect a block reference: ")
    opts.SetRejectMessage("\nMust be block reference...")
    opts.AddAllowedClass(typeof<BlockReference>, true)

    let res = ed.GetEntity(opts)

    if res.Status = PromptStatus.OK then
        let db = doc.Database

        addRegAppId(db)

        use trans = db.TransactionManager.StartTransaction()

        // Open the BlockReference for read.
        // We know its a BlockReference because we set a filter in
        // our PromptEntityOptions
        let blkRef = trans.GetObject(res.ObjectId, OpenMode.ForRead) :?> BlockReference
        let attRefCol = blkRef.AttributeCollection
       
        // Iterate through  ObjectIds of all AttributeReferences
        // attached to the BlockReference, opening each
        // AttributeReference and adding xdata to it.
        blkRef.AttributeCollection
        |> Seq.cast<ObjectId>
        |> Seq.map(fun objId -> trans.GetObject(objId, OpenMode.ForWrite))
        |> Seq.iter(fun attRef ->
            // Create new xdata containing the overrule filter name
            use resBuf = new ResultBuffer(new TypedValue(1001, regAppName), new TypedValue(1000, "Dummy text"))
            // Add the xdata
            attRef.XData <- resBuf)

        trans.Commit()

        // Create and register our overrule and turn overruling on.
        activateOverrule()

[<CommandMethod("DontKeepStraight")>]
let removeXdata() =

    let doc = Application.DocumentManager.MdiActiveDocument
    let ed = doc.Editor

    // Select a block reference

    let opts = new PromptEntityOptions("\nSelect a block reference: ")
    opts.SetRejectMessage("\nMust be block reference...")
    opts.AddAllowedClass(typeof<BlockReference>, true)

    let res = ed.GetEntity(opts)

    if res.Status = PromptStatus.OK then
        let db = doc.Database

        addRegAppId(db)

        use trans = db.TransactionManager.StartTransaction()

        // Open the BlockReference for read.
        // We know its a BlockReference because we set a filter in
        // our PromptEntityOptions
        let blkRef = trans.GetObject(res.ObjectId, OpenMode.ForRead) :?> BlockReference
        let attRefCol = blkRef.AttributeCollection
       
        // Iterate through  ObjectIds of all AttributeReferences
        // attached to the BlockReference, opening each
        // AttributeReference and adding xdata to it.
        blkRef.AttributeCollection
        |> Seq.cast<ObjectId>
        |> Seq.map(fun objId -> trans.GetObject(objId, OpenMode.ForWrite))
        |> Seq.iter(fun attRef ->
            // Create new xdata containing the overrule filter name
            use resBuf = new ResultBuffer(new TypedValue(1001, regAppName))
            // Add the xdata.
            // Because we leave this blank except for the regappid,
            // it erases any Xdata we previously added.
            attRef.XData <- resBuf)

        trans.Commit()


BonusPlugin
With some refactorisation
Corrected code so that it works whatever the block insertion plane (see Reply #40)
Corrected code so that it works whatever the attribute text justification (see Reply #62)

Code: [Select]
module HorizontalAttributes

open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Geometry
open Autodesk.AutoCAD.GraphicsInterface
open Autodesk.AutoCAD.Runtime

// Mutable value that indicates whether the overrule is registered
let mutable isRegistered = false

// Registered Application Id for Xdata
let regAppName = "ADSK_ATTRIBUTE_ZERO_OVERRULE"

// No need to create a new type derived from DrawableOverrule,
// just create an 'object expression' based on the DrawableOverrule type
// and override the WorldDraw, ViewportDraw and SetAttributes methods.
let billboardAttributesOverrule = { new DrawableOverrule() with

    // Returning False from WorldDraw tells AutoCAD this entity has
    // viewport dependent graphics.
    override this.WorldDraw(drawable, wd) = false;

    // Called for each viewport so entity can draw itself differently
    // viewport dependent graphics.
    override this.ViewportDraw(drawable, vd) =
        // Cast drawable to type AttributeReference (we know it's an
        // AttributeReference because that's the only class we register our
        // overrule for).
        let attRef = drawable :?> AttributeReference

        // First calculate the transformation matrix to rotate from the
        // BlockReference's current orientation to the view. 
        let org =
            match attRef.Justify with
            | AttachmentPoint.BaseLeft
            | AttachmentPoint.BaseAlign
            | AttachmentPoint.BaseFit -> attRef.Position
            | _ -> attRef.AlignmentPoint
        let viewMat =
            Matrix3d.PlaneToWorld(new Plane(org, vd.Viewport.ViewDirection)) *
            Matrix3d.WorldToPlane(new Plane(org, attRef.Normal)) *
            Matrix3d.Rotation(-attRef.Rotation, attRef.Normal, org)

        // Apply the transformation
        vd.Geometry.PushModelTransform(viewMat) |> ignore
        // Draw the 'per viewport geometry
        base.ViewportDraw(drawable, vd)
        // Remove the transformation - we don't want any other objects
        // to draw themselves in the view plane.
        vd.Geometry.PopModelTransform() |> ignore

    // This function tells AutoCAD to dynamically update the
    // AttributeReference during view transitions (e.g. 3DORBIT).
    // Comment it out to improve graphic update performance.
    override this.SetAttributes(drawable, traits) =
        base.SetAttributes(drawable, traits) ||| 2048
    }

let addXdata id resBuf (db:Database) =
    use trans = db.TransactionManager.StartTransaction()

    // First create our RegAppId (if it doesn't already exist)
    let appTbl = trans.GetObject(db.RegAppTableId, OpenMode.ForRead) :?> RegAppTable

    if not (appTbl.Has(regAppName)) then
        let appTblRec = new RegAppTableRecord()
        appTbl.UpgradeOpen()
        appTblRec.Name <- regAppName
        appTbl.Add(appTblRec) |> ignore
        trans.AddNewlyCreatedDBObject(appTblRec, true)

    // Open the BlockReference for read.
    // Iterate through  ObjectIds of all AttributeReferences
    // attached to the BlockReference, opening each
    // AttributeReference and adding xdata to it.
    (trans.GetObject(id, OpenMode.ForRead) :?> BlockReference).AttributeCollection
    |> Seq.cast<ObjectId>
    |> Seq.map(fun objId -> trans.GetObject(objId, OpenMode.ForWrite))
    |> Seq.iter(fun attRef -> attRef.XData <- resBuf)

    trans.Commit()

let getBlkRef (ed:Editor) =
    let opts = new PromptEntityOptions("\nSelect a block reference: ")
    opts.SetRejectMessage("\nMust be block reference...")
    opts.AddAllowedClass(typeof<BlockReference>, true)
    ed.GetEntity(opts)


[<CommandMethod("ActivateBillboardOverrule")>]
let activateOverrule() =
   
    // We only want to register our overrule instance once,
    // so we check if it is not already registered
    // (i.e. this may be the 2nd time we've run the command)
    if not isRegistered then
        Overrule.AddOverrule(
            RXClass.GetClass(typeof<AttributeReference>),
            billboardAttributesOverrule, false)
        isRegistered <- true

    // Specify which Attributes will be overruled
    billboardAttributesOverrule.SetXDataFilter(regAppName)
    // Make sure overruling is turned on so our overrule works
    Overrule.Overruling <- true


[<CommandMethod("BillboardAttributes")>]
let implementOverrule() =

    let doc = Application.DocumentManager.MdiActiveDocument

    // Select a block reference
    let res = getBlkRef doc.Editor

    if res.Status = PromptStatus.OK then
        // Create and register our overrule and turn overruling on.
        activateOverrule()

        // Add xdata to all AttributeReferences of the selected block reference
        use resBuf = new ResultBuffer(
                        new TypedValue(1001, regAppName),
                        new TypedValue(1000, "Dummy text"))
        addXdata res.ObjectId resBuf doc.Database

[<CommandMethod("DontBillboardAttributes")>]
let removeXdata() =

    let doc = Application.DocumentManager.MdiActiveDocument

    // Select a block reference
    let res = getBlkRef doc.Editor

    if res.Status = PromptStatus.OK then
        // Erases xdata to all AttributeReferences of the selected block reference.
        use resBuf = new ResultBuffer(new TypedValue(1001, regAppName))
        addXdata res.ObjectId resBuf doc.Database
« Last Edit: February 11, 2012, 05:07:36 am by gile »
Speaking English as a French Frog

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: AutoCAD - First Plug-in Training
« Reply #31 on: December 31, 2011, 04:36:45 am »
For those who're interested by F#, can have some fun with this video: An Introduction to Microsoft F#
Speaking English as a French Frog

MP

  • Seagull
  • Posts: 16257
  • Warning: I may be trolling.
Re: AutoCAD - First Plug-in Training
« Reply #32 on: December 31, 2011, 05:48:12 am »
Oh sh!t, why why why did I start watching that?
\|// Set goal. Experiment tirelessly until
|oo| practice has become expertise.  Loop.
|- | Dropbox | O'Reilly School | UltraEdit

MP

  • Seagull
  • Posts: 16257
  • Warning: I may be trolling.
Re: AutoCAD - First Plug-in Training
« Reply #33 on: December 31, 2011, 06:31:16 am »
That was a well spent 1:18 (despite the fact I should be sleeping). Thanks for posting this Giles (she doesn't know it yet but my wife is going to hate you).
\|// Set goal. Experiment tirelessly until
|oo| practice has become expertise.  Loop.
|- | Dropbox | O'Reilly School | UltraEdit

SGP2012

  • Newt
  • Posts: 37
Re: AutoCAD - First Plug-in Training
« Reply #34 on: January 03, 2012, 04:02:02 pm »
Oh wow! This turned into a long thread. Its my first day back at work today, so my brian is a little mushy, but as far as I can tell the two main feedback items were:

1. My bonus app (lesson #8) is causing attributes to display slightly in the wrong place - so I need to go back and double check my transformation matrices.
2. You don't like my use of implicit casting in the VB.NET code.

I'm ambivalent about #2. I can understand why a C++/C# purist doesn't like it (and that it causes problems when using automatic code translators). But this is a feature of VB (and other languages) for a reason. For starters, its one less thing a beginner has to worry about while they learn the basics - and this tutorial is aimed squarely at beginner programmers (not programmers who are AutoCAD API beginners).

Any other areas I need to look at improving?

And (at the risk of going off on a tangent) any more comments on #2? I'd be interested to know if VB.NET developers dislike implicit casting as much as the C# developers  :evil:.



Stephen Preston
Autodesk Developer Network

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: AutoCAD - First Plug-in Training
« Reply #35 on: January 03, 2012, 04:57:12 pm »
Just some precisions

I didn't wan't to offend anyone with my questions (appologies if I did due to my poor English).

I sincerly ask myself why can we so often hear that VB is easier than C#.
As far as I know, the to main differences are syntax and strictness. in my opinion the syntax is not really an issue (only personnal convinience). Looking at strictness, my question is: is it more or less difficult to learn  stricter programmation language than one which can be more or less strict (and wrtitten different ways) according to the setting of some options (infer, strict, explicit)...
May be some VBers or bi-linguist can reply.

To be clear, I'm not a "programmer who is AutoCAD API beginner". I'm an AutoCAD user and self-educated AutoCAD 'programmer', using LISP since 10 years, and I jumped into the .NET adventure 4 years ago. At this time I dicovered all at the same time: .NET, OOP, C# (and strong static typing).
Speaking English as a French Frog

TT

  • Swamp Rat
  • Posts: 826
Re: AutoCAD - First Plug-in Training
« Reply #36 on: January 04, 2012, 02:04:25 am »

I tried this and it makes the Attribute appear horizontal during the Jig of the Insert but crashes with a eOpenforWrite error after the rotation is selected......


I think that's because this simple example wasn't done correctly to start with (Sorry 'bout that Mr. Preston).

The TransformBy() method takes a Matrix3d and applies the transformation defined by the matrix.

If you want to alter the transformation in some way, you don't supermessage the base type and pass it the original Matrix3d parameter as supplied (which applies the tranformation), and then perform a second transformation, by setting the rotation property. Sorry again, but that is the classic definition of a kludge.

What the author should have done was modify the Matrix3d that was passed into the override of TransformBy() to eliminate any rotation, and then supermessage the base type and pass it the modified matrix.

As far as the errors you may be seeing, the object(s) involved are already open for write when the method is called, but may not always be open in a transaction in certain circumstances.

MUQTAR

  • Newt
  • Posts: 68
Re: AutoCAD - First Plug-in Training
« Reply #37 on: January 04, 2012, 07:30:09 am »
thanks kerry

Jeff H

  • Needs a day job
  • Posts: 4632
Re: AutoCAD - First Plug-in Training
« Reply #38 on: January 04, 2012, 09:37:42 am »

I tried this and it makes the Attribute appear horizontal during the Jig of the Insert but crashes with a eOpenforWrite error after the rotation is selected......


I think that's because this simple example wasn't done correctly to start with (Sorry 'bout that Mr. Preston).

The TransformBy() method takes a Matrix3d and applies the transformation defined by the matrix.

If you want to alter the transformation in some way, you don't supermessage the base type and pass it the original Matrix3d parameter as supplied (which applies the tranformation), and then perform a second transformation, by setting the rotation property. Sorry again, but that is the classic definition of a kludge.

What the author should have done was modify the Matrix3d that was passed into the override of TransformBy() to eliminate any rotation, and then supermessage the base type and pass it the modified matrix.

As far as the errors you may be seeing, the object(s) involved are already open for write when the method is called, but may not always be open in a transaction in certain circumstances.

Thanks,
and appreciate the concise explanation

SGP2012

  • Newt
  • Posts: 37
Re: AutoCAD - First Plug-in Training
« Reply #39 on: January 04, 2012, 03:42:07 pm »
Hi TheMaster,

I agree that one shouldn't call a method on an entity from an overrule for that method called for that same entity. For example, if I were to call Attribute.TransformBy from my overrule of Attribute.TransformBy.

However, your description implies that the Attribute.Rotation property 'set' function calls TransformBy in its implementation. It doesn't. If it did, then my code would have crashed the first time it was run as the overruled function was recursively called. That being the case, I really don't consider setting the Rotation property in the overrule as being any different from (say) setting the Color property.

I understand your point about using the transformation matrix, but I remain satisfied that the solution I use efficiently achieves exactly the behavior I wanted to implement in a single line of code (not a kludge). It also serves the additional purpose of avoiding having to present a larger and more complicated chunk of matrix manipulation code in a beginner tutorial. I'm happy to agree to differ on this - working through the transformation matrix is also a good solution if that is your preference.

Also, a note to everyone - Thank you for contributing your energy and expertise in providing feedback for this tutorial. It - and future projects - will be all the better as a result.

Cheers,

Stephen
Stephen Preston
Autodesk Developer Network

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: AutoCAD - First Plug-in Training
« Reply #40 on: January 04, 2012, 04:03:19 pm »
Maybe I've found something to fix the issue with the transformation matrix while block references are inserted in planes non parallel to WCS XY

Heres the C# code for the ViewportDraw method

Code: [Select]
        public override void ViewportDraw(Drawable drawable, ViewportDraw vd)
        {
            // Cast drawable to type AttributeReference (we know it's an
            // AttributeReference because that's the only class we register our
            // overrule for).
            AttributeReference attRef = (AttributeReference)drawable;

            // First calculate the transformation matrix to rotate from the
            // BlockReference's current orientation to the view.
            Point3d org = attRef.Position;
            Vector3d zVec = vd.Viewport.ViewDirection;
            Vector3d yVec = vd.Viewport.CameraUpVector;
            Vector3d xVec = yVec.CrossProduct(zVec).GetNormal();
            Matrix3d viewMat =
                Matrix3d.AlignCoordinateSystem(
                Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis, Vector3d.ZAxis,
                org, xVec, yVec, zVec) *
                Matrix3d.WorldToPlane(new Plane(org, attRef.Normal)) *
                Matrix3d.Rotation(-attRef.Rotation, attRef.Normal, org);

            // Apply the transformation
            vd.Geometry.PushModelTransform(viewMat);
            // Draw the 'per viewport geometry
            base.ViewportDraw(drawable, vd);
            // Remove the transformation - we don't want any other objects
            // to draw themselves in the view plane.
            vd.Geometry.PopModelTransform();
        }

And the VB code:

Code: [Select]
        Public Overrides Sub ViewportDraw(ByVal drawable As Drawable,
                                          ByVal vd As ViewportDraw)
            ' Cast drawable to type AttributeReference (we know it's an
            '  AttributeReference because that's the only class we register our
            '  overrule for).
            Dim attRef As AttributeReference = drawable

            ' First calculate the transformation matrix to rotate from the
            '  BlockReference's current orientation to the view.
            Dim org As Point3d = attRef.Position
            Dim zVec As Vector3d = vd.Viewport.ViewDirection
            Dim yVec As Vector3d = vd.Viewport.CameraUpVector
            Dim xVec As Vector3d = yVec.CrossProduct(zVec).GetNormal
            Dim viewmat As Matrix3d =
                Matrix3d.AlignCoordinateSystem(
                    Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis, Vector3d.ZAxis,
                    org, xVec, yVec, zVec) *
                Matrix3d.WorldToPlane(New Plane(org, attRef.Normal)) *
                Matrix3d.Rotation(-attRef.Rotation, attRef.Normal, org)

            ' Apply the transformation
            vd.Geometry.PushModelTransform(viewmat)
            'Draw the 'per viewport geometry
            MyBase.ViewportDraw(drawable, vd)
            ' Remove the transformation - we don't want any other objects
            '  to draw themselves in the view plane.
            vd.Geometry.PopModelTransform()

        End Sub
« Last Edit: January 04, 2012, 04:48:06 pm by gile »
Speaking English as a French Frog

TT

  • Swamp Rat
  • Posts: 826
Re: AutoCAD - First Plug-in Training
« Reply #41 on: January 05, 2012, 03:10:30 am »
Hi TheMaster,

I agree that one shouldn't call a method on an entity from an overrule for that method called for that same entity. For example, if I were to call Attribute.TransformBy from my overrule of Attribute.TransformBy. 


Yes, you can't call TransformBy() from the Overrule's TransformBy (at least not without disabling overruling temporarily to prevent reentance).

Quote

However, your description implies that the Attribute.Rotation property 'set' function calls TransformBy in its implementation. It doesn't.


No, I wasn't trying to imply that at all. And of course, it doesn't do that because if it did, that code would fail with a stack overflow. Actually, it's quite the opposite. The implementation of AcDbText::transformBy() is what sets the rotation member along with other members to perform the complete transformation.

Quote

If it did, then my code would have crashed the first time it was run as the overruled function was recursively called. That being the case, I really don't consider setting the Rotation property in the overrule as being any different from (say) setting the Color property.

I understand your point about using the transformation matrix, but I remain satisfied that the solution I use efficiently achieves exactly the behavior I wanted to implement in a single line of code (not a kludge). It also serves the additional purpose of avoiding having to present a larger and more complicated chunk of matrix manipulation code in a beginner tutorial.


Well, if a somewhat advanced topic like overrules are suitable as examples in an entry-level tutorial that targets those with no previous programming experience, then I think it should be done correctly and in the most efficient way (by manipulating the matrix). Perhaps its because I've always held the view that training materials both formal and informal, should always emphasize correctness and should not promote kludges as ways to solve problems, because kludges almost always come back to bite you in the arse.

On that subject, your tutorial purports to assume no prior programming experience. Perhaps you meant no prior object-oriented programming experience? I mention that because while it only very briefly touches on the most-basic of programming concepts such as conditional branching with 'if', variables, etc., It seems to rush the user past all of that and somewhat hastily proceeds to dive head-first into advanced oop-related concepts like inheritance, polymorphism, virtual methods, and so forth. It's almost as if it were trying to squeeze years of learning into one short, informal lesson.

While it certainly is useful for giving an entry level person a hands-on taste of what can be done using the APIs, I would not recommend it as a 'first lesson' for those with no prior programming experience. In fact, introductory training should have nothing to do with AutoCAD development at all, as it only interjects more confusion into something that is already confusing enough :d

I've come to view serious development with .NET and ObjectARX as something that should really be done by professional programmers rather than AutoCAD users that happen to also be weekend programmers. Scripting with LISP is certainly in the domain of the latter, but complex development with any flavor of ObjectARX is something that should be left to skilled professionals, because of the potential damage that inexperienced programmers can do.  I remember one case where an inexperienced LISP programmer was able to corrupt a very large dataset to the extent that repairing it ultimately cost over 1000 times the fee he was paid.

Quote

I'm happy to agree to differ on this - working through the transformation matrix is also a good solution if that is your preference.


Well, doing it via manipulating the matrix also helps to show how one might apply the same kind of constraint to other types of entities like for example, block references that represent annotative elements or symbols containing text, dimensions, leaders, etc., more generally, cases where certain geometry and text should always have the same relative orientation.

SEANT

  • Bull Frog
  • Posts: 257
Re: AutoCAD - First Plug-in Training
« Reply #42 on: January 05, 2012, 03:38:40 am »
Tony T.?  Is that you?
Sean Tessier
AutoCAD 2014 Mechanical

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: AutoCAD - First Plug-in Training
« Reply #43 on: January 05, 2012, 03:53:14 am »
Tony T.?  Is that you?

It looks like, this remind me something:
Quote
I remember one case where an inexperienced LISP programmer was able to corrupt a very large dataset to the extent that repairing it ultimately cost over 1000 times the fee he was paid.

If that's you Tony, I'm glad to see you back. I missed you.
« Last Edit: January 05, 2012, 01:26:05 pm by gile »
Speaking English as a French Frog

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 10732
  • class keyThumper<T>:ILazy<T>
Re: AutoCAD - First Plug-in Training
« Reply #44 on: January 05, 2012, 04:20:40 am »

< .. > this remind me something:
Quote
I remember one case where an inexperienced LISP programmer was able to corrupt a very large dataset to the extent that repairing it ultimately cost over 1000 times the fee he was paid.

< .. >

I thought it sounded familiar to me as well .... but then I've been known to mix up memories :D

Welcome TheMaster .. enjoy.

Regards
kdub.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

--> Donate<--