How to get vertex of a selected face or a component that is created by push-pull?

Hi guys, I wrote a code to select a face and then push up it into a box Layer1 (a component). How could I created a new box (Layer2) based on the previous one? Like two layers together.

Here is the code for selecting face and push pull:

# only_faces = sel.grep(Sketchup::Face)
   only_faces.each do |f|
     group = ent.add_group(f)
         # Extrude the face to create a wall with the specified thickness (LenY)
     f.pushpull(200.mm)

     # Convert the group to a component and set its name and attributes
     inst = group.to_component
     inst.definition.name = "Layer 1" 
     inst.name = "Layer 1 + material" 

I was thinking about two ways. The first is to get the vertex of the selected face (“only_faces”

in the code)with its X,Y,Z, Y or X adding 200.mm (for position) that create new vertex and then create face and push pull again. The second is to directly get vertex of the face of the component, and then create face and push pull again. But I had hard time to get vertex in both way, could anybody give tips?

Thank you in advance!

To get the vertices of original face:
The Face #vertices method is used to get an array of all of the vertices that bound the face.

However, because the vertices above can be in “random” order, this maybe be better here.:
Face #outer_loop method is used to retrieve the outer loop that bounds the face.
Then you can get an array of the Loop #vertices that define the loop in an ordered sequence.

Then you can get the position (Point3d) of vertex:
The Vertex #position method is used to retrieve the Point3d position of a vertex, so you can add an offset to its coordinates. E.g. The Point3d #x= method is used to set the x value of a 3D point.

Then the Entities #add_face method can be used to create a face with these Point3d’s as parameter.


BTW.
The posted snippet is missing the end at the end.

You can retrieve the new face in question (parallel to original) after pushpull e.g. like:

new_face = group.entities.grep(Sketchup::Face).select{|face| 
  face.normal.parallel?(f.normal) && face != f
}

Since you made a component, you can also just insert another instance offset by 200 mm from the first instance’s origin.

Something like ( untested ):

origin = inst.transformation.origin
vec = f.normal
vec.length= 200.mm
new_pt = origin.offset(vec)
trans = Geom::Transformation.translation(new_pt)
inst2 = ent.add_instance(inst.definition, trans)

Then, paint the second instance with a different material.

1 Like

Hi Dezmo! Thank you for the reply. The logic is clear and I tried the method writing new code below:

# Create a new group for the face(s) selected by the user
    only_faces = sel.grep(Sketchup::Face)
    only_faces.each do |f|
      group = ent.add_group(f)
# Extrude the face to create a wall with the specified thickness (LenY)
      f.pushpull(len_y)

      # Convert the group to a component and set its name and attributes
      inst = group.to_component
      inst.definition.name = "Layer naming later"
      inst.name = len_y.to_s

      # Get the outer loop of the previous component's face
      loop = only_faces.outer_loop

      # Get the vertices of the loop
      vertices = loop.vertices

      # Offset the Y-coordinate of each vertex by 200.mm
      new_positions = vertices.map { |vertex| vertex.position.offset([0, 200.mm, 0]) }

      # Create a new face using the modified vertex positions
      ent.add_face(new_positions)

    end

But it didn’t work for creating a new face (for push-pull later on). It still kept functioning same before loop function. I am confused

I’m almost sure you have got an error message in Ruby Console… because only_faces is an Array of faces, not a single face
I guess, this should be:

loop = f.outer_loop

No, it is still not working with “f”. I tried another code that worked:

# Get the outer loop of the previous component's face
loop = inst.definition.entities.grep(Sketchup::Face).first.outer_loop

# Get the vertices of the loop
vertices = loop.vertices

# Offset the Y-coordinate of each vertex by 200.mm
new_positions = vertices.map { |vertex| vertex.position.offset([0, 200.mm, 0]) }

# Create a new group for the face
new_group = ent.add_group
new_group.entities.add_face(new_positions)

I selected a face of the component rather than the original one “f” in the Array. These two approaches (face from the original and from the component) seem have the same principle. The former is not working and I am confused about it, but the latter works practically.

