Optimize plugin code
-
Howdy all;
I'm developing a plugin to get Sketchup to work with DAYSIM (http://www.daysim.com). For those not familiar with DAYSIM, it's a daylight analysis program. Basically, you send it some geometry in RADIANCE format along with a simple file describing the locations of the points within that geometry you want analyzed, and it calculates a number of daylighting metrics at each of the points. My plug-in is meant to create the necessary input files for DAYSIM, and then import the results back in to Sketchup for interpretation.
Right now, I'm specifically concerned with the import of results. My problem is this: the import just takes a really, really long time, despite being relatively simple computationally. It's not too bad on models in which there isn't already a lot of geometry, but if there is the import can can take a very long time. I'll post the code for import class below if anyone would like to specifically look at what I've done, but I'd also love to hear any tips or general principles anyone has with respect to making plugins run faster. The only one I've heard is that it's good to use the built-in Ruby functions as much as possible.
Any help would be greatly appreciated!
Thanks,
Josh
require "su2dslib/exportbase.rb" ## this class imports and displays DAYSIM analysis results ## new for su2ds class ResultsGrid < ExportBase def initialize @lines = [] ## array of DAYSIM results read from Daysim output file @xLines = [] ## @lines array sorted in order of ascending x coordinates @yLines = [] ## @lines array sorted in order of ascending y coordinates @spacing = 0 ## analysis grid spacing @minV = 0 ## minimum value of results @maxV = 0 ## maximum value of results @layerName = getLayerName('results') ## get unique layer name for results layer Sketchup.active_model.layers.add(@layerName) @entities = Sketchup.active_model.entities @resultsGroup = @entities.add_group @resultsGroup.layer = @layerName $nameContext = [] ## added for ExportBase.uimessage to work $log = [] ## no log implemented at this point; again, justed added for ExportBase.uimessage end ## read and pre-process DAYSIM results def readResults # get file path path = UI.openpanel("select results file",'','') if not path uimessage("import cancelled") return false end # read file f = File.new(path) @lines = f.readlines f.close cleanLines processLines return true end ## convert numbers to floats, remove comments lines and such def cleanLines newlines = [] @lines.each { |l| # skip comment lines if l.strip[0,1] == "#" next end parts = l.split begin parts.collect! { |p| p.to_f} newlines.push(parts) rescue uimessage("line ignored; '#{l}'") end } @lines = newlines end ## calculate @xLines, @yLines, @spacing, @minV, @maxV def processLines # create @xLines and @yLines @xLines = @lines.sort @yLines = @lines.sort { |x,y| [x[1], x[0], x[2], x[3]] <=> [y[1], y[0], y[2], y[3]] } x = [] v = [] # calculate @minV and @maxV @lines.collect { |l| v.push(l[3]) } v.sort! @minV = v[0] @maxV = v[v.length - 1] # calculate spacing @xLines.each_index { |i| if @xLines[i][0] != @xLines[i+1][0] if i == (@xLines.length - 1) uimessage("improperly formatted grid; grid spacing could not be calculated") break end @spacing = @xLines[i+1][0] - @xLines[i][0] break else next end } end ## draw coloured grid representing results def drawGrid # create "north-south" grid lines @xLines.each_index { |i| if i == (@xLines.length - 1) next end # check if next point on same "north-south" line if @xLines[i][0] == @xLines[i+1][0] # check if next point spaced at @spacing if ((@xLines[i][1] + @spacing) * 1000).round == (@xLines[i+1][1] * 1000).round # check if z-coordinate equal if @xLines[i][2] == @xLines[i+1][2] # create geometry createEdge(@xLines[i], @xLines[i+1]) end end end } # create "east-west" grid lines @yLines.each_index { |i| if i == (@yLines.length - 1) next end # check if next point on same "east-west" line if @yLines[i][1] == @yLines[i+1][1] # check if next point spaced at @spacing if ((@yLines[i][0] + @spacing) * 1000).round == (@yLines[i+1][0] * 1000).round # check if z-coordinate equal if @yLines[i][2] == @yLines[i+1][2] # create geometry createEdge(@yLines[i], @yLines[i+1]) end end end } puts ## hack -- stops @yLines from being output to Ruby console end ## create Sketchup;;Edge object between two points, create any possible faces and ## colour faces appropriately def createEdge(pt1, pt2) # convert coordinates to Sketchup units ptc = [pt1[0..2], pt2[0..2]].each { |p| p.collect! { |e| e/$UNIT}} # create edge edges = @resultsGroup.entities.add_edges(ptc[0], ptc[1]) # set edge characteristics, and draw faces edges.each { |e| e.layer = @layerName e.hidden = true value = (pt1[3] + pt2[3]) / 2 e.set_attribute("values", "value", value) # draw faces if e.find_faces > 0 faces = e.faces faces.each { |f| processFace(f) } end } end ## process Faces (ie, set characteristics) def processFace(f) if f.edges.length > 4 f.erase! else f.layer = @layerName val = 0 f.edges.each { |e| val += e.get_attribute("values", "value") / 4 } setColor(f, val) end end ## set Face color def setColor(f, val) colorVal = (val - @minV) * 255 / (@maxV - @minV) faceCol = Sketchup;;Color.new faceCol.red = 127 faceCol.blue = 127 faceCol.green = colorVal f.material = faceCol f.back_material = f.material end end # class
-
I've not looked at your code yes - I'll do so later.
But just wanted to post some general links first from the Developer section at this forum:Optimization Tips
http://forums.sketchucation.com/viewtopic.php?f=180&t=25305
Key info here: creating variables are expensive. But do use variables to void recalculation within a loop.In regard to "simple computationally" - in Ruby every single - even simple - operation adds significant time. http://forums.sketchucation.com/viewtopic.php?f=180&t=27307
-
Hi thomthom;
Thanks a lot for the response! I seem to have solved my problem quite simply: by adding
Sketchup.active_model.start_operation("task",true)
to my code before calling my import method.Thanks again for the links -- very useful!
Josh
-
Ah, yes. Forgot to mention that. Something I took for granted. SU is very slow when adding geometry. It slows down more as when there are more existing geometry in the model space you add to.
start_operation
is one way to speed things up, when you use the argument that disables the UI. And it is also worth doing as much as you can in bulks. -
Ah, you did mention it -- I found that in the links you provided.
What exactly do you mean by "doing as much as you can in bulks?"
-
For instance: iterating through a collection of entities and deleting some:
Example - deleting faces.
This is slow
entities.each { |e| e.erase! if e.is_a?(Sketchup::Face) }
This is faster:
faces = [] entities.each { |e| faces << e if e.is_a?(Sketchup::Face) } entities.erase_entities(faces)
Same goes for selection/deselecting.
Anything where SU offers to do either per item or as an array of items - try to pass array of items.
Advertisement