My first ruby plugin to generate an Archimedean spiral is executing too slowly

I have been working on a plugin to create an Archimedean spiral for a few days. Ruby is not my cup of tea but I have been programming all kinds of development tools since mid 80’s and I’m trying to adapt. My first ruby plugin works but is it way too slow. I have created this tool with concentric circles 0.1mm in radius apart from each other and I connect these circles together with an edge from one circle to the next spiraling one edge further on each circle. Maybe it is better to see what I am trying to do with the script itself, see below.
To enlarge the pitch of the spiral I tried to raise the number of edges per circle but that really makes it sluggish. What can I do to speed up execution time?

require 'sketchup.rb'

module Spiral
    module DrawSpiral
    def self.create_spiral
        model = Sketchup.active_model
        model.start_operation('create spiral', true)
		# I failed to set ruby dimensions to engineering units (mm)
		# to be investigated later...
		# using ".mm" after each length value as a workaround
		#model.options["UnitsOptions"]["LengthFormat"]=4 # engineering units (mm)
		center_point = Geom::Point3d.new(0,0,0)
		normal_vector = Geom::Vector3d.new(0,0,1.mm)
		entities = Sketchup.active_model.entities
		radius = 5.0.mm # 0.8.mm # starting radius
		edges_per_revolution = 800   # 220 smallest at 0.8.mm radius
		wall_thickness = 16.0.mm
		inner_pt1 = Geom::Point3d.new(0,0,0)
		inner_pt2 = Geom::Point3d.new(0,0,0)
		outer_pt1 = Geom::Point3d.new(0,0,0)
		outer_pt2 = Geom::Point3d.new(0,0,0)
		counter = 0
		prev_inner_array = nil
		prev_outer_array = nil
		while radius <= 100.01.mm # +0.01 to avoid floating point inaccuracy
			inner_edges = entities.add_circle center_point, normal_vector, radius, edges_per_revolution
			outer_edges = entities.add_circle center_point, normal_vector, radius+wall_thickness, edges_per_revolution
			first_inner_edge = inner_edges[0]
			first_outer_edge = outer_edges[0]
			arccurve = first_inner_edge.curve
			arccurve = first_outer_edge.curve
			inner_pt2 = inner_edges[counter].start.position
			outer_pt2 = outer_edges[counter].start.position
			counter = counter - 1
			if  counter < 0
				counter = edges_per_revolution-1
			end
            entities.add_line(inner_pt1, inner_pt2)
            entities.add_line(outer_pt1, outer_pt2)
            inner_pt1 = Geom::Point3d.new(inner_pt2.x,inner_pt2.y,inner_pt2.z)
			outer_pt1 = Geom::Point3d.new(outer_pt2.x,outer_pt2.y,outer_pt2.z)
			entities.add_face(outer_edges)
			# delete all circles except the outer one
			if  prev_inner_array != nil
			    entities.erase_entities(prev_inner_array)
			end
			if  prev_outer_array != nil
			    entities.erase_entities(prev_outer_array)
			end
			prev_inner_array = inner_edges
			prev_outer_array = outer_edges
			radius = radius + 0.1.mm
		end # while
        model.commit_operation
    end # def

    unless file_loaded?(__FILE__)
        menu = UI.menu('Plugins')
        menu.add_item('Create spiral') {
            self.create_spiral
        }
        file_loaded(__FILE__)
    end # unless

  end # module DrawSpiral
end # module Spiral

maybe use larger values (e.g. 1m) and fewer segments (e.g. 360, or 90) then use the SU scale tool to reduce the output to the size you want?

really?
When I manually draw a circle with that many segments, it shows on the screen within a blink of the eye. Drawing 110 concentric circles (and deleting these afterwards) takes more than half an hour.
Or is drawing on a larger scale faster for another reason?

Try this code

# Setting the parameters of the Archimedean spiral
a = 1 # Distance between revolutions
b = 0.1 # Angular step

num_points = 100
theta = 0

# Create an array to store spiral points
points = []

