Good initiative, I’ve been meaning to do a write-up or tutorial on this but haven’t found the right time. Maybe now is the time.
Basics
For starters, the full potential of the technique is still to be discovered, I’d imagine everyone using it in different ways until things settle and clear pros and cons can be identified, but I can try and summarise the initial intent of why it got implemented in the first place.
Multiple families is a complete shift in thinking; the inverse of using a single family. That is, rather than associating multiple plug-ins to a family, you associate multiple families to a plug-in. Put yet another way, rather than associating multiple operations to a single type of data, you dynamically redefine data to encompass multiple operations.
Examples
Consider these scenarios - one with single family and one with multiple families.
Single family
import pyblish.api
import pyblish.util
class CollectInstances(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
def process(self, context):
instance = context.create_instance("capeModel")
instance.data["family"] = "model"
class ValidateNormals(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["model"]
def process(self, instance):
self.log.info("Validating normals..")
class ValidateHierarchy(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["model"]
def process(self, instance):
self.log.info("Validating hierarchy..")
pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ValidateNormals)
pyblish.api.register_plugin(ValidateHierarchy)
pyblish.util.publish()
Multiple families
import pyblish.api
import pyblish.util
class CollectInstances(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
def process(self, context):
instance = context.create_instance("capeModel")
instance.data["families"] = ["geometry", "prop"]
class ValidateNormals(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["geometry"]
def process(self, instance):
self.log.info("Validating normals..")
class ValidateHierarchy(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["prop"]
def process(self, instance):
self.log.info("Validating hierarchy..")
pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ValidateNormals)
pyblish.api.register_plugin(ValidateHierarchy)
pyblish.util.publish()
Both of which achieve the same end result.
Adding an external plug-in
Now consider adding an external plug-in to your pipeline. Let’s imagine you just found and downloaded this file off the internet.
validate_tangency.py
ValidateTangency:
- description: "Ensure no angles between two edges exceed 60 degrees."
- families: ["organicMaterial"]
- complexity: O(n)
- running time: 2ns/vertex
You don’t know or need to know how it works, you just know what it does and the families it operates on.
Single family
In the single family example, the only way you could use this plug-ins, is by either redefining your existing families to instead use organicMaterial
, or by re-writing this plug-in to also cover families you would like for it to apply.
ValidateTangency:
- families: ["organicMaterial", "model"]
Rewriting one isn’t a big problem. Updating becomes a hassle, but it’s doable.
Rewriting many on the other hand is a no-go. Considering that the goal is to facilitate installing plug-ins via a package manager, similar to Sublime Text or Atom, there must be a better way.
Multiple families
In this case, “installing” this plug-in is under your control. Specifically, under the control of your Collector(s). You don’t need to modify the external plug-in.
class CollectInstances(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
def process(self, context):
instance = context.create_instance("capeModel")
instance.data["families"] = ["geometry", "prop", "organicMaterial"]
Mindset
Before
With single families, the assumed mindset of the implementer is this.
- In our pipeline, we have
Models
, Character Rigs
and Animation
-
Models
should be exported as obj
and validated for x, y and z.
-
Character Rigs
are exported as .ma
and validated for a, b and c.
-
Animation
are published as .abc
and validated for q, w and e.
You then build plug-ins that live up to these requirements and associate families to content matching the description.
After
Once multiple families have settled and we’ve got our package manager, I’d imagine a mindset like this.
- In our pipeline, we have
Models
, Character Rigs
and Animation
- Models in our pipeline are published using the families
["geometry", "napoleon.proxy", "xyzKit.reviewEntity"]
- Character rigs are published with
["rig", "filmKit.rig", "xyzKit.animatableEntity"]
- Animations are published with
["animation", "filmKit.animation"]
Where napoleon
, xyzKit
and filmKit
are external vendors you’ve installed, and their contained plug-ins are prefixed with the name of the package. The assignments to each instance is made via your Collector. In the case of Magenta, the Collectors delegates the responsibility to the artist, in which case the artist is the one who makes these assignments.
The external vendors might have dozens of additional plug-ins and families, but in your pipeline, you only care about a handful. The rest is taken care of by bespoke plug-ins developed in-house, in this case for families geometry
, rig
and animation
.
Whether multiple families is for you, your studio, or your extension is hard to say. I would experiment with both to try and find which of the two makes more sense to you.