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")
			ents.add_edges(pt1,pt3)
			vec3 = vec1.clone
			vec3.length = width
			1.upto(spaces) do |i|
				pt0=pt1.clone
				pt1.transform!(vec3)
				pt3.transform!(vec3)
				ents.add_edges(pt3,pt1,pt0)
			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.

Thanks in advance!
Nick

See here for an example.

Your view parameter should be the offset instead.

Try something like this:

offset = [12, 0, 0]
dim = Sketchup.active_model.entities.add_dimension_linear(pt1, pt2, offset)
2 Likes

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
  dim  = ents.add_dimension_linear(dim_start, dim_end, offset)
# 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[0] 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. :wink:

# 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  = ents.add_dimension_linear(dim_start, dim_end, offset)
    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