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

How to pass ObjectIdCollection into built-in Rotate command?

23 REPLIES 23
SOLVED
Reply
Message 1 of 24
Anonymous
4129 Views, 23 Replies

How to pass ObjectIdCollection into built-in Rotate command?

Hello,

 

First time user here. I am trying to rotate a bunch of entities like so:

 

rotate_Conveyro.png

 

This image represents a bunch of lines, blocks, circles etc being rotated using the built-in AutoCAD rotate command. 

 

Knowing that I can access all of those object IDs and store them into an ObjectIDCollection, how do I go about passing that collection into the rotate command? Is this possible? Or do I need to build a rotate command from scratch? Perhaps there's an easier way of doing all this ...?

23 REPLIES 23
Message 2 of 24
SENL1362
in reply to: Anonymous

search for JIG: Google "c# autoCAD jig"
Entity Jig for one entity
Draw Jig for multiple entities
Message 3 of 24
Anonymous
in reply to: SENL1362

I was seeing that as a last step if ever I had to create the rotate command from scratch ... If I had to, then I'd need to see the entities as I rotate them and the DrawJig would be useful. However, I was looking more along the lines of using the existing rotate command that is built-in AutoCAD and pass my ObjectIdCollection as a parameter in the command.
Message 4 of 24
SENL1362
in reply to: Anonymous

Each Database Object (ID) also has a Handle.
Using Lisp you pass those handles to the command, like
(entget (handent """ & theID.Handle & """))

But you might be limited by the max length of commandline input.

Message 5 of 24
SENL1362
in reply to: SENL1362
Message 6 of 24
Anonymous
in reply to: SENL1362

Yeah I saw those options when I was searching for a solution. My only issue was, I prompt the user to select his object (or one of the lines related to it) as a single click. I don't prompt the user to choose all his lines. Can I pass an ObjectIdCollection into a SelectionSet? That was where I was stuck using that method ... I tried to pass my object IDs into a SelectionSet but really could not figure it out!

Any tips on how to acheive that?

Thanks for the help by the way!
Message 7 of 24
SENL1362
in reply to: Anonymous

I doubt you can add to a SelectionSet other than the PickFirst Set.
Message 8 of 24
_gile
in reply to: SENL1362

Hi,

 

You can build a selection set from an array of ObjectIds using the static SelectionSet.FromObjectIds() method.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 9 of 24
Anonymous
in reply to: _gile

Cool! I had no idea I could do that ... Now I'm not too sure how to pass this into a rotate command using SendStringToExecute:

 

<CommandMethod("My-Rotate")> _
Public Sub MyRotate()
    'Get the current document and database 
    Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
    Dim acCurDb As Database = acDoc.Database
    Dim acObj As Object
    Dim entRes As PromptEntityResult
    Dim entOpts As PromptEntityOptions
    Dim rb As ResultBuffer
    Dim FoundHunter As Boolean
    Dim acBlkTbl As BlockTable
    Dim acBlkTblRec As BlockTableRecord
    Dim pickedPolyline As Polyline = Nothing
    Dim SelSet As SelectionSet
    Dim Lst_ObjId As New List(Of ObjectId)

    'Prompt user to select the conveyor he wants to rotate
    Autodesk.AutoCAD.Internal.Utils.SetFocusToDwgView()
    entOpts = New PromptEntityOptions(vbLf & "Choose the object you wish to rotate")
    entRes = acDoc.Editor.GetEntity(entOpts)

    If (entRes.Status = PromptStatus.OK) Then

        'Start a transaction 
        Using acTrans As Transaction = acCurDb.TransactionManager.StartTransaction()
            acObj = entRes.ObjectId.GetObject(OpenMode.ForRead)

            'Make sure the selected object was a polyline
            If Not TypeOf acObj Is Polyline Then MsgBox("You must choose a line or polyline") : Exit Sub

            rb = New ResultBuffer
            rb = entRes.ObjectId.GetObject(OpenMode.ForRead).XData()

