Creating a component from a face crashes SketchUp!

Hello everyone,

In order to get the exact UVQ coordinates of the materials applied to all the faces inside my components, I have no other solution than to group the faces and convert them to a component:

def all_faces_componant(sel, faces)
  sel.grep(Sketchup::ComponentInstance).each do |s|
    s.definition.entities.grep(Sketchup::Face).each do |f|
      faces << f
    end
    all_faces_componant(s.definition.entities, faces)
  end
end
faces = []
mod = Sketchup.active_model
ents = mod.active_entities
sel = mod.selection
all_faces_componant(sel, faces)
mod.start_operation('UVQ save', true)
for f in faces
  group = ents.add_group(f)
  inst = group.to_component
  #uvq = []
  #saves_uv(inst, uvq) => Trigger my method to record UVQ in array!
  inst.explode
  p f.valid? # => Return true!
end
mod.commit_operation

I manage with this method to retrieve all the information I need but SketchUp takes revenge by crashing when I close or open another project!

I figured out that SketchUp doesn’t like to create groups or component instances from a single face!

Do you know for what and how to get around the problem?

If you want to try the method here is an example of a face inside a component or my code produces this error:

Sketchup Face.skp (127.7 KB)

Thank you in advance for your help.

Explain what is is that you want to achieve. Why cannot you get UVQ for the faces inside a component properly ?

FYI, a group is not much different than a component. as groups are merely instances whose definition has the #group? flag set true.

I can’t answer why the attached file crashes …

But a few notes:

  • grep method has its own “each” function.
    Enumerable #grep If the optional block is supplied, each matching element is passed to it…
    So can be written e.g. like this:
def all_faces_componant(sel, faces)
  sel.grep(Sketchup::ComponentInstance) do |s|
    s.definition.entities.grep(Sketchup::Face) do |f|
      faces << f
    end
    all_faces_componant(s.definition.entities, faces)
  end
end

.


  • When you are creating the groups it is created at the origin of the model. Let see the animation below what I mean by running your code in a simple example:
    grface

I also do not have experience with UVQs (and no information the method you are using) but, I assuming that you heve got “your expected result” about UVQs with your method above, just because you recreated the faces at the model origin and checked that faces for UVQ coordinates.

Most probably you have to examine and use the transformation of the instances of the original faces to get your desired UVQs without “group the faces and convert them to a component”…
.


  • Be aware that the faces does not exist without boundary edges. When you are crating the groups with “faces only” there will be edges too. When you are exploding the instances above there will be faces and edges overlapping, merging…
    .

  • Would be nice if you are completing your profile, because SketchUp Version: 2014 stated but the file is version 2017, and we do not know which platform are you using (Windows or Mac).
    (I guess you are using SU 2017 Make on Windows? )
1 Like

There is no real need to do the iteration block as #grep returns an array. Instead you can use the splat operator to pass a list of the grepped array to Array#push …

def all_faces_componant(sel, faces)
  sel.grep(Sketchup::ComponentInstance) do |s|
    faces.push(*s.definition.entities.grep(Sketchup::Face))
    all_faces_componant(s.definition.entities, faces)
  end
end

Another thing I see is the definitions and therefore the faces are not being uniquified.

def all_faces_componant(sel, faces)
  cdefs = sel.grep(Sketchup::ComponentInstance).map(&:definition)
  cdefs.uniq.each do |definition|
    faces.push(*definition.entities.grep(Sketchup::Face))
    all_faces_componant(definition.entities, faces)
  end
end
1 Like

My goal to achieve:
My goal is to convert all faces of a component to a component without losing the materials and their positions.

We have already worked on it in this topic and we have come to the conclusion that it is better to redraw faces from an edge.

This is why I need to find the exact UVs to apply to my new face component.

Is causing problem:
It seems to me that the method “face.get_UVHelper” included in are equoition the scale of the material and the position of the vertices of the face used.

As my method aims to convert these faces to a component, the position of the vertices are not the same because the original axis of the parent changes.

Conclusion:
Using get_UVHelper on a face before converting it to a component will not give the correct material position coordinates because the original axis will have changed.


I have just modified my profile as you requested, yes I am using Windows and SketchUp Make 2017.

Thank you for this tip which will prevent me from using “each” unnecessarily to browse a group.
I am aware that a group is created from the original axes of the model and that the faces have no edges.
Calculating the UVs of all the faces of a component is simple, the problem is that I convert my faces into a component which changes the UVs.


