Skip to content

Commit c385dac

Browse files
committed
add cpp generator for xthreejs autogenerates classes
1 parent 274198f commit c385dac

File tree

5 files changed

+724
-2
lines changed

5 files changed

+724
-2
lines changed

js/scripts/generate-wrappers.js

+349
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const baseDir = path.resolve(scriptDir, '..');
1313

1414
const jsSrcDir = path.resolve(baseDir, 'src/');
1515
const pySrcDir = path.resolve(baseDir, '..', 'pythreejs');
16+
const cppSrcDir = path.resolve(baseDir, '..', 'xthreejs/xthreejs');
17+
const cmakeSrcDir = path.resolve(baseDir, '..', 'xthreejs');
1618
const docSrcDir = path.resolve(baseDir, '..', 'docs', 'source', 'api');
1719
const templateDir = path.resolve(scriptDir, 'templates');
1820

@@ -99,6 +101,9 @@ const jsWrapperTemplate = compileTemplate('js_wrapper');
99101
const jsIndexTemplate = compileTemplate('js_index');
100102
const pyWrapperTemplate = compileTemplate('py_wrapper');
101103
const pyTopLevelInitTemplate = compileTemplate('py_top_level_init');
104+
const cppWrapperTemplate = compileTemplate('cpp_wrapper');
105+
const headerCppTemplate = compileTemplate('cppheader_wrapper');
106+
const cmakeListsTemplate = compileTemplate('cmake_wrapper');
102107
const docTemplate = compileTemplate('autodoc');
103108
const docIndexTemplate = compileTemplate('autodoc_index');
104109

@@ -227,6 +232,10 @@ function relativePathToPythonImportPath(relativePath) {
227232
return result;
228233
}
229234

235+
function camelCaseToUnderscore (str) {
236+
return str.replace(/([a-zA-Z])(?=[A-Z])/g, '$1_').toLowerCase()
237+
}
238+
230239
// Execute a function for each match to a glob query
231240
//
232241
// Parameters:
@@ -1059,6 +1068,322 @@ function createTopLevelPythonModuleFile() {
10591068

10601069
}
10611070