'Sets the correct Project Conveyor ProjectConveyor.SetByDataTable(GetPKFromResultBuffer(rb), Project.PK_Project)

'Open the Block table for read acBlkTbl = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead)

'Open the Block table record Model space for write
acBlkTblRec = acTrans.GetObject(acBlkTbl(BlockTableRecord.ModelSpace), OpenMode.ForWrite) 'Go through the Block Table Record and build the collection ID For Each acObjId As ObjectId In acBlkTblRec rb = New ResultBuffer rb = acObjId.GetObject(OpenMode.ForRead).XData() FoundHunter = False If Not rb Is Nothing Then For Each tv As TypedValue In rb If tv.TypeCode = DxfCode.ExtendedDataRegAppName Then If tv.Value = "MY_PROGRAM_NAME" Then FoundHunter = True End If If FoundHunter And tv.TypeCode = DxfCode.ExtendedDataInteger32 Then If tv.Value = ProjectConveyor.PK_ProjectConveyor Then Lst_ObjId.Add(acObjId) 'Sets up all object IDs correctly here Exit For End If End If Next rb.Dispose() End If Next 'Create a selection set from object IDs SelSet = SelectionSet.FromObjectIds(Lst_ObjId.ToArray) 'Use AUTOCAD's Rotate function knowing we have all selections in selection set acDoc.SendStringToExecute(" ROTATION ", False, False, False) 'Save the new objects to the database ProjectConveyor.Update() acTrans.Commit() End Using End If End Sub

 

That is the code I use but I am really not sure how to execute the ROTATION command. Afterwards, when the conveyor is selected, the user will follow AutoCAD's rotate guidelines.

 

 

Any ideas?

Message 10 of 24
_gile
in reply to: Anonymous

Rather than using SendStringToExecute(), if you're targeting AutoCAD 2015, use the new Editor.Command() method.

For prior versions, Tony "DiningPhiliosopher" Tanzillo, provided a wrapper for the undocumented RunCommand() method.

Both work the same way and accept selection sets as arguments :

 

acDoc.Editor.Command("_.rotate", SelSet, "", basePt, angleInCurrentAngularUnits)

 basePt can be expressed as a string : "10,20,0" or as a Point3d, the same for angleInCurrentAngularUnits (string or double).

If the last parameters are omitted Editor.Command() prompts the user to input them.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 11 of 24
Anonymous
in reply to: _gile

We're using AutoCAD 2014. Do you have the link towards Tony's RunCommand() ? I think we're very close!

Merci! 😉
Message 12 of 24
Anonymous
in reply to: _gile

Thanks for the link.

 

So I implemented a module that contains exactly what Tony suggested for the RunCommand wrapper. I have no errors, everything seems to be passing fluidly. 

 

Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Linq.Expressions
Imports System.Reflection
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.EditorInput

Module EditorInputExtensionMethods
    <System.Runtime.CompilerServices.Extension()> _
    Public Function Command(editor As Editor, ParamArray args As Object()) As PromptStatus
        If editor Is Nothing Then
            Throw New ArgumentNullException("editor")
        End If
        Return runCommand(editor, args)
    End Function
    Dim runCommand As Func(Of Editor, Object(), PromptStatus) = GenerateRunCommand()
    Private Function GenerateRunCommand() As Func(Of Editor, Object(), PromptStatus)
        Dim method As MethodInfo = GetType(Editor).GetMethod( _
        "RunCommand", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.[Public])
        Dim instance As ParameterExpression = Expression.Parameter(GetType(Editor), "editor")
        Dim args As ParameterExpression = Expression.Parameter(GetType(Object()), "args")
        Return Expression.Lambda(Of Func(Of Editor, Object(), PromptStatus)) _
        (Expression.Call(instance, method, args), instance, args).Compile()
    End Function
End Module

 

Although, it doesn't provide a rotate command to my drawing. 

 

SelSet.png

 

I also tried "_.rotation" since my AutoCAD is in French but it didn't change anything. Is it because my SelSet has "Unavailable" entries or something? That doesn't seem right to me ... 

 