Hi, Dan, thanks for the idea!

I will try your idea soon and I am thinking how to move(translation) the face or box out of the previous component. So there will be two components (the two layers) for further development.

Me too, because for me its works.
ppf

BTW
Did you get error messages you got in Ruby console? If you post it it may help us to help you…

It does not show error and It finally works for me! But here is an issue. When I select the orgianl face that generate a box (No.1, the component), the new face (No.2) is always created based on the point (0,0,0) regardless where the orginal face is.

def bim_wall
  mod = Sketchup.active_model # Open model
  ent = mod.entities # All entities in model
  sel = mod.selection # Current selection

  # Default values
  material = "CLT"
  len_y = 100.mm

 # Create a new group for the face(s) selected by the user
    only_faces = sel.grep(Sketchup::Face)
    only_faces.each do |f|
      group = ent.add_group(f)
 # Extrude the face to create a wall with the specified thickness (LenY)
      f.pushpull(len_y)

      # Convert the group to a component and set its name and attributes
      inst = group.to_component
      inst.definition.name = "Layer naming later"
      inst.name = len_y.to_s
    # Get the outer loop of the previous component's face
      loop = f.outer_loop

      # Get the vertices of the loop
      vertices = loop.vertices

      # Offset the Y-coordinate of each vertex by 200.mm
      new_positions = vertices.map { |vertex| vertex.position.offset([0.mm, 200.mm, 0.mm]) }

      # Create a new face using the modified vertex positions
      ent.add_face(new_positions)



    end

How can I create the new face that is just attached on the box (No.1)

In addition, I tried another way of selecting the face of the component, here is the code:

def bim_wall
  mod = Sketchup.active_model # Open model
  ent = mod.entities # All entities in model
  sel = mod.selection # Current selection

  # Default values
  material = "CLT"
  len_y = 100.mm

 # Create a new group for the face(s) selected by the user
    only_faces = sel.grep(Sketchup::Face)
    only_faces.each do |f|
      group = ent.add_group(f)
 # Extrude the face to create a wall with the specified thickness (LenY)
      f.pushpull(len_y)

      # Convert the group to a component and set its name and attributes
      inst = group.to_component
      inst.definition.name = "Layer naming later"
      inst.name = len_y.to_s

 # Retrieve all faces within the component
      component_faces = inst.definition.entities.grep(Sketchup::Face)

       # Retrieve a certain face's outer loop
      nearby_face_loop = component_faces[1].outer_loop   # component_faces[0].outer_loop, 0,1,2,3 ...first, second, third...

      # Get the vertices of the loop
      vertices = nearby_face_loop.vertices

      # Offset the Y-coordinate of each vertex by 10.mm
      new_positions = vertices.map { |vertex| vertex.position.offset([0, 10.mm, 0]) }

      # Create a new face using the modified vertex positions
      new_face = inst.definition.entities.add_face(new_positions)
      
      new_face.vertices.each { |vertex| vertex.position.y = len_y }
      
      new_face.pushpull(200.mm)
 
      end

It works but the new face is inside of the component. Is there a way to make the new face out of the previous component?

Lets analyse your first snipped:
I removed your comments and added the missing end to the end, then added row numbering to be able to refer to it.
image

  • The pushpull distance ( len_y) used in row 10, is different than the offset distance at row 17
  • When you are creating the group at row 9, the actual face is became the part of that group which is using its own coordinate system…
  • … therefore later on when you creating the new position you need to consider it (first example)
  • or you need to calculate coordinates of new face vertices position before you make a group, pushpull and convert it to component (second example)

I assuming you want to pushpull the selected face in the direction of face orientation, then add a new face parallel to original face in a direction to pushpull.

First example.

