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_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 _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 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 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_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 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_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_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_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_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_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 _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 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 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(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 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 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 _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_invalid_template_raises(self): s = Sequence.create("8-12") template = "image.26.exr" with self.assertRaises(ValueError): s.expand(template)
def test_spec_single_number(self): s = Sequence.create( 10) self.assertEqual(s.to(":","%",";"), "10")
def test_spec_range(self): s = Sequence.create( 0, 10 ) self.assertEqual(s.to(":","%",";"), "0:10")
def setUp(self): self.seq = Sequence.create("1-10") self.node = hou.node("job1")
def test_bad_spec_step(self): with self.assertRaises(ValueError): Sequence.create("1-10xf")
def test_expand_single_hash(self): s = Sequence.create("8-12") template = "image.#.exr" result = s.expand(template) self.assertIn("image.8.exr", result) self.assertIn("image.12.exr", result)
def test_expand_many_captures(self): s = Sequence.create("8-12") template = "/some/directory_###/image.#####.exr" result = s.expand(template) self.assertIn("/some/directory_012/image.00012.exr", result) self.assertIn("/some/directory_008/image.00008.exr", result)
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_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_progressive_list_is_progression(self): s = Sequence.create([1, 3, 5, 7, 9]) self.assertIsInstance(s, Progression)
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_expand_padded_hash(self): s = Sequence.create("8-12") template = "image.#####.exr" result = s.expand(template) self.assertIn("image.00008.exr", result) self.assertIn("image.00012.exr", result)
def test_spec_step_range(self): s = Sequence.create(1, 9, 2) self.assertEqual(s.to(":","%",";"), "1:9%2")
def test_bad_spec(self): with self.assertRaises(ValueError): Sequence.create("f")
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 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_strings(self): s = Sequence.create("1-5") self.assertEqual(s.start, 1) self.assertEqual(s.end, 5) self.assertEqual(s.step, 1)