My object IDs are correctly added to the list. Perhaps I'm missing something very obvious... It doesn't seem to prompt me for anything when I execute this operation.

 

Thanks in advance!

Message 13 of 24
_gile
in reply to: Anonymous

It seems to me that with VB (I'm not very familiar with VB), you need to import the module where the extension method is defined in the file where the command which use it is defined :

 

Import EditorInputExtensionMethods

 

If your AutoCAD is French use "rotation" / ".rotation" or "_rotate" / "_.rotate". The underscore is used to call the global command name and the dot insure to call the native (undefined) command.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 14 of 24
Anonymous
in reply to: _gile

I thought I needed to import the module as well but if it is in the same namespace it will still accept it. I tried importing it anyways and get a green swiggly error, probably because it is already included and not needed.

I tried "rotation", ".rotation", "_rotate", "_.rotate", "rotate" and no luck ... Any idea what the next step should be?

I appreciate your help.
Message 15 of 24
Anonymous
in reply to: _gile

If it helps, I tried to see what was returned as a value from the acDoc.Editor.Command() method and it returns Error(-5001).
Message 16 of 24
_gile
in reply to: Anonymous

This works for me (AutoCAD 2014). I hope it helps.

 

Imports System.Linq.Expressions
Imports System.Reflection
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Runtime

Namespace MeasureSample

    Module EditorInputExtensionMethods

        <System.Runtime.CompilerServices.Extension> _
        Public Function Command(editor As Editor, ParamArray args As Object()) As PromptStatus
            If editor Is Nothing Then
                Throw New ArgumentNullException("editor")
            End If
            Return runCommand(editor, args)
        End Function

        Dim runCommand As Func(Of Editor, Object(), PromptStatus) = GenerateRunCommand()

        Private Function GenerateRunCommand() As Func(Of Editor, Object(), PromptStatus)
            Dim method As MethodInfo = GetType(Editor).GetMethod( _
                "RunCommand", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.[Public])
            Dim instance As ParameterExpression = Expression.Parameter(GetType(Editor), "editor")
            Dim args As ParameterExpression = Expression.Parameter(GetType(Object()), "args")
            Return Expression.Lambda(Of Func(Of Editor, Object(), PromptStatus))( _
                Expression.Call(instance, method, args), instance, args).Compile()
        End Function

    End Module

    Public Class CommandMethods

        <CommandMethod("TEST")>
        Public Sub Test()
            Dim doc As Document = Application.DocumentManager.MdiActiveDocument
            Dim ed As Editor = doc.Editor
            Dim db As Database = doc.Database
            Dim ids As ObjectIdCollection = New ObjectIdCollection()
            Using tr As Transaction = db.TransactionManager.StartOpenCloseTransaction()
                Dim space As BlockTableRecord = _
                    DirectCast(tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead), BlockTableRecord)
                For Each id As ObjectId In space
                    ids.Add(id)
                Next
            End Using
            Dim array(ids.Count - 1) As ObjectId
            ids.CopyTo(array, 0)
            Dim selSet As SelectionSet = SelectionSet.FromObjectIds(array)
            ed.Command("_.rotate", selSet, "")
        End Sub

    End Class

End Namespace

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 17 of 24
Anonymous
in reply to: _gile

Gilles, 

 

After some more testing, here's what I came up with. I have AutoCAD 2014 as well.

 

I've been testing this using my Acad Standard executable:

acad_std.png

 

I just tested it out in my Mechanical version and it works correctly.

acad_mech.png

 

I utilize the Standard version (normally) and it doesn't work in that. Any idea why? That's so odd! Thanks for going through that much trouble though. I just need to figure out why it's glitchy in standard Acad.

Message 18 of 24
Anonymous
in reply to: _gile

Hi Gilles,

 

Quick question related to this editor.Command() wrapper.

 

I want to update my database once the command is completed. Although, when I step through the code, it goes through the command and finishes the sub before the user can actually do any input. This means I cannot update the database with the newly gotten points (Point3d) because it is already out of the End Sub routine.

 

