-
Notifications
You must be signed in to change notification settings - Fork 1
Selection Set Helper
When we create a selection set in AutoCAD, we usually want to iterate over the selected entities.
The managed API doesn’t make that as easy to do as I think it should be.
The steps required are:
- Get the document
- Get the database
- Start a trans
- Build the selection set prompt, filter and options
- Select the entities
- Get the return value of the prompt object
- Iterate over the selection set
- Get the entity object for each element in the set
- Do something with that entity (change the color, for example)
- Commit our transaction
That is 10 steps (more if we were to expand setting the prompt options and building the filter)
Why should we have to do that every time we need a selection set?
Why can’t we just add a little dash of Ruby goodness
select_on_screen.each_for_write{ |ent| ent.color_index = 3}
Or, if we only need to read the items in the selection set
select_on_screen.each_for_read{ |ent| puts ent.layer }
Sure, this takes some additional code, but it is written once and then used over and over.
We start by opening the Aei::SelectionSet class and adding a couple of methods
#Extend Aei::SelectionSet
class Aei::SelectionSet
def each_for_read
begin
helper = TransHelper.new
helper.trans do |tr,db,tables|
self.each do |sel_object|
yield helper.get_obj(sel_object.ObjectId, :Read)
end
end
rescue Exception => e
puts_ex e
end
end
#
def each_for_write
begin
helper = TransHelper.new
helper.trans([:Layer]) do |tr,db,tables|
self.each do |sel_object|
ent = helper.get_obj(sel_object.ObjectId, :Read)
layer = helper.get_obj(ent.LayerId)
if !layer.IsLocked
ent.UpgradeOpen
yield ent
end
end
end
rescue Exception => e
puts_ex e
end
end
end
These new selection set methods depend on the TransHelper class and the select_on_screen method for AcadHelper.rb
#helper class for using managed transactions #it won't handle every need every time, but does handle #common usages quite well. class TransHelper def initialize(use_this_db=nil) @open_modes = {:Read => Ads::OpenMode.ForRead, :Write => Ads::OpenMode.ForWrite, } @space_hash = {:ModelSpace => Ads::BlockTableRecord.ModelSpace, :PaperSpace => Ads::BlockTableRecord.PaperSpace} app = Aas::Application doc = app.DocumentManager.MdiActiveDocument
@db = use_this_db #if the caller didn't supply a database, use the active doc database @db ||= doc.Database @tr = @db.TransactionManager.StartTransaction end
#transaction helper that executes a code block within the context of a managed transaction #arguements to yield provide access to the the current Transaction and the current database #along with any requested tables (see below). To access these objects in the block, corresponding arguments #must be passed with the block. See Example below #trans takes an array argument of table names, eg [:Block, :Layer, :Linetype]. trans will #create an OpenStruct call @tables that will contain a reference to the #{table}Table so #that, for example, the LayerTable can be accessed via tables.Layer[layer_name] def trans(table_names=[], commit_after_block=true) begin tables_hash = {} #add ModelSpace and PaperSpace to the tables_hash @space_hash.each_pair do |key,value| if table_names.include?(key) tables_hash[key] = get_space_record key table_names.delete key end end
if table_names.size > 0 table_names.each do |key| table_id = get_db_property "#{key.to_s}TableId" tables_hash[key] = get_obj table_id end end @tables = OpenStruct.new(tables_hash)
yield @tr, @db, @tables
@tr.Commit if commit_after_block rescue Exception => e @tr.Dispose puts_ex e
end end
def get_obj(object_id, mode=:Read) @tr.get_object(object_id, @open_modes[mode]) end
def dispose @tr.Dispose end
private
def get_space_record(space, mode=:Read) bt = @tr.GetObject(@db.BlockTableId, @open_modes[mode]) @tr.GetObject(bt[@space_hash[space]], @open_modes[mode]) end
def get_db_property(property_name) ptype = @db.GetType prop = ptype.GetProperty(property_name) prop.GetValue(@db, nil) end
end
#Selection set creation helpers #filter is array of arrays of form [ [dxfCode, Value], [dxfCode, Value] ] #example usage [ [0,"Circle"], [8, "Layer1"], [62, 5] ] would select only circles of color 5 on Layer1 #Dxfcodes can be found in the AutoCAD Developer Help. #Common ones are 0-Entity Type(as string), 2-Block Name(as string), 8-LayerName(as String) #62-Color(0-256 as integer), 67-SpaceIndicator(0 or omitted=Modelspace, 1=Paperspace). #Also support -4 codes and associated operators. #Returns an object of type <em>Autodesk.AutoCAD.EditorInput.SelectionSet</em> def select_on_screen( filter_data=[]) begin filter = build_selection_filter filter_data ssPrompt = ed.GetSelection( Aei::SelectionFilter.new(filter)) if ssPrompt.Status == Aei::PromptStatus.OK return ssPrompt.Value else #should we return nil or raise an exception here? nil end rescue Exception => e puts_ex e end
end
#helper method used by select* methods #see entry for select_on_screen for more information def build_selection_filter( filter_data = []) begin #build the appropriate filter array typed_value = Ads::TypedValue[] filter = System::Array.of( typed_value).new(filter_data.size)
if filter_data.size > 0 filter_data.each_with_index{ |elem,i| filter.set_value Ads::TypedValue.new( elem[0], elem[1]), i } end filter rescue Exception => e puts_ex e end
end
All of these helpers are avaiable in the AcadHelper.rb file, available in the source listing