10#include "FgSerial.hpp"
12#include "FgTestUtils.hpp"
16#include "FgGuiApi.hpp"
17#include "Fg3dMeshIo.hpp"
19#include "FgSystem.hpp"
20#include "FgAnthropometry.hpp"
21#include "FgRender.hpp"
24#pragma warning(disable:4996)
33String8 formatDsfPath(String8
const & str)
35 String8 slash = str.replace(
'\\',
'/');
36 return replaceCharWithString(slash,
char32_t(
' '),String8(
"%20"));
39string dateTimeISO8601()
43 char buff[
sizeof "2019-10-22T08:06:00Z"];
44 strftime(buff,
sizeof buff,
"%FT%TZ",gmtime(&now));
49 String8
const & scmPathBase,
50 String8
const & outPathBase,
51 Sam3Coord
const & coord,
52 Bytes
const & detailEnc,
55 PushIndent op(pathToName(scmPathBase).m_str);
57 Scm3 scm(scmPathBase);
58 time.report(
"SCM load");
59 ImgRgba8 detail = scm.transformDetail(detailEnc);
60 time.report(
"Detail transform");
61 ImgRgba8 colorMap = scm.applyFaceCoord(coord.ts(1,0),detail);
62 time.report(
"SCM application");
63 String ext = getImgFormatInfo(imgFormat).extensions.at(0);
64 saveImage(colorMap,outPathBase+
"."+ext);
65 time.report(
"Save image");
68void createCustomBodyMap(
69 String8
const & basePathName,
71 String8
const & outPathBase,
74 PushIndent op(pathToName(basePathName).m_str);
76 ImgRgba8 baseImg = loadImage(basePathName);
77 ImgRgba8 shiftImg = applyColorFac(baseImg,colorFac);
78 String ext = getImgFormatInfo(imgFormat).extensions.at(0);
79 saveImage(shiftImg,outPathBase+
"."+ext);
85 String8
const & scmPathBase,
86 String8
const & dazPath,
87 String8
const & outPathBase,
88 Sam3Coord
const & coord,
89 Bytes
const & detailBlob,
90 TattooImage
const & tattooImage,
91 Arr3D renderFac={1,1,1})
95 Scm3 scm {scmPathBase};
96 ImgUC faceFade = toUC(loadImage(scmPathBase+
"_fade.jpg"));
98 ImgRgba8 scmMap = scm.calcScmColorMap(coord);
100 ImgRgba8 dazImg = loadImage(dazPath);
102 ImgRgba8 detail = scm.transformDetail(detailBlob);
104 ImgUC skinSampleRegion = getSkinSampleRegion(scm.detailXfm);
105 Arr3D colorFac = calcColorFac(dazImg,scmMap,skinSampleRegion),
106 facegenFac = mapMul(renderFac,colorFac);
109 tattooImage(scm.detailXfm,scmMap);
112 ImgRgba8 detailMap = applyColorFac(imgModulate(scmMap,detail),renderFac);
114 ImgRgba8 dazMapAdj = applyColorFac(dazImg,facegenFac),
115 blendMap = imgBlend(detailMap,dazMapAdj,faceFade);
117 saveImage(blendMap,outPathBase+
".jpg");
122ostream & operator<<(ostream & os,IdxVec3F
const & v)
124 os <<
" [ " << v.idx <<
", "
131void dazSaveDsfMorphV6(
132 String8
const & libPath,
133 String8
const & relPath,
134 String8
const & controlName,
135 String8
const & parent,
136 string const & modifierNodeId,
138 IdxVec3Fs
const & morph,
140 Arr<Vec3F,2> eyeCentreDeltas)
142 if (group.m_str[0] !=
'/')
143 group.m_str.insert(group.m_str.begin(),
'/');
144 Ofstream ofs(libPath+relPath+controlName+
".dsf");
145 String8 controlID =
"FG"+controlName,
146 parentFmt = formatDsfPath(parent);
151 ofs.setf(ios::fixed);
153 "file_version" : "0.6.0.0",
155 "id" : "/)" << formatDsfPath(relPath+controlName) << R"(.dsf",
158 "author" : "FaceGen",
160 "website" : "https://facegen.com"
163 "modified" : ")" << dateTimeISO8601() << R"("
165 "modifier_library" : [
167 "id" : ")" << controlID << R"(",
168 "name" : ")" << controlID << R"(",
169 "parent" : ")" << parentFmt << modifierNodeId << R"(",
171 "type" : "Modifier/Shape",
175 "colors" : [ [ 0.1568627, 0.1568627, 0.1568627 ], [ 1, 1, 1 ] ]
181 "label" : ")" << controlName << R"(",
186 "display_as_percent" : true,
191 "group" : ")" << group << R"(",
193 Strings coords {"x",
"y",
"z"};
194 for (
size_t ss=0; ss<2; ++ss) {
195 String eye = (ss==0) ?
"lEye" :
"rEye";
196 for (uint dd=0; dd<3; ++dd) {
199 "output" : ")" << eye << ":" << parentFmt << eye <<
"?center_point/" << coords[dd] << R
"(",
201 { "op" : "push", "url" : ")" << modifierNodeId << ":#" << controlID << R
"(?value" },
202 { "op" : "push", "val" : )" << eyeCentreDeltas[ss][dd] << R"( },
206 if ((ss==0) || (dd<2))
213 "vertex_count" : )" << totNumVerts << R"(,
215 "count" : )" << morph.size() << R"(,
219 for (
size_t ii=1; ii<morph.size(); ++ii)
220 ofs <<
",\n" << morph[ii];
230 "id" : ")" << controlID << R"(-1",
231 "url" : "#)" << controlID << R"("
240Svec<DazGenesis> cDazGenesisInfo()
243 {DazGenesisVer::G1,
"1"},
244 {DazGenesisVer::G2,
"2"},
245 {DazGenesisVer::G3,
"3"},
246 {DazGenesisVer::G8,
"8"},
247 {DazGenesisVer::G81,
"81"},
248 {DazGenesisVer::G9,
"9"},
252ostream & operator<<(ostream & os,DazGenesisVer ver)
254 return os << findFirst(cDazGenesisInfo(),ver).str;
257Strings dazGenesisBodyMapNames(DazGenesisVer ver)
259 map<DazGenesisVer,Strings> verMaps {
260 {DazGenesisVer::G1,{
"limbs",
"torso"}},
261 {DazGenesisVer::G2,{
"limbs",
"torso"}},
262 {DazGenesisVer::G3,{
"arms",
"legs",
"torso"}},
263 {DazGenesisVer::G8,{
"arms",
"legs",
"torso"}},
264 {DazGenesisVer::G81,{
"arms",
"body",
"head",
"legs"}},
266 return verMaps.at(ver);
269String dazRelMorphDir(DazGenesisVer ver,
bool female)
271 map<DazGenesisVer,string> morphVerDir {
272 {DazGenesisVer::G1,
"Genesis"},
273 {DazGenesisVer::G2,
"Genesis 2"},
274 {DazGenesisVer::G3,
"Genesis 3"},
275 {DazGenesisVer::G8,
"Genesis 8"},
276 {DazGenesisVer::G81,
"Genesis 8"},
278 bool isV1 = (ver == DazGenesisVer::G1);
279 string genderStr = isV1 ?
"" : (female ?
"Female" :
"Male"),
280 morphGenderVer = (ver == DazGenesisVer::G81 ?
" 8_1" :
""),
281 morphGenderDir = isV1 ?
"Base" : genderStr;
282 return "data/DAZ 3D/" + morphVerDir[ver] +
"/" + morphGenderDir + morphGenderVer +
"/";
286static String8s getContentDirs(String8
const registryPath)
290 Opt<ulong> numDirs = getWinRegistryUlong(registryPath,
"NumContentDirs");
291 if (!numDirs.has_value())
295 String8 nameRoot =
"ContentDir";
296 for (ulong ii=0; ii<numDirs.value(); ++ii) {
297 String8 name = nameRoot + toStr(ii);
298 Opt<String8> dirOpt = getWinRegistryString(registryPath,name);
299 if (dirOpt.has_value()) {
300 String8 dirSlash = toNativeDirSep(dirOpt.value()+
"/");
301 if (!contains(ret,dirSlash))
302 ret.push_back(dirSlash);
308String8s getDazContentDirs()
310 String8s releaseDirs = getContentDirs(
"Software\\Daz\\Studio4");
313 String8s betaDirs = getContentDirs(
"Software\\Daz\\Studio4PublicBuild");
315 for (String8
const & dir : cat(releaseDirs,betaDirs)) {
316 if (!contains(ret,dir)) {
324String8 getDazContentDirContaining(String
const & relPath)
326 String8s cds = getDazContentDirs();
327 for (String8
const & cd : cds) {
328 if (pathExists(cd+relPath))
334String8 getDazConnectDirContaining(String
const & relDir)
336 auto findFn = [&](String
const & registryPath)
338 Opt<String8> cdo = getWinRegistryString(registryPath,
"CloudDir");
339 if (cdo.has_value()) {
340 String8
const & cd = cdo.value();
341 DirContents dcs = getDirContents(cd+
"/data/cloud/");
342 String8s vers = sortAll(select(dcs.dirnames,[](String8
const & d){return d.beginsWith(
"1_"); }));
343 for (
auto it = vers.rbegin(); it != vers.rend(); ++it) {
344 String8 ret = cd+
"/data/cloud/"+*it+
"/";
345 String8 path = ret + relDir;
346 if (pathExists(path))
352 String8 ret = findFn(
"Software\\Daz\\Studio4");
354 ret = findFn(
"Software\\Daz\\Studio4PublicBuild");
361 String8
const & contentDir,
362 Sam3Coord
const & coord,
363 Bytes
const & detail,
365 String8
const & group,
366 String8
const & controlName,
367 String8s
const & baseMapNames,
368 WorkerCallback
const & fnUpdate,
369 EmbossMesh
const & embossMesh,
370 TattooImage
const & tattooImage)
373 map<DazGenesisVer,string> fullVerStr {
374 {DazGenesisVer::G1,
""},
375 {DazGenesisVer::G2,
"2"},
376 {DazGenesisVer::G3,
"3"},
377 {DazGenesisVer::G8,
"8"},
378 {DazGenesisVer::G81,
"8_1"},
380 map<DazGenesisVer,string> nodeIdVerStr {
381 {DazGenesisVer::G1,
""},
382 {DazGenesisVer::G2,
""},
383 {DazGenesisVer::G3,
"3"},
384 {DazGenesisVer::G8,
"8"},
385 {DazGenesisVer::G81,
"8_1"},
387 bool isV1 = (ver == DazGenesisVer::G1);
388 string verStr = toStr(ver),
389 genderStr = isV1 ?
"" : (female ?
"Female" :
"Male"),
390 genderChar = female ?
"F" :
"M",
391 morphRelDir = dazRelMorphDir(ver,female),
392 genFileBase =
"Genesis" + fullVerStr[ver] + genderStr;
393 string parent =
"/" + morphRelDir + genFileBase +
".dsf#",
394 modifierNodeId =
"Genesis" + nodeIdVerStr[ver] + (isV1 ?
"" : genderStr);
395 String8 dd = dataDir() +
"artist/daz/Genesis" + verStr + genderChar +
"/";
396 Mesh dazShape = loadTri(dd+
"allVerts_eyeSurfs.tri"),
397 fgShape = loadTri(dd+
"fit_allVerts_eyeSurfs.tri");
398 FGASSERT(dazShape.verts.size() == fgShape.verts.size());
399 String8 ssmName = sphericalEyes ?
"fit_allVerts.egm" :
"fit_allVerts_eyeSurfs.egm";
400 Ssm3 ssm(fgShape.verts,dd+ssmName);
401 Vec3Fs idShape = ssm.applyCoord(coord);
402 Vec3Fs dazEyeCentres = loadTri(dd+
"eyeCentres.tri").verts;
403 Ssm3 fgEyeCentres(dd+
"fit_eyeCentres");
404 Vec3Fs eyeDeltas = fgEyeCentres.applyCoord(coord) - dazEyeCentres;
405 Arr<Vec3F,2> eyeCentreDeltas {eyeDeltas[0],eyeDeltas[1]};
409 Mesh fgShapeFace = loadTri(dd+
"tex_face.tri");
410 embossMesh(loadFim(dd+
"tex_face.fim"),fgShapeFace);
411 idShape += (fgShapeFace.verts - fgShape.verts);
414 for (
size_t ii=0; ii<idShape.size(); ++ii) {
415 Vec3F delta = idShape[ii] - dazShape.verts[ii];
416 if (delta.magD() > 0.0f)
417 morph.emplace_back(scast<uint>(ii),delta);
419 string morphDir = morphRelDir +
"Morphs/FaceGen/";
420 createPath(contentDir+morphDir);
421 dazSaveDsfMorphV6(contentDir,morphDir,controlName,parent,modifierNodeId,idShape.size(),morph,group,eyeCentreDeltas);
422 if (baseMapNames.empty())
425 if (fnUpdate && fnUpdate(
true))
427 Strings bodyMapNames = dazGenesisBodyMapNames(ver);
428 FGASSERT(baseMapNames.size() == bodyMapNames.size()+1);
429 String8 outPath = contentDir+
"Runtime/Textures/FaceGen/Genesis"+verStr+genderChar+
"/"+controlName+
"/";
431 outPath += controlName +
"_";
432 Arr3D colorFac = createFaceMap(dd+
"tex_face",baseMapNames[0],outPath+
"face",coord,detail,tattooImage);
433 if (fnUpdate && fnUpdate(
true))
435 exportScmMap(dd+
"tex_eyes",outPath+
"eyes",coord,detail,ImgFormat::jpg);
436 for (
size_t ii=0; ii<bodyMapNames.size(); ++ii) {
437 if (fnUpdate && fnUpdate(
true))
439 createCustomBodyMap(baseMapNames[ii+1],colorFac,outPath+bodyMapNames[ii],ImgFormat::jpg);
443void dazSaveDsfMorphV6G9(
444 String8
const & libPath,
445 String8
const & relPath,
446 String8
const & controlName,
447 String8
const & parent,
449 IdxVec3Fs
const & morph,
451 Arr<Vec3F,2> eyeCentreDeltas,
452 Arr<float,2> eyeScaleDeltas)
454 String8 fname = libPath+relPath+controlName+
".dsf";
455 Ofstream ofs {fname};
456 String8 controlID =
"FaceGen_"+controlName;
461 ofs.setf(ios::fixed);
463 "file_version" : "0.6.0.0",
465 "id" : "/)" << formatDsfPath(relPath+controlName) << R"(.dsf",
468 "author" : "FaceGen",
470 "website" : "https://facegen.com"
473 "modified" : ")" << dateTimeISO8601() << R"("
475 "modifier_library" : [
477 "id" : ")" << controlID << R"(",
478 "name" : ")" << controlID << R"(",
479 "parent" : ")" << parent << R"(",
481 "type" : "Modifier/Shape",
485 "colors" : [ [ 0.1568627, 0.1568627, 0.1568627 ], [ 1, 1, 1 ] ]
491 "label" : ")" << controlName << R"(",
496 "display_as_percent" : true,
501 "group" : ")" << group << R"(",
503 if (cMagD(eyeCentreDeltas) > 0) {
504 Strings coords {
"x",
"y",
"z"};
505 for (
size_t ss=0; ss<2; ++ss) {
506 String eye = (ss==0) ?
"l_eye" :
"r_eye";
507 for (uint dd=0; dd<3; ++dd) {
510 "output" : ")" << eye << ":/data/Daz%203D/Genesis%209/Base/Genesis9.dsf#" << eye <<
"?center_point/" << coords[dd] << R
"(",
512 { "op" : "push", "url" : "Genesis9:#)" << controlID << R"(?value" },
513 { "op" : "push", "val" : )" << eyeCentreDeltas[ss][dd] << R"( },
522 if (cMagD(eyeScaleDeltas) > 0) {
523 for (
size_t ss=0; ss<2; ++ss) {
524 String eye = (ss==0) ?
"l_eye" :
"r_eye";
527 "output" : ")" << eye << ":/data/Daz%203D/Genesis%209/Base/Genesis9.dsf#" << eye << R
"(?scale/general",
529 { "op" : "push", "url" : "Genesis9:#)" << controlID << R"(?value" },
530 { "op" : "push", "val" : )" << eyeScaleDeltas[ss] << R"( },
541 "vertex_count" : )" << totNumVerts << R"(,
543 "count" : )" << morph.size() << R"(,
547 for (
size_t ii=1; ii<morph.size(); ++ii)
548 ofs <<
",\n" << morph[ii];
558 "id" : ")" << controlID << R"(-1",
559 "url" : "#)" << controlID << R"("
564 fgout << fgnl << fname;
567void dazExportShapeG9(
568 Sam3Coord
const & coord,
569 String8
const & contentDir,
570 String8
const & group,
571 String8
const & name,
572 IdxVec3Fs
const & headDeltas)
574 String8 dd = dataDir() +
"artist/daz/Genesis9/";
575 Arr<Vec3F,2> eyeCentreDeltas;
576 Arr<float,2> eyeScaleDeltas;
578 Mesh lmsMesh = loadMesh(dd+
"lms.fgmesh");
579 FaceLmPos3Fs lmsBase = toFaceLmPoss(lmsMesh.markedVertsAsNameVecs());
580 Ssm3 ssm {dd+
"nrr-lms"};
581 Vec3Fs lmsTarg = ssm.applyCoord(coord);
582 Arr<ScaleTrans3F,2> xfs = cEyesTransform(lmsBase,lmsTarg);
583 Mesh eyeL = loadMesh(dd+
"eyeL.fgmesh"),
584 eyeR = loadMesh(dd+
"eyeR.fgmesh");
592 for (
size_t ss=0; ss<2; ++ss) {
593 Mesh
const & eye = (ss==0) ? eyeL : eyeR;
594 Vec3F eyeCentreB = eye.joints[0].pos,
595 eyeCentreT = xfs[ss] * eyeCentreB;
596 eyeCentreDeltas[ss] = eyeCentreT - eyeCentreB;
597 eyeScaleDeltas[ss] = xfs[ss].scale - 1;
601 Vec3Fs base = loadMesh(dd+
"base.fgmesh").verts;
602 size_t V = base.size();
603 Ssm3 ssm {dd+
"nrr-base"};
604 Vec3Fs targ = cHead(ssm.applyCoord(coord),V);
605 accDeltaMorph_(headDeltas,targ);
606 IdxVec3Fs morph = toIndexedDeltaMorph(targ-base,0);
607 String8 relDir =
"data/daz 3D/genesis 9/base/morphs/FaceGen/",
608 parent =
"/data/Daz%203D/Genesis%209/Base/Genesis9.dsf#Genesis9-1";
609 createPath(contentDir+relDir);
610 dazSaveDsfMorphV6G9(contentDir,relDir,name,parent,V,morph,group,eyeCentreDeltas,eyeScaleDeltas);
613 Vec3Fs base = loadMesh(dd+
"mouth.fgmesh").verts;
614 size_t V = base.size();
615 Ssm3 ssm {dd+
"nrr-mouth"};
616 Vec3Fs targ = cHead(ssm.applyCoord(coord),V);
617 IdxVec3Fs morph = toIndexedDeltaMorph(targ-base,0);
618 String8 relDir =
"data/DAZ 3D/Genesis 9/Genesis 9 Mouth/Morphs/FaceGen/",
619 parent =
"/data/Daz%203D/Genesis%209/Genesis%209%20Mouth/Genesis9Mouth.dsf#Genesis9Mouth-1";
620 createPath(contentDir+relDir);
621 dazSaveDsfMorphV6G9(contentDir,relDir,name,parent,V,morph,group,{},{});
625Strings dazGetMapPathsG9(String8
const & materialDuf,Strings
const & parts)
627 Strings partSrcPaths (parts.size());
629 Any duf = parseJson(loadRawString(materialDuf));
630 JsonObject
const & obj = duf.ref<JsonObject>();
631 Anys
const & lib = findFirst(obj,
"image_library").val.as<Anys>();
632 for (
size_t ii=0; ii<lib.size(); ++ii) {
633 Any
const & node = lib[ii];
634 JsonObject
const & map = node.as<JsonObject>();
635 String
const &
id = findFirst(map,
"id").val.as<String>();
636 for (
size_t pp=0; pp<parts.size(); ++pp) {
637 String ss =
"_" + parts[pp] +
"_D_";
638 if (containsSubstr(
id,ss)) {
639 Anys
const & mm = findFirst(map,
"map").val.as<Anys>();
640 JsonObject
const & mo = mm.at(0).as<JsonObject>();
641 partSrcPaths[pp] = findFirst(mo,
"url").val.as<String>();
649void saveImagePrint(ImgRgba8
const & img,String8
const & fname)
651 saveImage(img,fname);
652 fgout << fgnl << fname;
654void copyFilePrint(String8
const & from,String8
const & to)
656 copyFile(from,to,
true);
660void dazExportColorG9(
661 Sam3Coord
const & coord,
662 Bytes
const & detail,
663 String8
const & contentDir,
664 String8s
const & baseMapPaths,
665 String8
const & group,
666 String8
const & name,
668 WorkerCallback
const & updateFn,
669 TattooImage
const & tattoo)
671 FGASSERT(baseMapPaths.size() == 5);
672 if (updateFn && updateFn(
true))
675 String8 imgRelDir =
"Runtime/Textures/FaceGen/Genesis9/" + name +
"/",
676 imgSaveDir = contentDir + imgRelDir;
677 createPath(imgSaveDir);
678 String8 ddg9 = dataDir()+
"artist/daz/Genesis9/",
679 headPath = baseMapPaths[0];
680 FGASSERT(!headPath.empty());
681 String8 imgSaveDirBase = imgSaveDir +
"Head";
682 Arr3D
const irayFac = adjustIray ? Arr3D{1,1.2,1.2} : Arr3D{1,1,1};
683 Arr3D colorFac = createFaceMap(ddg9+
"nrr-head",headPath,imgSaveDirBase,coord,detail,tattoo,irayFac);
685 copyFilePrint(imgSaveDirBase+
".jpg",imgSaveDirBase+
"-SSS.jpg");
686 Strings parts {
"Head",
"Arms",
"Body",
"Legs"};
687 for (
size_t pp=1; pp<4; ++pp) {
688 if (updateFn && updateFn(
true))
690 String part = parts[pp];
691 String8 baseMapPath = baseMapPaths[pp];
692 FGASSERT(!baseMapPath.empty());
695 imgSaveDirBase = imgSaveDir+part;
696 ImgRgba8 baseImg = loadImage(baseMapPath);
697 ImgRgba8 adjImg = applyColorFac(baseImg,colorFac);
698 saveImagePrint(adjImg,imgSaveDir+part+
".jpg");
699 copyFilePrint(imgSaveDirBase+
".jpg",imgSaveDirBase+
"-SSS.jpg");
702 if (updateFn && updateFn(
true))
706 Sam3 sam {dataDir()+
"photofit/si"};
707 sam.transform(cIbfcsToHcsi());
708 sam.applyFace(Face3{coord,detail});
709 ImgUC fgIrisSample = toUC(loadImage(dataDir()+
"photofit/si-iris-sample.png")),
710 g9IrisRegion = toUC(loadImage(ddg9+
"eyes-iris-region.png"));
711 ImgRgba8 g9EyesMap = loadImage(baseMapPaths[4]);
712 Arr3D clrFg = sampleColor(sam.mesh.surfaces[0].albedoMapRef(),fgIrisSample),
713 clrFgAdj = mapMul(clrFg,irayFac),
714 clrG9 = sampleColor(g9EyesMap,g9IrisRegion),
715 clrDel = clrFgAdj-clrG9;
716 shiftColor_(clrDel,g9IrisRegion,g9EyesMap);
717 saveImagePrint(g9EyesMap,imgSaveDir+
"Eyes.jpg");
720 String8 dufRelPathBase =
"People/Genesis 9/Materials" + group +
"/" + name;
721 JsonObject contributor {
722 {
"author",Any{String{
"FaceGen"}}},
723 {
"email",Any{String{}}},
724 {
"website",Any{String{
"FaceGen.com"}}},
726 JsonObject asset_info {
727 {
"id",Any{
"/"+encodeUrl(dufRelPathBase)+
".duf"}},
728 {
"type",Any{String{
"preset_material"}}},
729 {
"contributor",Any{contributor}},
730 {
"revision",Any{String{
"1.0"}}},
731 {
"modified",Any{getDateTimeString()}},
734 String idBase =
"FG-G9-" + encodeUrl(name) +
"-";
735 auto addImageFn = [&](String
const & part)
737 String
id = idBase + part,
738 url =
"/" + encodeUrl(imgRelDir + part +
".jpg");
746 {
"map_gamma",Any{0.0}},
747 {
"map",Any{Anys{Any{ul}}}},
749 image_library.push_back(Any{ile});
751 for (String
const & part : parts) {
753 addImageFn(part+
"-SSS");
757 auto addLinkFn = [&](String
const & partRef,String
const & imgName)
759 String url =
"name://@selection#materials/" + partRef +
"/image_file",
760 path =
"/" + encodeUrl(imgRelDir+imgName+
".jpg");
761 Any key {Anys{Any{0.0},Any{path}}};
767 animations.push_back(Any{anim});
769 for (String
const & part : parts) {
770 addLinkFn(part+
":?diffuse",part);
771 addLinkFn(part+
":?extra/studio_material_channels/channels/Translucency%20Color",part+
"-SSS");
773 addLinkFn(
"Eye%20Left:?diffuse",
"Eyes");
774 addLinkFn(
"Eye%20Right:?diffuse",
"Eyes");
776 {
"animations",Any{animations}},
779 {
"file_version",Any{String{
"0.6.1.0"}}},
780 {
"asset_info",Any{asset_info}},
781 {
"image_library",Any{image_library}},
782 {
"scene",Any{scene}},
784 createPath(contentDir+dufRelPathBase);
786 String8 fname = contentDir+dufRelPathBase+
".duf";
787 saveRaw(writeJson(Any{duf}),fname);
788 fgout << fgnl << fname;
791 ImgRgba8 thumb = renderSoft({91U,91U},{sam.mesh},{0,0,0,1});
792 saveImagePrint(thumb,contentDir+dufRelPathBase+
".png");