Using the observers and parsing the Ruby args correctly?

Hello,

I would like to have an observer to register changes to the entities so I can call functions in my plugin on the C++ side. I have the following snippet to attach an observer to the model in my .rb file:

class myExtSketchupObserver < Sketchup::EntitiesObserver
  def onElementModified(entities, entity)
    if entity.is_a?(Sketchup::Face)
      SUEX_myExt.onElementModified(entity)
    end
  end
end
Sketchup.active_model.entities.add_observer(myExtSketchupObserver.new)

Then I use the observer to call other methods in the C++ code:

rb_define_module_function(mSUEX_myExt, "onElementModified", VALUEFUNC(DoOnElementModified), -1);

and,

static VALUE DoOnElementModified(int argc, VALUE* argv, VALUE klass)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	VALUE entity;
	if (argc > 0)
	{
		rb_scan_args(argc, argv, "1", &entity);
		//TODO - parse the argv
        //call other extension functions
	}
	return true;
}

Couple of questions:

  1. I would like to be able to track what has changed so this would be a good spot for me to collect the persistent IDs of the entities maybe?

  2. If there is a cube and you pull on a face then DoOnElementModified gets triggered 5 times (1 for the pulled face 4 for the connected ones). I would like to know when the last call is. I also have a DoOnTransactionCommit override in C++ by attaching a Sketchup::ModelObserver, this gets called twice before AND twice after DoOnElementModified. (4 times in total) is there some pattern I can use to determine the last call on this function?

  3. What is the best way to parse the arguments in C++? say I want to input:
    SUEX_myExt.onElementModified(entity, num_faces) how would I then parse the argv so that I have an SUEntitiesRef and an int?

  4. On a tangent note: if I add an override also for onElementAdded then I don’t think onElementModified is called? I tried to attach them on separate observers and also override both methods in one observer. Is there a way to use both functions?

Thank you,
Ege

There is no override. All Observers are abstract since the 2016 overhaul. This means that they have no functionality to pass down to subclasses and all of the empty callback methods that they once had were removed in the mentioned overhaul. Since then the SketchUp engine polls observer objects to determine which callback methods an observer has implemented. If you don’t define a certain callback, then SketchUp will not attempt to call it.

The observers are not without bugs, including making multiple calls to callback methods when we would expect only one. So do not make assumptions. Test.

Check the API issue tracker for open issues on Observers:
API Issue Tracker - labels: “observer” “RubyAPI”


The 2020.2 release added a few C functions to pass objects between the Ruby and C APIs:
API Release Notes: 2020.2 - Ruby and C Exchange API

ADD: And the reference page in the C API documentation is: “ruby_api.h”

1 Like

Have you downloaded the book on Writing Ruby Extensions in C from my Ruby Resources book list?

It’s at the bottom of the Downloadable GENERIC list:

There are a couple of examples in the the official repo, but they do not yet have any thing of substance other than accepting a string value from Ruby:

I would say instead look through the “Samples” folder of the C SDK.


First of all, you do not need to prefix your C side identifier with "SUEX_". That is something that the Extensibility Team did that means “SketchUp EXample”.

It is customary to use a unique author or company prefix. On the Ruby side this translates to a top level namespace module identifier. Your extension should be wrapped in a submodule of this namespace module. Then you can mimic this on the C side. (Perhaps even the C++ section could use the same namespace identifiers?)

Okay, as to getting a C side reference to SUEntitiesRef, you cannot pass this as an entity because it is not an Sketchup::Entity subclass.

SketchUp’s collections are C++ collections and are exposed to the Ruby API as direct subclasses of Object.

If you will be using the new SUEntityFromRuby C function, then your Ruby side EntitiesObserver#onElementModified observer callback will only be able to pass the 1 entity subclass object. (Meaning that an integer count is unnecessary.)

So once you have the SUEntityRef from the new SUEntityFromRuby C function, you can ask it what parent collection it is a member of with SUEntityGetParentEntities.

2 Likes

hello, thank you very much for the pointers and clarifications. This is very helpful.

1 Like

What changes are you looking for?

I think we need to see some code here. Because I’m not seeing what you describe:

class MyEntitiesObserver < Sketchup::EntitiesObserver

  def onElementAdded(entities, entity)
    puts "onElementAdded: #{entity}"
  end

  def onElementModified(entities, entity)
    puts "onElementModified: #{entity}"
  end

  def onElementRemoved(entities, entity_id)
    puts "onElementRemoved: #{entity_id}"
  end

end


Sketchup.active_model.entities.add_observer(MyEntitiesObserver.new)

class MyModelObserver < Sketchup::ModelObserver

  def onTransactionCommit(model)
    puts "onTransactionCommit: #{model}"
  end

end

Sketchup.active_model.add_observer(MyModelObserver.new)

observers

You are not passing the entities param back to your C Extension:

 SUEX_myExt.onElementModified(entity)

You are passing only the entity.

If you want to forward both arguments you can just as well define your C function to take 2 parameters instead of variable arguments: (Btw, your code returned true to a function that declare returning VALUE. All Ruby functions should return a VALUE type and not native C/C++ types)

rb_define_module_function(mSUEX_myExt, "onElementModified", VALUEFUNC(DoOnElementModified), 2);
static VALUE DoOnElementModified(VALUE self, VALUE entities, VALUE entity)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	VALUE entity;
	// TODO: ...
	return Qnil;
}

If you want to exchange from Ruby API SketchUp types to C API types we have some utility methods for that: SketchUp C API: SketchUpAPI/application/ruby_api.h File Reference

Though nothing to exchange Sketchup::Entities to SUEntitiesRef. (I’ll add a feature request for that.)
But you can take the entity and get the parent entities by using SUEntityGetParentEntities

static VALUE DoOnElementModified(VALUE self, VALUE ruby_entities, VALUE ruby_entity)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());
  SUEntityRef entity_ref = SU_INVALID;
  SUEntityFromRuby(ruby_entity, &entity_ref); // TODO: Check error return value

  // TODO: Check that entity_ref because the Ruby entity value might have been
  // referring to a deleted entity.

  SUEntitiesRef entities_ref = SU_INVALID;
  SUEntityGetParentEntities(entity_ref, &entities_ref); // TODO: Check error return value

  // TODO: ...
  return Qnil;
}

No, that should not happen. For any issues like this we need reproducible examples. It’s likely that you are doing something different from what would be expected.

1 Like

In general, I would recommend that you test your observers first in Ruby instead of trying to do that while at the same time trying to work out Ruby => Ruby C Extension => Live SketchUp C API.

Makes it a lot easier when working on only one issue at a time.

2 Likes

Noted… and thanks for further help and clarifications! I was able to pass on more arguments this way and making headway.

I will start over with the Ruby Console to test the observers’ calling order.

again, thanks for the help -this should be plenty to get me going :slight_smile:

1 Like

hi there,

I have a follow up question on this, I have the following to collect the changed entities.

std::vector<SUEntitiesRef> onAddList;

If I collect the entities the way you describe it what would be a good way to check whether entities_ref is already added to the array. (say, it might be the parent of multiple faces that are moved)

//get the entity type
SUEntitiesRef entities_ref = SU_INVALID;
SURefType entityType = SUEntityGetType(entity_ref);
if (entityType == SURefType_Group) 
{
	SUGroupRef groupRef = SUGroupFromEntity(entity_ref);
	SUGroupGetEntities(groupRef, &entities_ref);
}
else
{
	SUEntityGetParentEntities(entity_ref, &entities_ref);
}
onAddList.push_back(entities_ref);

I see that there is a SUEntityGetPersistentID(entity, &entity_pid); method for SUEntityRef that seems safe to use for comparing. Is there a similar thing I could use in this case for SUEntitiesRef ?

thanks,
Ege

I think I asked the compare question to Thomas previously, and he responded that there is a boolean API macro or function for direct comparison of references.

Let me look … (looks … finds it) … yes it is here …

SketchUp C API: SketchUpAPI/common.h File Reference

The SUAreEqual() function macro.

2 Likes

perfect, thanks!

1 Like