# Calculate the coordinates of the spiral points
num_points.times do
  r = a + b * theta
  x = r * Math.cos(theta)
  y = r * Math.sin(theta)
  z = 0 # We are building a spiral in the XY plane
  points << [x, y, z]
  
  theta += b
end

# We build lines between the points of the spiral
Sketchup.active_model.entities.add_curve(points)
1 Like

Nobody is going to learn anything if you just get chatGPT to do it though.

fewer vectors - faster speed. larger initial view then scale down - perception of the resolution and not challenging the SU limits. just making a suggestion to try it out since you have variables… but you do you.

thank you bobecka,

That is extremely fast code. I have changed it a bit because I needed two spirals with 4 mm in between. Also the variable ‘a’ is not the distance between revolutions but the distance from the center to the start of the spiral. I have not figured out how to set the distance between revolutions yet.

require 'sketchup.rb'

module Spiral
    module DrawSpiral
    def self.create_spiral
        model = Sketchup.active_model
        model.start_operation('create spiral', true)

		# Setting the parameters of the Archimedean spiral
		a1 = 10.mm # Starting point from the center
		a2 = 14.mm # Starting point from the center
		b = 0.1 # Angular step

		num_points = 2000
		theta = 0

		# Create an array to store spiral points
		points1 = []
		points2 = []

		# Calculate the coordinates of the spiral points
		num_points.times do
  			r1 = a1 + b * theta
  			r2 = a2 + b * theta
  			x1 = r1 * Math.cos(theta)
  			y1 = r1 * Math.sin(theta)
  			x2 = r2 * Math.cos(theta)
  			y2 = r2 * Math.sin(theta)
  			z = 0 # We are building a spiral in the XY plane
  			points1 << [x1, y1, z]
  			points2 << [x2, y2, z]
	
  			theta += b
		end

		# We build lines between the points of the spiral
		model.entities.add_curve(points1)
		model.entities.add_curve(points2)
        model.commit_operation
    end # def

    unless file_loaded?(__FILE__)
        menu = UI.menu('Plugins')
        menu.add_item('Create spiral') {
            self.create_spiral
        }
        file_loaded(__FILE__)
    end # unless

  end # module DrawSpiral
end # module Spiral

You may wish to have a look at @jimhami42’s (Jim Hamilton’s) UV-Polygen:


By the way, each developer should choose a unique top-level namespace module to separate all their extensions from everyone else’s. The identiofier “Spiral” is too common a word for a unique namespace.


Are you really using SketchUp Make 8 on Windwos XP ?

1 Like

Thanks for the link. Even if I might use the code above, it is always nice to have a look at someone else’s work.

I will change that to “Spiral_HP24”, thanks.

I occasionally use SketchUp to create 3D print designs. I do not own a printer myself that’s why I installed the free version. It works without problems in a XP virtual machine without internet connection and I also have it installed on my Linux laptop in “PlayOnLinux”. The last one does not work stable.
I want to use the spiral to design and 3D print a scroll pump.

1 Like

Some thoughts and comments.

Internally, all lengths in SketchUp are stored in inches. The Length class stores values in inches as well.
A number of methods have been added to the Ruby Numeric class to do units conversions.
Using .mm after each length value is not a workaround, it is a right way… :wink:
__

The setting for the Length Format and Length Unit can be retrieved and set from/by the Sketchup::Model#options by querying/setting the “UnitsOptions” Sketchup::OptionsProvider for “LengthFormat” and “LengthUnit” respectively.
The value of 4 you used above is not valid, you need to use Constant as described in the begening of Length class docu.

BTW: Sketchup is defining the “engineering unit format” differently that you think. (They are engineers from USA… :wink: ) See in Model info>>Units:
image

__

SketchUp internal tolerance is 1/1000th of an inch (0.0254mm). It is used to see if one length is less than another length.
SketchUp does not designed to handle very short distances when creating/intersecting geometries.
For example when you create circle with 10mm diamater and 800 segments the edges will be ~0,03927mm, which is more or less comparable with the internal tolerance, that would create an issues…
I would suggest to do your geometry in meters then scale down it afterwards.


In your first code you are creating a bunch of geometries then used one of them for reference of point then deleted in a next step. Creating and erasing geometries is always slower than “pure math” calculation. I would prefer to use “pure math” against temporary geometries, like the other post in this topic…

2 Likes

I have already replaced my own code with the math version from bobecka.
I had a reason to use many segments in my own code. In the first revolution that is overkill but when spiraling outwards the edges grow bigger and I did not know at that moment how far to expand. Since I started using bobecka’s code that is no longer an issue.
Also I don’t need to have many revolutions in the spiral. I don’t need to compress air with the scroll pump, just “silent” displacement of air and 1.5 revolutions will just do that. My insights have changed since I started working on this project :slight_smile:

When I installed SketchUp I had to choose a template and the one I chose was “Product Design and Woodworking- Millimeters”, in my memory that was wrong. Engineers obviously work in feet or meters and I wanted smaller units than meters or feet.

I have no problems using both imperial and metric. I’ve spent much time in Britain and they told me that in British building supplies they sell PVC sewer pipes in dimensions of “six by four”, 6 meters long and 4 inches diameter. How about that :slight_smile:
I am pleased however that Sketchup doesn’t use quantities of 1/8, 1/16 and 1/32. I hope I didn’t wake up any developers now, to add that to the program.

Do I really have to add “.mm” to every length in Ruby, even when I have set the UnitOptions LengthFormat?

Yes. The UnitOptions LengthFormat is only how the SU user interface is displaying the units.

All lengths in SketchUp internally are handled in inches. This is the fact! You can not change that.
If you are define any constant length not in inches you have to convert it to inch. As described in a Numeric class.

1 Like

Certainly, there is an option to select that LengthFormat in the Model Info/Unit or via Ruby, if someone else, (but not you) wish :wink: .
(I’m not sure, how was it in SU verion 8… as far as I remember it should be there already)
Again, it is about how to display the units on the user interface.

1 Like

File: Extension Requirements — SketchUp Ruby API Documentation
Typically you can use the name of your company or your name, followed by the name of your extension.
e.g:

module HenniePeters
  module SpiralHP24
    # Code goes here...
  end
end
1 Like

@hennep

I wanted to check how an Sketchup::EntitiesBuilder would compare with the Entities#add_* methods. The code below has an ‘if’ condition which switches between the two.

The EntitiesBuilder is about twenty times faster on Windows 11, and ten times faster on an iMac. It is only available on SU 2022 and later, and the edges are not created as ‘curves’.

I rearranged the code and changed some variables to control/separate the creation parameters.

module Spiral
  module DrawSpiral
    def self.create_spiral
      model = Sketchup.active_model
      model.start_operation('create spiral', true)

      time = -Process.clock_gettime(Process::CLOCK_MONOTONIC)

      # Setting the parameters of the Archimedean spiral
      a1 = 10.mm # Starting point from the center
      a2 = 14.mm # Starting point from the center
      steps_per_revolution = 360
      radius_increase = 10.0.mm
      revolutions = 10

      # set values for creation
      b = 2.0 * Math::PI/steps_per_revolution
      radius_increment = radius_increase/steps_per_revolution.to_f
      num_points = revolutions * steps_per_revolution + 1
      theta = 0
      r1 = a1
      r2 = a2

      # Create an array to store spiral points
      points1 = []
      points2 = []

      # Calculate the coordinates of the  spiral points
      num_points.times do |i|
        cos = Math.cos(theta)
        sin = Math.sin(theta)
        x1 = r1 * cos
        y1 = r1 * sin
        x2 = r2 * cos
        y2 = r2 * sin
        z = 0 # We are building a spiral in the XY plane
        points1 << [x1, y1, z]
        points2 << [x2, y2, z]

        r1 += radius_increment
        r2 += radius_increment
        theta += b
      end

      # We build lines between the points of the spiral
if true
      model.entities.build { |builder|
        builder.add_edges(points1)
        builder.add_edges(points2)
      }
else
      model.entities.add_curve(points1)
      model.entities.add_curve(points2)
