Récupérer les valeurs par défaut de kick
Par Narann le samedi, 10 décembre 2022, 23:47 - Script et code - Lien permanent
Aujourd’hui, un petit bout de code très simple pour récupérer les valeurs par défaut que Arnold (ou la ligne de commande kick) donne à ses nœuds quand on ne les lui fournit pas.
Ce type d’information est utile, entre autres, dans un pipeline qui génère ses .ass
lui-même. :siffle:
Bonne lecture ! :sauteJoie:
La ligne de commande kick
La première chose à faire avec une ligne de commande, c’est de regarder d’accéder à sa page d’aide :
$ kick --help
Dès lors, on identifie rapidement deux arguments intéressants :
-nodes [n|t] List all installed nodes, sorted by Name (default) or Type
-info [n|u] %s Print detailed information for a given node, sorted by Name or Unsorted (default)
L’argument -nodes
liste les nœuds et -info
donne des informations sur les paramètres de ces nœuds :
$ kick -nodes
built-in nodes sorted by name:
abs shader
add shader
alembic shape (procedural)
ambient_occlusion shader
aov_read_float shader
aov_read_int shader
...
Le nœud options
étant le nœud de paramètres de rendu d’Arnold, il y a de fortes chances que la ligne de commande qui vous intéresse en venant ici soit :
$ kick -info options
node: options
type: options
output: (null)
parameters: 119
multioutputs: 0
filename: <built-in>
version: 7.1.4.1
Type Name Default
------------ -------------------------------- --------------------------------
INT AA_samples 1
INT AA_seed 1
FLOAT AA_sample_clamp 1e+30
FLOAT indirect_sample_clamp 10
BOOL AA_sample_clamp_affects_aovs false
INT AA_samples_max 20
FLOAT AA_adaptive_threshold 0.015
INT threads 0
ENUM thread_priority low
BOOL abort_on_error true
BOOL abort_on_license_fail false
BOOL skip_license_check false
RGB error_color_bad_texture 1, 0, 0
RGB error_color_bad_pixel 0, 0, 1
RGB error_color_bad_shader 1, 0, 1
STRING[] outputs (empty)
STRING[] light_path_expressions (empty)
NODE[] aov_shaders (empty)
INT xres 320
INT yres 240
INT region_min_x -2147483648
INT region_min_y -2147483648
INT region_max_x -2147483648
INT region_max_y -2147483648
FLOAT pixel_aspect_ratio 1
ENUM fis_filter none
FLOAT fis_filter_width 3
INT bucket_size 64
ENUM bucket_scanning spiral
VECTOR2[] buckets (empty)
BOOL ignore_textures false
BOOL ignore_shaders false
BOOL ignore_atmosphere false
BOOL ignore_lights false
BOOL ignore_shadows false
BOOL ignore_subdivision false
BOOL ignore_displacement false
BOOL ignore_bump false
BOOL ignore_motion false
BOOL ignore_motion_blur false
BOOL ignore_dof false
BOOL ignore_smoothing false
BOOL ignore_sss false
BOOL ignore_operators false
BOOL ignore_imagers false
STRING[] ignore_list (empty)
INT auto_transparency_depth 10
INT texture_max_open_files 0
FLOAT texture_max_memory_MB 4096
BOOL texture_per_file_stats false
STRING texture_searchpath
BOOL texture_automip true
INT texture_autotile 0
BOOL texture_accept_untiled true
BOOL texture_accept_unmipped true
BOOL texture_use_existing_tx true
BOOL texture_auto_generate_tx true
STRING texture_auto_tx_path
INT texture_failure_retries 0
BOOL texture_conservative_lookups true
FLOAT texture_max_sharpen 1.5
NODE camera (null)
NODE subdiv_dicing_camera (null)
BOOL subdiv_frustum_culling false
FLOAT subdiv_frustum_padding 0
NODE background (null)
BYTE background_visibility 255
NODE atmosphere (null)
NODE shader_override (null)
NODE color_manager (null)
NODE operator (null)
FLOAT meters_per_unit 1
STRING scene_units_name
FLOAT indirect_specular_blur 1
FLOAT luminaire_bias 1e-06
FLOAT low_light_threshold 0.001
BOOL skip_background_atmosphere false
BOOL sss_use_autobump false
BYTE max_subdivisions 255
INT curves_rr_start_depth 0
BOOL curves_rr_aggressive true
FLOAT reference_time 0
FLOAT frame 0
FLOAT fps 24
STRING osl_includepath
STRING procedural_searchpath
STRING plugin_searchpath
BOOL procedural_auto_instancing true
BOOL enable_procedural_cache true
BOOL parallel_node_init true
BOOL enable_new_quad_light_sampler true
BOOL enable_new_point_light_sampler true
BOOL enable_progressive_render false
BOOL enable_adaptive_sampling false
BOOL enable_dependency_graph false
BOOL enable_microfacet_multiscatter true
BOOL enable_deprecated_hair_absorp... false
BOOL dielectric_priorities true
BOOL enable_fast_ipr true
BOOL force_non_progressive_sampling false
FLOAT imager_overhead_target_percent 1
INT GI_diffuse_depth 0
INT GI_specular_depth 0
INT GI_transmission_depth 2
INT GI_volume_depth 0
INT GI_total_depth 10
INT GI_diffuse_samples 2
INT GI_specular_samples 2
INT GI_transmission_samples 2
INT GI_sss_samples 2
INT GI_volume_samples 2
ENUM render_device CPU
ENUM render_device_fallback error
STRING gpu_default_names *
INT gpu_default_min_memory_MB 512
INT gpu_max_texture_resolution 0
BOOL gpu_sparse_textures true
INT min_optix_denoiser_sample 0
STRING name
Mais on va essayer d’aller plus loin et de récupérer les valeurs par défaut de tous les nœuds d’Arnold. :reflexionIntense:
Le code
Comme d’habitude, je vous donne le script en brut et je l’explique après :
from __future__ import (absolute_import,
division,
print_function,
unicode_literals)
import re
import subprocess
class ANodeDef(object):
def __init__(self, name, type, rest=None):
self.name = name
self.type = type
self.rest = rest
self.attrs = []
def __repr__(self):
return "{}('{}', '{}')".format(self.__class__.__name__,
self.name,
self.type)
class AAttrDef(object):
def __init__(self, name, type, default):
self.name = name
self.type = type
self.default = default
def __repr__(self):
return "{}('{}', '{}', '{}')".format(self.__class__.__name__,
self.name,
self.type,
self.default)
# Print Arnold version.
process = subprocess.Popen(['kick', '--help'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
first_line = out.split("\n")[0]
print(first_line)
# Get node list.
process = subprocess.Popen(['kick', '-nodes'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
node_line_re = re.compile(r"\s(?P<name>\w+)\s+(?P<type>\w+)(?P<rest>.*)")
node_defs = []
for line in out.split("\n"):
match_grp = node_line_re.match(line)
if not match_grp:
continue
name = match_grp.group('name')
type_ = match_grp.group('type')
rest = match_grp.group('rest')
node = ANodeDef(name, type_, rest)
node_defs.append(node)
# For each node, get its default parameters.
attr_line_re = re.compile(r"(?P<type>\w+)\s+(?P<name>\w+)\s+(?P<default>.*)")
for node_def in node_defs:
process = subprocess.Popen(['kick', '-info', node_def.name],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
param_start = False
for line in out.split("\n"):
if line.startswith("----"):
param_start = True
continue
if not param_start:
continue
match_grp = attr_line_re.match(line)
if not match_grp:
continue
type_ = match_grp.group('type')
name = match_grp.group('name')
default = match_grp.group('default')
attr = AAttrDef(name, type_, default)
node_def.attrs.append(attr)
# Print final output.
for node_def in sorted(node_defs, key=lambda n: n.name):
print("{} ({})".format(node_def.name, node_def.type))
for attr in sorted(node_def.attrs, key=lambda a: a.name):
print(" {} {} {}".format(attr.name, attr.type, attr.default))
Et voilà, du pâté. :enerve:
Vous pouvez copier-coller ça et voir ce que ça donne. Mais nous savons tous que ce qui anime vos vies, c’est le besoin de comprendre les choses, pas vrai ? :baffed:
Explication
Alors c’est parti !
from __future__ import (absolute_import,
division,
print_function,
unicode_literals)
Houdini étant en pleine transition vers Python 3, ce bloc permet de garder un peu de cohérence entre Python 2 et 3.
Ensuite nous avons deux classes :
class ANodeDef(object):
def __init__(self, name, type, rest=None):
self.name = name
self.type = type
self.rest = rest
self.attrs = []
def __repr__(self):
return "{}('{}', '{}')".format(self.__class__.__name__,
self.name,
self.type)
class AAttrDef(object):
def __init__(self, name, type, default):
self.name = name
self.type = type
self.default = default
def __repr__(self):
return "{}('{}', '{}', '{}')".format(self.__class__.__name__,
self.name,
self.type,
self.default)
Ces deux classes sont des dataclass (classes de données). L’idée étant de stocker des données dans des objets spécifiques pour éviter d’utiliser des dictionnaires ou des namedtuple (mais vous pouvez utiliser l’un et l’autre si vous êtes pressé).
J’implémente souvent les __repr__
sur mes dataclass pour visualiser rapidement les objets dans le debugger ou via des print()
.
ANodeDef
stockera les informations (nom et type) liées à chaque nœud.AAttrDef
stockera les informations (nom, type et valeur par défaut) liées à chaque paramètre de nœuds.- Les objets
AAttrDef
seront mis dans la listeattrs
de chaqueANodeDef
.
Ça nous donnera, grosso modo, une hiérarchie sous la forme :
ANodeDef
name: alembic
type: shape
rest: (procedural)
attrs: [
AAttrDef
name: invert_normals
type: BOOL
default: false
AAttrDef
name: ray_bias
type: FLOAT
default: 1e-06
...
...
Beaucoup de prise de tête pour rien, me rétorqueriez-vous, « Utilise des dicos ! Ça va plus vite, le code c’est quand même plus beau quand ça fait un max de choses en une seule ligne, gnagnagna… ».
Ne perdez pas votre temps en débats stériles, vous avez déjà perdu, alors avançons :
# Print Arnold version.
process = subprocess.Popen(['kick', '--help'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
first_line = out.split("\n")[0]
print(first_line)
Cette partie est la plus surement la plus simple à comprendre : On lance kick --help
et on affiche uniquement la première ligne, celle qui affiche la version :
Arnold 7.1.4.1 [c989b21f] linux x86_64 clang-10.0.1 oiio-2.4.1 osl-1.12.0 vdb-7.1.1 adlsdk-7.4.2.47 clmhub-3.1.1.43 rlm-14.2.5 optix-6.6.0 2022/11/29 11:24:49
La suite :
# Get node list.
process = subprocess.Popen(['kick', '-nodes'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
node_line_re = re.compile(r"\s(?P<name>\w+)\s+(?P<type>\w+)(?P<rest>.*)")
node_defs = []
for line in out.split("\n"):
match_grp = node_line_re.match(line)
if not match_grp:
continue
name = match_grp.group('name')
type_ = match_grp.group('type')
rest = match_grp.group('rest')
node = ANodeDef(name, type_, rest)
node_defs.append(node)
Ça commence à devenir intéressant. On lance (via subprocess.Popen()
) la commande kick -nodes
qui va nous lister tous les nœuds disponibles, et on interprète chaque ligne pour en extraire les informations qu’on stocke dans un objet ANodeDef
qu’on ajoute à la liste globale node_defs
.
Chaque ligne ressemblera à :
node type information_supplémentaires
Par exemple :
...
add shader
alembic shape (procedural)
complex_ior shader [deprecated]
...
L’expression régulière node_line_re
s’occupe d’attraper le contenu de chaque ligne :
\s (?P<name>\w+) \s+ (?P<type>\w+) (?P<rest>.*)
1 espace|un mot appelé name|1 ou plusieurs espaces|un mot appelé type|le reste, optionnel appelé rest
Une fois qu’on a cette liste de nœuds, on va lancer la ligne de commande kick -info
pour chaque nœud et attraper le résultat, c’est ce que fait le bloc de code suivant :
# For each node, get its default parameters.
attr_line_re = re.compile(r"(?P<type>\w+)\s+(?P<name>\w+)\s+(?P<default>.*)")
for node_def in node_defs:
process = subprocess.Popen(['kick', '-info', node_def.name],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
param_start = False
for line in out.split("\n"):
if line.startswith("----"):
param_start = True
continue
if not param_start:
continue
match_grp = attr_line_re.match(line)
if not match_grp:
continue
type_ = match_grp.group('type')
name = match_grp.group('name')
default = match_grp.group('default')
attr = AAttrDef(name, type_, default)
node_def.attrs.append(attr)
Le code est plus verbeux, mais on va expliquer tout ça. La sortie de la commande kick -info
ressemble à ça :
Type Name Default
------------ -------------------------------- --------------------------------
BYTE visibility 255
BYTE sidedness 255
BOOL receive_shadows true
BOOL self_shadows true
On a donc besoin d’une expression régulière que je ne détaille pas, car elle est assez similaire à la précédente. :redface:
La seule subtilité c’est qu’on attend (via la variable param_start
) d’avoir passé la ligne commençant par ----
avant de commencer à parser chaque paramètre.
Après tous les if
passé, on récupère les valeurs de l’expression régulière, on fabrique un objet AAttrDef
et on l’ajoute à la liste des attributs du nœud qu’on inspecte. :reflechi:
Et maintenant qu’on a notre petite hiérarchie, on affiche le tout en triant par nom :
# Print final output.
for node_def in sorted(node_defs, key=lambda n: n.name):
print("{} ({})".format(node_def.name, node_def.type))
for attr in sorted(node_def.attrs, key=lambda a: a.name):
print(" {} {} {}".format(attr.name, attr.type, attr.default))
Cela nous donne ça :
options (options)
AA_adaptive_threshold FLOAT 0.015
AA_sample_clamp FLOAT 1e+30
AA_sample_clamp_affects_aovs BOOL false
etc.
À vous de l’afficher comme vous l’entendez ! :banaeyouhou:
Conclusion
Python, c’est bien, il faut faire du Python, faites du Python.
:marioCours: