Temporarility Changing Materials
-
Hi,
I've got an implementation question I was hoping I could get some guidance on.I'm implementing a custom view mode whereby I switch up the color and/or texture of specific entities based on attributes. The first part, being able to change to a new material was pretty straight forward, but I'm having trouble being able to restore the original material.
My initial thought was to temporarily place the entities original material within its attribute dictionary, and then pull it out and restore it when the user exits the custom view mode. Right now, for some reason the material comes out of the attribute dictionary null. I'm thinking this maybe a ref count issue (the object on the dictionary gets deleted when replaced with a new one)... I've tried putting a clone and dup of the material on the attribute dictionary but neither of those methods work either.
Is there a best-practice for doing this sort of material swap-out? Should I be able to store materials within the attribute dictionary? Is there a proper way to clone materials aside from the standard ruby methods?
Thanks for the help,
Josh -
@joshb said:
I've tried putting a
clone
anddup
of the material on the attribute dictionary but neither of those methods work either.General "rule of thumb"...
clone
anddup
, as inherited from Ruby, are only for pure Ruby objects.These methods do not work for C++ objects (that have Ruby access wrappers.)
So.. a Sketchup class must purposely implement a customclone
and/ordup
method, for it to work.
Refering to the API method index, you will see that onlyGeom::Point3d
,Geom::Transformation
andGeom::Vector3d
, have custom APIclone()
methods defined. -
Now.. you cannot store a reference to a Material object directly.. basically an
AttributeDictionary
needs to be able to convert toString
(or perhaps marshal,) the object. (But normally only Ruby base classes, can do this by default.)You can store the Material name into the attribute (which is a
String
,)... then when restoring, use thatString
name, to retreive the Material object from the Materials collection.` # assume a local ref dict pointing at one
of the ent's attribute dictionaries:
dict['prev_matl']= ent.material.name
do your viewing thangy
matls = model.materials
prev_matl = dict['prev_matl']
ent.material= matls[prev_matl]`The other way would be to store the
obj_id = ent.material.object_id.to_s
into the attribute, and restore it viaObjectSpace._id2ref( obj_id.to_i )
More info, see:
[Info] Allowable Classes for "set_attribute" -
Thanks again Dan! Storing the material name works great.
-
@joshb said:
Thanks again Dan! Storing the material name works great.
What if the user purges the model - and the material doesn't exist any more?
-
Well.. good practice would be to test the result of
matls[prev_matl]
to be sure it is notnil
, BEFORE attempting to reset the entity's material.It might be advisible, to use a
MaterialsObserver
to be notified, actually when aMaterial
is deleted.OR... he could create some temporary invisible objects (say cpoints,) that have the previous material applied, so as to prevent them from being purged. When he's done, he deletes these temporary cpoints (which he should keep an
Array
of refs to, just for that purpose.) -
@joshb said:
Thanks again Dan! Storing the material name works great.
Your welky!
However writing attributes into the model DOM.. is going to slow things down (and add extra items onto the undo stack.)
Since this is temporary... why not just keep the references in memory?
A
Hash
object will do fine:# a new Hash to hold the material refs for each entity prev_matls = {} # keep the entity refs held in an Array, like; ents = model.entities.to_a # use Ruby's built-in Array iterator to save material refs ents.each {|e| prev_matls[e]= e.material } # now the hash has unique keys because each entity ref is unique # # your view wizard.. whatever # # restore them; ents.each {|e| (e.material= prev_matls[e]) if prev_matls[e].valid? }
Advertisement