Unique numbers for each of the faces in a model

Hi!

I am working in a plugin code to export a sketchup file containing a room to other applications.
In order to work with the exported room it is very important to provide a unique number to each of
the surfaces (Sketchup::Face) in the room, such that if the room is modified (for example by
deleting some of the surfaces or creating new ones) then the numbers of deleted surfaces are ignored
and new numbers (starting from maxSurfaceNumber) are used to numbering new surfaces. This is useful
for example to asses materials to the surfaces and retain this information even though the room is
modified.

with this aim I am attaching an attribute dictionary to the entities that are Sketchup::Face.

if (entity.is_a? Sketchup::Face)
  value = entity.get_attribute "MyDictoinary", "SurfaceNo",0
  if (value==0) #this is a new surface not numbered before
    maxSurfaceNumber+=1
    entity.set_attribute "MyDictoinary", "SurfaceNo",	maxSurfaceNumber
  end
end  

where MaxSurfaceNumber is obtained beforehand looking at faces and keeping the highest value.

if (entity.is_a? Sketchup::Face)
  value = entity.get_attribute "MyDictoinary", "SurfaceNo",0
  if (value > maxSurfaceNumber) 			    
    maxSurfaceNumber = value 
  end 
end

My problem is that when the face I am looking at is a “copy” of another face in the model (for example
if it is part of a ComponentInstance or a Group constructed by copying a master surface) then changing the
SurfaceNo in the copy is modifiying the master surface number and not the one of the copy I am looking at.

I figured out a way to do this if the copies belong to a ComponentInstance by using “make_unique” on
ComponentInstances that are copies before numbering the surfaces. Also notice that if I edit the groups
that are copies manually in SU (don’t know how to edit a group using ruby api) then I am getting individual
surfaces that can be numbered using the above code. Other solution is exploding Groups/Components in
the model before numbering.

I have the following questions:

1.- Is there any way to make this numbering of surfaces leaving the model unchanged?
2.- Is it possible to store this maxSurfaceNumber in the model once the numbering is done such that
we don’t need to look at all surfaces numbers every time the room is modified?

Thank you very much for your help, please let me know if you need further information to give me an answer.

Best Regards,
Carlos

Please format your code using appropriate tags - it’s hard to read.
Also give your faces a unique Attribute Dictionary name…

If the actual numbering is unimportant then
Time.now.to_f
will generate a unique incrementing reference…

You can tag all faces in the model using
model.entities.grep(Sketchup::Face)
to get an array

You can tag faces inside a group in a similar way…

