diff --git a/bam/blend/blendfile_pack.py b/bam/blend/blendfile_pack.py index ca5a3d5..6882081 100755 --- a/bam/blend/blendfile_pack.py +++ b/bam/blend/blendfile_pack.py @@ -119,6 +119,9 @@ def pack( # converting: '../../bar' --> '__/__/bar' # so all paths are nested and not moved outside the session path. blendfile_src_dir_fakeroot=None, + + # Read variations from json files. + use_variations=True, ): """ :param deps_remap: Store path deps_remap info as follows. @@ -181,6 +184,12 @@ def pack( """ filepath = blendfile_path_walker.utils.compatpath(filepath) + if use_variations: + if blendfile_levels_dict_curr: + filepath = blendfile_levels_dict_curr.get(filepath, filepath) + + # ... + # first remap this blend file to the location it will end up (so we can get images relative to _that_) # TODO(cam) cache the results fp_basedir_conv = _relpath_remap(os.path.join(rootdir, b'dummy'), base_dir_src, base_dir_src, blendfile_src_dir_fakeroot)[0] @@ -197,6 +206,70 @@ def pack( path_temp_files.add(filepath_tmp) return filepath_tmp + # ----------------- + # Variation Support + # + # Use a json file to allow recursive-remapping of variations. + # + # file_a.blend + # file_a.json '{"variations": ["tree.blue.blend", ...]}' + # file_a.blend -> file_b.blend + # file_b.blend --> tree.blend + # + # the variation of `file_a.blend` causes `file_b.blend` + # to link in `tree.blue.blend` + + if use_variations: + blendfile_levels = [] + blendfile_levels_dict = [] + blendfile_levels_dict_curr = {} + + def blendfile_levels_rebuild(): + # after changing blend file configurations, + # re-create current variation lookup table + blendfile_levels_dict_curr.clear() + for d in blendfile_levels_dict: + if d is not None: + blendfile_levels_dict_curr.update(d) + + # use variations! + def blendfile_level_cb_enter(filepath): + import json + + filepath_json = os.path.splitext(filepath)[0] + b".json" + if os.path.exists(filepath_json): + with open(filepath_json, encoding='utf-8') as f_handle: + variations = [f.encode("utf-8") for f in json.load(f_handle).get("variations")] + # convert to absolute paths + basepath = os.path.dirname(filepath) + variations = { + # Reverse lookup, from non-variation to variation we specify in this file. + # {"/abs/path/foo.png": "/abs/path/foo.variation.png", ...} + # .. where the input _is_ the variation, + # we just make it absolute and use the non-variation as + # the key to the variation value. + b".".join(f.rsplit(b".", 2)[0::2]): f for f_ in variations + for f in (os.path.normpath(os.path.join(basepath, f_)),) + } + else: + variations = None + + blendfile_levels.append(filepath) + blendfile_levels_dict.append(variations) + + if variations: + blendfile_levels_rebuild() + + def blendfile_level_cb_exit(filepath): + blendfile_levels.pop() + blendfile_levels_dict.pop() + + if blendfile_levels_dict_curr: + blendfile_levels_rebuild() + else: + blendfile_level_cb_enter = blendfile_level_cb_exit = None + blendfile_levels_dict_curr = None + lib_visit = {} fp_blend_basename_last = b'' @@ -207,6 +280,10 @@ def pack( recursive=True, recursive_all=all_deps, lib_visit=lib_visit, + blendfile_level_cb=( + blendfile_level_cb_enter, + blendfile_level_cb_exit, + ) ): # we could pass this in! @@ -222,6 +299,15 @@ def pack( path_src = blendfile_path_walker.utils.abspath(path_rel, fp.basedir) path_src = os.path.normpath(path_src) + # apply variation (if available) + if use_variations: + if blendfile_levels_dict_curr: + path_src_variation = blendfile_levels_dict_curr.get(path_src) + if path_src_variation is not None: + path_src = path_src_variation + path_rel = os.path.join(os.path.dirname(path_rel), os.path.basename(path_src)) + del path_src_variation + # destination path realtive to the root # assert(b'..' not in path_src) assert(b'..' not in base_dir_src) @@ -238,7 +324,6 @@ def pack( path_dst_final = b'//' + path_dst_final fp.filepath = path_dst_final - # add to copy-list # never copy libs (handled separately) if not isinstance(fp, blendfile_path_walker.FPElem_block_path) or fp.userdata[0].code != b'LI': diff --git a/bam/blend/blendfile_path_walker.py b/bam/blend/blendfile_path_walker.py index e49c5d7..6fafd57 100644 --- a/bam/blend/blendfile_path_walker.py +++ b/bam/blend/blendfile_path_walker.py @@ -19,7 +19,8 @@ # ***** END GPL LICENCE BLOCK ***** import os -VERBOSE = os.environ.get('BAM_VERBOSE', False) +# gives problems with scripts that use stdout, for testing 'bam deps' for eg. +# VERBOSE = os.environ.get('BAM_VERBOSE', False) TIMEIT = False @@ -199,10 +200,17 @@ class FilePath: # prevents cyclic references too! # {lib_path: set([block id's ...])} lib_visit=None, + + # optional blendfile callbacks + # These callbacks run on enter-exit blend files + # so you can keep track of what file and level you're at. + blendfile_level_cb=(None, None), ): # print(level, block_codes) import os + filepath = os.path.abspath(filepath) + if VERBOSE: indent_str = " " * level # print(indent_str + "Opening:", filepath) @@ -212,7 +220,12 @@ class FilePath: log_deps.info("%s%s" % (indent_str, filepath.decode('utf-8'))) log_deps.info("%s%s" % (indent_str, set_as_str(block_codes))) - basedir = os.path.dirname(os.path.abspath(filepath)) + blendfile_level_cb_enter, blendfile_level_cb_exit = blendfile_level_cb + + if blendfile_level_cb_enter is not None: + blendfile_level_cb_enter(filepath) + + basedir = os.path.dirname(filepath) if rootdir is None: rootdir = basedir @@ -416,8 +429,13 @@ class FilePath: rootdir=rootdir, level=level + 1, lib_visit=lib_visit, + blendfile_level_cb=blendfile_level_cb, ) + if blendfile_level_cb_exit is not None: + blendfile_level_cb_exit(filepath) + + # ------------------------------------------------------------------------ # Direct filepaths from Blocks # diff --git a/bam/cli.py b/bam/cli.py index 2dacfd1..dbaffea 100755 --- a/bam/cli.py +++ b/bam/cli.py @@ -818,7 +818,7 @@ class bam_commands: print("]") else: for f_src, f_dst, f_dst_abs, f_status in status_walker(): - print(" %r -> (%r = %r) %s" % (f_src, f_dst, f_dst_abs, f_status))ia + print(" %r -> (%r = %r) %s" % (f_src, f_dst, f_dst_abs, f_status)) @staticmethod def pack( diff --git a/tests/blends/variations/cone.red.blend b/tests/blends/variations/cone.blend similarity index 53% rename from tests/blends/variations/cone.red.blend rename to tests/blends/variations/cone.blend index 713b6ee..70a93aa 100644 Binary files a/tests/blends/variations/cone.red.blend and b/tests/blends/variations/cone.blend differ diff --git a/tests/blends/variations/cone.blue.blend b/tests/blends/variations/cone.blue.blend index d9db754..5e24216 100644 Binary files a/tests/blends/variations/cone.blue.blend and b/tests/blends/variations/cone.blue.blend differ diff --git a/tests/blends/variations/lib_endpoint.blend b/tests/blends/variations/lib_endpoint.blend index 0f68dad..9022dfb 100644 Binary files a/tests/blends/variations/lib_endpoint.blend and b/tests/blends/variations/lib_endpoint.blend differ diff --git a/tests/blends/variations/lib_user.blend b/tests/blends/variations/lib_user.blend index 312666d..a4d9e74 100644 Binary files a/tests/blends/variations/lib_user.blend and b/tests/blends/variations/lib_user.blend differ diff --git a/tests/test_cli.py b/tests/test_cli.py index 8a5f75e..a050192 100755 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -860,6 +860,66 @@ class BamCheckoutTest(BamSessionTestCase): # checkout inside of the existing session, should raise exception self.assertRaises(RuntimeError, bam_run, ["checkout", file_name, "--output", session_path], session_path) + def test_checkout_variation(self): + session_name = "mysession" + proj_path, session_path = self.init_session(session_name) + variation_path = os.path.join(session_path, "variations") + + if 1: + import shutil + # path cant already exist, ugh + shutil.copytree( + os.path.join(CURRENT_DIR, "blends", "variations"), + variation_path, + ) + stdout, stderr = bam_run(["commit", "-m", "test message"], session_path) + self.assertEqual("", stderr) + + + listing = bam_run_as_json(["ls", "variations", "--json"], session_path) + listing_expect = [ + ["cone.blend", "file"], + ["cone.blue.blend", "file"], + ["lib_endpoint.blend", "file"], + ["lib_user.blend", "file"], + ] + + self.assertEqual(listing, listing_expect) + + f_variation = os.path.join(variation_path, "lib_user.json") + + # now create variation file & commit it + file_quick_write( + f_variation, + data='{"variations": ["cone.blue.blend"]}', + ) + + stdout, stderr = bam_run(["commit", "-m", "add variation"], session_path) + self.assertEqual("", stderr) + + listing = bam_run_as_json(["ls", "variations", "--json"], session_path) + listing_expect.append(["lib_user.json", "file"]) + listing_expect.sort() + self.assertEqual(listing, listing_expect) + + # now clear the repo and do a fresh checkout, and see that the variation is applied. + # remove the path + shutil.rmtree(session_path) + + # checkout the file again + file_name = "variations/lib_endpoint.blend" + session_path = session_path + "_a" + stdout, stderr = bam_run(["checkout", file_name, "--output", session_path], proj_path) + self.assertEqual("", stderr) + + ret = bam_run_as_json(["deps", "lib_endpoint.blend", "--json", "--recursive"], session_path) + ret.sort() + + self.assertEqual(ret[0][1], "//lib_user.blend") + self.assertEqual(ret[0][3], "OK") + self.assertEqual(ret[1][1], "//cone.blue.blend") + self.assertEqual(ret[1][3], "OK") + class BamUpdateTest(BamSessionTestCase): """Test for the `bam update` command. @@ -1356,54 +1416,6 @@ class BamRelativeAbsoluteTest(BamSessionTestCase): # self.assertEqual(ret[0], ["house_rel.blend", "file"]) -class BamVariation(BamSessionTestCase): - """ - """ - def __init__(self, *args): - self.init_defaults() - super().__init__(*args) - - def test_variation(self): - """ - """ - - session_name = "mysession" - proj_path, session_path = self.init_session(session_name) - variation_path = os.path.join(session_path, "variations") - - if 1: - import shutil - # path cant already exist, ugh - shutil.copytree( - os.path.join(CURRENT_DIR, "blends", "variations"), - variation_path, - ) - stdout, stderr = bam_run(["commit", "-m", "test message"], session_path) - self.assertEqual("", stderr) - - - listing = bam_run_as_json(["ls", "variations", "--json"], session_path) - self.assertEqual( - listing, - [["cone.blue.blend", "file"], - ["cone.red.blend", "file"], - ["lib_endpoint.blend", "file"]], - ["lib_user.blend", "file"]], - ) - - f_variation = os.path.join(variation_path, "lib_user.json") - - # now create variation file & commit it - file_quick_write( - session_path, - f, - "", - append=True) - - - # import time; time.sleep(10000000) - - class BamIgnoreTest(BamSessionTestCase): """Checks out a project, creates a .bamignore file with a few rules and tries to commit files that violate them.