Since posting about the ability to display transient graphics as an ongoing feature that can react to mouse input, I’ve been thinking of the steps that would be needed to generate a custom gizmo comparable with AutoCAD’s viewcube.
The post starts to go in that direction by displaying a couple of types of transient graphics in AutoCAD as a “standard” feature: firstly we’re going to show screen-fixed text (with code pulled directly from this post) and then we’re going to place a a transient box in the drawing itself.
This may be pre-cursor to displaying a box-like gizmo, but then again it may not. It’s still early days as far as this implementation is concerned, and I felt that just the code that uses WorldGeometry.Shell() was relevant to many developers, as it can be quite tricky to get these kind of graphics to display properly.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Colors;
using System.Collections.Generic;
namespace TransientSelection
{
public class TransientBox : Transient
{
// Internal state
private TextStyle _style;
Point3dCollection _verts;
IntegerCollection _faces;
EdgeData _edgeData;
FaceData _faceData;
VertexData _vertData;
public TransientBox(double side)
{
// Create the style for our text
_style = new TextStyle();
_style.Font =
new FontDescriptor("Calibri", false, true, 0, 0);
_style.TextSize = 10;
// Add our vertices manually (8 of them for a box)
_verts = new Point3dCollection();
_verts.Add(Point3d.Origin); // 0
_verts.Add(new Point3d(side, 0, 0)); // 1
_verts.Add(new Point3d(side, side, 0)); // 2
_verts.Add(new Point3d(0, side, 0)); // 3
_verts.Add(new Point3d(0, 0, side)); // 4
_verts.Add(new Point3d(side, 0, side)); // 5
_verts.Add(new Point3d(side, side, side)); // 6
_verts.Add(new Point3d(0, side, side)); // 7
// Our faces are defined in sets of 3 vertices
// (listed below two per line, each making a square face)
int[] polys =
{
0, 1, 2, 2, 3, 0, // Bottom
0, 4, 5, 5, 1, 0, // Front
1, 5, 6, 6, 2, 1, // Right
2, 6, 7, 7, 3, 2, // Back
0, 3, 7, 7, 4, 0, // Left
4, 7, 6, 6, 5, 4 // Top
};
const int polySize = 3;
// Create our faces from the polys array
_faces = new IntegerCollection();
for (int p = 0; p < (polys.Length / polySize); p++)
{
_faces.Add(polySize);
for (int v = 0; v < polySize; v++)
{
_faces.Add(polys[p * polySize + v]);
}
}
// Create our edge data
_edgeData = new EdgeData();
// Each face's edges should have the same colour as the face
_edgeData.SetColors(
new short[]
{
1, 1, 1, 2, 2, 2, 3, 3, 3,
4, 4, 4, 5, 5, 5, 6, 6, 6,
7, 7, 7, 8, 8, 8, 9, 9, 9,
10, 10, 10, 11, 11, 11, 12, 12, 12
}
);
// Create our face data
_faceData = new FaceData();
// The face colours match their edges
_faceData.SetColors(
new short[]
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
}
);
// Create our vertex data
_vertData = new VertexData();
_vertData.OrientationFlag = OrientationType.Clockwise;
}
protected override int SubSetAttributes(DrawableTraits traits)
{
return (int)DrawableAttributes.None;
}
protected override void SubViewportDraw(ViewportDraw vd)
{
// Draw our screen-fixed text
DrawText(vd.Geometry, "ViewportDraw");
}
protected override bool SubWorldDraw(WorldDraw wd)
{
// Draw our box
wd.Geometry.Shell(
_verts, _faces, _edgeData, _faceData, _vertData, false
);
// Draw our screen-fixed text
DrawText(wd.Geometry, "WorldDraw");
return true;
}
private void DrawText(Geometry g, string text)
{
// We make use of another interface to push our transforms
if (g != null)
{
// Push our transforms onto the stack
g.PushOrientationTransform(OrientationBehavior.Screen);
g.PushPositionTransform(
PositionBehavior.Screen,
new Point2d(30, 30)
);
// Draw our screen-fixed text
g.Text(
new Point3d(0, 0, 0), // Position
new Vector3d(0, 0, 1), // Normal
new Vector3d(1, 0, 0), // Direction
text, // Text
true, // Rawness
_style // TextStyle
);
// Remember to pop our transforms off the stack
g.PopModelTransform();
g.PopModelTransform();
}
}
protected override void OnDeviceInput(DeviceInputEventArgs e)
{
base.OnDeviceInput(e);
}
protected override void OnPointInput(PointInputEventArgs e)
{
base.OnPointInput(e);
}
}
public class Commands
{
TransientBox _tb = null;
[CommandMethod("TB")]
public void TransientBox()
{
_tb = new TransientBox(10);
// Tell AutoCAD to call into this transient's extended
// protocol when appropriate
Transient.CapturedDrawable = _tb;
// Go ahead and draw the transient
TransientManager.CurrentTransientManager.AddTransient(
_tb, TransientDrawingMode.Main,
128, new IntegerCollection()
);
}
[CommandMethod("TBR")]
public void RemoveTransientBox()
{
// Erase the transient graphics and dispose of the transient
if (_tb != null)
{
TransientManager.CurrentTransientManager.EraseTransient(
_tb,
new IntegerCollection()
);
_tb.Dispose();
_tb = null;
}
}
}
}
I originally defined the box’s faces with four sides, but then decided to split them into three-sided polygons, instead (this is something that AutoCAD will do, in any case, and it allowed me to assign different colours within a single face of the cube), mainly for “debugging” purposes.
I struggled for a long time with getting the mesh to display properly: some of the faces were displayed inappropriately above others. I originally assumed this was due to generation of the face normals – so I added code to generate them manually, as well as trying to switch on automatic face normal generation via the last argument of the Shell() method – but it turned out to be related to the transient graphics display mode I’d chosen: changing this to TransientDrawingMode.Main from TransientDrawingMode.DirectShortTem fixed all my problems. I also decided not to generate or define face normals at all, as this didn’t appear to have any effect on the quality of the geometry or the display performance.
Here’s what we see when we run the TB command and adjust the view while still in wireframe mode:
While we’re orbiting or when we switch to a shaded visual style, we see the cube’s faces as well as the text displayed properly:
In the next post, we’re going to take a look at some pretty interesting capabilities of the Shell() method, to see what impact adding per-vertex colours has on our transient geometry.