Best way to rotate/stretch a face with API?

I’m a complete novice with Ruby and the Sketchup API, and I’m looking for a sensible way to extend the end of an I-beam, but chopped at an angle. e.g., from red to green:

I could use Transformation.rotation() on the end face, but that wouldn’t extend the top edge of the green beam out along the same line as the red.

I have a horrible solution (that works) using a Transformation.translation() along the line… of 5 separate edges :grimacing: and calculating 6 different distances. There must be a better way?!

ridgey = height*Math.tan(angle)*Math.cos(angle)
ridgez = height*Math.tan(angle)*Math.sin(angle)
tr = Geom::Transformation.translation(Geom::Vector3d.new(0,ridgey.mm,ridgez.mm))
entities.transform_entities(tr, entities[20])
ridgey = (height-45)*Math.tan(angle)*Math.cos(angle)
ridgez = (height-45)*Math.tan(angle)*Math.sin(angle)
tr = Geom::Transformation.translation(Geom::Vector3d.new(0,ridgey.mm,ridgez.mm))
entities.transform_entities(tr, entities[17])
entities.transform_entities(tr, entities[21])
ridgey = 45*Math.tan(angle)*Math.cos(angle)
ridgez = 45*Math.tan(angle)*Math.sin(angle)
tr = Geom::Transformation.translation(Geom::Vector3d.new(0,ridgey.mm,ridgez.mm))
entities.transform_entities(tr, entities[15])
entities.transform_entities(tr, entities[23])

(I’m using SketchUp 2013, if that makes a difference)

Many thanks

I’d make a plane for the new end face (see overview of Geom module.)

Then use each edge object’s #line() method to get an array of lines to intersect with the plane.

See Geom::intersect_line_plane module method.

Something like (untested):

# assume var "plane" was set using a point
#  on the plane and it's normal vector,
# and var "edges" is an array of the horizontal edge objects

lines = edges.map {|e| e.line }
new_points = lines.map {|line| Geom.intersect_line_plane(line, plane) }

vecs = []
verts = []

new_points.each_with_index {|pt,i|
  verts << edges[i].end
  vecs << edges[i].end.position.vector_to(pt)
}

ents = edges[0].parent
ents.transform_by_vectors( verts, vecs )

Ref related topic in this category:

1 Like

Oh, and BTW, your can get a valid plane for the starting face via:
Sketchup::Face#plane()

… and then create a rotational transform (about the lower edge,) and apply it to the two members (point and vector) of the plane array, to get the new face’s plane.

Many thanks, but I’ve been struggling to understand that explanation! Sorry, still very much a novice, and I haven’t used planes yet.

I probably should have included a few more lines of code, to show that my code created that object in the first place with just a face + pushpull, so hopefully it should be easy to grab that new end ‘face’ and manipulate it?

This is the code I’m now using:

# create 3D object from array of Geom::Point3d, origin (x,y,z)
face1 = entities.add_face(points)
face1.pushpull(-length_pushpull.mm)

# create face at far end of 3D object
points2 = points.map { |a| Geom::Point3d.new(a.x, a.y+length_pushpull.mm, a.z) }
face2 = entities.add_face(points2)

# scale new face on z-axis only, then rotate
zscale = 1/Math.cos(angle)
tr1 = Geom::Transformation.scaling(Geom::Point3d.new(x.mm, y+length_pushpull.mm, z.mm), 1,1,zscale)
tr2 = Geom::Transformation.rotation(Geom::Point3d.new(x.mm, y+length_pushpull.mm, z.mm), X_AXIS, -angle)
entities.transform_entities(tr2 * tr1, face2)

which I’m MUCH happier with :smiley:

Is there a better way to get a face at the far end of the pushpull, rather than manually copy of the array? I thought I could use something like face2 = face1.pushpull(distance, true) but that doesn’t return the face.

I would only do a pushpull inside a group with the face to be pp’d. (Later explode the group if necessary.)

You know the vector direction of the pp operation, so the end face will have a normal vector pointing the same direction.

endface = group.entities.grep(Sketchup::Face).find {|face| face.normal == direction }
1 Like

Many thanks, that works beautifully :slight_smile:

1 Like

Just be aware Enumerable.find will return nil if no match, so always check result for “nilness” and bail if it is.

return false if endface.nil?

It prevents NoMethodError like:
Error #<NoMethodError: undefined method 'transform!' for NilClass>, … etc.

Ta. I can see that is good practice in a general case, but surely if it can’t find a face I created on only the previous line, then something far more serious has gone wrong?!