AutoCAD .NET API provides two concrete Jig classes for us to jig different things in different circumstances, EntityJig and DrawJig. EntityJig is to jig a specific entity as its name indicates and the DrawJig is to jig anything that has graphics to draw, which can be a single entity, a group of entities, or something that is not available natively in AutoCAD.
In this post, let us see how to use the DrawJig to move, rotate and scale multiple entities which can be as many as we like and can have any types in a single group.
Here is the whole DrawJig implementation class along with a test command inside it.
#region Namespaces
using System;
using System.Text;
using System.Linq;
using System.Xml;
using System.Reflection;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Windows;
using Autodesk.AutoCAD.GraphicsSystem;
using Autodesk.AutoCAD.GraphicsInterface;
using MgdAcApplication = Autodesk.AutoCAD.ApplicationServices.Application;
using MgdAcDocument = Autodesk.AutoCAD.ApplicationServices.Document;
using AcWindowsNS = Autodesk.AutoCAD.Windows;
#endregion
namespace AcadNetAddinCS
{
public class MoveRorationScaleJig : DrawJig
{
#region Fields
Autodesk.AutoCAD.Geometry.Point3d mBase;
private List<Entity> mEntities = new List<Entity>();
private int mTotalJigFactorCount = 3;
private int mCurJigFactorIndex = 1; // Jig Factor Index
public Autodesk.AutoCAD.Geometry.Point3d mLocation; // Jig Factor #1
public Double mAngle; // Jig Factor #2
public Double mScaleFactor; // Jig Factor #3
#endregion
#region Constructors
public MoveRorationScaleJig(Point3d basePt)
{
mBase = basePt.TransformBy(UCS);
//TODO: Initialize jig factors and transform them if necessary.
mLocation = mBase;
mAngle = 0;
mScaleFactor = 1;
}
#endregion
#region Properties
public Autodesk.AutoCAD.Geometry.Point3d Location
{
get { return mLocation; }
set { mLocation = value; }
}
public Double Angle
{
get { return mAngle; }
set { mAngle = value; }
}
public Double ScaleFactor
{
get { return mScaleFactor; }
set { mScaleFactor = value; }
}
public Autodesk.AutoCAD.Geometry.Point3d Base
{
get { return mBase; }
set { mBase = value; }
}
public Editor AcEditor
{
get
{
return MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
}
}
public Matrix3d UCS
{
get
{
return AcEditor.CurrentUserCoordinateSystem;
}
}
public Matrix3d Transformation
{
get
{
//return Matrix3d.Identity; //* Change it to anything else meaningful.
return Matrix3d.Scaling(mScaleFactor, mLocation).
PostMultiplyBy(Matrix3d.Rotation(mAngle, Vector3d.ZAxis.TransformBy(UCS), mLocation)).
PostMultiplyBy(Matrix3d.Displacement(mBase.GetVectorTo(mLocation)));
}
}
#endregion
#region Methods
public void AddEntity(Entity ent)
{
mEntities.Add(ent);
}
public void TransformEntities()
{
Matrix3d mat = Transformation;
foreach (Entity ent in mEntities)
{
ent.TransformBy(mat);
}
}
#endregion
#region Overrides
protected override bool WorldDraw(Autodesk.AutoCAD.GraphicsInterface.WorldDraw draw)
{
Matrix3d mat = Transformation;
WorldGeometry geo = draw.Geometry;
if (geo != null)
{
geo.PushModelTransform(mat);
foreach (Entity ent in mEntities)
{
geo.Draw(ent);
}
geo.PopModelTransform();
}
return true;
}
protected override SamplerStatus Sampler(JigPrompts prompts)
{
switch (mCurJigFactorIndex)
{
case 1:
JigPromptPointOptions prOptions1 = new JigPromptPointOptions("\nMove:");
// Set properties such as UseBasePoint and BasePoint of the prompt options object if necessary here.
prOptions1.UserInputControls = UserInputControls.GovernedByOrthoMode | UserInputControls.GovernedByUCSDetect;
PromptPointResult prResult1 = prompts.AcquirePoint(prOptions1);
if (prResult1.Status == PromptStatus.Cancel && prResult1.Status == PromptStatus.Error)
return SamplerStatus.Cancel;
if (prResult1.Value.Equals(mLocation)) //Use better comparison method if necessary.
{
return SamplerStatus.NoChange;
}
else
{
mLocation = prResult1.Value;
return SamplerStatus.OK;
}
case 2:
JigPromptAngleOptions prOptions2 = new JigPromptAngleOptions("\nRotate:");
prOptions2.UseBasePoint = true;
prOptions2.BasePoint = mLocation;
prOptions2.UserInputControls = UserInputControls.GovernedByOrthoMode | UserInputControls.GovernedByUCSDetect;
PromptDoubleResult prResult2 = prompts.AcquireAngle(prOptions2);
if (prResult2.Status == PromptStatus.Cancel && prResult2.Status == PromptStatus.Error)
return SamplerStatus.Cancel;
if (prResult2.Value.Equals(mAngle)) //Use better comparison method if necessary.
{
return SamplerStatus.NoChange;
}
else
{
mAngle = prResult2.Value;
return SamplerStatus.OK;
}
case 3:
JigPromptDistanceOptions prOptions3 = new JigPromptDistanceOptions("\nScale:");
prOptions3.UseBasePoint = true;
prOptions3.BasePoint = mLocation;
prOptions3.UserInputControls = UserInputControls.GovernedByOrthoMode | UserInputControls.GovernedByUCSDetect;
PromptDoubleResult prResult3 = prompts.AcquireDistance(prOptions3);
if (prResult3.Status == PromptStatus.Cancel && prResult3.Status == PromptStatus.Error)
return SamplerStatus.Cancel;
if (prResult3.Value.Equals(mScaleFactor)) //Use better comparison method if necessary.
{
return SamplerStatus.NoChange;
}
else
{
mScaleFactor = prResult3.Value;
return SamplerStatus.OK;
}
default:
break;
}
return SamplerStatus.OK;
}
#endregion
#region Method to Call
public bool Jig()
{
try
{
PromptResult pr;
do
{
pr = AcEditor.Drag(this);
if (pr.Status == PromptStatus.Keyword)
{
// Keyword handling code
}
else
this.mCurJigFactorIndex++;
} while ((pr.Status != PromptStatus.Cancel && pr.Status != PromptStatus.Error)
&& this.mCurJigFactorIndex <= this.mTotalJigFactorCount);
if (this.mCurJigFactorIndex == this.mTotalJigFactorCount + 1)
return true;
else
return false;
}
catch { return false; }
}
#endregion
#region Commands
[CommandMethod("TestMoveRorationScaleJig")]
public static void TestMoveRorationScaleJig_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
PromptSelectionResult selRes = ed.GetSelection();
if (selRes.Status != PromptStatus.OK) return;
PromptPointOptions prOpt = new PromptPointOptions("\nBase point:");
PromptPointResult pr = ed.GetPoint(prOpt);
if (pr.Status != PromptStatus.OK) return;
MoveRorationScaleJig jigger = new MoveRorationScaleJig(pr.Value);
using (Transaction tr = db.TransactionManager.StartTransaction())
{
foreach (ObjectId id in selRes.Value.GetObjectIds())
{
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForWrite);
jigger.AddEntity(ent);
}
if (jigger.Jig())
{
jigger.TransformEntities();
tr.Commit();
}
else
tr.Abort();
}
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.ToString());
}
}
#endregion
}
}
After the test assembly is loaded and the test command run, we will see the jigger behaves perfectly well.
So, with so concise code which can be condensed a bit further such as removing those unused namespaces, we have reproduced almost the exact behavior of the looking quite profound AutoCAD Move, Rotate, and Scale commands.
A few highlights about the code may be more helpful.
• The key of the DrawJig is its WorldDraw() method. It provides the abilities to draw any graphics.
• The WorldGeometry instance provides various methods to draw anything we want.
• It also provides us a structural and easy way for us to handle any matrix transformations as we did to the placement, rotation and scaling matrix here.
• The Sampler() override is another core method which is used to collect the user input such as the base point and the scale factor in the case.
• Though the Sampler() looks like exactly the same as the one in EntityJig, it has a fundamental difference. In the EntityJig, the collected point is in UCS unless explicitly specified with the Input Control; but in the DrawJig, the point coordinate is in WCS.
• However, the Editor.GetPoint() method returns the base point in UCS.
• The WorldGeometry can draw anything that is derived from the Drawable interface in turn the Entity class, so we can redraw the entities with the new position, rotation angle and scale factor.
• The PushModelTransform and PopModelTransform mechanism makes our life a lot easier. We don’t have to cache the new position, rotation angle and scale factor even if they change with the cursor every moment.
• In generally, we do not pass the entity pointer to the DrawJig as it should not only care about graphics of a particular entity, but here, we pass a list of the entity pointers into the DrawJig so as to update their graphics at the same moment.
• Some efficient and good matrix techniques are used to perform kinds of entity transformations as can be noted.
Enjoy it! More and cooler jigging samples and cases will be demonstrated in the future. Please stay tuned.
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) provides a coder, Draw Jigger, to help us create starter code for DrawJig implementations automatically, quickly and reliably.
thank you for this.
could you add a line (ghost line or rubberband, not an entity) to flag the user of start location & current location (but keeping text & other selected items)? i was thinking after the close brace of "foreach (ObjectId id in selRes.Value.GetObjectIds())", but am unsure.
karl
Posted by: karl | 07/15/2014 at 05:50 PM
Karl, glad you like it. The first change is very easy, setting the UseBasePoint, BasePoint, and the Cursor properties of the JigPrompPointOptions as follows:
JigPromptPointOptions prOptions1 = new JigPromptPointOptions("\nMove:");
prOptions1.UseBasePoint = true;
prOptions1.BasePoint = mBase;
prOptions1.UserInputControls = UserInputControls.GovernedByOrthoMode | UserInputControls.GovernedByUCSDetect;
prOptions1.Cursor = CursorType.RubberBand;
PromptPointResult prResult1 = prompts.AcquirePoint(prOptions1);
Hiding the original entity graphics is not in this jig sample. In fact, we deliberately kept the original graphics there but demonstrated using the PushModelTransform() and the PopModelTransform() of the WorldGeometry to always transform the original geometries instead of those by the last jig/drag:
WorldGeometry geo = draw.Geometry;
if (geo != null)
{
geo.PushModelTransform(mat);
foreach (Entity ent in mEntities)
{
geo.Draw(ent);
}
geo.PopModelTransform();
}
If the latter behavior is needed, some other posts have addressed it. Here is one:
AutoCAD .NET: EntityJig – Move&Rotate Block (BlockReference/INSERT)
http://spiderinnet1.typepad.com/blog/2012/02/autocad-net-entityjig-moverotate-block-blockreferenceinsert.html
We can simply remove the sub-transaction highlighting the entity there to make the original graphics disappear.
Posted by: Spiderinnet1 | 07/15/2014 at 09:47 PM
thank you.
i though i had to create a line for the jig. i was unaware of the cursor controls.
i'm building a routine to rotate block attribs as a group but leave the block untouched.
the rubberband helps the user.
karl
Posted by: karl | 07/16/2014 at 04:16 PM
Karl, you are welcome. In terms of rotating a block insert along with its attributes, we have another post addressing it. Not sure if it will be more helpful:
AutoCAD .NET: Drag Blocks & Attributes with EntityJig (a Better INSERT Command)
http://spiderinnet1.typepad.com/blog/2012/10/autocad-net-drag-blocks-attributes-with-entityjig-a-better-insert-command.html
Posted by: Spiderinnet1 | 07/16/2014 at 08:21 PM
my rubberband does not center on my chosen point but on what i've discovered is an equivalent to "lastpoint" (acad variable). how may i control the startpoint of the rubberband?
karl
Posted by: karl | 07/17/2014 at 12:49 PM
Karl, it seems the BasePoint was assigned to the last jigged point instead of the original block position. You may want to keep the picked base point into a separate variable, which will not take part into any transformations.
Posted by: Spiderinnet1 | 07/17/2014 at 02:15 PM