# 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|
# 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?

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

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|
# 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

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

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.

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|
# 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

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|
# 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.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?

I removed your comments and added the missing `end` to the end, then added row numbering to be able to refer to it.

• 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...
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])
}
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...
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
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|
# 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.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|
# 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.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|
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)
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â€¦

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