Going back to the basic problem:
I now know that grouping a face in a component necessarily causes Sketchup to crash when exiting the model:

mod = Sketchup.active_model
ents = mod.active_entities
sel = mod.selection	
sel.grep(Sketchup::ComponentInstance) do |s| 
  s.definition.entities.grep(Sketchup::Face) do |f| 
    ents.add_group(f)
  end
end

:thinking: Rather, I assume the problem is that a drawingelement (which already exists in one entities collection, which is even can be nested) you are adding to another group (collection of another entities).This “double association” makes SU to confuse…

I made a quick demonstration what I mean:
Modified snippet to see the ids of faces are same in both entities collection:

mod = Sketchup.active_model
ents = mod.active_entities
sel = mod.selection	
sel.grep(Sketchup::ComponentInstance) do |s| 
  s.definition.entities.grep(Sketchup::Face) do |f| 
    group = ents.add_group(f)
    puts group.entities.grep(Sketchup::Face).first
    puts f
  end
end

corrupted

That shouldn’t be needed. It can be computed. Lets try figure that one out instead of doing these slow temporary entities. Where are you stuck on this?

Regarding the crash, can you share full repro code?

I reproduced it. You can see in my example a post above of yours. :wink:

Instead of giving you big, incomprehensible explanations, here is my code in a simplified version:

def faces_uv_front(f, uv_front)  
  posy = [] 
  uv = []
  uv_helper = f.get_UVHelper(true, false)
  f.outer_loop.vertices.each do |vert|
    qut = posy.count
    if qut <= 3
      posy << vert.position
      uvq = uv_helper.get_front_UVQ(vert.position)
      u = uvq.x / uvq.z
      v = uvq.y / uvq.z
      uv << Geom::Point3d.new(u,v)	  
    end		
  end 
  map = posy.zip(uv)
  uv_front << map.flatten!
end

def convert_faces_to_componant(f)
  mod = Sketchup.active_model
  ents = mod.active_entities
  uv_front = []
  faces_uv_front(f, uv_front)      
  grp = ents.add_group(f.edges)
  inst = grp.to_component
  tr = inst.transformation
  pts = tr.origin     
  defn = inst.definition
  defn.name = "Component#1"
  new_face = []
  defn.entities.grep(Sketchup::Edge).each{|e| e.find_faces}
  defn.entities.grep(Sketchup::Face).each{|f| new_face << f }
  face = new_face[0]
  normal = face.normal
  if normal != f.normal
    face.reverse!
  end 
  mat_avant = face.material = f.material 
  face.position_material(mat_avant, uv_front[0], true) 	    
  inst.erase!
  f.parent.entities.add_instance(defn, pts)
  f.erase!
end

def face_all_componant(sel, faces)
  sel.grep(Sketchup::ComponentInstance) do |s|
    s.definition.entities.grep(Sketchup::Face) do |f|
      faces << f
    end
    face_all_componant(s.definition.entities, faces)
  end
end

faces = []
mod = Sketchup.active_model
sel = mod.selection
mod.start_operation('Convert faces to components', true)
face_all_componant(sel,faces)
for f in faces
  convert_faces_to_componant(f)  
end
mod.commit_operation  

Here is a model where my method is not able to position the materials correctly:

EXAGON.skp (143.9 KB)

You just need to apply my method after selecting the exagon to understand my problem.


Concerning the carsh, here is a simplified example which is enough to produce it:

mod = Sketchup.active_model
ents = mod.active_entities
sel = mod.selection	
sel.grep(Sketchup::ComponentInstance) do |s| 
  s.definition.entities.grep(Sketchup::Face) do |f| 
    ents.add_group(f)
  end
end

Apply this method on the component below then change or close the model to see SketchUp crash.

Sketchup Face.skp (127.7 KB)

Do the tests and tell me what you think of these errors?

Thank you

I’d like to hear it. Because I want to make sure that the API isn’t lacking anything that would force you to modify a model in a process that should be read-only.

What version and OS are you using? Did you submit the BugSplats? (If so did you enter any details that we can use to look up the crash?)

I tested with SU2021.1 on Windows, opened Sketchup Face.skp, selected the instance and pasted your code. No crash.