1071+
//
1072+
// Cpp wrapper writer
1073+
//
1074+
1075+
class CppWrapper {
1076+
1077+
constructor(modulePath, className) {
1078+
1079+
this.modulePath = modulePath;
1080+
this.dirRelativePath = path.dirname(modulePath);
1081+
this.destDirAbsolutePath = path.resolve(cppSrcDir, this.dirRelativePath);
1082+
this.destDirRelativeToBase = path.relative(this.destDirAbsolutePath, cppSrcDir);
1083+
1084+
this.basename = path.basename(modulePath, '.js');
1085+
1086+
if (className) {
1087+
this.className = className;
1088+
} else {
1089+
this.className = this.basename.replace(/\./g, '_');
1090+
const extraDefines = getExtraDefines(this.className);
1091+
extraDefines.forEach(function(extraClassName) {
1092+
createCppWrapper(modulePath, extraClassName);
1093+
});
1094+
}
1095+
1096+
this.cppDestPath = path.resolve(this.destDirAbsolutePath, this.getUnderscoreRep(this.className) + '.hpp');
1097+
this.cppAutoDestPath = path.resolve(this.destDirAbsolutePath, this.getUnderscoreRep(this.className) + '_' + AUTOGEN_EXT + '.hpp');
1098+
1099+
this.cppBaseRelativePath = path.relative(this.destDirAbsolutePath, cppSrcDir);
1100+
//this.cppBaseRelativePath = relativePathToPythonImportPath(this.cppBaseRelativePath);
1101+
1102+
// check if manual file exists
1103+
this.hasOverride = fse.existsSync(this.cppDestPath);
1104+
1105+
this.isCustom = CUSTOM_CLASSES.indexOf(modulePath) !== -1;
1106+
1107+
this.hasParameters = false;
1108+
1109+
this.config = getClassConfig(this.className);
1110+
1111+
this.processSuperClass();
1112+
this.processDependencies();
1113+
this.processProperties();
1114+
1115+
// Template and context
1116+
this.context = {
1117+
now: new Date(),
1118+
generatorScriptName: path.basename(__filename),
1119+
threejs_docs_url: this.docsUrl,
1120+
cpp_base_relative_path: this.cppBaseRelativePath,
1121+
constructor: {
1122+
args: this.constructorArgs,
1123+
hasParameters: this.hasParameters,
1124+
},
1125+
1126+
className: this.getUnderscoreRep(this.className, false),
1127+
xclassName: this.getUnderscoreRep(this.className),
1128+
header: 'XTHREE_' + this.getUnderscoreRep(this.className, false).toUpperCase() + '_HPP',
1129+
modelName: this.className + 'Model',
1130+
superClass: this.superClass,
1131+
properties: this.properties,
1132+
dependencies: this.dependencies,
1133+
hasOverride: this.hasOverride,
1134+
isCustom: this.isCustom,
1135+
};
1136+
1137+
if (this.className == 'ShapeGeometry')
1138+
{
1139+
console.log(this.context);
1140+
}
1141+
if (this.className == 'ParametricGeometry')
1142+
{
1143+
console.log(this.context);
1144+
}
1145+
// Render template
1146+
this.output = cppWrapperTemplate(this.context);
1147+
1148+
}
1149+
1150+
getUnderscoreRep(className, with_x=true) {
1151+
const REPLACE = {
1152+
'web_g_l': 'webgl'
1153+
};
1154+
1155+
let class_name;
1156+
_.mapObject(REPLACE, function(new_str, old_str) {
1157+
class_name = camelCaseToUnderscore(className);
1158+
class_name = class_name.replace(old_str, new_str);
1159+
});
1160+
return (with_x) ? 'x' + class_name : class_name;
1161+
}
1162+
1163+
getRequireInfoFromClassDescriptor(classDescriptor) {
1164+
1165+
const result = {};
1166+
1167+
if (typeof classDescriptor === 'string') {
1168+
1169+
if (classDescriptor in classConfigs) {
1170+
const config = getClassConfig(classDescriptor);
1171+
result.className = classDescriptor;
1172+
result.relativePath = config.relativePath;
1173+
let res = result.relativePath.split("/");
1174+
res[res.length - 1] = this.getUnderscoreRep(res[res.length - 1]);
1175+
result.relativePath = res.join("/");
1176+
} else {
1177+
result.className = path.basename(classDescriptor, '.js');
1178+
result.relativePath = classDescriptor;
1179+
}
1180+
1181+
} else {
1182+
throw new Error('invalid classDescriptor: ' + classDescriptor);
1183+
}
1184+
1185+
// get path of dependency relative to module dir
1186+
if (result.className == 'Three') {
1187+
result.relativePath = './base/xthree';
1188+
};
1189+
result.absolutePath = path.resolve(cppSrcDir, result.relativePath);
1190+
1191+
if (!fse.existsSync(result.absolutePath + '.hpp')) {
1192+
result.absolutePath += '_' + AUTOGEN_EXT;
1193+
}
1194+
1195+
result.requirePath = path.relative(this.destDirAbsolutePath, result.absolutePath);
1196+
//result.cppRelativePath = relativePathToPythonImportPath(result.requirePath);
1197+
result.cppRelativePath = result.requirePath;
1198+
1199+
return result;
1200+
1201+
}
1202+
1203+
processSuperClass() {
1204+
1205+
this.superClass = this.getRequireInfoFromClassDescriptor(this.config.superClass);
1206+
1207+
if (this.superClass.className === 'Three') {
1208+
this.superClass.className = 'three_widget';
1209+
}
1210+
this.superClass.className = this.getUnderscoreRep(this.superClass.className, false);
1211+
this.superClass.xclassName = this.getUnderscoreRep(this.superClass.className);
1212+
}
1213+
1214+
processDependencies() {
1215+
1216+
const dependencies = {};
1217+
1218+
// process explicitly listed dependencies
1219+
_.reduce(this.config.dependencies, function(result, depName) {
1220+
1221+
result[depName] = this.getRequireInfoFromClassDescriptor(depName);
1222+
return result;
1223+
1224+
}, dependencies, this);
1225+
1226+
// infer dependencies from any properties that reference other Three types
1227+
_.reduce(this.config.properties, function(result, prop) {
1228+
1229+
if (prop instanceof Types.ThreeType || prop instanceof Types.InitializedThreeType ||
1230+
prop instanceof Types.ThreeTypeArray || prop instanceof Types.ThreeTypeDict) {
1231+
if (prop.typeName !== 'this') {
1232+
if (typeof prop.typeName === 'string') {
1233+
let typeName = prop.typeName || './base/xthree.hpp';
1234+
result[typeName] = this.getRequireInfoFromClassDescriptor(typeName);
1235+
if (result[typeName].className === 'Three') {
1236+
result[typeName].className = 'ThreeWidget';
1237+
}
1238+
} else if (prop.typeName instanceof Array) {
1239+
prop.typeName.forEach(function(typeName) {
1240+
result[typeName] = this.getRequireInfoFromClassDescriptor(typeName);
1241+
}, this);
1242+
}
1243+
}
1244+
}
1245+
return result;
1246+
1247+
}, dependencies, this);
1248+
1249+
this.dependencies = dependencies;
1250+
1251+
}
1252+
1253+
processProperties() {
1254+
1255+
this.properties = _.mapObject(this.config.properties, function(prop, key) {
1256+
if (prop.getCppProperty(key) !== 'undefined') {
1257+
return {
1258+
xproperty: prop.getCppProperty(key),
1259+
defaultJson: prop.getCppDefaultValue(),
1260+
};
1261+
}
1262+
}, this);
1263+
1264+
}
1265+
1266+
1267+
getOutputFilename() {
1268+
return this.cppAutoDestPath;
1269+
}
1270+
}
1271+
1272+
function createCppWrapper(modulePath, className) {
1273+
1274+
let wrapper;
1275+
try {
1276+
wrapper = new CppWrapper(modulePath, className);
1277+
} catch (e) {
1278+
console.log(e);
1279+
console.log('skipping: ' + modulePath + (className ? ':' + className : ''));
1280+
return Promise.resolve(false);
1281+
}
1282+
let fname = wrapper.getOutputFilename();
1283+
let cppPromise = fse.outputFile(fname, wrapper.output);
1284+
1285+
// Also output documentation for the Python API
1286+
//let docfname = wrapper.getDocFilename();
1287+
//console.log(docfname);
1288+
//let docPromise = Promise.resolve();
1289+
//let docPromise = fse.outputFile(docfname, wrapper.getDocOutput());
1290+
1291+
return Promise.all([cppPromise]);//, docPromise]);
1292+
}
1293+
1294+
function writeCMakeLists() {
1295+
1296+
console.log('Writing CMakeLists...');
1297+
1298+
// Regexp's
1299+
const RE_AUTOGEN_EXT = /\.autogen\.hpp$/;
1300+
1301+
const excludes = ['build', 'CMakeLists.txt'];
1302+
1303+
const allFilesSync = (dir, fileList = []) => {
1304+
fse.readdirSync(dir).forEach(file => {
1305+
const filePath = path.join(dir, file)
1306+
1307+
const shouldExclude = _.any(excludes, function(testPattern) {
1308+
if (testPattern instanceof RegExp) {
1309+
return testPattern.test(file);
1310+
} else if (typeof testPattern === 'string') {
1311+
return testPattern === file;
1312+
}
1313+
});
1314+
if (!shouldExclude) {
1315+
if (fse.statSync(filePath).isDirectory()) {
1316+
allFilesSync(filePath, fileList);
1317+
}
1318+
else {
1319+
fileList.push(path.relative(cmakeSrcDir, filePath));
1320+
}
1321+
}
1322+
})
1323+
return fileList
1324+
}
1325+
1326+
var fileList = [];
1327+
allFilesSync(cmakeSrcDir, fileList);
1328+
const context = {
1329+
hppfiles: fileList
1330+
};
1331+
const output = cmakeListsTemplate(context);
1332+
const outputPath = path.join(cmakeSrcDir, 'CMakeLists.txt');
1333+
1334+
return fse.outputFile(outputPath, output);
1335+
}
1336+
1337+
function writeHeaderCppFiles() {
1338+
1339+
console.log('Writing HeaderCppFiles...');
1340+
1341+
// Regexp's
1342+
const RE_AUTOGEN_EXT = /\.autogen\.hpp$/;
1343+
1344+
const excludes = ['build', 'CMakeLists.txt'];
1345+
1346+
const allFilesSync = (dir, fileList = []) => {
1347+
fse.readdirSync(dir).forEach(file => {
1348+
const filePath = path.join(dir, file)
1349+
1350+
const shouldExclude = _.any(excludes, function(testPattern) {
1351+
if (testPattern instanceof RegExp) {
1352+
return testPattern.test(file);
1353+
} else if (typeof testPattern === 'string') {
1354+
return testPattern === file;
1355+
}
1356+
});
1357+
if (!shouldExclude) {
1358+
if (fse.statSync(filePath).isDirectory()) {
1359+
allFilesSync(filePath, fileList);
1360+
}
1361+
else {
1362+
fileList.push(path.relative(cppSrcDir, filePath));
1363+
}
1364+
}
1365+
})
1366+
return fileList
1367+
}
1368+
1369+
fse.readdirSync(cppSrcDir).forEach(dir => {
1370+
const filePath = path.join(cppSrcDir, dir);
1371+
if (fse.statSync(filePath).isDirectory()) {
1372+
var fileList = [];
1373+
allFilesSync(filePath, fileList);
1374+
const context = {
1375+
dir: dir,
1376+
header: dir.toUpperCase(),
1377+
hppfiles: fileList
1378+
};
1379+
1380+
const output = headerCppTemplate(context);
1381+
const outputPath = path.join(cppSrcDir, 'x' + dir + '.hpp');
1382+
1383+
return fse.outputFile(outputPath, output);
1384+
}
1385+
});
1386+
}
10621387