Is there a fix for this? 

Message 19 of 24
_gile
in reply to: Anonymous

As far as I know, the Editor.Command() (this wrapper as the 2015 built-in one) needs to have all its arguments to run synchronously.

 

With AutoCAD 2015, the Editor.CommandAsync() allows what you want to do.

 

        Public Async Sub TestCommand()
            ' ...
            Await ed.CommandAsync("_.rotate", selSet, "", Editor.PauseToken, Editor.PauseToken)
            ' ...
        End Sub

 

With prior versions of AutoCAD, you could easily write this in AutoLISP.

If you need .NET, you'll have to take the Jig route, here's a little C# sample.

 

    public class CommandMethods
    {
        [CommandMethod("TEST")]
        public void Test()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            PromptSelectionResult selection = ed.GetSelection();
            if (selection.Status != PromptStatus.OK)
                return;

            PromptPointResult pointResult = ed.GetPoint("\nBase point: ");
            if (pointResult.Status != PromptStatus.OK)
                return;

            SelectionSet selSet = selection.Value;
            Matrix3d ucs = ed.CurrentUserCoordinateSystem;
            Point3d basePoint = pointResult.Value.TransformBy(ucs);
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                Entity[] entities = new Entity[selSet.Count];
                for (int i = 0; i < selSet.Count; i++)
                {
                    entities[i] = (Entity)tr.GetObject(selSet[i].ObjectId, OpenMode.ForWrite);
                }

                // create a new instance of RotateJig
                RotateJig jig = new RotateJig(entities, basePoint, ucs.CoordinateSystem3d.Zaxis);
                // pass it to the Editor.Drag method
                PromptResult result = ed.Drag(jig);

                // if the user didn't cancel...
                if (result.Status == PromptStatus.OK)
                {
                    // apply the rotation to each entity
                    foreach (Entity entity in entities)
                    {
                        entity.TransformBy(jig.Rotation);
                    }
                }
                tr.Commit();
            }
        }
    }

    // the RotateJig inherits from DrawJid to deal with multiple entities
    class RotateJig : DrawJig
    {
        private Entity[] entities;
        private Point3d basePoint;
        double angle = 0.0;
        Vector3d axis;

        // public property (needed from the calling method)
        public Matrix3d Rotation { get; private set; }

        // constructor
        public RotateJig(Entity[] ents, Point3d basePoint, Vector3d axis)
        {
            this.entities = ents;
            this.basePoint = basePoint;
            this.axis = axis;
        }

        // dynamically rotate the entities
        protected override bool WorldDraw(Autodesk.AutoCAD.GraphicsInterface.WorldDraw draw)
        {
            Autodesk.AutoCAD.GraphicsInterface.WorldGeometry geo = draw.Geometry;
            if (geo != null)
            {
                geo.PushModelTransform(this.Rotation);
                foreach (Entity ent in entities)
                {
                    geo.Draw(ent);
                }
                geo.PopModelTransform();
            }
            return true;
        }

        // prompt the user to specify a rotation
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            JigPromptAngleOptions options = new JigPromptAngleOptions("\nSpecify rotation: ");
            options.BasePoint = basePoint;
            options.UseBasePoint = true;
            options.Cursor = CursorType.RubberBand;
            PromptDoubleResult result = prompts.AcquireAngle(options);
            if (result.Value == angle)
            {
                return SamplerStatus.NoChange;
            }
            angle = result.Value;
            this.Rotation = Matrix3d.Rotation(angle, axis, basePoint);
            return SamplerStatus.OK;
        }
    }

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 20 of 24
Anonymous
in reply to: _gile

****! I was afraid of that. I was hoping for a way to run my command and code synchronously. So I guess either I upgrade to AutoCAD 2015 and use the Editor.CommandAsync() or I recreate the rotate function using the DrawJig?

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

Post to forums  

Technology Administrators


Autodesk Design & Make Report