Efficient Frame Creation


#1

What’s an efficient way to create a frame shape using the Ruby API? I don’t mean a door/window frame specifically just a basic frame that looks something like this.
image

I was wondering if anyone had any ideas on how to improve/simplify the code I’ve written as I see a number of minor issues.

Here’s what I got.

horz = X_AXIS.clone
horz.length = 10
vert = Z_AXIS.clone
vert.length = 10

outer_pts = []
outer_pts << ORIGIN
outer_pts << ORIGIN + horz
outer_pts << ORIGIN + horz + vert
outer_pts << ORIGIN + vert

frame_horz = horz.clone
frame_horz.length = 2
frame_vert = vert.clone
frame_vert.length = 2

inner_pts = []
inner_pts << ORIGIN + frame_horz + frame_vert
inner_pts << ORIGIN + horz - frame_horz + frame_vert
inner_pts << ORIGIN + horz - frame_horz + vert - frame_vert
inner_pts << ORIGIN + frame_horz + vert - frame_vert

ent = Sketchup.active_model.entities
ent.add_face(outer_pts)
ent.add_face(inner_pts).erase!

If I were to add this code into a function I’d also maybe need to add some sort of validation to ensure that both a negative and a positive frame lengths weren’t chosen to avoid intersecting the inner face with the outer face but also allow for cases in which 2 negative frame lengths were used. Once I added that my code would be fairly long for the simple task I’m trying to achieve. Maybe this highlights a larger problem in my methodology or perhaps I’m simply expecting too much from too little code.

Another idea I had was creating the face using edges and calling find_faces
This is purely curiosity by the way and is not a pressing issue.


#2

The code is relatively logical and it is normal that automated tasks are more code than the manual click action.

I would rather not use find_faces because you cannot control whether it finds only the desired faces and no more, especially if your edges are connected to existing geometry.

You should wrap your code into a method and turn all numbers (and ORIGIN) into method parameters.

# Draws a planar rectangular frame in xz-plane.
# @param origin [Sketchup::Point3d] the bottom left point of the frame
# @param width [Length] the horizontal outer length of the frame
# @param height [Length] the vertical outer length of the frame
# @param breadth [Length] the offset of the inner hole from the outer edges
def draw_frame(origin=ORIGIN, width=10, height=10, breadth=2)
  raise ArgumentError.new("The breadth of the frame must be negative") if breadth < 0
  raise ArgumentError.new("The width and height must be at least twice the breadth") if width < 2*breadth || height < 2*breadth
  # ...

Then you could think about what can be simplified. You see half of the method is repeated in a similar way. What do both cases have in common? They create points from the input parameters and draw rectangles.
You can refactor this into a more generic method draw_rectangle and call it with different origin / sizes to obtain two faces, then delete the inner face.

def draw_rectangle(entities, origin, width, height)
  horz = X_AXIS.clone
  horz.length = width
  vert = Z_AXIS.clone
  vert.length = height

  points = []
  points << origin
  points << origin + horz
  points << origin + horz + vert
  points << origin + vert

  return entities.add_face(points)


def draw_frame(entities, origin, width, height, breadth)
  # ...
  outer_face = draw_rectangle(entities, origin, width, height)
  inner_face = draw_rectangle(entities,
                              origin + Geom::Vector3d.new(breadth, 0, breadth), 
                              width - 2*breadth, 
                              height - 2*breadth)
  inner_face.erase!
  return outer_face

Then you could think about frames that are not in xz-plane. Would you pass a vector for horizontal direction (instead of x), or rather a normal vector? Do frames need to be vertical, or would you better pass x- and y-vectors?


#3

This is perfect! This is exactly the kind of food for thought I had wanted. I could learn a lot from your pattern of thought.

I shared that concern but thought if everything that needed to be separate was grouped then it wouldn’t be a big problem… however this would require too much unneeded consideration.

I’d probably want to provide vectors to the draw_rectangle class, perhaps with default values.