Apply a material with onComponentInstanceAdded

Yes it IS bugged ... (click to view) ...

Could be. Yes it IS bugged.

It has always done this behavior. Think about it for a minute. Whilst attached to the cursor the user has not yet actually created a new ComponentInstance, so it is before the insert still a ComponentDefinition. The core rendering code likely is shared.

No doubt. At the console …

Sketchup::ComponentDefinition.instance_method(:material=).owner
#=> Sketchup::Drawingelement

Unless it is for supporting some “Team” feature. It is not explicitly documented on the definition class page.

Weirdly, Face#material= is documented (with a note that it is inherited,) but only to compliment the documentation of the Face#back_material= method.

It would be nice if the implementation was “formalized” as to what it’s purpose is or may be later. (Ie, sometimes they did things because they planned on supporting something later, but have never gotten around to the “later” part.)

Exactly what I was thinking ! And it would be nice to have a switch so it could be on or off.

WAIT. I just tested, and it gets weirder. When you place the component the instance is white. But when you save the model file, all the instances INHERIT their definition’s material.

But why not until the model is saved ? (That is just weird behavior.)

This is not implemented correctly. Then after the save, the “cursor definitions” STOP displaying the definition material because the save distributed the definition’s material to all the instances, THEN cleared the definition’s material (ie, reset it to nil.)

Changing the definition’s material to something else, then placing new instances, works as before up until the file save. At that point ALL the instances are repainted with the defintion’s current material (even the previously “Red” ones.)

On the one hand, it’s a nifty “trick” to repaint all instances of a certain component, but not really faster manually than the right-click “Select All instances” then paint the selection. And for code it may not be any better than simply painting the instances collection with an each iterator.

Issue Logged …

ComponentDefinition material assignment has weird behavior · Issue #19 · SketchUp/api-issue-tracker · GitHub

1 Like

Your code is beautiful sWilliams.

It works really well and you’ve thought about every detail!

This is the second example of code that I was given in the forum with the use of constants.

I understand better and better the advantage of using them.

With this type of example I progress much faster.

Thank you very much. :smiley:

I just noticed that My Entities Observer, is activated only at the start of sketchUp.

If I open a new project it does not work anymore. :open_mouth:

Is there a way to activate it anytime?

you should first work out how to turn it off…

john

Why turn it off if click-kitchen 2 needs it all the time?

You need to use an AppObserver to detect new models and opening of existing models, and attach your EntitiesObserver in the AppObserver callbacks.

I’m happy this is helping you learn.
I you run into problems please don’t hesitate to post your questions back to this thread. Best!

Because your extension only needs the observer attached when instances are being placed. There is no point in having it attached when users are drawing circles or rectangles, etc. ie, using other tools.

I liked your first idea in the 1st post of this thread, where you only watch YOUR ClickCuisine definitions with the DefinitionObserver#onComponentInstanceAdded()
… this could be attached using a ToolsObserver to watch for the ComponentTool (id: 21013).

@sWilliams, (1) why watch the model’s entire Entities collection and test every darn entity that changes for any reason ?
… and (2), you do realize that this solution will break as soon as the user changes editing context away from the toplevel model entities ?

2 Likes

The observer only works for existing entities in Click-Kitchen 2.
Otherwise, the observer is in standby.

Can the user encounter problems with an standby observer?

For what not to have said it earlier Dan?
Before the intervention of Williams, I was convinced to move in the wrong direction.

If I’m not mistaken, it seems to me that the id of a tool is not the same on every computer!

It becomes difficult to attach the right tool?

I found a solution by integrating the observer into a method instead of creating a class:

def add_ents_observer
  @MyEntitiesObserver = Sketchup::EntitiesObserver
    def @MyEntitiesObserver.onElementAdded(entities, entity)
      puts "Coloring: #{entity.definition.name}"
    end
  Sketchup.active_model.entities.add_observer(@MyEntitiesObserver)
end

Then I call this method in the furniture insertion method:

def Component_import
  add_ents_observer
  mod = Sketchup.active_model
  path = Sketchup.find_support_file("MyComponent.skp",
    "Plugins/plugin folder/subfolder/"
  )
  Component = mod.import(path) 
end	

It is possible to delete the observer when want with this method:

def remove_ents_observer		
  entities = Sketchup.active_model.entities
  status = entities.remove_observer @MyEntitiesObserver
end

I still have to find how to use the remove_ents_observer method!
This method should be enabled when the user uses any other tools in SketchUp.

How can I do this?

Thank you

You could observe when the active tool is changed.
Better, you could let the entities observer remove itself once the component was inserted.

@myEntitiesObserver = Sketchup::EntitiesObserver.new

Reverse the order and pass the model to the add_ents_observer method. This method should not have to assume to which model you want to add the observer!

Style-wise, it should be component_import and @myEntitiesObserver.

You won’t always be able to cross-access instance variables as if they were global. By “blindly” using instance variables like you do, you shoot yourself in the foot. They create “application state”, that means the behavior of methods depends on that instance variables have the correct value at this time point. But that is unpredictable because it could have been changed from outside. When methods are used in incorrect order, instance variables my hold an unexpected value, or they are not yet initialized (and you would get errors because of calling methods on nil).

This creates spaghetti code, because everything becomes interconnected and depends on everything else. Instead, use a more pure functional style. Everything that a method needs should be passed as a parameter. Everything that the method produces (and that other methods need) should be returned as return value (not stored in an instance variable). Once you have learned good programming, you know how to design good applications in object-oriented style.

Use composition when necessary! If your entities observer needs to access other objects (it cannot access outer instance variables), you should initialize it with references to these.

class MyEntitiesObserver < Sketchup::EntitiesObserver
  # If the observer method (e.g. onElementAdded) needs to have access to another object, you can share it with this observer as parameter in the constructor:
  # def initialize(reference_to_needed_object)
  #   @reference_to_needed_object = reference_to_needed_object
  # end
  def onElementAdded(entities, entity)
    # The entity could be ANY entity. An entity has no definition, only a component instance has!
    if entity.is_a?(Sketchup::ComponentInstance)
      puts("Coloring: #{entity.definition.name}")
    end
    entities.remove_observer(self)
  end
end

def component_import
  model = Sketchup.active_model
  # Create and add a entities observer to observer when the component was inserted.
  entities_observer = MyEntitiesObserver.new
  model.entities.add_observer(entities_observer)
  path = Sketchup.find_support_file("MyComponent.skp", "Plugins/plugin folder/subfolder/")
  model.import(path) 
end

I am amazed by the simplicity of your Aerilius solution!

From the beginning of my research I tried to integrate observer entity into a method:

    def add_ents_observer
      Sketchup.active_model.entities.add_observer(MyEntitiesObserver.new)
    end  

Calling a method only by name is not working if it is in another class.

Which led me to delete class and all store in an instance variable.

If I create the method “add_ents_observer” in the class where it is called, all works fine.

_

Finding the solution to remove the observer was more difficult to find without example!

class MyEntitiesObserver < Sketchup::EntitiesObserver
  def onElementAdded(entities, entity)
    if entity.is_a?(Sketchup::ComponentInstance)
      puts("Coloring: #{entity.definition.name}")
    end
    entities.remove_observer(self)
  end
end

It would have been impossible for me to guess that I had to use (self)!

Thank you Aerilius, for having offered more than just clues because things are much clearer now.

Cordially

David

I cannot control what other people say, nor when. This is a public forum.

Ruby is multi-paradigm. There is more than one way to accomplish tasks, but not all are equal in benefits or side-effects.

Doing things wrong helps you learn what not to do. And sometimes learning is “painful”.
But the rewards are great, so enjoy that “pain” while it lasts. :stuck_out_tongue_winking_eye:

Good news! You are mistaken. :wink: The SketchUp native tool IDs are constants and published in the API documentation. It is the Ruby extension tool IDs that can very with each session and order of extension load.

This code above would define a singleton method upon the Sketchup::EntitiesObserver class itself. That is a very big “no-no”.

@Aerilius is correct you should at the least, use an instance …

@myEntitiesObserver = Sketchup::EntitiesObserver::new

But I think you are beginning to understand that all the API observer classes are abstract. This means they do not have any functionality to pass down to their subclasses.

Also the #add_obserever and #remove_observer, as well as the SketchUp core (when it polls what it believes are observer objects,) … does not do any type checking.
It only does “duck-typing”, … to ask the object if it responds to named callback methods.

So this means that observers do not have to be implemented as a class. And if they are, they actually do not need to be a subclass of any observer superclass in particular.

Also, you can create hybrid observers that have the mixed functionality from multiple observer “classes”.

If you only need 1 observer, then it could also a single module. (I myself use a module quite often.)
Or as you showed above it could an instance of something else, to which you define a singleton callback “on the fly”.

In summation, SketchUp only cares that an observer object has a public callback method that it can call for a particular situation.

1 Like

I was not talking about native tools but tools offered by Click-Kitchen 2, of course. :grin:

Except that, … this thread deals with placing components which is done with SketchUp’s native ComponentTool.

You score a point! :scream:

1 Like

When making model changes inside of observers the operations should be made transparent in order to allow the user to undo the model changes in a single operation:

      #start a model operation
      entity.model.start_operation('Color Faces', true, false, true)
        meubels_verven(entity)
      entity.model.commit_operation

Thank you for this information.

Cancel the operation may be useful in other cases but not in this one.

It is imperative that the furniture finish with the applied materials on their faces!
This will allow to maintain the correct texturing scale for 3D renderings.

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