I am learning to write code for SketchUp and over a series of days to successfully script a roller coaster importer from a program called No Limits 2. My code does a bit too much to understand and keep straight at the moment and I want to begin to modularize the whole thing in to multiple scripts. Right now the code parses data, draws rails, and creates components placed along a spline path, all while maintaining the banking information attached to the imported spline.
I have been working to modularize my code but can’t seem to get anything to work once the scripts are separated. I have tried using Visual Studio Code and the Ruby Code Editor extension. I haven’t found any resources teaching how to properly set up a directory for extension development, where that directory needs to be, if it needs to be signed, or how to get a script to call modules/tools in other scripts that are in the same directory.
Right now I would like to break my current code in to a main.rb, ui.rb, data_parser.rb, tuber_generator.rb, tie_generate_placer.rb. My future plans include an XML importer for importing structural support data, a square track spine generator, a clean up function, a tagging module, a wireframe module, and more.
Try my code out! It’s exciting to see a roller coaster in SketchUp! Please let me know what insights you have, advice you have for resources, or if anybody feels interested in one-one-one tutoring. I am happy to pay, though I can’t afford much, just really excited to be learning!
require 'csv'
require 'sketchup'
def import_and_tube_track
model = Sketchup.active_model
definitions = model.definitions
file_path = UI.openpanel("Select CSV File", "", "CSV Files|*.csv;||")
return unless file_path
prompts = ["Distance between rails (m)", "Spine vertical offset (m)", "Diameter of rails (m)", "Diameter of spine (m)", "Amount of segments per tube", "Interval for drawing (every n points)"]
defaults = ["1.2", "-0.4", "0.158", "0.513", "8", "2"]
input = UI.inputbox(prompts, defaults, "Enter Tube Specifications and Drawing Interval")
return unless input
total_rail_distance, spine_vertical_offset, rail_diameter, spine_diameter, segments, every_n_points = input.map(&:to_f)
rail_offset = (total_rail_distance / 2) * 39.3701
spine_vertical_offset = spine_vertical_offset * 39.3701
rail_diameter = rail_diameter * 39.3701 # Convert meters to inches
spine_diameter = spine_diameter * 39.3701 # Convert meters to inches
spine_points = []
left_rail_points = []
right_rail_points = []
csv_options = { headers: true, col_sep: "\t", quote_char: '"', skip_blanks: true, liberal_parsing: true }
CSV.foreach(file_path, **csv_options).with_index do |row, i|
next if i % every_n_points != 0
x, y, z = row['PosX'].to_f * 39.3701, row['PosZ'].to_f * 39.3701, row['PosY'].to_f * 39.3701
center_point = Geom::Point3d.new(x, y, z)
up_vector = Geom::Vector3d.new(row['UpX'].to_f, row['UpZ'].to_f, row['UpY'].to_f)
up_vector.length = spine_vertical_offset
spine_points << center_point.offset(up_vector)
left_vector = Geom::Vector3d.new(row['LeftX'].to_f, row['LeftZ'].to_f, row['LeftY'].to_f)
left_vector.length = rail_offset
right_vector = left_vector.reverse
right_vector.length = rail_offset
left_rail_points << center_point.offset(left_vector)
right_rail_points << center_point.offset(right_vector)
end
model.start_operation('Create Rails, Spine, and Track Ties', true)
rails_and_spine_group = model.active_entities.add_group
create_tubes(rails_and_spine_group, spine_points, spine_diameter, segments)
create_tubes(rails_and_spine_group, left_rail_points, rail_diameter, segments)
create_tubes(rails_and_spine_group, right_rail_points, rail_diameter, segments)
track_ties_group = create_and_place_track_ties(file_path, total_rail_distance, spine_vertical_offset)
main_group = model.active_entities.add_group([rails_and_spine_group, track_ties_group])
# Mirror transformation across the green axis at the origin
mirror_transformation = Geom::Transformation.scaling(1, -1, 1)
main_group.transform!(mirror_transformation)
model.commit_operation
end
def create_tubes(group, points, diameter, segments)
return if points.length < 2
spline = group.entities.add_curve(points)
direction_vector = points[1] - points[0]
circle = group.entities.add_circle(points.first, direction_vector, diameter / 2, segments)
face = group.entities.add_face(circle)
face.followme(spline)
end
def create_and_place_track_ties(file_path, total_rail_distance, spine_vertical_offset)
model = Sketchup.active_model
definitions = model.definitions
track_tie_def = definitions['Triangular Track Tie'] || definitions.add("Triangular Track Tie")
entities = track_tie_def.entities
entities.clear!
half_rail_distance = (total_rail_distance / 2) * 39.3701
spine_offset = spine_vertical_offset # Use the converted spine_vertical_offset directly
# Define the triangular track tie geometry
pts = [
[-half_rail_distance, 0, 0], # Left rail end
[half_rail_distance, 0, 0], # Right rail end
[0, spine_offset, 0] # Center of the spine
]
base_face = entities.add_face(pts)
thickness = 8.cm # Convert cm to inches within SketchUp API
base_face.pushpull(-thickness / 2)
base_face.pushpull(thickness / 2)
track_ties_group = model.active_entities.add_group
# Read CSV and place track ties
csv_options = { headers: true, col_sep: "\t", quote_char: '"', skip_blanks: true, liberal_parsing: true }
CSV.foreach(file_path, **csv_options) do |row|
x = row['PosX'].to_f * 39.3701 # Convert meters to inches
y = row['PosZ'].to_f * 39.3701 # Convert meters to inches
z = row['PosY'].to_f * 39.3701 # Convert meters to inches
position = Geom::Point3d.new(x, y, z)
up_vector = Geom::Vector3d.new(row['UpX'].to_f, row['UpZ'].to_f, row['UpY'].to_f).normalize
left_vector = Geom::Vector3d.new(row['LeftX'].to_f, row['LeftZ'].to_f, row['LeftY'].to_f).normalize
transformation = Geom::Transformation.new(position, left_vector, up_vector)
instance = track_ties_group.entities.add_instance(track_tie_def, transformation)
end
track_ties_group
end
# Trigger the function to start the process
import_and_tube_track
Kumba.skp (2.0 MB)
kumba.csv.zip (85.2 KB)