Hello all! I’m new here, and a bit of a novice when it comes to programming (Ruby especially), but I’ve been trying to it and the SketchUp API to streamline my use of the program, mostly by building some custom tools.
Right now I’m trying to combine a (slightly modified) Line Tool and Dimensions tool, so that if I were to draw a series of edges (let’s say 16, 8 and 12 feet in length) they would be labeled with dimensions of 16’, 8’, and 12’ automatically upon placement.
Below is the meat of my modified Line Tool which successfully creates the edge line that I need:
def create_geometry(pt1, pt2, view)
ents = view.model.active_entities
dist = pt1.distance(pt2)
vec1 = pt2-pt1
vec2 = Geom::Vector3d.new(vec1.y,-vec1.x,0.0)
vec2.reverse! if @@reverse_direction
vec2.length = @depth
width = @width
if @angle != 90
trans = Geom::Transformation.rotation(pt1, Geom::Vector3d.new(0,0,1), (@angle-90).degrees)
vec2.length = @depth + ((Math::sin((90-@angle).degrees)/Math::sin(@angle.degrees))*@width)
width = @width/(Math::sin(@angle.degrees))
spaces = (dist/width).to_i
width = dist/spaces.to_f if width > 0
pt3 = pt1.transform(vec2)
vec3 = vec1.clone
vec3.length = width
1.upto(spaces) do |i|
And here is a visual, showing what the tool can currently do vs. what I would like to achieve:
This definitely helped get me on the right track and I’m starting to make sense of things. I’m now trying to decipher how to use edge.start.position and edge.end.position, since I can almost achieve my desired result by simply using pt1 and pt2:
dim_start = pt1
dim_end = pt2
The portion of the tool that draws the edge lines automatically snaps (or rounds down) to the nearest 2 foot increment based on your second click and spits out those little tick marks at each 2’ increment along the entire length. It does so by using vec1 (the difference between pt1 and pt2) and then chopping it up by however many times width (which is assigned a value of 2.feet) can fit into it… so unfortunately, that means pt2 can’t help me here because if I click, say, 11 feet away from pt1… the Dimension line will wind up being 11’ (while my actual edge line with the tick marks snaps down to 10’)
It seems like there has to be a more elegant solution here than running the dimension through the same calculations as the line tool; some way to reference the end point that is created at 10’ (in spite of my click that might be anywhere between 10’ and 12’ away) ?
So! I think that’s where I think your suggestion on using the edge start/end positions comes in? But I’m not quite understanding how to make sure the appropriate reference is stored into edge so that I can use it in the way you suggested.
I feel like I should be doing something like this, so that I have a reference to the entity to work with?
edge = ents.add_edges(pt1,pt3)
… But I get an error when trying to invoke the start method on edge if that’s how I’ve defined it:
Error: #<NoMethodError: undefined method `start' for #<Array:0x000001ee1b9d1b28>>
Any idea what I am missing here? I really appreciate the help so far!
I just did a call chain rather than assign the vertex to a reference and then call it’s #position method, since we are not interested in the vertex itself other than it’s 3D location.
Yes. But you need to defer the drawing of the 2 foot hash edges, until after the dimension is created, otherwise the small edges will divide up the longer edges into a bunch of 2 foot increments.
I suggest using pseudo code to map out the general tasks, which may be along the lines of how to divide up the creation code into submethods.
On pt3 draw edge
Pass edge to create_linear_dim()
Pass edge to create_hash_marks()
… other stuff …
Set pt1 to the end of edge
Reset tool to state 1 asking user for second point
Well this is because the the #add_edges (plural) method returns an array of edges.
So you can iterate that array or do edge = edges and you have a new reference to the first Sketchup::Edge that is referenced in the returned array.
To make 1 edge object at a time, you can use the (misnamed) Entities#add_line method. ie:
edge = ents.add_line(pt1,pt3)
Now you could probably also do …
edge_array = ents.add_edges(pt1,pt3)
# If edge creation failed, return value might be nil
# (normal for Ruby) of we might get an empty array object.
if edge_array && !edge_array.empty?
edge = edge_array.first
# do more stuff with edge as it's tested valid
The lesson here is that your code should always test the result of the Entities factory geometry creation methods.
In Ruby, a “factory” method is one that creates new objects in a very controlled way because the new objects need to belong to something else, most commonly a collection object (which probably belongs to some other object, in our case the model object.) In these situations the object’s class has it’s public constructor method undefined or made private. Ie, you cannot call the ::new constructor on the object’s class.
Here are a couple of nifty methods you can use in your tool. It is probably not very readable, nor maintainable to cram all of the creation code into the one create_geometry method.
You can define and call other local methods from that method. This makes it much more readable especially when you must do the same task repeatedly (like creating the dimensions.)
Note that I have not added any type checking for the arguments in these methods.
But since they’ll be internal and only called by your code, then we’ll assume your code passes the correct kind of objects.
# Add a linear dimension to an edge.
# @param edge [Sketchup::Edge]
# @param distance [Numeric] the offset distance.
# @param text [String] Optionally override the dimension text.
# @return [Sketchup::DimensionLinear] The new dim object.
def create_linear_dim(edge, distance, text = '')
dim_start = edge.start.position
dim_end = edge.end.position
vec = dim_start.vector_to(dim_end)
offset = vec.transform(
Geom::Transformation::rotation(dim_start, Z_AXIS, 90.degrees)
# Begin undo operation unless this call is from within one.
ents = Sketchup.active_model.active_entities
dim = ents.add_dimension_linear(dim_start, dim_end, offset)
dim.text= text unless text.empty?
# End undo operation
# Swap a dimension to the other side of what it is drawn on.
# @param dim [Sketchup::DimensionLinear] A dimension object.
# @param length [Numeric] Optionally change the offset distance.
# Negation of values ignored. (The absolute value will be applied
# to the offset vector if the length argument does not equal 0.)
# @return [Sketchup::DimensionLinear] The dim object (for call chaining.)
def reverse_linear_dim(dim, length = 0)
vec = dim.offset.reverse
vec.length= length.abs if length != 0