sketchucation logo sketchucation
    • Login
    ℹ️ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    Optimize plugin code

    Scheduled Pinned Locked Moved Plugins
    6 Posts 2 Posters 1.3k Views 2 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J Offline
      jeemang
      last edited by

      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
      
      
      1 Reply Last reply Reply Quote 0
      • thomthomT Offline
        thomthom
        last edited by

        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

        Thomas Thomassen — SketchUp Monkey & Coding addict
        List of my plugins and link to the CookieWare fund

        1 Reply Last reply Reply Quote 0
        • J Offline
          jeemang
          last edited by

          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

          1 Reply Last reply Reply Quote 0
          • thomthomT Offline
            thomthom
            last edited by

            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.

            Thomas Thomassen — SketchUp Monkey & Coding addict
            List of my plugins and link to the CookieWare fund

            1 Reply Last reply Reply Quote 0
            • J Offline
              jeemang
              last edited by

              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?"

              1 Reply Last reply Reply Quote 0
              • thomthomT Offline
                thomthom
                last edited by

                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.

                Thomas Thomassen — SketchUp Monkey & Coding addict
                List of my plugins and link to the CookieWare fund

                1 Reply Last reply Reply Quote 0
                • 1 / 1
                • First post
                  Last post
                Buy SketchPlus
                Buy SUbD
                Buy WrapR
                Buy eBook
                Buy Modelur
                Buy Vertex Tools
                Buy SketchCuisine
                Buy FormFonts

                Advertisement