model.definitions.each{|d|
  next if d.image?
  next unless d.group?
  if d.instances[1]
    groups = [ d.instances[0] ]
    d.instances[1..-1].each{|i|
      i.make.unique
      groups << i.definition.instances[0]
    }
  else
    groups = d.instances # only one group
  end
  # now process groups
  groups.each{|group|
    faces = group.entities.grep(Sketchup:Face)
    faces.each{|face| # assign a unique uid attribute number to face }
  }
}

If you want to process components invert the logic of the group? test - however, you probably don’t want to make component-instances unique…
In that case perhaps tag faces in just the first instance and count up instances to multiply the count later ?


You can set attributes for the model just as you did with the faces. After assigning a uid to the face just reset the model's attribute so you always know the last one. However, if the actual numbering is unimportant using my time-tag idea sidesteps the need to remember tha last used uid ??

Thanks for your post TIG.

The actual numbering is very important, what I want is to preserve the number given to a face if it remains in the model after a modification and assign new numbers to new faces (starting the count from maxSurfaceNumber +1). For example if I have a cube then the numbering of faces will be [1 2 3 4 5 6] (maxSurfaceNumber=6), if then I modify the model by deleting two faces of the cube (say numbers 3 5) and adding a new cube with 6 more faces then the model will have 10 faces numbered (1 2 4 6 7 8 9 10 11 12). Then in the exported file I will know that faces 1 2 4 6 are still there and I wont need to reassess materials and other properties of individual surfaces that I am working with outside SketchUp (such as transmission/reflection coefficients, porosity, color, … ), we will only need to include those properties for newly created surfaces ([7 8 9 10 11 12] in my example).

My problem comes when I have faces that are copies of other surfaces in the model. That is the case if I have groups/components in the model that are copied several times. In order to reach all faces in the model I am using the recursive routine self.read_entities explained in this post Extract geometrical information from a .skp file - #2 by tt_su

 def self.read_entities(entities, transformation = IDENTITY)
   entities.each { |entity|
     if (entity.is_a? Sketchup::Group) 
       definition = entity.definition
       tr = transformation * entity.transformation
       self.read_entities(definition.entities, tr)
     else
       if (entity.is_a? Sketchup::Face)  
         value = entity.get_attribute "MyDictoinary", "SurfaceNo",0
         if (value != 0)   #checking if SurfaceNo already exist
           for i in (0..@SurfaceNoVector.length-1) 
             if (@SurfaceNoVector[i] == value)  # Repeated Number
	           value = 0
	           break
             end    
           end      	 
         end        # if value !=0
		 
         if (value== 0)     # then assign a new Number
           @maxSurfaceNumber +=  1
           value = entity.set_attribute "MyDictoinary", "SurfaceNo",@maxSurfaceNumber
         end
		 
    	 @SurfaceNoVector[@SurfaceNoVector.length] = value
 
		 ##########
		 EXPORT (write to file) geometrical information of this surface
		  - SurfaceNo = value
		  - List of vertices in case we have a surface with just one loop
		  - List of Edges in case we have a surface with multiple loops  
		 ##########
  	   end
     end
   }
 end

For example, if I have a cube and make a Group/Component with it and then copy this cube twice for a model with 3 cubes. In this case when I try to give a number to a face of the second cube (a copy) then I am seeing value is not 0 but the number I gave to the master surface (corresponding face in the 1st cube). If I try to modify the surface number of this face in the second cube then I am changing the number of the master surface ending up with a wrong numbering in the model.

By the way, I managed to save maxSurfaceNumber as an attribute of the model once the numbering is made such that next time the model is exported there is no need to look at all numbers in all the faces :slight_smile:

When you copy a group using group.make_unique stops any changes in that group affecting its original sibling group.
However, they will still have the same attributes assigned to the faces.
Make an array of the group’s definition instances and process each in turn.
In that case I’d take the original group (instances[0]) and make an array of the group’s face reference - ‘numbers’.
Then iterate the remaining instances (instances[1…-1]) and make_unique for each, collecting a reference to each of those to process later.
Now process the unique groups, resetting the attribute numbers for each group’s faces, using something like:

# get number to maxSurfaceNumber from model attribute
number.next! if numbers.include?(number)
# set face attribute to 'number'
numbers << number
# increment maxSurfaceNumber for the model attribute

I managed to identify copies of Groups and Components in the model using this recursive function:

 def self.read_entitiesAndMake_uniqueGroupsAndComponents(entities)
   # go through all entities and make_unique those that are copies
   entities.each { |entity|
 	   
     if (entity.is_a? Sketchup::Group)
	 
	   if (entity != entity.definition.instances[0]) #this is a group copy 
	     entity.make_unique 
		 @GroupsMadeUniqueNo +=1
		 @CopiesSelection << entity
	   end	   
       
       self.read_entitiesAndMake_uniqueGroupsAndComponents(entity.definition.entities)		 

     elsif (entity.is_a? Sketchup::ComponentInstance) 

         if (entity != entity.definition.instances[0]) #this is a ComponentInstance copy 
    	   entity.make_unique 
           @ComponentsMadeUniqueNo +=1
           @CopiesSelection << entity			   
	     end	  
		
         self.read_entitiesAndMake_uniqueGroupsAndComponents(entity.definition.entities)
		     
     end        
    }             
   	 
 end  

All the copies are stored in @CopiesSelection and I can assign to all surfaces contained in these groups a SurfaceNo = 0. I edited the function self.read_entities (see my previous post) used to export the information I need from the model, now I am assigning the surfaceNo as an attribute of individual entites (no copies in the model, so it is not possible to overwrite surface number of a master surface). If the surface has not a SurfaceNo or it has a number already existing then it is given a new number starting the count from maxSurfaceNumber.

This code is doing what I need but it is very slow, I think the problem is due to the checking in self.read_entities where we look for repeated SurfaceNumbers. It is happening that newly created surfaces inherit the SurfaceNumber in at least 2 cases:

  1. When they are copied/pasted from a numbered surface.
  2. When a numbered surface is modified to create other surfaces (for example if it is split in two).

Is there a way to detect these newly created surfaces with repeated numbers?, such that we can now beforehand Surfaces with repeated numbers and avoid the checking in self.read_entities?. Also if you can see other things that can be causing the slowness that should be great to know.

What if the user modifies the faces?

A long time ago, I did SU4AC for SoundVision and DDA. I knew that German competitor also. Had a similar request for round-tripping. At the time, I thought about it and said no. Between performance issues, modified faces (mats, area, etc), it’s a lot of code with potential stability issues.

SU 2017 has an Entity#persistent_id attribute that might help, but restricting the SU version may not be desired. It might not be ‘human readable’…

Good luck with it.

I think the user can modify a numbered surface (i.e. changing position of points of the surface) and the number set in MyDictoinary will remain the same. I couldn’t find a way to change this attribute in a surface inside Sketchup (out of the API console). It is ok if the user don’t know about this numbering at the end this is something that is just needed when we export.

thanks for the idea of persistent_id, I will investigate if there is any benefit on using that for numbering the surfaces.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.