Dynamic families

Had an idea I’d like to run by you guys.

Goal

To facilitate plug-ins that operate on Instance's under certain circumstances, such as after a failure or custom event.

For example, if an instance of family “myFamily” fails validation, it’s family is dynamically, and temporarily changed to something like “myFamily.failed”.

Because it is now considered a different family, it will be compatible with other sets of plug-ins, plug-ins that support failed families.

Implementation

An example of how it could be used to repair broken Instance's.

class SelectModel(pyblish.Selector):
  def process(self, context):
    instance = context.create_instance("MyModel")
    instance.set_data("family", "model")

class ValidateModel(pyblish.Validator):
  families = ["model"]
  def process(self, instance):
    if False:
      current_family = instance.data("family")
      instance.set_data("family", current_family + ".failed")
      assert False, "Model is invalid"

class RepairModel(pyblish.Action):
  families = ["model.failed"]
  def process(self, instance):
    # repair model here

In practice, instances would be re-evaluated once publishing has completed and if there are plug-ins that support the new family, they would appear in the GUI ready to be processed individually similar to how repair works today.

Discussion

This would be an alternative to Actions, the benefit is alignment with processing in general, decoupling arbitrary actions from other plug-ins and the ability to develop Pyblish Action Packages.

On the other hand, the current coupling between a validator and it’s solution is very intuitive and it might be difficult - or at least of very little gain - to decouple them into separate plug-ins/files.

Thoughts?

That’s interesting:)

On the other hand, the current coupling between a validator and it’s solution is very intuitive and it might be difficult - or at least of very little gain - to decouple them into separate plug-ins/files.

If we can have the repair class and validator class in the python file, I don’t think its a big problem.

My main problem with this is the confusion of having a some instances to begin with, and then suddenly there are more instances after trying to publish.

It’s the plug-ins that would appear after a publish, the instances will remain the same and have their families changed.

1 Like

What happens if the Extractor would tell something it fails and it changes family to something else. What would happen with the plug-ins in that family which are earlier in order? Do we want to go back in order after a family change? Or do we only proceed forward? And do we still quit after being found invalid by a validator.

Personally I think this would actually push it more towards what you’re trying to avoid. It sounds like it would trigger when the process would just find something wrong with the scene, and then start doing something. I feel like we’ve covered the ground that automated fixes quickly become the root of (all?) evil. :wink:

We want the Action to be triggered by a user and know that he CAN work with it in another way. He knows the rule of the game and is aware that is scene is undergoing a change, which might not be obvious/expected when running a Pyblish process.

Also this could become a very annoying behaviour since you’re basically stepping out of the chain on the first found error? It doesn’t run through the other Validators and show you all the errors? Or when does it make the switch to the new family?

Also this is spot on.

That’s a good point.

The switch would have to happen afterwards, and not manually within the actual process() function as in the example above to prevent messing up subsequent plug-ins. Especially when the order is undefined, in case of multiple plug-ins of an identical order.

Maybe something like this.

class ValidateModel(pyblish.Validator):
  families = ["model"]
  def process(self, instance):
    if False:
      current_family = instance.data("family")
      instance.set_data("postFamily", current_family + ".failed")
      assert False, "Model is invalid"

But that still leaves doubt in case two or more plug-ins attempt to alter the family in different ways.

Maybe the behaviour is better suited for events of sorts.

class ValidateModel(pyblish.Validator):
  families = ["model"]
  def process(self, instance):
    if False:
      self.emit("failed")
      assert False, "Model is invalid"

At which point the corresponding actions could be listening.

class RepairModel(pyblish.Action):
  on = ["model.failed"]
  def process(self, instance):
    # repair model here

At the event of model.failed, RepairModel would appear in the list of available plug-ins to trigger.

No no, you misunderstand. The plug-ins that appear would be interactive, like the repair is currently. They would pop up alongside the other plug-ins, giving the user an option to run them individually.

Possibly nested, in a tree-like fashion, to indicate where they come from and what they relate to.

The primary benefit of this is still that actions can be provided in an individual Pyblish package, and not get coupled with existing plug-ins. Whether that’s useful or not, I’m not sure.

What’s the reason to not choose for regular data accessible on context/instance that could trigger something like that?

The family changing solution wouldn’t be that graceful when you’d want to trigger multiple Actions (or anything to get enabled) that are from different packages (and so might hold different family naming conventions)?

The event trigger makes more sense to me. (Not in the way that it enables a button by just emitting, but the idea that it triggers something that enables a button does make sense to me).

`

Sorry, could you rephrase that, not sure I get what you mean?

I meant something like:

class RepairModel(pyblish.Action):
  on_data = {"need_repair": True}
  def process(self, instance):
    # repair model here

class ValidateModel(pyblish.Validator):
  families = ["model"]
  def process(self, instance):
    if False:
      instance.set_data("need_repair", True)
      assert False, "Model is invalid"

So the value of data on context/instance is what trigger something else.

To be honest the emit to trigger an event (like enabling buttons) does make more sense to me.

Ah, that’s an interesting technique. The data could then be evaluated at each step of the way, and if there is a plug-in “listening” for any particular set or combination of data it would become “enabled”.