September 16, 2009

Jigging an AutoCAD solid using IronRuby and .NET (yes, finally)

The sample didn’t work with IronRuby 0.3, but recently David Blackmon got in touch to let me know he had a version of the code working with IronRuby 0.9. Now that I’ve started preparing for my upcoming AU class, AutoCAD® .NET: Developing for AutoCAD® Using IronPython and IronRuby, I decided to take a closer look at the update to IronRuby and more specifically at the changes to the code David made to get it to work.

David has been having some real fun with IronRuby and AutoCAD: for those interested, I strongly recommend checking out the work he’s posted on github. David claims to have been inspired by my previous IronRuby posts to develop some helper classes for AutoCAD development and the results are really interesting: having someone passionate about Ruby take a look at this is great, as it demonstrates to the rest of us some of the possibilities.

I haven’t used these helpers in the below code – which only borrows the tricks David used to get the jig to work – but that’s more for consistency with my previous post than for any other reason.

Here’s the updated IronRuby code, with the modified/additional lines marked in red (and the full source file can be downloaded from here):

1require 'acmgd.dll'

2require 'acdbmgd.dll'

3

4Ai = Autodesk::AutoCAD::Internal

5Aiu = Autodesk::AutoCAD::Internal::Utils

6Aas = Autodesk::AutoCAD::ApplicationServices

7Ads = Autodesk::AutoCAD::DatabaseServices

8Aei = Autodesk::AutoCAD::EditorInput

9Ag = Autodesk::AutoCAD::Geometry

10Ar = Autodesk::AutoCAD::Runtime

11

12def print_message(msg)

13 app = Aas::Application

14 doc = app.DocumentManager.MdiActiveDocument

15 ed = doc.Editor

16 ed.WriteMessage(msg)

17end

18

19# Function to register AutoCAD commands

20

21def autocad_command(cmd)

22 cc = Ai::CommandCallback.new method(cmd)

23 Aiu.AddCommand(

24 'rbcmds', cmd, cmd, Ar::CommandFlags.Modal, cc)

25

26 # Let's now write a message to the command-line

27

28 print_message("\nRegistered Ruby command: " + cmd)

29end

30

31def add_commands(names)

32 names.each { |n| autocad_command n }

33end

34

35# Let's do something a little more complex...

36

37class SolidJig < Aei::EntityJig

38

39 # Constructor

40

41 def SolidJig.new(ent)

42 super

43 end

44

45 # The function called to run the jig

46

47 def start_jig(ed, pt)

48

49 # The start point is specified outside the jig

50

51 @start = pt

52 @end = pt

53 @sol = self.Entity

54

55 return ed.Drag(self)

56 end

57

58 # The sampler function

59

60 def sampler(prompts)

61

62 # Set up our selection options

63

64 jo = Aei::JigPromptPointOptions.new

65 jo.UserInputControls = (

66 Aei::UserInputControls.Accept3dCoordinates |

67 Aei::UserInputControls.NoZeroResponseAccepted |

68 Aei::UserInputControls.NoNegativeResponseAccepted)

69 jo.Message = "\nSelect end point: "

70

71 # Get the end point of our box

72

73 res = prompts.AcquirePoint(jo)

74

75 if @end == res.Value

76 return Aei::SamplerStatus.NoChange

77 else

78 @end = res.Value

79 end

80

81 return Aei::SamplerStatus.OK

82 end

83

84 # The update function

85

86 def update()

87

88 # Recreate our Solid3d box

89

90 begin

91

92 # Get the width (x) and depth (y)

93

94 x = @end.X - @start.X

95 y = @end.Y - @start.Y

96

97 # We need a non-zero Z value, so we copy Y

98

99 z = y

100

101 # Create our box and move it to the right place

102

103 if x.abs > 0 and y.abs > 0

104 @sol.CreateBox(x.abs,y.abs,z.abs)

105 @sol.TransformBy(

106 Ag::Matrix3d.Displacement(

107 Ag::Vector3d.new(

108 @start.X + x/2,

109 @start.Y + y/2,

110 @start.Z + z/2)))

111 end

112 rescue

113 return false

114 end

115 return true

116 end

117end

118

119# Create a box using a jig

120

121def boxjig

122

123 app = Aas::Application

124 doc = app.DocumentManager.MdiActiveDocument

125 db = doc.Database

126 ed = doc.Editor

127

128 # Select the start point before entering the jig

129

130 ppr = ed.GetPoint("\nSelect start point: ")

131

132 if ppr.Status == Aei::PromptStatus.OK

133

134 # We'll add our solid to the modelspace

135

136 tr = doc.TransactionManager.StartTransaction

137 bt =

138 tr.GetObject(

139 db.BlockTableId,

140 Ads::OpenMode.ForRead)

141 btr =

142 tr.GetObject(db.CurrentSpaceId,Ads::OpenMode.ForWrite)

143

144 # Make sure we're recording history to allow grip editing

145

146 sol = Ads::Solid3d.new

147 sol.RecordHistory = true

148

149 # Now we add our solid

150

151 btr.AppendEntity(sol)

152 tr.AddNewlyCreatedDBObject(sol, true)

153

154 # And call the jig before finishing

155

156 begin

157

158 sj = SolidJig.new sol

159

160 ppr2 = sj.start_jig(ed, ppr.Value)

161

162 # Only commit if all completed well

163

164 if ppr2.Status == Aei::PromptStatus.OK

165 tr.Commit

166 end

167

168 rescue

169

170 print_message("\nProblem found: " + $! + "\n")

171

172 end

173

174 tr.Dispose

175 end

176end

177

178add_commands ["boxjig"]

Let’s look at the specific changes:

Lines 1-2 just removes the paths, to make the code more portable. I've also removed the load of acmgdinternal.dll, as this is no longer a separate assembly with AutoCAD 2010.

Lines 39 and 41 turn my prior initialization attempt into a constructor. Line 53 sets our "@sol" member to be the entity passed into the constructor, although this code has to be in start_jig to work properly, for some reason – it can’t be in the constructor itself.

Lines 47, 60, 86 & 160 are make the code more Rubyesque (as mentioned in my last IronRuby post, Ruby functions should really be named with the lowercase_and_delimited convention).

Lines 103-4 and 111 allow a box to be created even when the cursor crosses the start point (either to its left or below it, which would previously have thrown an exception as we create a box with zero/negative height/width/depth).

To load the .rb file, I simply used the RBLOAD command implemented by the C# loader application shown in previous IronRuby posts.

Now let’s run the BOXJIG command. After selecting a start point, we drag off to the top-right and see our box changing size (displayed using the 3D hidden visual style, in this case):

As we drag to the bottom left of our original point and select a point, we see our box created in that direction:

In my next post I plan on taking the lid off some of David’s AutoCAD helper classes, as well as showing some of IronPython’s debugging capabilities when integrated with Visual Studio 2008.