diff --git a/src/config.ts b/src/config.ts index 545a3a0..c9ecd46 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,9 +1,9 @@ export interface IChuckIfcConfig { - ifcConvertPath: string; + blenderPath: string; } const config: IChuckIfcConfig = { - ifcConvertPath: process.env.CHUCK_IFC_IFCCONVERTPATH || 'IfcConvert' + blenderPath: process.env.CHUCK_BLENDER_BLENDERPATH || 'blender' }; export default config; diff --git a/src/convert.py b/src/convert.py new file mode 100644 index 0000000..b192c88 --- /dev/null +++ b/src/convert.py @@ -0,0 +1,47 @@ +import sys +import bpy + +# -- STEP Import -- + +# Dependencies : +# - https://gumroad.com/l/stepper +# - https://blenderartists.org/t/step-import/1203804/199 + +# How we proceed: +# 1. First, we use the very good plugin by ambi: https://gumroad.com/l/stepper +# 2. Then, we use a second one by vink to take the color from the vertices and put them as material +# 3. Finally, we export the file as FBX + +# 0. Extract the src/dest paths. +# The argv look like this. We don't care about whats before the "--" +# blender -b --python .\step_to_fbx.py -- "Source (STEP)/LUMINAIRE AIRLIE N°1 PORTE.stp" "Blender (FBX)/LUMINAIRE AIRLIE N°1 PORTE.fbx" +argv = sys.argv +separator_index = argv.index("--") +src = None +dest = None + +if separator_index and len(argv) >= separator_index + 3: + argv = argv[separator_index + 1:] + src = argv[0] + dest = argv[1] + +if src is None or dest is None: + print('\nYou must provide the file to process and the destination folder like this:', file=sys.stderr) + print('> blender -b --python .\step_to_fbx.py -- "Source (STEP)/LUMINAIRE AIRLIE N°1 PORTE.stp" "Blender (FBX)"', file=sys.stderr) + exit(1) + +# And remove the cube, while we're at it +print('\nRemoving the damn cube...') +bpy.ops.object.delete() + +# 1. Import the STEP file +print('\nSTEP import starting... \'' + src + '\'') +bpy.ops.import_scene.occ_import_step(filepath=src) + +# 2. Vertex color => materials +bpy.ops.wi_cadtools.vertexc_mat() + +# 3. Export as FBX +bpy.ops.export_scene.fbx(filepath = dest, use_selection = True) + +print('\nFinished!') diff --git a/src/index.ts b/src/index.ts index 4f8c067..ec9794e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,57 +5,75 @@ import * as os from 'os'; import * as path from 'path'; import config from './config'; -export interface IExecIfcStepsContext extends IDownloadAssetsStepsContext { +export interface IExecBlenderStepsContext extends IDownloadAssetsStepsContext { convertedAssetsDir: string; } export function describe(): IStepDescription { return { - code: 'exec-ifcopenshell', - name: 'Execute IfcOpenShell to convert IFC files into Collada files', + code: 'exec-blender', + name: 'Execute Blender to convert STEP files into FBX files', priority: 11, // after assets download }; } -export function shouldProcess(conv: IConversion, context: IExecIfcStepsContext) { +/** + * The predicate to process only STEP/STP files + * @param path + */ +function isStep(path: string) { + const lowerCasePath = path.toLowerCase(); + return lowerCasePath.endsWith('.step') || lowerCasePath.endsWith('.stp'); +} + +export function shouldProcess(conv: IConversion, context: IExecBlenderStepsContext) { + console.log(context, context.assetsPaths.some(isStep)); return context.assetsPaths && context.assetsPaths.length && - context.assetsPaths.some(assetPath => assetPath.toLowerCase().endsWith('.ifc')); + context.assetsPaths.some(isStep); } -export async function process(conv: IConversion, context: IExecIfcStepsContext, progress: ProgressFn): Promise { +export async function process(conv: IConversion, context: IExecBlenderStepsContext, progress: ProgressFn): Promise { //=> Create a temporary folder for the assets - const tmpDir = path.resolve(`${os.tmpdir()}/chuck/exec-ifcopenshell-${Date.now()}`); + const tmpDir = path.resolve(`${os.tmpdir()}/chuck/exec-blender-${Date.now()}`); await fs.mkdirp(tmpDir); context.convertedAssetsDir = tmpDir; const convertOptions = conv.conversionOptions; //=> Execute each conversion sequentially - // @todo If IfcConvert is mono-threaded, we could make it concurrent + // @todo If Blender is mono-threaded, we could make it concurrent for (const assetPath of context.assetsPaths) { - if (assetPath.toLowerCase().endsWith('.ifc')) { - await progress('convert-start', `Converting "${assetPath}" from IFC to Collada`); + if (isStep(assetPath)) { + await progress('convert-start', `Converting "${assetPath}" from STEP to FBX`); await convertAndStoreAssets(convertOptions, context, assetPath); } } } -export async function cleanup(context: IExecIfcStepsContext): Promise { +export async function cleanup(context: IExecBlenderStepsContext): Promise { await fs.remove(context.convertedAssetsDir); } async function convertAndStoreAssets( convertOptions: string[], - context: IExecIfcStepsContext, - ifcFilePath: string + context: IExecBlenderStepsContext, + stepFilePath: string ): Promise { - const colladaFileName = path.parse(ifcFilePath).name + '.dae'; - const colladaFilePath = path.resolve(`${context.convertedAssetsDir}/${colladaFileName}`); + const fbxFileName = path.parse(stepFilePath).name + '.fbx'; + const fbxFilePath = path.resolve(`${context.convertedAssetsDir}/${fbxFileName}`); - const spawnArgs = [ifcFilePath, colladaFilePath, '-y'].concat(convertOptions); + // Expected command: + // blender -b --python .\step_to_fbx.py --python-exit-code 1 -- "path/file.stp" "path/file.fbx" + const spawnArgs = [ + '-b', + '--python', path.join(__dirname, '..', 'src', 'convert.py').toString(), + '--python-exit-code', '1', + '--', + stepFilePath, + fbxFilePath].concat(convertOptions); - const converterProcess = spawn(config.ifcConvertPath, spawnArgs); + const converterProcess = spawn(config.blenderPath, spawnArgs); //=> Watch process' stdout to log in real time, and keep the complete output in case of crash let stdoutAggregator = ''; @@ -70,22 +88,22 @@ async function convertAndStoreAssets( //=> Watch for the process to terminate, check return code converterProcess.once('close', (code) => { if (code !== 0) { - const message = `Conversion of IFC file ${path.basename(ifcFilePath)} to Collada has failed!`; - return reject(new IfcConvertCrashError(message, stdoutAggregator)); + const message = `Conversion of STEP file ${path.basename(stepFilePath)} to FBX has failed!`; + return reject(new BlenderCrashError(message, stdoutAggregator)); } - const index = context.assetsPaths.findIndex(path => path === ifcFilePath); - context.assetsPaths[index] = colladaFilePath; + const index = context.assetsPaths.findIndex(path => path === stepFilePath); + context.assetsPaths[index] = fbxFilePath; - resolve(colladaFilePath); + resolve(fbxFilePath); }); }); } -export class IfcConvertCrashError extends Error { - constructor(message: string, public readonly ifcConvertLog: string) { +export class BlenderCrashError extends Error { + constructor(message: string, public readonly BlenderLog: string) { super(message); - Object.setPrototypeOf(this, IfcConvertCrashError.prototype); + Object.setPrototypeOf(this, BlenderCrashError.prototype); } } diff --git a/yarn.lock b/yarn.lock index 462886a..e68783a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3140,8 +3140,8 @@ string_decoder@~1.0.0: safe-buffer "~5.0.1" stringstream@~0.0.4: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1"