Écriture de script Python
Le mode Python Scripting offre des stylisations de lignes entièrement programmables. Dans ce mode de contrôle, toutes les opérations de style sont écrites sous forme de scripts Python appelés modules de style dans la terminologie Freestyle. L’entrée d’un module de style est une carte de vue (c’est-à-dire un ensemble de contours de caractéristiques détectés), et la sortie est un ensemble de traits stylisés.
Un module de style est composé d’appels successifs à cinq opérateurs de base: sélection, chaînage, découpage, tri et création de traits. L’opérateur de sélection identifie en entrée un sous-ensemble d’arêtes caractéristiques sur la base d’une ou plusieurs conditions de sélection définies par l’utilisateur (prédicats). Les arêtes sélectionnées sont traitées avec les opérateurs de chaînage, de division et de tri pour créer des chaînes d’arêtes caractéristiques. Ces opérateurs sont également contrôlés par des prédicats et des fonctions fournis par l’utilisateur afin de déterminer comment transformer les arêtes des entités en chaînes. Enfin, les chaînes sont transformées en traits stylisés par l’opérateur de création de traits, qui prend une liste de shaders de traits définis par l’utilisateur.
Les modules de style Python sont stockés dans les fichiers blend sous forme de blocs de données texte. Les fichiers du module de style externe doivent d’abord être chargés dans l’éditeur de texte. Ensuite, le menu de sélection dans une entrée de la pile de modules de style vous permet de sélectionner un module dans la liste des modules de style chargés.
Freestyle pour Blender est livré avec un certain nombre de modules de style Python qui peuvent servir de point de départ à l’écriture de votre propre module de style. Voir aussi la section de l’API Python Freestyle dans le manuel de référence de l’API Python Blender pour le détail complet des constructions du module de style.
Écriture de Modules de style
Un module de style est un morceau de code responsable de la stylisation du dessin au trait Freestyle. L’entrée d’un module de style est un ensemble d’arêtes d’entités appelées carte de vue (ViewMap). La sortie est un ensemble de lignes stylisées également appelées traits (strokes). Un module de style est structuré comme un pipeline d’opérations qui permet de créer des traits à partir des arêtes d’entrée dans la carte de visualisation.
Il existe cinq types d’opérations (listées avec les fonctions d’opérateur correspondantes):
Sélection
Operators.select()
Chaînage
Operators.chain(), Operators.bidirectional_chain()
Fractionner
Operators.sequential_split(), Operators.recursive_split()
Trier
Operators.sort()
Création de trait
Operators.create()
La carte de vue d’entrée est renseignée avec un ensemble d’objets ViewEdge. L’opération de sélection est utilisée pour récupérer les ViewEdges ayant un intérêt pour les artistes sur la base de conditions de sélection définies par l’utilisateur (prédicats). Les opérations de chaînage prennent le sous-ensemble ViewEdges et créent des chaînes en concaténant ViewEdges selon des prédicats et des fonctions définis par l’utilisateur. Les chaînes peuvent être encore affinées en les divisant en morceaux plus petits (par exemple aux points où les arêtes font un virage aigu) et en sélectionnant une fraction d’entre eux (par exemple pour ne garder que ceux qui dépassent un seuil de longueur). L’opération de tri est utilisée pour organiser l’ordre d’empilement des chaînes pour tracer une ligne sur une autre. Les chaînes sont finalement transformées en traits stylisés par l’opération de création de traits appliquant une série de shaders de traits aux chaînes individuelles.
ViewEdges, Chains et Strokes sont génériquement appelés éléments unidimensionnels (1D). Un élément 1D est une polyligne qui est une série de lignes droites connectées. Les sommets des éléments 1D sont appelés éléments 0D en général.
Tous les opérateurs agissent sur un ensemble d’éléments 1D actifs. L’ensemble actif initial est l’ensemble ViewEdges dans la carte de vues en entrée. L’ensemble actif est mis à jour par les opérateurs.
Sélection
L’opérateur de sélection parcourt chaque élément de l’ensemble actif et ne conserve que ceux satisfaisant un certain prédicat. La méthode Operators.select()
prend comme argument un prédicat unaire qui fonctionne sur n’importe quelle Interface1D
qui représente un élément 1D. Par exemple:
Operators.select(QuantitativeInvisibilityUP1D(0))
Cette opération de sélection utilise le prédicat QuantitativeInvisibilityUP1D
pour sélectionner uniquement les ViewEdge
visibles (plus précisément ceux dont l’invisibilité quantitative est égale à 0). L’opérateur de sélection est destiné à appliquer sélectivement le style à une fraction des éléments 1D actifs.
Il est à noter que QuantitativeInvisibilityUP1D
est une classe implémentant le prédicat qui teste la visibilité de la ligne, et la méthode Operators.select()
prend une instance de la classe de prédicat comme argument. Le test du prédicat pour un élément 1D donné est en fait effectué en appelant l’instance de prédicat, c’est-à-dire en appelant la méthode __call__ de la classe de prédicat. En d’autres termes, la méthode Operators.select() prend en argument un foncteur qui prend à son tour un objet Interface0D
en argument. L’API Freestyle Python utilise largement des foncteurs pour implémenter des prédicats, ainsi que des fonctions.
Chaining
Les opérateurs de chaînage agissent sur l’ensemble des objets ViewEdge
actifs et déterminent la topologie des futurs traits. L’idée est d’implémenter un itérateur pour parcourir le graphique ViewMap en marchant le long de ViewEdges. L’itérateur définit une règle de chaînage qui détermine le prochain ViewEdge à suivre à un sommet donné (voir ViewEdgeIterator). Plusieurs de ces itérateurs sont fournis dans le cadre de l’API Python Freestyle (voir ChainPredicateIterator
et ChainSilhouetteIterator
). Les itérateurs personnalisés peuvent être définis en héritant de la classe ViewEdgeIterator
. L’opérateur de chaînage prend également en argument un UnaryPredicate travaillant sur Interface1D
comme critère d’arrêt. Le chaînage s’arrête lorsque l’itérateur a atteint un ViewEdge
satisfaisant ce prédicat pendant la marche le long du graphe.
Le chaînage peut être unidirectionnel Operators.chain()
ou bidirectionnel``Operators.bidirectional_chain()``. Dans ce dernier cas, le chaînage se propagera dans les deux sens à partir du bord de départ.
Voici un exemple de code de chaînage bidirectionnel:
Operators.bidirectional_chain(
ChainSilhouetteIterator(),
NotUP1D(QuantitativeInvisibilityUP1D(0)),
)
L’opérateur de chaînage utilise le ChainSilhouetteIterator
comme règle de chaînage et arrête le chaînage dès que l’itérateur est arrivé à un ViewEdge
invisible.
Les opérateurs de chaînage traitent l’ensemble des objets ViewEdge
actifs dans l’ordre. Les ViewEdges actifs peuvent être préalablement triés à l’aide de la méthode Operators.sort()
(voir ci-dessous). Il démarre une chaîne avec le premier ViewEdge
de l’ensemble actif. Tous les ViewEdges qui ont déjà été impliqués dans le processus de chaînage sont marqués (dans le cas de l’exemple ci-dessus, l’horodatage de chaque ViewEdge
est modifié par défaut), afin de ne pas traiter deux fois le même ViewEdge
. Une fois que le chaînage atteint un ViewEdge
qui satisfait le prédicat d’arrêt, la chaîne est terminée. Ensuite, une nouvelle chaîne est démarrée à partir du premier ViewEdge
non marqué dans l’ensemble actif. Cette opération est répétée jusqu’à ce que le dernier ViewEdge
non marqué de l’ensemble actif ait été traité. A la fin de l’opération de chaînage, l’ensemble actif est positionné sur les Chains qui viennent d’être construites.
Diviser
L’opération de découpage permet d’affiner la topologie de chaque chaîne. Le fractionnement est effectué de manière séquentielle ou récursive. Le fractionnement séquentiel Operators.sequentialSplit()
dans sa forme de base, analyse la chaîne à une résolution arbitraire donnée et évalue un prédicat unaire (travaillant sur des éléments 0D) à chaque point le long de la chaîne. Chaque fois que le prédicat est satisfait, la chaîne est divisée en deux chaînes. À la fin de l’opération de fractionnement séquentiel, l’ensemble de chaînes actif est défini sur les nouvelles chaînes.
Operators.sequentialSplit(TrueUP0D(), 2)
Dans cet exemple, la chaîne est divisée toutes les 2 unités. Une version plus élaborée utilise deux prédicats au lieu d’un: l’un pour déterminer le point de départ de la nouvelle chaîne et l’autre pour déterminer son point d’arrivée. Cette seconde version peut conduire à un ensemble de Chaines disjointes ou qui se chevauchent si les deux prédicats sont différents (voir Operators.sequentialSplit()
pour plus de détails).
Le fractionnement récursif Operators.recursiveSplit()
évalue une fonction sur les éléments 0D le long de la chaîne à une résolution donnée et trouve le point qui donne la valeur maximale pour la fonction. La chaîne est ensuite divisée en deux à ce stade. Ce processus est répété de manière récursive sur chacune des deux nouvelles chaînes, jusqu’à ce que la chaîne d’entrée satisfasse une condition d’arrêt spécifiée par l’utilisateur.
func = Curvature2DAngleF0D()
Operators.recursive_split(func, NotUP1D(HigherLengthUP1D(5)), 5)
Dans l’exemple de code ci-dessus, les chaînes sont divisées de manière récursive aux points de la courbure 2D la plus élevée. La courbure est évaluée en des points le long de la chaîne à une résolution de 5 unités. Les chaînes de moins de 5 unités ne seront plus divisées.
Tri
L’opérateur de tri Operators.sort()
organise l’ordre d’empilement des éléments 1D actifs. Il prend comme argument un prédicat binaire utilisé comme opérateur “plus petit que” pour ordonner deux éléments 1D.
Operators.sort(Length2DBP1D())
Dans cet exemple de code, le tri utilise le prédicat binaire Length2DBP1D
pour trier les objets Interface1D
dans l’ordre croissant en termes de longueur 2D.
Le tri est particulièrement utile lorsqu’il est combiné avec la densité causale. En effet, la densité causale évalue la densité de l’image résultante au fur et à mesure de sa modification. Si nous souhaitons utiliser un tel outil pour décider de supprimer des traits chaque fois que la densité locale est trop élevée, il est important de contrôler l’ordre dans lequel les traits sont tracés. Dans ce cas, on utiliserait l’opérateur de tri pour s’assurer que les lignes les plus “importantes” sont tracées en premier.
Création d’un trait
Enfin, l’opérateur de création de trait Operators.create()
prend l’ensemble actif de Chains en entrée et construit Strokes. L’opérateur prend deux arguments. Le premier est un prédicat unaire qui fonctionne sur Interface1D
et qui est conçu pour effectuer une dernière sélection sur l’ensemble des chaînes. Une chaîne qui ne satisfait pas à la condition ne conduira pas à un Stroke. La deuxième entrée est une liste de shaders qui seront responsables de l’ombrage de chaque trait construit.
shaders_list = [
SamplingShader(5.0),
ConstantThicknessShader(2),
ConstantColorShader(0.2,0.2,0.2,1),
]
Operators.create(DensityUP1D(8,0.1, IntegrationType.MEAN), shaders_list)
Dans cet exemple, le prédicat DensityUP1D
est utilisé pour supprimer toutes les chaînes dont la densité moyenne est supérieure à 0,1. Chaque chaîne est transformée en trait en la rééchantillonnant de manière à avoir un point toutes les 5 unités et en lui attribuant une épaisseur constante de 2 unités et une couleur constante gris foncé.
Contrôle utilisateur sur la définition du pipeline
L’écriture de modules de style offre différents types de contrôle utilisateur, même si les modules de style individuels ont une structure de pipeline fixe. L’un est le séquençage de différentes structures de contrôle de pipeline et l’autre passe par la définition d’objets foncteurs qui sont passés en argument tout au long du pipeline.
Différentes structures de contrôle de pipeline peuvent être définies en séquençant les opérations de sélection, de chaînage, de division et de tri. La création du trait est toujours la dernière opération qui conclut un module de style.
Les prédicats, les fonctions, les itérateurs de chaînage et les shaders de trait peuvent être définis en héritant des classes de base et en remplaçant les méthodes appropriées. Consultez les entrées du manuel de référence des classes de base suivantes pour plus d’informations sur les constructions scriptables par l’utilisateur.
Voir aussi
Les prédicats, les fonctions, les itérateurs de chaînage et les shaders de trait peuvent être définis en héritant des classes de base et en remplaçant les méthodes appropriées. Voir le Freestyle python module
pour plus d’informations sur les constructions scriptables par l’utilisateur.