Since SU2017 exploding a group replaces all entities

To calculate the volume of a solid I used to group the faces of the solid together temporarily, store the volume and explode it immediate after that. In SU2016 the faces don’t change, in SU2017 all my faces are replaced by new faces. Since I store the references of those faces, they now point to deleted entities. Both entityId and persistent_id are new too.

To see the difference, create a cube and run the following in the console:

      before = Sketchup.active_model.entities.grep(Sketchup::Face)
      puts before
      group = Sketchup.active_model.entities.add_group(before)
      volume = group.volume / (1.m * 1.m * 1.m)
      group.explode
      after = Sketchup.active_model.entities.grep(Sketchup::Face)
      puts after
      puts before == after

Is this the intended behaviour? Is there another way compatible with both versions?

Interesting behavior … this returns ‘false’ in 8, 2015, and 2017, but returns ‘true’ in 2016.

This is the intended behavior starting with version 2017. It is because all entities have a persistent ID in version 2017.

entityId has been around since v6, persistent_id is the new one…

in some that I’ve ‘fixed’ I realised the temp group wasn’t even needed and I could use the existing group entities…

in others, moving the explode to the end of tools code worked…

in one of mine I ‘now’ store the face.bounds.center.z and then after exploding re-find the face using that as a conditional…

john

Sure, I was trying to say that both change.

Okay, so how do I get the volume of this solid without creating a temporary group that destroys my faces? Sure, I probably can “find” the faces again, but this feels quite dirty to me.

My brain is slow today. Could you please explain why persistent ID’s make it necessary to delete and recreate entities when you explode a group? I’m struggling with the following thought analogy: I have a box of marbles. If I dump them out of the box, aren’t they still the same marbles?

SketchUp doesn’t dump them out, it crushes the marbles in a hydraulic press then makes new marbles.

I have no idea if it’s necessary or not. It’s just how SketchUp now works.

I thought I remember the reason given was persistent ID’s but I could be mistaken.

persistent_id’s will work in the given example, if you compare them sorted…

before = Sketchup.active_model.entities.grep(Sketchup::Face)
pids1 = []
before.each{|f| pids1 << f.persistent_id }

group = Sketchup.active_model.entities.add_group(before)
volume = group.volume / (1.m * 1.m * 1.m)
group.explode
after = Sketchup.active_model.entities.grep(Sketchup::Face)
pids2 = []
after.each do |f| 
  pids2 << f.persistent_id
  Sketchup.active_model.selection.add([f, f.edges]) if pids1.include? f.persistent_id
end  
     
pids1.sort == pids2.sort      

if it’s a ‘solid’ it’s already a group, why do you need the temp group?

john

The preservation of entityId when exploding in previous versions was an implementation detail. The API never guarantied this. When we implemented persistent ID’s this implementation detail changed for various reasons in order to ensure consistent persistent IDs. The regular entityIDs has always been a very transient property.

1 Like

Hi everybody, I have the same issue as Bliss.

I try to reformulate it my way :

I want to compute the volume of a manifold not stored in a group. To do so, I create a temporary group, compute the volume, store the result in a variable and then explode it. My geometry hasn’t changed and it’s okay, nervertheless all the entities (faces, …) change their reference so all my variable that previously pointed to them now point to some “Deleted Entity” and I get an error if I work on them : <TypeError: reference to deleted Face> .

My questions are :

  • can create a groupe with the copy of my entities to compute the volume and thus not deleting the original entities ?
  • is there an other way to compute the volume without grouping ?

Thank you in advance for your precious help.

Adri

INJECT: posted below: No temporary group method (based on old AdamB post)


This seems to work, tested on SU2016:

# get_volume(geometry)
# Create a temporary group, get the volume and undo.
#
# @param geometry [Array<Drawingelement>] Array of collected geometry references.
# @return [Float] the computed volume if successful, 0.0 if not.
def get_volume(geometry)
  volume = 0.0
  model = Sketchup::active_model
  model.start_operation('Temp Group',true)
    #
    ents = model.active_entities
    grp = ents.add_group(geometry)
    volume = grp.volume
    #
  model.commit_operation
rescue => e
  puts e.inspect
else
  Sketchup::undo
ensure
  puts "get_volume() returned: #{volume} in³"
  return volume
end

However, if some other extension creates a undo operation and attaches it to this one (from behind or next) their’s will also be undone. (IMO, that is their fault.)


EDIT: The example assumes that the geometry objects are in the current active entities collection.
If they are not, then you’ll need to modify the code to get the parent entities collection of the geometry:

# perhaps just before the undo operation ...
sire = geometry.first.parent
ents = sire.entities
return 0.0 unless geometry.all? {|e| e.parent == sire }

… and remove the line inside the operation that assigns ents.

Let’s us assume the geometry in question is temporarily placed inside a 'group'
The explode returns an array of objects.

ex = group.explode

This includes all resulting edges and faces, and more besides - like curves…
You can filter it - see below

So to make your code suitable for all supported versions you could consider adding a ‘marker’ attribute to say the faces [or edges if desired].
This will be passed to the replacement entities after the explode, so before grouping we ‘mark’ the faces…

faces.each_with_index{|face, i|  face.set_attribute('adrigon', 'marker', i) }

After exploding retrieve the replacement faces:

hashed_faces = {}
ex.grep(Sketchup::Face).each{|face|
  next unless i = face.get_attribute('adrigon', 'marker', nil)
  hashed_faces[i] = face
}
sorted_ex_faces = []
hashed_faces.keys.sort.each{|k|
  sorted_ex_faces << hashed_faces[k]
}

You now have an array of those newly exploded faces, ordered as they were in the original geometry [and ‘faces’ array]…

If desirable you can also finally reset your ‘marker’ attribute associated with these replacement faces to ‘nil’…


This will work across all SketchUp versions. BUT if you only want >= v2017 compatibility, then just use the newer persistent ids and trap for `Sketchup.version` to suit.......

@adrigon, @TIG pointed me toward an old thread on SketchUcation in which …
@AdamB proposed a totally mathematical solution, without a temporary group.

Following is my rendition of this solution.

Mine does not modify API class Geom::Point3d, but converts to an Array which already has a #dot method.
Mine also uses #grep which is very fast when comparing class identity, … instead of calling #is_a? on each and every object in the geometry collection on the Ruby-side, during each iteration.

# calculate_volume(geometry)
# Calculate volume on a set of faces without a temporary group.
#
# Based on an old SCF topic thread post by AdamB:
# http://sketchucation.com/forums/viewtopic.php?p=14598#p14598
#
# @param geometry [Array<Drawingelement>] Array of collected geometry references.
# @return [Float] the computed volume (in inches) if successful, 0.0 if not.
def calculate_volume(geometry)
  geometry.grep(Sketchup::Face).map {|f|
    (2 * f.area * (f.vertices[0].position.to_a.dot(f.normal))) / 6      
  }.reduce(:+)
rescue => e
  puts e.inspect
  return 0.0
end

Snipped off volume format string to new topic:

Thank you so much Dan.
It looks perfect. I just need to give it a little try.

A work around could be to make a temporarily copy of the group which you can delete when done…

Except, … that there is no group. It is raw geometry. (Please read the thread.)

@DanRathbun, on a 1.0m³ your code is returning 1550.0m³…

john

That’s because 1m^3 = 61023.744in^3. If you treat that as inches and apply format_length, it works out to 1550.0m. The format_volume_in_model_units method given is wrong!

complaints ...

Removed from previous posts and began new topic on volume format string :

you are right…and surly it isn’t a beautiful advice… copy the raw geometry to a temporarily group - measure the volume - then delete the temporarily group again.