end
      model.commit_operation

      time += Process.clock_gettime Process::CLOCK_MONOTONIC
      puts format("%6.3f seconds", time)
    end # def
  end # module DrawSpiral
end # module Spiral

Spiral::DrawSpiral.create_spiral
Sketchup.active_model.active_view.zoom_extents

Thanks Greg,

I think that I have everything I need now. I had to comment out the timing. SketchUp 8.0 didn’t like: Process::CLOCK_MONOTONIC
I’m using an inputbox now, to be able to try different properties without the need to change the Ruby script everytime.

I didn’t like the way the plugin was shown in the installed list. It was shown with my name instead of the function. When you make more than one plugin, will you get your name in the list twice? I concatenated name and function as the identifier of the outer module.

plugins

require 'sketchup.rb'

module HenniePeters_Spiral_HP24
  module Spiral_HP24
    def self.create_spiral
      model = Sketchup.active_model
      model.start_operation('create spiral', true)

      #time = -Process.clock_gettime(Process::CLOCK_MONOTONIC)

      # Setting the parameters of the Archimedean spiral
      a1 = 10.mm # Starting point from the center of spiral 1
      a2 = 14.mm # Starting point from the center of spiral 2
      steps_per_revolution = 360
      radius_increase = 20.0.mm
      revolutions = 2

	  prompts = [ "Steps per revolution: ", "Radius increase: ", "Nr. of revolutions: ", "Starting point Spiral 1: ", "Starting point Spiral 2: " ]
	  defaults = [steps_per_revolution, radius_increase, revolutions, a1, a2]
	  results = inputbox prompts, defaults, "Enter parameters"
	  return if not results
	  steps_per_revolution = results[0]
	  radius_increase = results[1]
	  revolutions = results[2]
	  a1 = results[3]
	  a2 = results[4]

      # set values for creation
      b = 2.0 * Math::PI/steps_per_revolution
      radius_increment = radius_increase/steps_per_revolution.to_f
      num_points = revolutions * steps_per_revolution + 1
      theta = 0
      r1 = a1
      r2 = a2

      # Create an array to store spiral points
      points1 = []
      points2 = []

      # Calculate the coordinates of the  spiral points
      num_points.times do |i|
        cos = Math.cos(theta)
        sin = Math.sin(theta)
        x1 = r1 * cos
        y1 = r1 * sin
        x2 = r2 * cos
        y2 = r2 * sin
        z = 0 # We are building a spiral in the XY plane
        points1 << [x1, y1, z]
        points2 << [x2, y2, z]

        r1 += radius_increment
        r2 += radius_increment
        theta += b
      end

      # We build lines between the points of the spiral
      model.entities.add_curve(points1)
      model.entities.add_curve(points2)
      #model.entities.build { |builder|
      #  builder.add_edges(points1)
      #  builder.add_edges(points2)
      #}
      model.commit_operation

      #time += Process.clock_gettime Process::CLOCK_MONOTONIC
      #puts format("%6.3f seconds", time)
    end # def
    unless file_loaded?(__FILE__)
        menu = UI.menu('Plugins')
        menu.add_item('Create spiral') {
            self.create_spiral
        }
        file_loaded(__FILE__)
    end # unless
  end # module Spiral_HP24
end # module HenniePeters_Spiral_HP24

To name your extension and other properties, see SketchupExtension.

SketchUp 8 uses a very old version of Ruby, that’s the reason Process.clock_gettime doesn’t work with the constant. I was just using that code to compare the two ways to create the drawing elements. I tested it with SketchUp 2024…

I usually don’t add UI code until I’ve got the code working, sorry about that.

I wasn’t sure how you wanted to define the parameters for the curves, I changed them to what allowed me to easily verify that my code was working correctly…

One more thing, not really necessary but nice to have. Is there a way to retrieve the version nr. from SketchUp to execute the faster version with model.entities.builder when that’s available?

Sketchup.version_number class_method

Sketchup.version class_method

Checking SketchUp version in API - Developers / Ruby API - SketchUp Community

You wrote earlier that model.entities.builder is available since version 22.
Would this be sufficient?

if Sketchup.version.to_i < 22
    # old
else
    # new
end