10631388
function createJavascriptFiles() {
10641389
return mapPromiseFnOverThreeModules(createJavascriptWrapper)
@@ -1106,13 +1431,37 @@ function createPythonFiles() {
11061431

11071432
}
11081433

1434+
function createCppFiles() {
1435+
1436+
// Prevent python file generation when outside dir (e.g. npm install in dependent)
1437+
if (!fse.existsSync(cppSrcDir)) {
1438+
return Promise.resolve();
1439+
}
1440+
1441+
return mapPromiseFnOverThreeModules(
1442+
function(relativePath) {
1443+
return createCppWrapper(relativePath);
1444+
})
1445+
.then(function() {
1446+
return mapPromiseFnOverFileList(CUSTOM_CLASSES, function(relativePath) {
1447+
return createCppWrapper(relativePath)
1448+
});
1449+
})
1450+
.then(function() {
1451+
writeHeaderCppFiles();
1452+
})
1453+
.then(function() {
1454+
return writeCMakeLists();
1455+
});
1456+
}
11091457

11101458

11111459
function generateFiles() {
11121460

11131461
return Promise.all([
11141462
createJavascriptFiles(),
11151463
createPythonFiles(),
1464+
//createCppFiles(),
11161465
]);
11171466

11181467
}

0 commit comments

Comments
 (0)