def _union_sequence(images): if not images: return None rng = image_range(images[0]) seq = Sequence.create(*rng) for image in images[1:]: rng = image_range(image) other = Sequence.create(*rng) seq = seq.union(other) return seq
def test_glob_dedups_when_many_files_match(self): glob.populate(Sequence.create("1-20").expand("/some/file.####.exr")) d = PathList() files = ["/some/file.*.exr", "/some/*.exr"] d.add(*files) d.glob() self.assertEqual(len(d), 20)
def test_glob_when_files_match_with_questoion_mark(self): glob.populate(Sequence.create("1-20").expand("/some/file.####.exr")) d = PathList() file = "/some/file.00?0.exr" d.add(file) d.glob() self.assertEqual(len(d), 2)
def test_self_glob_when_files_match(self): glob.populate(Sequence.create("1-20").expand("/some/file.####.exr")) d = DependencyList() file = "/some/file.*.exr" d.add(file, must_exist=False) d.glob() self.assertEqual(len(d), 20)
def test_glob_when_files_dont_match(self): glob.populate(Sequence.create("1-20").expand("/other/file.####.exr")) d = PathList() file = "/some/file.*.exr" d.add(file) d.glob() self.assertEqual(len(d), 0)
def test_two_the_same_substitution(self): template = "/path/%(frame)d/image.%(frame)04d.tif" result = list(Sequence.permutations(template, frame="0-20x2")) self.assertIn("/path/8/image.0008.tif", result) self.assertIn("/path/12/image.0012.tif", result) self.assertEqual(len(result), 11)
def scout_frame_sequence(node): """Generate Sequence from value in scout_frames parm.""" try: spec = node.parm("scout_frames").eval() return Sequence.create(spec) except (ValueError, TypeError): return None
def test_counts_from_1_to_10(self): s = Sequence.create("1-10") ss = s.subsample(1) self.assertEqual(len(ss), 1) self.assertEqual(list(ss), [6]) ss = s.subsample(2) self.assertEqual(len(ss), 2) self.assertEqual(list(ss), [3, 8]) ss = s.subsample(3) self.assertEqual(len(ss), 3) self.assertEqual(list(ss), [2, 6, 9]) ss = s.subsample(4) self.assertEqual(len(ss), 4) self.assertEqual(list(ss), [2, 4, 7, 9]) ss = s.subsample(5) self.assertEqual(len(ss), 5) self.assertEqual(list(ss), [2, 4, 6, 8, 10]) ss = s.subsample(6) self.assertEqual(len(ss), 6) self.assertEqual(list(ss), [1, 3, 5, 6, 8, 10]) ss = s.subsample(7) self.assertEqual(len(ss), 7) self.assertEqual(list(ss), [1, 3, 4, 6, 7, 8, 10]) ss = s.subsample(8) self.assertEqual(len(ss), 8) self.assertEqual(list(ss), [1, 2, 4, 5, 6, 7, 9, 10]) ss = s.subsample(9) self.assertEqual(len(ss), 9) self.assertEqual(list(ss), [1, 2, 3, 4, 6, 7, 8, 9, 10]) ss = s.subsample(10) self.assertEqual(len(ss), 10) self.assertEqual(list(ss), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) ss = s.subsample(11) self.assertEqual(len(ss), 10) self.assertEqual(list(ss), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
def test_resolve_one_template_to_many_filenames(self): s = Sequence.create("1-5") template = "image.{frame:02d}.exr" result = s.expand_dollar_f(template) self.assertEqual(len(result), 5) self.assertIn("image.02.exr", result) self.assertIn("image.05.exr", result)
def _get_sources(self): """ Get the images/layers, along with associated Sequence objects. If we are not rendering a custom range, then the sequence for each image may be different. Returns: list of dict: elements contain an image along with the Sequence that represents the image range. """ images = ix.api.OfObjectArray() self.node.get_attribute("images_and_layers").get_values(images) use_custom = self.node.get_attribute("use_custom_frames").get_bool() # cast to list because OfObjectArray is true even when empty. if not list(images): ix.log_error( "No render images. Please reference one or more image items") seq = self.sequence["main"] result = [] for image in images: if not use_custom: seq = Sequence.create(*frames_ui.image_range(image)) result.append({"image": image, "sequence": seq}) return result
def scout_frame_sequence(obj): """Generate Sequence from value in scout_frames attribute.""" try: spec = obj.get_attribute("scout_frames").get_string() return Sequence.create(spec) except (ValueError, TypeError): return None
def test_one_substitution(self): template = "image.%(frame)04d.tif" result = list(Sequence.permutations(template, frame="0-20x2")) self.assertIn("image.0008.tif", result) self.assertIn("image.0012.tif", result) self.assertEqual(len(result), 11)
def test_three_substitutions(self): template = "image_%(uval)02d_%(vval)02d.%(frame)04d.tif" kw = {"uval": "1-2", "vval": "1-2", "frame": "10-11"} result = list(Sequence.permutations(template, **kw)) self.assertIn("image_01_01.0010.tif", result) self.assertIn("image_02_02.0011.tif", result) self.assertIn("image_02_01.0010.tif", result) self.assertEqual(len(result), 8)
def test_chunk_count(self): s = Sequence.create("1-100") s.chunk_size = 7 self.assertEqual(s.chunk_count(), 15) s.chunk_size = 15 self.assertEqual(s.chunk_count(), 7) s.chunk_size = 10 self.assertEqual(s.chunk_count(), 10)
def test_best_chunk_size(self): s = Sequence.create("1-100") s.chunk_size = 76 self.assertEqual(s.best_chunk_size(), 50) s.chunk_size = 37 self.assertEqual(s.best_chunk_size(), 34) s.chunk_size = 100 self.assertEqual(s.best_chunk_size(), 100)
def test_include_parts_of_sequence_that_exist(self): seq = Sequence.create("96-105") result = [ f for f in dependency_scan.fetch( self.node, seq, 3) if f.startswith("/path/to/shader3")] self.assertIn("/path/to/shader3/tex.0097.jpg", result) self.assertEqual(len(result), 5)
def test_create_chunks_cycle(self): s = Sequence.create("1-100") s.chunk_size = 10 s.chunk_strategy = "cycle" chunks = s.chunks() self.assertEqual(list(chunks[0]), list(range(1, 100, 10))) s.chunk_size = 7 chunks = s.chunks() self.assertEqual(list(chunks[0]), list(range(1, 100, 15)))
def test_include_parts_of_sequence_that_exist(self): seq = Sequence.create("96-105") result = [ f for f in dependency_scan.fetch(self.node, seq, 3) if f.startswith("/path/to/shader3") ] self.assertIn("/path/to/shader3/tex.0097.jpg", result) self.assertEqual(len(result), 5)
def test_resolve_many_templates_to_many_filenames(self): s = Sequence.create("1-3") templates = [ "/folder_1/image.$2F.exr", "/folder_2/image.$2F.exr", "/folder_3/image.$2F.exr" ] result = s.expand_dollar_f(*templates) self.assertEqual(len(result), 3) self.assertIn("/folder_2/image.02.exr", result) self.assertIn("/folder_3/image.03.exr", result)
def initialize(data): """Generate list of files from fixture data.""" global FILES for item in data["files"]: if item.get("params"): for fn in Sequence.permutations(item["path"], **item["params"]): FILES.append(fn) else: FILES.append(item["path"]) FILES = sorted(set(FILES))
def _attribute_sequence(attr, **kw): """Get the sequence associated with a filename attribute. Many attributes have an associated sequence_mode attribute, which when set to 1 signifies varying frames, and makes availabel start, end, and offset attributes to help specify the sequence. If the keyword intersector is given, then work out the intersection with it. Why? Because during dependency scanning, we can optimize the number of frames to upload if we use only those frames specified in the sequence attribute, and intersect them with the frame range specified in the job node. """ intersector = kw.get("intersector") obj = attr.get_parent_object() mode_attr = obj.attribute_exists("sequence_mode") if not (mode_attr and mode_attr.get_long()): ix.log_error("Attribute is not a sequence mode") global_frame_rate = ix.application.get_prefs( ix.api.AppPreferences.MODE_APPLICATION).get_long_value( "animation", "frames_per_second") attr_frame_rate = obj.get_attribute("frame_rate").get_long() if not attr_frame_rate == global_frame_rate: ix.log_error( "Can't get attribute sequence when global \ fps is different from fps on the attribute") start = obj.get_attribute("start_frame").get_long() end = obj.get_attribute("end_frame").get_long() if intersector: # If there's a frame offset on the attribute, then we need to # do the intersection in the context of that offset. offset = obj.get_attribute("frame_offset").get_long() return Sequence.create(start, end, 1).offset( offset).intersection(intersector).offset(-offset) return Sequence.create(start, end, 1)
def range_frame_sequence(node): """Generate Sequence from value in the standard range parmTuple.""" try: chunk = _chunk_parameters(node) start, end, step = node.parmTuple("fs").eval() return Sequence.create( start, end, step, chunk_size=chunk["size"], chunk_strategy=chunk["strategy"]) except (ValueError, TypeError): return None
def custom_frame_sequence(obj): """Generate Sequence from the value in custom_frames attribute.""" try: spec = obj.get_attribute("custom_frames").get_string() seq = Sequence.create( spec, chunk_size=obj.get_attribute("chunk_size").get_long() ) return seq except (ValueError, TypeError): return None
def _union_sequence(images): """ Computes the Sequence that is the union of frame ranges of all provided images. Args: images (list of OfObject): The images to consider. Returns: Sequence: The union Sequence. """ if not images: return None rng = image_range(images[0]) seq = Sequence.create(*rng) for image in images[1:]: rng = image_range(image) other = Sequence.create(*rng) seq = seq.union(other) return seq
def custom_frame_sequence(node): """Generate Sequence from value in custom_range parm.""" try: spec = node.parm("custom_range").eval() chunk = _chunk_parameters(node) return Sequence.create( spec, chunk_size=chunk["size"], chunk_strategy=chunk["strategy"] ) except (ValueError, TypeError): return None
def validate_scout_range(node, **_): """Set valid tickmark for scout range spec. TODO Currently we only validate that the spec produces valid frames. We should also validate that at least one scout frame exist in the main frame range. """ takes.enable_for_current(node, "scout_valid") spec = node.parm("scout_frames").eval() valid = Sequence.is_valid_spec(spec) node.parm("scout_valid").set(valid) update_frame_stats_message(node)
def test_three_substitutions(self): template = "image_%(uval)02d_%(vval)02d.%(frame)04d.tif" kw = { "uval": "1-2", "vval": "1-2", "frame": "10-11" } result = list(Sequence.permutations(template, **kw)) self.assertIn("image_01_01.0010.tif", result) self.assertIn("image_02_02.0011.tif", result) self.assertIn("image_02_01.0010.tif", result) self.assertEqual(len(result), 8)
def scout_frame_sequence(obj): """ Generate Sequence from value in scout_frames attribute. Args: obj (ConductorJob): Item whose attribute to get parameters from. Returns: Sequence: Sequence that represents scout frames. """ try: spec = obj.get_attribute("scout_frames").get_string() return Sequence.create(spec) except (ValueError, TypeError): return None
def _get_sequence(self): """Create the sequence object. In a simulation job, there is no need to duplicate the frame range section in the conductor::job node. Therefore it is hidden, and instead the frame range comes directly from the driver node. Scout frames will be None. """ start, end, step = [ self._source["node"].parm(parm).eval() for parm in [ 'f1', 'f2', 'f3'] ] sequence = Sequence.create(start, end, step) return { "main": sequence, "scout": None }
def validate_custom_range(node, **_): """Set valid tickmark for custom range spec. A custom range is valid when it is a comma separated list of arithmetic progressions. These can can be formatted as single numbers or ranges with a hyphen and optionally a step value delimited by an x. Example, 1,7,10-20,30-60x3,1001, Spaces and trailing commas are allowed, but not letters or other non numeric characters. """ takes.enable_for_current(node, "custom_valid") spec = node.parm("custom_range").eval() valid = Sequence.is_valid_spec(spec) node.parm("custom_valid").set(valid) update_frame_stats_message(node) uistate.update_button_state(node)
def custom_frame_sequence(obj): """ Generates the custom_frames sequence. Returns: Sequence: A Sequence object that represents the value in custom_frames. It may be a comma-separated list of progressions. Example 1,7,10-20,30-60x3,1001. """ try: spec = obj.get_attribute("custom_frames").get_string() seq = Sequence.create( spec, chunk_size=obj.get_attribute("chunk_size").get_long() ) return seq except (ValueError, TypeError): return None
def initialize(data): """Generate nodes, types, and files from fixture data.""" global NODES global NODE_TYPES global FILES global GVARS GVARS.update(data["gvars"]) type_names = list(set([n["type"] for n in data["nodes"]])) for t in type_names: NODE_TYPES[t] = NodeType(t) for item in data["nodes"]: NODES[item["name"]] = Node(item) for item in data["files"]: if item.get("params"): for fn in Sequence.permutations(item["path"], **item["params"]): FILES.append({"path": fn, "type": item["type"]}) else: FILES.append(item)
def _get_sources(self): """Get the images, along with associated Sequence objects. If we are not rendering a custom range, then the sequence for each image may be different. """ images = ix.api.OfObjectArray() self.node.get_attribute("images").get_values(images) use_custom = self.node.get_attribute("use_custom_frames").get_bool() # cast to list because OfObjectArray is true even when empty. if not list(images): ix.log_error( "No render images. Please reference one or more image items") seq = self.sequence["main"] result = [] for image in images: if not use_custom: seq = Sequence.create(*frames_ui.image_range(image)) result.append({"image": image, "sequence": seq}) return result
def test_create_from_start_end_strings(self): s = Sequence.create("1-5") self.assertEqual(s.start, 1) self.assertEqual(s.end, 5) self.assertEqual(s.step, 1)
def test_spec_range(self): s = Sequence.create( 0, 10 ) self.assertEqual(s.to(":","%",";"), "0:10")
def test_spec_step_range(self): s = Sequence.create(1, 9, 2) self.assertEqual(s.to(":","%",";"), "1:9%2")
def test_complex_add_spaces(self): s = Sequence.create( "1-10, 14, 20-48x4") self.assertEqual(s.to(":","%","; "), "1:10; 14; 20:48%4")
def test_is_progression_method(self): s = Sequence.create([1, 3, 5, 7, 9]) self.assertTrue(s.is_progression()) s = Sequence.create([1, 3, 5, 7, 9, 10]) self.assertFalse(s.is_progression())
def test_negative_number(self): with self.assertRaises(ValueError): Sequence.create(-1)
def test_negative_end(self): with self.assertRaises(ValueError): Sequence.create(1, -10, 1)
def test_negative_step(self): with self.assertRaises(ValueError): Sequence.create(1, 10, -1)
def test_negative_end_str(self): with self.assertRaises(ValueError): Sequence.create("10--30")
def test_bad_spec(self): with self.assertRaises(ValueError): Sequence.create("f")
def test_bad_spec_step(self): with self.assertRaises(ValueError): Sequence.create("1-10xf")
def test_create_from_start_only(self): s = Sequence.create(1) self.assertEqual(s.start, 1) self.assertEqual(s.end, 1) self.assertEqual(s.step, 1)
def test_create_from_start_end_ints(self): s = Sequence.create(1, 5) self.assertEqual(s.start, 1) self.assertEqual(s.end, 5) self.assertEqual(s.step, 1)
def setUp(self): self.seq = Sequence.create("1-10") self.node = hou.node("job1")