def bim_wall_a
  mod = Sketchup.active_model
  ent = mod.entities
  sel = mod.selection
  material = "CLT"
  len_y = 100.mm
  only_faces = sel.grep(Sketchup::Face)
  mod.start_operation('Push_test_a', true)
  only_faces.each do |f|
    # check which direction pushpull will happen
    # and get the offset distance for new face
    if f.normal.samedirection?(Y_AXIS)
      offset_y = len_y
    else 
      offset_y = -len_y
    end
    
    # Make a group, pushpuul, conver to component...
    group = ent.add_group(f)
    f.pushpull(len_y)
    inst = group.to_component
    inst.definition.name = "Layer naming later"
    inst.name = len_y.to_s
    
    # calculate the new face vertices position
    # considering the transformation of the new instance
    # and add the new face to model entities
    loop = f.outer_loop
    vertices = loop.vertices
    tr_ins = inst.transformation
    new_positions = vertices.map {|vertex| 
      vertex.position.transform(tr_ins).offset([0.mm, offset_y, 0.mm])
    }
    ent.add_face(new_positions)
  end
  mod.commit_operation
end

second example

def bim_wall_b
  mod = Sketchup.active_model
  ent = mod.entities
  sel = mod.selection
  material = "CLT"
  len_y = 100.mm
  only_faces = sel.grep(Sketchup::Face)
  mod.start_operation('Push_test_b', true)
  only_faces.each do |f|
    # calculate the new face vertices position
    # before the face 'f' became to be the instance entity
    # so do not need to consider transformation
    if f.normal.samedirection?(Y_AXIS)
      offset_y = len_y
    else 
      offset_y = -len_y
    end
    loop = f.outer_loop
    vertices = loop.vertices
    new_positions = vertices.map {|vertex| 
      vertex.position.offset([0.mm, offset_y, 0.mm])
    }
    
    # Make a group, pushpuul, conver to component...
    group = ent.add_group(f)
    f.pushpull(len_y)
    inst = group.to_component
    inst.definition.name = "Layer naming later"
    inst.name = len_y.to_s
    
    # now add the new face to model entities
    ent.add_face(new_positions)
  end
  mod.commit_operation
end

The Model #start_operation method is used to notify SketchUp that a new operation (which can be undone) is starting.
The Model #commit_operation method commits an operation for undo.




I did not analysed your second snipped so deep, but:

You added the face to the instance definition entities:
new_face = inst.definition.entities.add_face(new_positions)

so do not surprise, that it will be there. You need to add it to model entities as you did in your first snippet.

Be careful with this. A grep method will give you a faces in “random” order, so the face, what you are looking for is not necessarily a second one as you assumed above. Better to use a condition like I show you above in my post #3.

1 Like

Really appreciate your reply with highly detailed and valued explanation! It took me a little while to go through. I changed my mind that I moved the vertices of selected face towards the previous push-pull’s direction instead of Y. Here is code:

def bim_wall
  mod = Sketchup.active_model # Open model
  ent = mod.entities # All entities in model
  sel = mod.selection # Current selection

  # Default values
  material = "CLT"
  len_y = 100.mm

  # Create a new group for the face(s) selected by the user
  only_faces = sel.grep(Sketchup::Face)
  only_faces.each do |f|
  group = ent.add_group(f)
  # Extrude the face to create a wall with the specified thickness (LenY)
      f.pushpull(len_y)

      # Convert the group to a component and set its name and attributes
      inst = group.to_component
      inst.definition.name = "Layer naming later"
      inst.name = len_y.to_s

      #################################
      # Modified vertex positions approach
      #################################

      # Get the outer loop of the previous component's face
      loop = f.outer_loop

      # Get the vertices of the loop
      vertices = loop.vertices

      # Calculate the translation vector based on the push direction
      push_direction = f.normal.reverse
      translation_vector = push_direction.transform(Geom::Transformation.scaling(200.mm))

      # Offset the vertices by the translation vector
      new_positions = vertices.map { |vertex| vertex.position.offset(translation_vector) }

      # Create a new face using the modified vertex positions
      new_face = ent.add_face(new_positions)
      #new_face.pushpull(-20.mm)
      mod.commit_operation
      ###################################
    end

At the moment, the direction of push-pull seems right but as you mentioned (in your first example) that the new face created by “new_positions” is still based on the origin (0, 0, 0). In the model, No.1 wall is created by selecting the face, and No.2 is the new face by “new_positions”. I am still having hard time of finding a way to create a face related to the selected face that then will be extruded rather than the origin.

Really appreciate your reply with highly detailed and valued explanation! It took me a little while to go through. I changed my mind that I moved the vertices of selected face towards the previous push-pull’s direction instead of Y. Here is code:

def bim_wall
  mod = Sketchup.active_model # Open model
  ent = mod.entities # All entities in model
  sel = mod.selection # Current selection

  # Default values
  material = "CLT"
  len_y = 100.mm

  # Create a new group for the face(s) selected by the user
  only_faces = sel.grep(Sketchup::Face)
  mod.start_operation('Push_test_a', true)
  only_faces.each do |f|
  group = ent.add_group(f)
  # Extrude the face to create a wall with the specified thickness (LenY)
      f.pushpull(len_y)

      # Convert the group to a component and set its name and attributes
      inst = group.to_component
      inst.definition.name = "Layer naming later"
      inst.name = len_y.to_s

      #################################
      # Modified vertex positions approach
      #################################

      # Get the outer loop of the previous component's face
      loop = f.outer_loop

      # Get the vertices of the loop
      vertices = loop.vertices

      # Calculate the translation vector based on the push direction
      push_direction = f.normal.reverse
      translation_vector = push_direction.transform(Geom::Transformation.scaling(200.mm))

      # Offset the vertices by the translation vector
      new_positions = vertices.map { |vertex| vertex.position.offset(translation_vector) }

      # Create a new face using the modified vertex positions
      new_face = ent.add_face(new_positions)
      #new_face.pushpull(-20.mm)
      mod.commit_operation
      ###################################
    end
end

At the moment, the direction of push-pull seems right but as you mentioned (in your first example) that the new face created by “new_positions” is still based on the origin (0, 0, 0). In the model, No.1 wall is created by selecting the face, and No.2 is the new face by “new_positions”. I am still having hard time of finding a way to create a face related to the selected face that then will be extruded rather than the origin.

I am little bit unfamiliar of the concept of inserting instance in Ruby but I tried something similar to your idea and something interesting happened. Here is the code:

model = Sketchup.active_model
entities = model.active_entities
selection = model.selection

selection.grep(Sketchup::Face).each do |face|
  group = entities.add_group(face)
  face.pushpull(50.mm)

  component = group.to_component
  component.definition.name = "Layer 1"
  component.name = "Layer 1 + material"

  origin = component.transformation.origin
  normal = face.normal
  offset_vector = normal.clone
  offset_vector.length = 200.mm
  new_origin = origin.offset(offset_vector)
  transformation = Geom::Transformation.translation(new_origin)
  entities.add_instance(component.definition, transformation)
end


When I selected the face it gave me this (see image). I felt the potential of creating what I want in this and that is when selecting a face, a wall created based on the face and a new wall (layer) also is created (later with third and more layers) next to each other. But I still need to explore this due to my limitation of knowledge in this method you provided.

  • The code you posted have a syntax error. Again (and again) missing the end at the end.
  • mod.commit_operation does not make sense without Ë›start_operation.
  • I do not understand the way (and reason) you calculating the translation_vector
  • Why are you using len_y = 100.mm for pushpull, then 200.mm for offset?

I suggest you keep doing it… :wink:

1 Like
  1. Syntax error: It was my mistake that I forget to copy the end. that is actually in the file.

  2. start_operation has being adding to the code. (I am pretty new to this method and thank you for mentioning)

  3. The reason to calculate translation_vector is to get the vector of the previous push-pull (N1 and N2 in image) so that the new face is always moved towards the same direction as the push-pull.

  4. 100.mm or 200.mm doesn’t matter now, just for observing.

The issue now fixed now with second example! Thank you so muck!!

1 Like