I see a crash when I close the model. Logged: entities.add_group related crash · Issue #752 · SketchUp/api-issue-tracker · GitHub

I’m confused about this other snippet you posted. For instance:

def face_all_componant(sel, faces)
  sel.grep(Sketchup::ComponentInstance) do |s|
    s.definition.entities.grep(Sketchup::Face) do |f|
      faces << f
    end
    face_all_componant(s.definition.entities, faces)
  end
end

faces = []
mod = Sketchup.active_model
sel = mod.selection
mod.start_operation('Convert faces to components', true)
face_all_componant(sel,faces)
for f in faces
  convert_faces_to_componant(f)  
end
mod.commit_operation  

You pass the faces to convert_faces_to_componant which on every iteration adds faces to that array, and then passes faces to convert_faces_to_componant. This means you are accumulating faces that are already processed. Is that your intent, or is that a bug in your snippet?

And I don’t understand the context of this model and your original post:

In order to get the exact UVQ coordinates of the materials applied to all the faces inside my components, I have no other solution than to group the faces and convert them to a component:

This model have materials applied to each face. What are the incorrect UVs you are getting from this model? I don’t see how grouping the faces would change anything. Can you elaborate please?

Thanks for submitting the crash error.

I finally found the solution to my problem!

My initial goal was to convert each existing face in a component to a new child component.
This is not without but the objective was for me to deepen my knowledge of various classes and methods in the API.
Thanks to this I discovered the class Face, Edge, Array, Transformation, UVHelper and ComponentInstance which is a good start. :slight_smile:


The first thing I wanted to do is convert each face of my component to a group directly in the parent component instance.
I then realized that life could not be so easy.
As faces have edges and vertices connected to other faces, it is impossible to group faces one by one without creating phantom faces as seen with Dan in this Topic.

The solution then is to redraw all the faces with the same texturing properties.
I first wanted to create an instance of edeges and use “find_faces” to create a new face.
It worked well but when I wanted to apply the UVs they were out of place.

I already explained it earlier in the topic, but when you create a new component instance, a new origin axis is automatically created, which therefore redefines the UVQ values ​​if the axis is not the same.
So if I copy the UVs before grouping to apply them to the new instance, the texuration will not be in the right place.

The solution is therefore to apply the UV before grouping and converting the face into a component instance.

For that I had to redraw all the faces outside the parent component, I then converted these faces into a group and then into a component instance to load them inside the parent component.

I know that it will be difficult for you to understand my explanations and that is why I post all my method below:

def faces_uv_front(f, uv_front)  
  posy = [] 
  uv = []
  uv_helper = f.get_UVHelper(true, false)
  f.outer_loop.vertices.each do |vert|
    qut = posy.count
    if qut <= 3
      posy << vert.position
      uvq = uv_helper.get_front_UVQ(vert.position)
      u = uvq.x / uvq.z
      v = uvq.y / uvq.z
      uv << Geom::Point3d.new(u,v)	  
    end		
  end 
  map = posy.zip(uv)
  uv_front << map.flatten!
end

def convert_faces_to_componant(f)
  mod = Sketchup.active_model
  ents = mod.active_entities  
  pts = []
  f.outer_loop.vertices.each{|vert| pts << vert.position }
  new_face = ents.add_face(pts) 
  uv_front = []
  faces_uv_front(f, uv_front)  
  uv_front.flatten!
  m = f.material   
  n = f.normal    
  normal = new_face.normal
  if normal != n
    new_face.reverse!
  end   
  mat = new_face.material = m
  new_face.position_material(mat, uv_front, true) 
  grp = ents.add_group(new_face)
  inst = grp.to_component
  tr = inst.transformation
  pts = tr.origin     
  defn = inst.definition
  defn.name = "Component#1"
  inst.erase!
  f.parent.entities.add_instance(defn, pts)
  f.erase!
end

def face_all_componant(sel, faces)
  sel.grep(Sketchup::ComponentInstance) do |s|
    s.definition.entities.grep(Sketchup::Face) do |f|
      faces << f
    end
    face_all_componant(s.definition.entities, faces)
  end
end

faces = []
mod = Sketchup.active_model
sel = mod.selection
mod.start_operation('Convert faces to components', true)
face_all_componant(sel,faces)
for f in faces
  convert_faces_to_componant(f)  
end
mod.commit_operation 

If you have things to do, you no longer need to waste your time replying to me on this topic. :wink: