Skip to content

Commit

Permalink
feat(nuke): pack-project: supports file name expression
Browse files Browse the repository at this point in the history
  • Loading branch information
NateScarlet committed Mar 24, 2023
1 parent b7e20b3 commit 00b8573
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 4 deletions.
4 changes: 2 additions & 2 deletions typings/_nuke.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1804,7 +1804,7 @@ class EvalString_Knob(String_Knob):
x.__init__(...) initializes x; see help(type(x)) for signature
"""
...
def evaluate(self) -> six.binary_type:
def evaluate(self, frame: int = ...) -> six.binary_type:
"""
Evaluate the string, performing substitutions.
Expand Down Expand Up @@ -1866,7 +1866,7 @@ class File_Knob(EvalString_Knob):
@return: None.
"""
...
def getEvaluatedValue(self, *args, **kwargs):
def getEvaluatedValue(self, oc: OutputContext = ...) -> bytes:
"""
self.getValue(oc) -> String.
Expand Down
65 changes: 64 additions & 1 deletion wulifang/_util/_file_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Text, Optional, Iterable
from typing import Text, Optional, Iterable, Iterator

import re
from wulifang.vendor.six.moves import xrange
import os
from ._iteritems import iteritems


def _iter_possible_frames(s):
Expand All @@ -18,6 +20,21 @@ def _iter_possible_frames(s):
yield int(s[start:end])


def _iter_possible_frame_placeholder(s):
# type: (Text) -> Text
if s.startswith("0"):
yield "%%0%dd" % (len(s),)
yield "#" * len(s)
return

yield "%d"
yield "#"
for i in range(2, len(s) + 1):
yield "%%%dd" % (i,)
yield "%%0%dd" % (i,)
yield "#" * i


class FileSequence(object):
_frame_placeholder_pattern = re.compile(r"%0?\d*d|#+")
_frame_value_pattern = re.compile(r"\d+")
Expand Down Expand Up @@ -71,3 +88,49 @@ def repl(match):
return m % (frame,)

return cls._frame_placeholder_pattern.sub(repl, expr)

@classmethod
def from_paths(cls, paths, frame_count_gt=0):
# type: (Iterable[Text], int) -> Iterator[FileSequence]
m = {} # type: dict[Text, set[int]]
for path in paths:
dirname, basename = os.path.split(path)
if path not in m:
m[path] = set()
for match in cls._frame_value_pattern.finditer(basename):
frame_text = match.group(0)
frame = int(frame_text)
for placeholder in _iter_possible_frame_placeholder(frame_text):
key = os.path.join(
dirname,
"%s%s%s"
% (
basename[: match.start(0)],
placeholder,
basename[match.end(0) :],
),
).replace("\\", "/")
if key not in m:
m[key] = set()
m[key].add(frame)

used_path = set() # type: set[Text]

for k, v in sorted(iteritems(m), key=lambda x: (len(x[1]) == 1, -len(x[1]), "#" not in x[0], x[0])): # type: ignore
k = k # type: Text
v = v # type: set[int]
if len(v) == 0 and k not in used_path:
yield FileSequence(k)
used_path.add(k)
continue

frame_count = 0
for frame in v:
path = FileSequence.expand_frame(k, frame)
if path in used_path:
continue
frame_count += 1
used_path.add(path)

if frame_count > frame_count_gt:
yield FileSequence(k, min(v), max(v))
39 changes: 39 additions & 0 deletions wulifang/_util/_file_sequence_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,42 @@ def test_expand_frame(expr, frame, expected):
def test_contains(expr, first, last, item, expected):
# type: (Text, Optional[int], Optional[int], Text, bool) -> None
assert (item in FileSequence(expr, first, last)) == expected


@pytest.mark.parametrize(
("paths", "expected"),
[
(("image.1.png", "image.2.png"), (("image.#.png", 1, 2),)),
(("image.1.png", "image.2.png", "image.3.png"), (("image.#.png", 1, 3),)),
(("example.txt",), (("example.txt", None, None),)),
(("example_v1.txt",), (("example_v1.txt", None, None),)),
(("image.0001.png", "image.0002.png"), (("image.####.png", 1, 2),)),
(("image.0001.png", "image.0002.png"), (("image.####.png", 1, 2),)),
(
(
"example.1001.exr",
"example.1002.exr",
"example.1003.exr",
),
(
(
"example.####.exr",
1001,
1003,
),
),
),
(
("shot001/image.0001.png", "shot001/image.0002.png"),
(("shot001/image.####.png", 1, 2),),
),
],
)
def test_from_paths(paths, expected):
# type: (tuple[Text, ...], tuple[tuple[Text,Optional[int], Optional[int]], ...]) -> None
got = list(FileSequence.from_paths(paths))
assert len(got) == len(expected)
for index in range(len(expected)):
assert got[index].expr == expected[index][0]
assert got[index].first == expected[index][1]
assert got[index].last == expected[index][2]
29 changes: 28 additions & 1 deletion wulifang/nuke/_pack_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,33 @@ def log(self, msg):
wulifang.message.info(msg)
self._log(msg)

def get_src(self, k):
# type: (nuke.File_Knob) -> Text
input = cast_text(k.getText())
if not input:
return ''
try:
file_sequence = next(
FileSequence.from_paths(
(
cast_text(k.evaluate(frame))
for frame in range(
nuke.Root().firstFrame(), nuke.Root().lastFrame() + 1
)
)
)
)
output = file_sequence.expr
if input != output:
self.log(
"%s.%s: 计算表达式:输入='%s', 输出='%s'"
% (k.node().fullName(), k.name(), input, output)
)
return output
except Exception as ex:
self.log("%s.%s: 无法处理表达式:'%s': %s" % (k.node().fullName(), k.name(), input, ex))
return input


def _hashed_dir(dir_path):
# type: (Text) -> Text
Expand Down Expand Up @@ -155,7 +182,7 @@ def pack_project():
for n in _iter_deep_all_nodes():
for _, k in iteritems(n.knobs()):
if isinstance(k, nuke.File_Knob):
old_src = cast_text(k.getText())
old_src = ctx.get_src(k)
if not old_src or old_src.startswith(ctx.file_dir_base):
continue
ctx.log("打包文件: %s.%s: %s" % (n.fullName(), k.name(), old_src))
Expand Down

0 comments on commit 00b8573

Please sign in to comment.