# Combining Line Tool & Dimension Tool Question!

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.

MY OBJECTIVE:
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
if vec2.length>0
vec2.length = @depth
width = @width
if @angle != 90
trans = Geom::Transformation.rotation(pt1, Geom::Vector3d.new(0,0,1), (@angle-90).degrees)
vec2.transform!(trans)
vec2.length = @depth + ((Math::sin((90-@angle).degrees)/Math::sin(@angle.degrees))*@width)
width = @width/(Math::sin(@angle.degrees))
end
spaces = (dist/width).to_i
if @@snapwall_width_type=="Minimum"
width = dist/spaces.to_f if width > 0
end
pt3 = pt1.transform(vec2)
view.model.start_operation("Snapwall lines")
vec3 = vec1.clone
vec3.length = width
1.upto(spaces) do |i|
pt0=pt1.clone
pt1.transform!(vec3)
pt3.transform!(vec3)
end
view.model.commit_operation
end
end
``````

And here is a visual, showing what the tool can currently do vs. what I would like to achieve:

I’m struggling to understand how to integrate/invoke the Dimension class (?) to achieve this, if someone might be able to get me started. I was thinking I’d have to begin with something like:

``````dim = Sketchup.active_model.entities.add_dimension_linear([pt1, pt2, view)
``````

…But I wasn’t having success with that.

Nick

See here for an example.

Try something like this:

``````offset = [12, 0, 0]
``````
1 Like

Except that the offset vector is usually perpendicular to the reference and dimension lines.

The OP’s lines are near horizontal, so `[0,12,0]` might be more appropriate.

For exactness …

``````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)
)
offset.length= 12 # or whatever distance is desired

# Begin undo operation
ents = Sketchup.active_model.active_entities
# End undo operation
``````
2 Likes

Thanks Dan! (and Neil!)

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!

Nick

Yes. edge is an edge object reference.
`edge.start` returns a reference to the starting vertex object of it’s edge.
`vertex.position` returns a reference to the `Geom::Point3d` for this vertex.

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

etc.

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
end
``````

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)
)
offset.length= distance
# Begin undo operation unless this call is from within one.
ents = Sketchup.active_model.active_entities
dim.text= text unless text.empty?
# End undo operation
return dim
end
``````
``````# 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
dim.offset= vec
return dim
end
``````