diff --git a/.gitignore b/.gitignore index d749bfa..634e5bf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,6 @@ data/assets/* data.orig data- data+ -local/* +local/*/ utilities/remote/bar utilities/remote/foo.m diff --git a/utilities/escapeShellArg.m b/utilities/escapeShellArg.m new file mode 100644 index 0000000..846bee7 --- /dev/null +++ b/utilities/escapeShellArg.m @@ -0,0 +1,4 @@ +function s = escapeShellArg(p) +% Minimal shell-arg escaping for POSIX shells +s = ['"' strrep(p, '"', '\"') '"']; +end \ No newline at end of file diff --git a/utilities/isSymlink.m b/utilities/isSymlink.m new file mode 100644 index 0000000..6a70322 --- /dev/null +++ b/utilities/isSymlink.m @@ -0,0 +1,13 @@ +function tf = isSymlink(p) +% Return true if p is a symbolic link (Unix/macOS). +% On Windows this returns false and we fall back to copyfile. +if ispc + tf = false; + return; +end +% test -L exits 0 if p is a symlink +[status, ~] = system(sprintf('test -L %s', escapeShellArg(p))); +tf = (status == 0); +end + + diff --git a/utilities/material/piMaterialText.m b/utilities/material/piMaterialText.m index fe15f8d..039bf36 100644 --- a/utilities/material/piMaterialText.m +++ b/utilities/material/piMaterialText.m @@ -10,7 +10,20 @@ p.addRequired('material', @isstruct); p.addRequired('thisR', @(x)(isa(x,'recipe'))); -p.addParameter('remoterender', false); +p.addParameter('remoterender', true); +% AJ: set default to true. +% Reason: +% - If being rendered locally (remoterender = false), +% it assumes the texture map is in the recipe's output dir. +% If not present, block 82-97 handles copying the texture map to output dir. +% - However, if the db is in a centralised location (not output dir), +% in which case we use a symlink to point to the db (for complex scenes), +% the texture map is likely also in the db, so this check would fail. +% It throws a Normal Map not found warning. +% - In remote rendering, we bypass this check, assuming the texture map exists remotely. +% - So, when remoterender = true, we skip the block 82-97, which works for symlinked db. +% - Ideally, we should do the file existence check in the db location +% through the symlink. p.parse(material, thisR, varargin{:}); diff --git a/utilities/pbrt/parse/parseBlockMaterial.m b/utilities/pbrt/parse/parseBlockMaterial.m index 1751b66..bc0fc19 100644 --- a/utilities/pbrt/parse/parseBlockMaterial.m +++ b/utilities/pbrt/parse/parseBlockMaterial.m @@ -40,7 +40,9 @@ end % Split the text line with ' "', '" ' and '"' to get key/val pair -thisLine = thisLine(~cellfun('isempty',thisLine)); +% trim each token + remove empty/whitespace-only cells +thisLine = cellfun(@strtrim, thisLine, 'uni', false); +thisLine = thisLine(~cellfun(@(s) isempty(s) || all(isspace(s)), thisLine)); % deal with mix material stringTypeIndex = find(contains(thisLine, 'string type')); diff --git a/utilities/pbrt/parse/parseGeometryText.m b/utilities/pbrt/parse/parseGeometryText.m index 4b3a0be..4a56b38 100644 --- a/utilities/pbrt/parse/parseGeometryText.m +++ b/utilities/pbrt/parse/parseGeometryText.m @@ -468,7 +468,13 @@ % subtree branches. if ~isempty(subtrees) rootAsset = piAssetCreate('type', 'branch'); - rootAsset.name = 'root_B'; + rootAsset.name = 'root_B'; % will be later renamed according to object + + % AJ: Higher level transforms should be saved in the object node + rootAsset.scale = scale; + rootAsset.rotation = rotation; + rootAsset.translation = translation; + trees = tree(rootAsset); % Graft each of the subtrees to the root node diff --git a/utilities/pbrt/parse/piParseObjectName.m b/utilities/pbrt/parse/piParseObjectName.m index 67020fd..47a564f 100644 --- a/utilities/pbrt/parse/piParseObjectName.m +++ b/utilities/pbrt/parse/piParseObjectName.m @@ -1,74 +1,55 @@ function [name, sz] = piParseObjectName(txt) % Parse an ObjectName string in 'txt' to extract the object name and size. -% -% Cinema4D produces a line with #ObjectName in it. The format of the -% #ObjectName line appears to be something like this: -% -% #ObjectName Plane:Vector(5000, 0, 5000) -% -% The only cases we have seen are NAME:Vector(X,Z,Y). Someone seems to -% know the meaning of these three values which are read into 'res' below. -% The length is 2*X, width is 2*Y and height is 2*Z. -% -% Perhaps these numbers should always be treated as in meters or maybe -% centimeters? We need to figure this out. For the slantedBar scene we -% had the example above, and we think the scene might be about 100 meters, -% so this would make sense. -% -% We do not have a routine to fill in these values for non-Cinema4D -% objects. - -% Find the location of one of these strings, such as #ObjectName patternList = {'#ObjectName','#object name','#CollectionName',... '#Instance MeshName','#MeshName','#Instance CollectionName','#Instance Parent'}; for ii = 1:numel(patternList) pattern = patternList{ii}; loc = strfind(txt,pattern); - if isempty(loc) - continue; - else + if ~isempty(loc) loc_dimension = strfind(txt,'#Dimension'); break; end end -% Look for a colon -% pos = strfind(txt,':'); if isempty(loc_dimension) - name = txt(loc(1)+length(pattern) + 1:end); + name = txt(loc(1)+length(pattern)+1:end); sz.l = []; sz.w = []; sz.h = []; else - name = txt(loc(1)+length(pattern) + 1:loc_dimension-1); + name = txt(loc(1)+length(pattern)+1:loc_dimension-1); posA = strfind(txt,'['); posB = strfind(txt,']'); - res = sscanf(txt(posA(1)+1:posB(1)-1),'%f'); - % Position minimima and maxima for lower left (X,Y), upper right. - sz.pmin = [-res(1)/2 -res(3)/2]; - sz.pmax = [res(1)/2 res(3)/2]; + raw = strtrim(txt(posA(1)+1:posB(1)-1)); - % We are not really sure what these coordinates represent with respect to - % the scene or the camera direction. For one case we analyzed (a plane) - % this is what the values meant. - sz.l = res(1); % length (X) - sz.w = res(2); % depth (Z) - sz.h = res(3); % height (Y) + if isempty(raw) + % No numbers inside the brackets → return empty sizes + sz.l = []; + sz.w = []; + sz.h = []; + else + res = sscanf(raw,'%f'); + if numel(res) < 3 + % Not enough numbers → treat as empty + sz.l = []; + sz.w = []; + sz.h = []; + else + % Assign values + sz.pmin = [-res(1)/2 -res(3)/2]; + sz.pmax = [ res(1)/2 res(3)/2]; + sz.l = res(1); + sz.w = res(2); + sz.h = res(3); + end + end end -% Remove quotes and spaces. +% Clean name name = erase(name,'"'); name = erase(name,' '); -% Consider this: -% -% If we only have the identifier, then treat name as empty. -% if isequal(name,'_B') || isequal(name,'_L') || isequal(name,'_O') -% name = ''; -% end - - end diff --git a/utilities/piWrite.m b/utilities/piWrite.m index 0f531fd..6384de5 100644 --- a/utilities/piWrite.m +++ b/utilities/piWrite.m @@ -152,9 +152,11 @@ if ~exist(workingDir,'dir') mkdir(workingDir); end -% Make a geometry directory -geometryDir = thisR.get('geometry dir'); -if ~exist(geometryDir, 'dir'), mkdir(geometryDir); end + +% AJ: geometry symlink is created in the write loop +% % Make a geometry directory +% geometryDir = thisR.get('geometry dir'); +% if ~exist(geometryDir, 'dir'), mkdir(geometryDir); end renderDir = thisR.get('rendered dir'); if ~exist(renderDir,'dir'), mkdir(renderDir); end @@ -303,23 +305,30 @@ function piWriteCopy(thisR,overwriteresources,overwritepbrtfile, verbosity) % Copy the spds and textures directory files. status = status && copyfile(fullfile(sources(i).folder, sources(i).name), fullfile(outputDir,sources(i).name)); elseif overwriteresources - if sources(i).isdir && (strcmpi(sources(i).name,'spds') || strcmpi(sources(i).name,'textures') || strcmpi(sources(i).name,'instanced')) - % Copy the spds and textures directory files. - status = status && copyfile(fullfile(sources(i).folder, sources(i).name), fullfile(outputDir,sources(i).name)); - else + % AJ: Lets use symlinks only. No copying + % if sources(i).isdir && (strcmpi(sources(i).name,'spds') || strcmpi(sources(i).name,'textures') || strcmpi(sources(i).name,'instanced')) + % % Copy the spds and textures directory files. + % status = status && copyfile(fullfile(sources(i).folder, sources(i).name), fullfile(outputDir,sources(i).name)); + % else % Selectively copy the files in the scene root folder [~, ~, extension] = fileparts(sources(i).name); % ChessSet needs input geometry because we can not parse it % yet. --zhenyi + thisSrc = fullfile(sources(i).folder, sources(i).name); + thisDst = fullfile(outputDir, sources(i).name); if ~(piContains(extension,'zip') || piContains(extension,'json')) - thisFile = fullfile(sources(i).folder, sources(i).name); - if verbosity > 1 - fprintf('Copying %s\n',thisFile) + if verbosity > 1, fprintf('Copying %s\n', thisSrc); end + if isSymlink(thisSrc) + % Recreate symlink in destination + [~, tgt] = system(sprintf('readlink %s', escapeShellArg(thisSrc))); + tgt = strtrim(tgt); + status = status && (system(sprintf('ln -sfn %s %s', ... + escapeShellArg(tgt), escapeShellArg(thisDst))) == 0); + else + status = status && copyfile(thisSrc, thisDst); end - status = status && copyfile(thisFile, fullfile(outputDir,sources(i).name)); - %status = status && system(sprintf('cp -r %s %s \n',thisFile, fullfile(outputDir,sources(i).name))); end - end + % end end end if(~status) diff --git a/utilities/recipe/piRecipeDefault.m b/utilities/recipe/piRecipeDefault.m index 2eb90c6..b37cf48 100644 --- a/utilities/recipe/piRecipeDefault.m +++ b/utilities/recipe/piRecipeDefault.m @@ -501,7 +501,8 @@ fname = fullfile(sceneDir,sceneFile); elseif ~strcmpi(fname, which(fname)) % Ignoring case. Might be a bad idea (BW). - error('File exists on your path, but not where expected.') + % AJ: throws error when pre-downloaded; commented out + % error('File exists on your path, but not where expected.') end %% If we are here, we found the file. So create the recipe. diff --git a/utilities/texture/piTextureCreate.m b/utilities/texture/piTextureCreate.m index fc47cdd..61bd938 100644 --- a/utilities/texture/piTextureCreate.m +++ b/utilities/texture/piTextureCreate.m @@ -229,8 +229,10 @@ texture.v2.type = 'vector3'; texture.v2.value = []; + % AJ: don't hardcode invert to false texture.invert.type = 'bool'; - texture.invert.value = 'false'; + texture.invert.value = []; + % texture.invert.value = 'false'; case 'checkerboard' texture.type = 'checkerboard'; diff --git a/utilities/texture/piTextureFileFormat.m b/utilities/texture/piTextureFileFormat.m index b652927..8653be6 100644 --- a/utilities/texture/piTextureFileFormat.m +++ b/utilities/texture/piTextureFileFormat.m @@ -38,10 +38,11 @@ texSlotName = textureList{ii}.filename.value; thisImgPath = fullfile(inputDir, texSlotName); - if ~exist(thisImgPath,'file') - % It could be the material presets - thisImgPath = which(texSlotName); - end + % % AJ: textures are symlinked, can't find on local path + % if ~exist(thisImgPath,'file') + % % It could be the material presets + % thisImgPath = which(texSlotName); + % end if isempty(find(strcmp(ext, {'.png','.PNG','.exr','.jpg'}),1)) if exist(thisImgPath, 'file') @@ -76,11 +77,17 @@ if contains(textureList{ii}.name,{'tex_'}) && ... exist(fullfile(inputDir, texSlotName),'file') && ... contains(textureList{ii}.name,{'.alphamap.'}) - - outputFile = fullfile(path,[name,'_alphamap.png']); - outputPath = fullfile(inputDir, outputFile); - [img, ~, alphaImage] = imread(thisImgPath); - + + % AJ: don't need to append _alphamap? + % for instance, a name was 'tlusfbvia_4K_Opacity_alphamap' + % outputFile = fullfile(path,[name,'_alphamap.png']); + outputFile = fullfile(path,[name, '.png']); + % AJ: the below hardcoding is due to local vs remote render setups + % Replace with your own texture directory as needed + texturesDir = '/acorn/data/iset/Resources/'; + outputPath = fullfile(texturesDir, outputFile); + [img, ~, alphaImage] = imread(outputPath); + if size(img,3)~=1 && isempty(alphaImage) && ~isempty(find(img(:,:,1) ~= img(:,:,2), 1)) disp('No alpha texture map is available.'); return; @@ -119,16 +126,19 @@ normalImgPath = thisMat.normalmap.value; thisMat.normalmap.type = 'string'; thisImgPath = fullfile(inputDir, normalImgPath); - - if ~exist(thisImgPath,'file') - % It could be the material presets - thisImgPath = which(normalImgPath); - end + + % % AJ: textures are symlinked, can't find on local path + % if ~exist(thisImgPath,'file') + % % It could be the material presets + % thisImgPath = which(normalImgPath); + % end if isempty(normalImgPath) continue; end - if exist(thisImgPath, 'file') && ~isempty(normalImgPath) + % % AJ: assume file exists (it doesn't locally; symlinked) + % if exist(thisImgPath, 'file') && ~isempty(normalImgPath) + if ~isempty(normalImgPath) [path, name, ext] = fileparts(pathToLinux(normalImgPath)); if strcmp(ext, '.exr') || strcmp(ext, '.png') || strcmp(ext, '.jpg') diff --git a/utilities/texture/piTextureText.m b/utilities/texture/piTextureText.m index 53f4005..bd25441 100644 --- a/utilities/texture/piTextureText.m +++ b/utilities/texture/piTextureText.m @@ -126,10 +126,11 @@ % in the base or in textures/*. If it does, we do not % need to copy it. - if ~isempty(getpref('ISETDocker','remoteHost')) && thisR.useDB - remoteSceneDir = getpref('ISETDocker','remoteSceneDir'); - texturePath = fullfile(remoteSceneDir,texturePath); - end + % AJ: we are using symlinks, no absolute path needed + % if ~isempty(getpref('ISETDocker','remoteHost')) && thisR.useDB + % remoteSceneDir = getpref('ISETDocker','remoteSceneDir'); + % texturePath = fullfile(remoteSceneDir,texturePath); + % end if exist(fullfile(oDir,thisVal),'file') % If the file is in the root of the scene, move it @@ -157,8 +158,9 @@ if ~isempty(getpref('ISETDocker','remoteHost'))&& thisR.useDB ... && ~strncmpi(thisVal,'/',1) % We trust that the texture will be there on the server - remoteFolder = fileparts(thisR.inputFile); - imgFile = fullfile(remoteFolder,'textures',thisVal); + % remoteFolder = fileparts(thisR.inputFile); % BUG + % AJ: writes full remote path + imgFile = fullfile('textures',thisVal); thisText = sprintf(' "%s %s" "%s" ',... thisType, textureParams{ii}, imgFile); else