Skip to content

Selection Set Helper

davidbl edited this page Sep 13, 2010 · 1 revision

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:

  1. Get the document
  2. Get the database
  3. Start a trans
  4. Build the selection set prompt, filter and options
  5. Select the entities
  6. Get the return value of the prompt object
  7. Iterate over the selection set
    1. Get the entity object for each element in the set
    2. Do something with that entity (change the color, for example)
  8. 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