n8n_3_CAD-BIM-批量转换器-管道
高级
这是一个Document Extraction, Multimodal AI领域的自动化工作流,包含 82 个节点。主要使用 If, Set, Code, Merge, ManualTrigger 等节点。 批量转换 CAD/BIM 文件为 XLSX/DAE,带验证和报告
前置要求
- •OpenAI API Key
- •Anthropic API Key
- •Google Gemini API Key
使用的节点 (82)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "u5MdPlYjhriENe3R",
"meta": {
"instanceId": "faa70e11b7175129a74fd834d3451fdc1862589b16d68ded03f91ca7b1ecca12"
},
"name": "n8n_3_CAD-BIM-批量转换器-管道",
"tags": [],
"nodes": [
{
"id": "be5c0de2-0df1-4c35-8b0e-26f8d1911bda",
"name": "便签 23",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1088,
224
],
"parameters": {
"width": 376,
"height": 368,
"content": "## 📋 快速入门指南"
},
"typeVersion": 1
},
{
"id": "d8a6fce0-af86-4769-8911-afb6c665f38d",
"name": "便签25",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1088,
608
],
"parameters": {
"color": 2,
"width": 380,
"height": 224,
"content": "## 🎓 视频教程"
},
"typeVersion": 1
},
{
"id": "1182ee14-cf89-45ee-96dc-d96a6864dcfa",
"name": "便签 28",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1088,
1168
],
"parameters": {
"color": 2,
"width": 372,
"height": 244,
"content": "## 🆘 支持"
},
"typeVersion": 1
},
{
"id": "8bd00e43-cb87-489a-87b6-a6258262a5bf",
"name": "便签 29",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1088,
848
],
"parameters": {
"color": 2,
"width": 380,
"height": 304,
"content": "## 🎯 使用案例"
},
"typeVersion": 1
},
{
"id": "3ccefd7c-ade0-4125-952c-928115a49ebf",
"name": "便签 31",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1088,
112
],
"parameters": {
"width": 372,
"height": 100,
"content": "⭐ **如果您觉得我们的工具有用**,请考虑在 [GitHub](https://github.com/datadrivenconstruction/cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto) 上为我们的仓库加星。**您的支持帮助我们**改进并继续为社区开发开放解决方案!"
},
"typeVersion": 1
},
{
"id": "c5388249-6dc3-4070-9057-2b5668251832",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-688,
112
],
"parameters": {
"width": 520,
"height": 1940,
"content": "## 📋 选项参数配置指南"
},
"typeVersion": 1
},
{
"id": "2fe7360d-df50-4eb7-8799-441e7aed7a1a",
"name": "手动触发器",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-80,
704
],
"parameters": {},
"typeVersion": 1
},
{
"id": "614d8fe1-6c80-4cb0-b941-5aa3998a2321",
"name": "捕获管道开始时间 1",
"type": "n8n-nodes-base.code",
"position": [
144,
656
],
"parameters": {
"jsCode": "// Capture pipeline start time at the very beginning\nconst now = new Date();\nreturn [{\n json: {\n pipeline_start_time: now.toISOString(),\n pipeline_start_timestamp: now.getTime(),\n pipeline_start_message: `Pipeline started at ${now.toLocaleString()}`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "e64288d1-9d53-4036-afde-437c9892a927",
"name": "合并管道开始与配置 1",
"type": "n8n-nodes-base.merge",
"position": [
688,
672
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "7b214d29-a619-4a41-8cfa-67f074470b89",
"name": "设置配置参数 1",
"type": "n8n-nodes-base.set",
"position": [
416,
512
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "converter-path",
"name": "converter_path",
"type": "string",
"value": "C:\\Users\\Artem Boiko\\Desktop\\n8n\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\DDC_Converter_Revit\\datadrivenlibs\\RvtExporter.exe"
},
{
"id": "source-folder",
"name": "source_folder",
"type": "string",
"value": "C:\\Users\\Artem Boiko\\Desktop\\n8n\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\Sample_Projects"
},
{
"id": "output-folder",
"name": "output_folder",
"type": "string",
"value": "C:\\Users\\Artem Boiko\\Desktop\\n8n\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\Sample_Projects"
},
{
"id": "include-subfolders",
"name": "include_subfolders",
"type": "boolean",
"value": false
},
{
"id": "file-extension",
"name": "file_extension",
"type": "string",
"value": ".rvt"
},
{
"id": "a811f4ba-b7a5-4774-a1fa-90d9c43a0de6",
"name": "options",
"type": "string",
"value": "basic schedule -no-collada"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "53648934-0bb7-49d3-83f9-ab4280bb3ec9",
"name": "查找 CAD 文件 1",
"type": "n8n-nodes-base.executeCommand",
"position": [
848,
688
],
"parameters": {
"command": "=powershell -Command \"Get-ChildItem -LiteralPath '{{ $json.source_folder }}' -Filter '*{{ $json.file_extension }}' {{ $json.include_subfolders ? '-Recurse' : '' }} | Select-Object -ExpandProperty FullName\""
},
"typeVersion": 1
},
{
"id": "894a4fae-cd2f-4418-b35b-fc6a06cda820",
"name": "合并配置与搜索结果 1",
"type": "n8n-nodes-base.merge",
"position": [
1008,
576
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "cc6f17d1-4393-4c77-947a-53c42c01543c",
"name": "处理文件列表 1",
"type": "n8n-nodes-base.code",
"position": [
1168,
576
],
"parameters": {
"jsCode": "// Process file list with configuration and pipeline start time\nconst configData = $input.first().json || {};\nconst searchData = $input.last().json || {};\n\nconst output = searchData.stdout || '';\nconst stderr = searchData.stderr || '';\n\n// Preserve pipeline start time\nconst pipeline_start_time = configData.pipeline_start_time || '';\nconst pipeline_start_timestamp = configData.pipeline_start_timestamp || Date.now();\n\nconsole.log('=== Processing File List ===');\nconsole.log('Pipeline start time:', pipeline_start_time);\nconsole.log('Config data:', configData);\nconsole.log('Raw output:', output);\nconsole.log('Files found:', output ? output.split(/\\r?\\n/).filter(x => x.trim()).length : 0);\n\nconst config = {\n converter_path: configData.converter_path || '',\n source_folder: configData.source_folder || '',\n output_folder: configData.output_folder || '',\n include_subfolders: configData.include_subfolders || false,\n file_extension: configData.file_extension || '.rvt',\n options: configData.options || ''\n};\n\nif (!output || !output.trim()) {\n return [{\n json: {\n files: [],\n total_files: 0,\n message: `No files found with extension '${config.file_extension}' in ${config.source_folder}`,\n error: stderr || 'No output from search command',\n config: config,\n pipeline_start_time: pipeline_start_time,\n pipeline_start_timestamp: pipeline_start_timestamp\n }\n }];\n}\n\nlet normalizedExtension = (config.file_extension || '').trim();\nif (normalizedExtension && !normalizedExtension.startsWith('.')) {\n normalizedExtension = '.' + normalizedExtension;\n}\n\nlet rawPaths = [];\ntry {\n rawPaths = output.trim().split(/\\r?\\n/)\n .map(path => (path || '').trim())\n .filter(path => path && path.length > 0);\n} catch (error) {\n console.log('Error splitting output:', error);\n return [{\n json: {\n files: [],\n total_files: 0,\n message: `Error processing search results: ${error.message}`,\n config: config,\n pipeline_start_time: pipeline_start_time,\n pipeline_start_timestamp: pipeline_start_timestamp\n }\n }];\n}\n\nconst filePaths = rawPaths.filter(path => {\n if (!path || !normalizedExtension) return false;\n const lowerPath = path.toLowerCase();\n const lowerExt = normalizedExtension.toLowerCase();\n return lowerPath.endsWith(lowerExt);\n});\n\nif (filePaths.length === 0) {\n return [{\n json: {\n files: [],\n total_files: 0,\n message: `No files found with extension '${normalizedExtension}' in ${config.source_folder}`,\n config: config,\n pipeline_start_time: pipeline_start_time,\n pipeline_start_timestamp: pipeline_start_timestamp\n }\n }];\n}\n\nconst files = filePaths.map((filePath, index) => {\n let fileName = 'unknown_file';\n let fileNameWithoutExt = 'unknown_file';\n \n try {\n if (filePath && typeof filePath === 'string') {\n const normalizedPath = filePath.replace(/\\\\/g, '/');\n const pathParts = normalizedPath.split('/');\n fileName = pathParts[pathParts.length - 1] || 'unknown_file';\n \n console.log(`Processing file ${index + 1}: ${fileName} from path: ${filePath}`);\n \n const lastDotIndex = fileName.lastIndexOf('.');\n if (lastDotIndex > 0) {\n fileNameWithoutExt = fileName.substring(0, lastDotIndex);\n } else {\n fileNameWithoutExt = fileName;\n }\n }\n } catch (error) {\n console.log(`Error parsing file path ${filePath}:`, error);\n }\n \n const extWithoutDot = normalizedExtension ? normalizedExtension.substring(1) : 'unknown';\n \n return {\n index: index + 1,\n file_path: filePath,\n file_name: fileName,\n file_name_without_ext: fileNameWithoutExt,\n converter_path: config.converter_path,\n expected_output: `${config.output_folder}\\\\${fileNameWithoutExt}_${extWithoutDot}.xlsx`,\n expected_output_dae: `${config.output_folder}\\\\${fileNameWithoutExt}_${extWithoutDot}.dae`,\n config: config,\n options: config.options,\n pipeline_start_time: pipeline_start_time,\n pipeline_start_timestamp: pipeline_start_timestamp\n };\n});\n\nconsole.log('Processed files:', files.map(f => f.file_name));\n\nreturn [{\n json: {\n files: files,\n total_files: files.length,\n message: `Found ${files.length} files to convert`,\n config: config,\n pipeline_start_time: pipeline_start_time,\n pipeline_start_timestamp: pipeline_start_timestamp\n }\n}];"
},
"typeVersion": 2
},
{
"id": "357d21f1-ff0b-4308-99a7-8ab3140b6dde",
"name": "检查文件是否存在 1",
"type": "n8n-nodes-base.if",
"position": [
1328,
576
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "files-exist-condition",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.files && $json.files.length || 0 }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "84eff3b1-f5a8-4c84-82cb-2fa1d1bc23ea",
"name": "拆分文件进行处理 1",
"type": "n8n-nodes-base.splitOut",
"position": [
1520,
560
],
"parameters": {
"options": {},
"fieldToSplitOut": "files"
},
"typeVersion": 1
},
{
"id": "0330e04c-9572-439d-b09f-aa932b5e81ad",
"name": "创建输出目录 1",
"type": "n8n-nodes-base.executeCommand",
"position": [
1696,
656
],
"parameters": {
"command": "=mkdir \"{{ $json.config.output_folder }}\" 2>nul || echo Output directory ready",
"executeOnce": false
},
"typeVersion": 1
},
{
"id": "1cbf1ee9-0710-4145-a54e-0c4c73e177a6",
"name": "合并文件数据 1",
"type": "n8n-nodes-base.merge",
"position": [
1856,
576
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "627097e5-5d07-4386-a9d7-22dffe7ba4ab",
"name": "捕获开始时间 1",
"type": "n8n-nodes-base.code",
"position": [
2032,
576
],
"parameters": {
"jsCode": "// Capture conversion start time and preserve ALL data including pipeline start\nreturn $input.all().map(item => {\n const json = {...item.json};\n json.conversion_start_time = new Date().toISOString();\n json.conversion_start_timestamp = Date.now();\n console.log(`Starting conversion for: ${json.file_name} at ${json.conversion_start_time}`);\n console.log(`Pipeline started at: ${json.pipeline_start_time}`);\n return {json};\n});"
},
"typeVersion": 2
},
{
"id": "edf42bdd-67ed-46d1-a522-cbc93538d894",
"name": "短暂延迟",
"type": "n8n-nodes-base.wait",
"position": [
2192,
576
],
"webhookId": "5a1cb608-ec3e-4832-a289-f6a52ef8bc7a",
"parameters": {
"unit": "milliseconds"
},
"typeVersion": 1
},
{
"id": "1c4e14d2-f9cc-4b4f-85f0-7c0245c32b18",
"name": "执行转换 1",
"type": "n8n-nodes-base.executeCommand",
"position": [
2352,
688
],
"parameters": {
"command": "=\"{{ $json.converter_path }}\" \"{{ $json.file_path }}\" \"{{ $json.expected_output }}\" \"{{ $json.expected_output_dae }}\" {{ $json.options }}",
"executeOnce": false
},
"typeVersion": 1,
"continueOnFail": true
},
{
"id": "f02f9f74-9b20-4857-92c3-4b0f4103f1b1",
"name": "计算转换时间 1",
"type": "n8n-nodes-base.code",
"position": [
2512,
688
],
"parameters": {
"jsCode": "// Calculate actual conversion time and rename outputs - preserve pipeline start time\nreturn $input.all().map(item => {\n const json = {...item.json};\n \n // Rename stdout/stderr\n json.conversion_stdout = json.stdout || '';\n json.conversion_stderr = json.stderr || '';\n json.conversion_exitCode = json.exitCode || 0;\n delete json.stdout;\n delete json.stderr;\n delete json.exitCode;\n \n // Calculate actual processing time with decimal precision\n if (json.conversion_start_timestamp) {\n const endTime = Date.now();\n const startTime = json.conversion_start_timestamp;\n const processingTimeMs = endTime - startTime;\n json.processing_time = processingTimeMs / 1000;\n json.processing_time_ms = processingTimeMs;\n json.conversion_end_time = new Date().toISOString();\n json.conversion_end_timestamp = endTime;\n console.log(`Conversion completed for: ${json.file_name} in ${json.processing_time.toFixed(2)} seconds`);\n } else {\n json.processing_time = 0;\n json.processing_time_ms = 0;\n console.warn(`No start timestamp found for: ${json.file_name}`);\n }\n \n // Ensure pipeline start time is preserved\n console.log(`Pipeline start preserved: ${json.pipeline_start_timestamp}`);\n \n return {json};\n});"
},
"typeVersion": 2
},
{
"id": "9255e34b-be94-4630-8887-fc7a0aafd739",
"name": "验证前合并数据 1",
"type": "n8n-nodes-base.merge",
"position": [
2672,
576
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "0e3dc51e-4891-4094-ad20-341424dd04df",
"name": "验证输出文件并获取大小 1",
"type": "n8n-nodes-base.executeCommand",
"position": [
2832,
576
],
"parameters": {
"command": "=powershell -Command \"$xlsx='{{ $json.expected_output }}'; $dae='{{ $json.expected_output_dae }}'; $revit='{{ $json.file_path }}'; $xlsxExists=Test-Path -LiteralPath $xlsx; $daeExists=Test-Path -LiteralPath $dae; $revitExists=Test-Path -LiteralPath $revit; $xlsxSize=if($xlsxExists){(Get-Item -LiteralPath $xlsx).Length}else{0}; $daeSize=if($daeExists){(Get-Item -LiteralPath $dae).Length}else{0}; $revitSize=if($revitExists){(Get-Item -LiteralPath $revit).Length}else{0}; Write-Output ('XLSX_EXISTS:'+$xlsxExists+'|XLSX_SIZE:'+$xlsxSize+'|DAE_EXISTS:'+$daeExists+'|DAE_SIZE:'+$daeSize+'|REVIT_EXISTS:'+$revitExists+'|REVIT_SIZE:'+$revitSize)\"",
"executeOnce": false
},
"typeVersion": 1
},
{
"id": "8d038788-44e7-49be-a999-b97ac4f96fac",
"name": "完成文件验证 1",
"type": "n8n-nodes-base.code",
"position": [
2992,
576
],
"parameters": {
"jsCode": "// Complete file verification with processing time - preserve pipeline start time\nconst allInputs = $input.all();\nconst mergedDataItems = $('Merge Data Before Verification1').all();\n\nconsole.log(`=== Complete File Verification ===`);\nconsole.log(`Total verification inputs: ${allInputs.length}`);\nconsole.log(`Total merged data items: ${mergedDataItems.length}`);\n\nconst results = [];\n\n// Process each file\nallInputs.forEach((input, index) => {\n const verificationData = input.json || {};\n \n // Find corresponding merged data by matching index\n const mergedData = mergedDataItems[index]?.json || {};\n \n // Extract all necessary data from merged node\n const fileName = mergedData.file_name || 'unknown_file';\n const filePath = mergedData.file_path || '';\n const expectedOutput = mergedData.expected_output || '';\n const expectedOutputDae = mergedData.expected_output_dae || '';\n const fileIndex = mergedData.index || index + 1;\n const processingTime = mergedData.processing_time || 0;\n const config = mergedData.config || {};\n const conversionStartTime = mergedData.conversion_start_time || '';\n const conversionEndTime = mergedData.conversion_end_time || '';\n const conversionEndTimestamp = mergedData.conversion_end_timestamp || Date.now();\n \n // IMPORTANT: Preserve pipeline start time\n const pipelineStartTime = mergedData.pipeline_start_time || '';\n const pipelineStartTimestamp = mergedData.pipeline_start_timestamp || 0;\n\n console.log(`Processing verification for file ${fileIndex}: ${fileName}`);\n console.log('Actual processing time:', processingTime, 'seconds');\n console.log('Pipeline start timestamp:', pipelineStartTimestamp);\n\n // Get verification output from the current input\n let verificationOutput = verificationData.stdout || verificationData.verification_output || '';\n\n // Parse PowerShell output\n let successXlsx = false;\n let successDae = false;\n let successRevit = false;\n let fileSizeXlsx = 0;\n let fileSizeDae = 0;\n let fileSizeRevit = 0;\n\n try {\n if (verificationOutput && verificationOutput.includes('|')) {\n const parts = verificationOutput.split('|');\n \n parts.forEach(part => {\n if (part && part.includes('XLSX_EXISTS:')) {\n successXlsx = part.includes('XLSX_EXISTS:True');\n }\n if (part && part.includes('XLSX_SIZE:')) {\n const sizeStr = part.replace('XLSX_SIZE:', '').trim();\n fileSizeXlsx = parseInt(sizeStr) || 0;\n }\n if (part && part.includes('DAE_EXISTS:')) {\n successDae = part.includes('DAE_EXISTS:True');\n }\n if (part && part.includes('DAE_SIZE:')) {\n const sizeStr = part.replace('DAE_SIZE:', '').trim();\n fileSizeDae = parseInt(sizeStr) || 0;\n }\n if (part && part.includes('REVIT_EXISTS:')) {\n successRevit = part.includes('REVIT_EXISTS:True');\n }\n if (part && part.includes('REVIT_SIZE:')) {\n const sizeStr = part.replace('REVIT_SIZE:', '').trim();\n fileSizeRevit = parseInt(sizeStr) || 0;\n }\n });\n }\n } catch (error) {\n console.log('Error parsing verification output:', error);\n }\n\n const finalSuccess = successXlsx && successDae && fileSizeXlsx > 0 && fileSizeDae > 0;\n\n const statusMessage = finalSuccess \n ? `✅ [${fileIndex}] Successfully converted: ${fileName} (XLSX: ${Math.round(fileSizeXlsx/1024)}KB, DAE: ${Math.round(fileSizeDae/1024)}KB, ${processingTime.toFixed(1)}s)`\n : `❌ [${fileIndex}] Failed to convert: ${fileName} (XLSX: ${successXlsx && fileSizeXlsx > 0 ? '✓' : '✗'}, DAE: ${successDae && fileSizeDae > 0 ? '✓' : '✗'}, ${processingTime.toFixed(1)}s)`;\n\n const result = {\n file_name: fileName,\n file_path: filePath,\n expected_output: expectedOutput,\n expected_output_dae: expectedOutputDae,\n success: finalSuccess,\n success_xlsx: successXlsx && fileSizeXlsx > 0,\n success_dae: successDae && fileSizeDae > 0,\n processing_time: processingTime,\n file_size: fileSizeXlsx + fileSizeDae,\n file_size_xlsx: fileSizeXlsx,\n file_size_dae: fileSizeDae,\n file_size_revit: fileSizeRevit,\n index: fileIndex,\n status: finalSuccess ? 'converted' : 'failed',\n message: statusMessage,\n timestamp: new Date().toISOString(),\n verification_output: verificationOutput,\n config: config,\n conversion_start_time: conversionStartTime,\n conversion_end_time: conversionEndTime,\n conversion_end_timestamp: conversionEndTimestamp,\n pipeline_start_time: pipelineStartTime,\n pipeline_start_timestamp: pipelineStartTimestamp\n };\n\n console.log(`Verification complete for ${fileName}: ${result.message}`);\n results.push({ json: result });\n});\n\nconsole.log(`=== Returning ${results.length} verification results ===`);\nreturn results;"
},
"typeVersion": 2
},
{
"id": "44336609-1655-4f5b-92b4-9b6165620036",
"name": "生成 HTML 报告 1",
"type": "n8n-nodes-base.code",
"position": [
3152,
576
],
"parameters": {
"jsCode": "// Generate HTML Report with CORRECT pipeline timing and no-collada handling\nconst results = $input.all();\n\n// Get the current time as pipeline end\nconst pipelineEndTime = Date.now();\n\n// Find the pipeline start timestamp from the data\nlet pipelineStartTimestamp = null;\nlet config = {};\n\n// Extract timing and config from results\nfor (const result of results) {\n if (result.json) {\n // Get pipeline start timestamp\n if (result.json.pipeline_start_timestamp && !pipelineStartTimestamp) {\n pipelineStartTimestamp = result.json.pipeline_start_timestamp;\n console.log('Found pipeline start timestamp:', pipelineStartTimestamp);\n }\n \n // Get config\n if (result.json.config && Object.keys(result.json.config).length > 0) {\n config = result.json.config;\n }\n }\n}\n\n// Check if no-collada option is enabled\nconst noColladaEnabled = config.options && config.options.includes('no-collada');\nconsole.log('No-collada mode enabled:', noColladaEnabled);\n\n// Calculate total pipeline time in minutes\nlet totalPipelineMinutes = '0.00';\nif (pipelineStartTimestamp) {\n const totalMs = pipelineEndTime - pipelineStartTimestamp;\n totalPipelineMinutes = (totalMs / 60000).toFixed(2);\n console.log('Pipeline timing:', {\n start: pipelineStartTimestamp,\n end: pipelineEndTime,\n totalMs: totalMs,\n totalMinutes: totalPipelineMinutes\n });\n} else {\n console.warn('No pipeline start timestamp found!');\n}\n\n// Use config output folder or fallback\nconst outputFolder = config.output_folder || 'C:\\\\temp';\n\n// Count successful and failed conversions\nlet totalFiles = 0;\nlet successfulConversions = 0;\nlet failedConversions = 0;\nlet totalInputSize = 0;\nlet totalOutputSize = 0;\n\nconst successfulFiles = [];\nconst failedFiles = [];\n\nresults.forEach((result, index) => {\n const data = result.json || {};\n totalFiles++;\n \n const processingTime = data.processing_time || 0;\n const xlsxSuccess = data.success_xlsx || false;\n const daeSuccess = data.success_dae || false;\n const fileSizeRevit = data.file_size_revit || 0;\n const fileSizeXlsx = data.file_size_xlsx || 0;\n const fileSizeDae = data.file_size_dae || 0;\n \n totalInputSize += fileSizeRevit;\n \n // Modified success logic for no-collada mode\n let isSuccess = false;\n if (noColladaEnabled) {\n // In no-collada mode, only XLSX matters\n isSuccess = xlsxSuccess;\n if (isSuccess) {\n totalOutputSize += fileSizeXlsx;\n }\n } else {\n // Normal mode: both XLSX and DAE must succeed\n isSuccess = xlsxSuccess && daeSuccess;\n if (isSuccess) {\n totalOutputSize += fileSizeXlsx + fileSizeDae;\n }\n }\n \n if (isSuccess) {\n successfulConversions++;\n successfulFiles.push({\n name: data.file_name || `File ${index + 1}`,\n originalSize: Math.round(fileSizeRevit / 1024),\n xlsxSize: Math.round(fileSizeXlsx / 1024),\n daeSize: Math.round(fileSizeDae / 1024),\n totalSize: Math.round((fileSizeXlsx + (noColladaEnabled ? 0 : fileSizeDae)) / 1024),\n xlsxPath: data.expected_output || '',\n daePath: data.expected_output_dae || '',\n noCollada: noColladaEnabled\n });\n } else {\n failedConversions++;\n failedFiles.push({\n name: data.file_name || `File ${index + 1}`,\n originalSize: Math.round(fileSizeRevit / 1024),\n xlsxStatus: xlsxSuccess ? '✓' : '✗',\n daeStatus: daeSuccess ? '✓' : '✗'\n });\n }\n});\n\nconst successRate = totalFiles > 0 ? Math.round((successfulConversions / totalFiles) * 100) : 0;\n\n// Generate timestamp for filename\nconst now = new Date();\nconst timestamp = now.toISOString().slice(0,19).replace(/:/g, '-');\nconst fileName = `CAD_Conversion_Report_${timestamp}.html`;\nconst fullPath = `${outputFolder}\\\\${fileName}`.replace(/\\\\\\\\/g, '\\\\');\n\nconsole.log('Report summary:', {\n totalFiles,\n successfulConversions,\n failedConversions,\n successRate: successRate + '%',\n totalPipelineTime: totalPipelineMinutes + ' minutes',\n outputPath: fullPath,\n noColladaMode: noColladaEnabled\n});\n\n// Create HTML content with professional styling\nconst htmlContent = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>CAD Project Batch Conversion Report</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n \n body { \n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; \n background: #f5f6fa;\n color: #2c3e50;\n line-height: 1.6;\n }\n \n .container { \n max-width: 1400px; \n margin: 0 auto; \n background: white; \n box-shadow: 0 0 40px rgba(0, 0, 0, 0.08); \n }\n \n .header { \n background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);\n color: white; \n padding: 60px 40px;\n position: relative;\n overflow: hidden;\n }\n \n .header::before {\n content: '';\n position: absolute;\n top: -50%;\n right: -10%;\n width: 40%;\n height: 200%;\n background: rgba(255, 255, 255, 0.05);\n transform: rotate(35deg);\n }\n \n .header-content {\n position: relative;\n z-index: 1;\n }\n \n .header h1 { \n font-size: 2.8rem; \n font-weight: 300;\n letter-spacing: -1px;\n margin-bottom: 15px;\n }\n \n .header .subtitle { \n font-size: 1.1rem;\n opacity: 0.85;\n font-weight: 400;\n }\n \n .header .timestamp {\n margin-top: 20px;\n font-size: 0.95rem;\n opacity: 0.7;\n }\n \n .content {\n padding: 40px;\n }\n \n .metrics { \n display: grid; \n grid-template-columns: repeat(5, 1fr); \n gap: 20px; \n margin: -20px 0 40px 0;\n position: relative;\n z-index: 10;\n }\n \n @media (max-width: 1200px) {\n .metrics {\n grid-template-columns: repeat(3, 1fr);\n }\n }\n \n @media (max-width: 768px) {\n .metrics {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n \n @media (max-width: 480px) {\n .metrics {\n grid-template-columns: 1fr;\n }\n }\n \n .metric { \n background: white;\n padding: 30px 20px;\n border-radius: 12px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n transition: all 0.3s ease;\n border: 1px solid #e8ecf1;\n position: relative;\n overflow: hidden;\n text-align: center;\n }\n \n .metric::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 4px;\n background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%);\n }\n \n .metric:hover {\n transform: translateY(-3px);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\n }\n \n .metric-value { \n font-size: 2.2rem; \n font-weight: 600;\n color: #1e3c72;\n margin-bottom: 8px;\n line-height: 1;\n }\n \n .metric-label { \n font-size: 0.85rem; \n color: #64748b;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n \n .metric.highlight {\n background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);\n border-color: #3b82f6;\n }\n \n .metric.highlight::before {\n background: linear-gradient(90deg, #10b981 0%, #059669 100%);\n }\n \n .section { \n margin: 40px 0;\n background: white;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 2px 15px rgba(0, 0, 0, 0.06);\n border: 1px solid #e8ecf1;\n }\n \n .section-header {\n background: #f8fafc;\n padding: 24px 32px;\n border-bottom: 1px solid #e8ecf1;\n }\n \n .section h2 { \n color: #1e293b;\n font-size: 1.4rem;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n \n .section-content {\n padding: 0;\n }\n \n table { \n width: 100%; \n border-collapse: collapse;\n }\n \n th, td { \n padding: 16px 24px;\n text-align: left;\n border-bottom: 1px solid #f1f5f9;\n }\n \n th { \n background: #f8fafc;\n font-weight: 600;\n color: #475569;\n font-size: 0.875rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n position: sticky;\n top: 0;\n z-index: 1;\n }\n \n tr:last-child td {\n border-bottom: none;\n }\n \n tr:hover { \n background: #f8fafc;\n }\n \n .status-success { \n background: #10b981;\n color: white;\n padding: 6px 12px;\n border-radius: 6px;\n font-size: 0.75rem;\n font-weight: 600;\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n \n .status-failed { \n background: #ef4444;\n color: white;\n padding: 6px 12px;\n border-radius: 6px;\n font-size: 0.75rem;\n font-weight: 600;\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n \n .status-skipped { \n background: #6b7280;\n color: white;\n padding: 6px 12px;\n border-radius: 6px;\n font-size: 0.75rem;\n font-weight: 600;\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n \n .config-section {\n background: #f8fafc;\n padding: 32px;\n border-radius: 12px;\n margin: 40px 0;\n }\n \n .config-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));\n gap: 24px;\n margin-top: 20px;\n }\n \n .config-item {\n display: flex;\n align-items: flex-start;\n gap: 16px;\n }\n \n .config-label {\n font-weight: 600;\n color: #475569;\n min-width: 140px;\n font-size: 0.9rem;\n }\n \n .config-value {\n color: #1e293b;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n background: white;\n padding: 8px 12px;\n border-radius: 6px;\n font-size: 0.85rem;\n word-break: break-all;\n flex: 1;\n border: 1px solid #e2e8f0;\n }\n \n .footer { \n background: #f8fafc;\n padding: 40px;\n text-align: center;\n color: #64748b;\n border-top: 1px solid #e8ecf1;\n }\n \n .footer .company {\n font-size: 1.1rem;\n font-weight: 600;\n color: #1e293b;\n margin-bottom: 8px;\n }\n \n .footer .version {\n font-size: 0.85rem;\n margin-top: 16px;\n color: #94a3b8;\n }\n \n .report-location {\n background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);\n border: 1px solid #3b82f6;\n border-radius: 12px;\n padding: 20px 28px;\n margin: 30px 0;\n text-align: center;\n color: #1e3c72;\n font-size: 0.95rem;\n }\n \n .report-location strong {\n color: #1e3c72;\n font-weight: 600;\n }\n \n .size-cell {\n font-family: 'SF Mono', Monaco, monospace;\n font-size: 0.9rem;\n }\n \n .file-link {\n color: #3b82f6;\n text-decoration: none;\n font-weight: 500;\n transition: all 0.2s ease;\n position: relative;\n display: inline-block;\n padding: 2px 0;\n }\n \n .file-link:hover {\n color: #2563eb;\n text-decoration: underline;\n }\n \n .tooltip {\n position: relative;\n display: inline-block;\n }\n \n .tooltip .tooltiptext {\n visibility: hidden;\n width: 280px;\n background-color: #333;\n color: #fff;\n text-align: center;\n border-radius: 8px;\n padding: 12px 16px;\n position: absolute;\n z-index: 1000;\n bottom: 125%;\n left: 50%;\n margin-left: -140px;\n opacity: 0;\n transition: opacity 0.3s;\n font-size: 0.85rem;\n line-height: 1.4;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n }\n \n .tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 50%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #333 transparent transparent transparent;\n }\n \n .tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n \n .no-collada-notice {\n background: #fef3c7;\n border: 1px solid #fbbf24;\n border-radius: 8px;\n padding: 16px;\n margin: 20px 0;\n color: #92400e;\n font-size: 0.9rem;\n text-align: center;\n }\n \n .no-collada-notice strong {\n color: #78350f;\n }\n \n @media print {\n body { background: white; }\n .container { box-shadow: none; }\n .metric { box-shadow: none; border: 1px solid #e5e7eb; }\n .section { box-shadow: none; page-break-inside: avoid; }\n .tooltip .tooltiptext { display: none; }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <div class=\"header-content\">\n <h1>CAD Project Batch Conversion Report</h1>\n <p class=\"subtitle\">Automated conversion pipeline execution summary</p>\n <p class=\"timestamp\">${now.toLocaleString('en-US', { \n weekday: 'long', \n year: 'numeric', \n month: 'long', \n day: 'numeric', \n hour: '2-digit', \n minute: '2-digit' \n })}</p>\n </div>\n </div>\n \n <div class=\"content\">\n <div class=\"metrics\">\n <div class=\"metric\">\n <div class=\"metric-value\">${totalFiles}</div>\n <div class=\"metric-label\">Total Files</div>\n </div>\n <div class=\"metric highlight\">\n <div class=\"metric-value\">${successRate}%</div>\n <div class=\"metric-label\">Success Rate</div>\n </div>\n <div class=\"metric\">\n <div class=\"metric-value\">${totalPipelineMinutes}</div>\n <div class=\"metric-label\">Total Time (Minutes)</div>\n </div>\n <div class=\"metric\">\n <div class=\"metric-value\">${Math.round(totalInputSize / 1048576)} MB</div>\n <div class=\"metric-label\">Total Input Size</div>\n </div>\n <div class=\"metric\">\n <div class=\"metric-value\">${Math.round(totalOutputSize / 1048576)} MB</div>\n <div class=\"metric-label\">Total Output Size</div>\n </div>\n </div>\n\n <div class=\"report-location\">\n <strong>Report Location:</strong> ${fullPath}\n </div>\n\n ${noColladaEnabled ? `\n <div class=\"no-collada-notice\">\n <strong>Note:</strong> DAE (Collada) file generation was skipped as per configuration (no-collada option enabled).\n </div>\n ` : ''}\n\n ${successfulFiles.length > 0 ? `\n <div class=\"section\">\n <div class=\"section-header\">\n <h2>✅ Successfully Converted Files (${successfulFiles.length})</h2>\n </div>\n <div class=\"section-content\">\n <table>\n <thead>\n <tr>\n <th>File Name</th>\n <th>Original Size</th>\n <th>Excel Output</th>\n ${noColladaEnabled ? '<th>DAE Output</th>' : '<th>DAE Output</th>'}\n <th>Total Output</th>\n </tr>\n </thead>\n <tbody>\n ${successfulFiles.map(file => `\n <tr>\n <td><strong>${file.name}</strong></td>\n <td class=\"size-cell\">${file.originalSize} KB</td>\n <td class=\"size-cell\">\n <a href=\"file:///${file.xlsxPath.replace(/\\\\/g, '/')}\" class=\"file-link\" target=\"_blank\">${file.xlsxSize} KB</a>\n </td>\n <td class=\"size-cell\">\n ${noColladaEnabled ? \n '<span class=\"status-skipped\">SKIPPED</span>' : \n `<div class=\"tooltip\">\n <a href=\"file:///${file.daePath.replace(/\\\\/g, '/')}\" class=\"file-link\" target=\"_blank\">${file.daeSize} KB</a>\n <span class=\"tooltiptext\">💡 Tip: Use the free CAD Assistant software for the best viewing experience of DAE files</span>\n </div>`\n }\n </td>\n <td class=\"size-cell\"><strong>${file.totalSize} KB</strong></td>\n </tr>\n `).join('')}\n </tbody>\n </table>\n </div>\n </div>\n ` : ''}\n\n ${failedFiles.length > 0 ? `\n <div class=\"section\">\n <div class=\"section-header\">\n <h2>❌ Failed Conversions (${failedFiles.length})</h2>\n </div>\n <div class=\"section-content\">\n <table>\n <thead>\n <tr>\n <th>File Name</th>\n <th>Original Size</th>\n <th>XLSX Status</th>\n <th>DAE Status</th>\n </tr>\n </thead>\n <tbody>\n ${failedFiles.map(file => `\n <tr>\n <td><strong>${file.name}</strong></td>\n <td class=\"size-cell\">${file.originalSize} KB</td>\n <td><span class=\"status-${file.xlsxStatus === '✓' ? 'success' : 'failed'}\">${file.xlsxStatus === '✓' ? 'SUCCESS' : 'FAILED'}</span></td>\n <td>${noColladaEnabled && file.xlsxStatus === '✓' ? \n '<span class=\"status-skipped\">SKIPPED</span>' : \n `<span class=\"status-${file.daeStatus === '✓' ? 'success' : 'failed'}\">${file.daeStatus === '✓' ? 'SUCCESS' : 'FAILED'}</span>`\n }</td>\n </tr>\n `).join('')}\n </tbody>\n </table>\n </div>\n </div>\n ` : ''}\n\n <div class=\"config-section\">\n <h2 style=\"color: #1e293b; font-size: 1.4rem; font-weight: 600; margin-bottom: 20px;\">\n ⚙️ Configuration Details\n </h2>\n <div class=\"config-grid\">\n <div class=\"config-item\">\n <span class=\"config-label\">Source Folder:</span>\n <span class=\"config-value\">${config.source_folder || 'Not specified'}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">Output Folder:</span>\n <span class=\"config-value\">${config.output_folder || 'Not specified'}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">File Extension:</span>\n <span class=\"config-value\">${config.file_extension || 'Not specified'}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">Include Subfolders:</span>\n <span class=\"config-value\">${config.include_subfolders ? 'Yes' : 'No'}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">Converter Options:</span>\n <span class=\"config-value\">${config.options || 'None'}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">Generated:</span>\n <span class=\"config-value\">${now.toLocaleString()}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">Workflow ID:</span>\n <span class=\"config-value\">FYFQhblt4gILLSpe</span>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"footer\">\n <div class=\"company\">DataDrivenConstruction.io</div>\n <div>Professional CAD Conversion Pipeline</div>\n <div class=\"version\">Version 2.0 | Automated Batch Processing System</div>\n </div>\n </div>\n</body>\n</html>`;\n\n// Return data for saving and opening\nreturn [{\n json: {\n html_content: htmlContent,\n file_name: fileName,\n full_path: fullPath,\n output_folder: outputFolder,\n total_files: totalFiles,\n successful_conversions: successfulConversions,\n failed_conversions: failedConversions,\n success_rate: successRate,\n total_pipeline_time: totalPipelineMinutes,\n config: config,\n summary_message: `✅ Conversion Complete! ${successfulConversions}/${totalFiles} files converted successfully (${successRate}% success rate) in ${totalPipelineMinutes} minutes`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "21c9bd6d-e1a0-4697-b99d-c51e666af986",
"name": "准备二进制数据 1",
"type": "n8n-nodes-base.code",
"position": [
3312,
576
],
"parameters": {
"jsCode": "// Prepare binary data for file saving\nconst item = $input.first().json;\n\nconsole.log('Preparing binary data for file:', item.file_name);\nconsole.log('Full path:', item.full_path);\n\nif (!item.html_content) {\n throw new Error('No HTML content found');\n}\n\nreturn [{\n json: item,\n binary: {\n report: {\n data: Buffer.from(item.html_content, 'utf-8').toString('base64'),\n mimeType: 'text/html',\n fileName: item.file_name,\n fileExtension: 'html'\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "3ba8b465-5da4-4b79-83e2-2bdb63222b6b",
"name": "保存 HTML 文件 1",
"type": "n8n-nodes-base.writeBinaryFile",
"position": [
3472,
576
],
"parameters": {
"options": {},
"fileName": "={{ $json.full_path }}",
"dataPropertyName": "report"
},
"typeVersion": 1
},
{
"id": "94de0bbb-402b-4dea-bcaf-f98fdc678048",
"name": "验证并准备路径 1",
"type": "n8n-nodes-base.code",
"position": [
3632,
576
],
"parameters": {
"jsCode": "// Verify file was saved and prepare for opening\nconst data = $input.first().json;\nconst fullPath = data.full_path || '';\n\nconsole.log('Preparing to open file:', fullPath);\n\n// Normalize path for Windows\nconst windowsPath = fullPath.replace(/\\\\\\\\/g, '\\\\').replace(/\\//g, '\\\\');\n\nreturn [{\n json: {\n ...data,\n windows_path: windowsPath,\n command_to_open: `cmd /c start \"\" \"${windowsPath}\"`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "6558ac19-23c1-44c9-8b7b-3615052cd4a5",
"name": "打开 HTML 报告 1",
"type": "n8n-nodes-base.executeCommand",
"position": [
3792,
576
],
"parameters": {
"command": "=cmd /c start \"\" \"{{ $json.windows_path }}\""
},
"typeVersion": 1
},
{
"id": "cdf12580-ade1-4ae0-a699-980aca156c98",
"name": "最终完成通知 1",
"type": "n8n-nodes-base.executeCommand",
"position": [
3952,
576
],
"parameters": {
"command": "=echo 🏁 CAD Conversion Pipeline Completed Successfully! & echo 📋 Summary: {{ $json.summary_message }} & echo 📁 Report Location: {{ $json.windows_path }} & echo ✨ All processes finished successfully! & echo. & echo 🎯 Next Steps: & echo 1. Check the HTML report that opened in your browser & echo 2. Verify output files in the specified folder & echo 3. Review conversion results and metrics & echo. & echo ✅ Pipeline execution completed!",
"executeOnce": false
},
"typeVersion": 1
},
{
"id": "dc253ac4-c296-4a8c-897c-380e5e14b683",
"name": "未找到文件响应 1",
"type": "n8n-nodes-base.set",
"position": [
1520,
784
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "no-files-message",
"name": "message",
"type": "string",
"value": "⚠️ No files found to convert with the specified parameters"
},
{
"id": "no-files-details",
"name": "details",
"type": "string",
"value": "Please check: 1) Source folder path exists, 2) File extension is correct, 3) Files exist in the specified location"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ee8bb887-c553-412b-9be5-c865f08fbd82",
"name": "便签7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
112
],
"parameters": {
"color": 6,
"width": 756,
"height": 844,
"content": "## 🏁 组 1:初始化"
},
"typeVersion": 1
},
{
"id": "0ca6f619-b073-41b1-a248-0f2b62333773",
"name": "便签9",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
112
],
"parameters": {
"color": 5,
"width": 1024,
"height": 840,
"content": "## 🔍 组 2:文件发现"
},
"typeVersion": 1
},
{
"id": "8dcff587-4f09-4e86-a065-bdd056be456d",
"name": "便签10",
"type": "n8n-nodes-base.stickyNote",
"position": [
1664,
112
],
"parameters": {
"color": 5,
"width": 316,
"height": 844,
"content": "## 🔄 组 3:批次准备"
},
"typeVersion": 1
},
{
"id": "ae685ee4-bbee-4b56-85e0-26f3da9053fd",
"name": "便签11",
"type": "n8n-nodes-base.stickyNote",
"position": [
2000,
112
],
"parameters": {
"color": 5,
"width": 800,
"height": 840,
"content": "## ⚡ 组 4:转换执行"
},
"typeVersion": 1
},
{
"id": "5fe7b6d6-7a36-41ae-a431-a93803227a50",
"name": "便签15",
"type": "n8n-nodes-base.stickyNote",
"position": [
2816,
112
],
"parameters": {
"color": 6,
"width": 284,
"height": 836,
"content": "## ✅ 组 5:验证"
},
"typeVersion": 1
},
{
"id": "2a199a8f-7206-4b18-9e87-f9dc4db56d8d",
"name": "便签16",
"type": "n8n-nodes-base.stickyNote",
"position": [
3120,
112
],
"parameters": {
"width": 456,
"height": 832,
"content": "## 📊 组 6:报告"
},
"typeVersion": 1
},
{
"id": "565d9855-8447-4c4e-ab57-b652a8733040",
"name": "便签17",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
112
],
"parameters": {
"width": 484,
"height": 828,
"content": "## 🎯 组 7:最终化"
},
"typeVersion": 1
},
{
"id": "32324229-f786-4023-bfc1-bc01ad05575d",
"name": "便签 30",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
976
],
"parameters": {
"color": 4,
"width": 488,
"height": 308,
"content": "## 📝 配置示例"
},
"typeVersion": 1
},
{
"id": "62e0570c-238d-40e2-8b1f-f1f71ca8f034",
"name": "计划触发器1",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-80,
560
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "637d319f-4c7d-4f14-8676-16e6a839812d",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
368,
304
],
"parameters": {
"color": 4,
"width": 192,
"height": 384,
"content": "## ⬇️ 仅在此处修改变量"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "7eb4a013-6adf-4f71-b5d8-1c78163410dd",
"connections": {
"Small Delay": {
"main": [
[
{
"node": "Execute Conversion1",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "Capture Pipeline Start Time1",
"type": "main",
"index": 0
}
]
]
},
"Find CAD Files1": {
"main": [
[
{
"node": "Merge Config with Search Results1",
"type": "main",
"index": 1
}
]
]
},
"Save HTML File1": {
"main": [
[
{
"node": "Verify and Prepare Path1",
"type": "main",
"index": 0
}
]
]
},
"Merge File Data1": {
"main": [
[
{
"node": "Capture Start Time1",
"type": "main",
"index": 0
}
]
]
},
"Open HTML Report1": {
"main": [
[
{
"node": "Final Completion Notice1",
"type": "main",
"index": 0
}
]
]
},
"Process File List1": {
"main": [
[
{
"node": "Check if Files Exist1",
"type": "main",
"index": 0
}
]
]
},
"Capture Start Time1": {
"main": [
[
{
"node": "Small Delay",
"type": "main",
"index": 0
},
{
"node": "Merge Data Before Verification1",
"type": "main",
"index": 0
}
]
]
},
"Execute Conversion1": {
"main": [
[
{
"node": "Calculate Conversion Time1",
"type": "main",
"index": 0
}
]
]
},
"Prepare Binary Data1": {
"main": [
[
{
"node": "Save HTML File1",
"type": "main",
"index": 0
}
]
]
},
"Check if Files Exist1": {
"main": [
[
{
"node": "Split Files for Processing1",
"type": "main",
"index": 0
}
],
[
{
"node": "No Files Found Response1",
"type": "main",
"index": 0
}
]
]
},
"Generate HTML Report1": {
"main": [
[
{
"node": "Prepare Binary Data1",
"type": "main",
"index": 0
}
]
]
},
"Create Output Directory1": {
"main": [
[
{
"node": "Merge File Data1",
"type": "main",
"index": 1
}
]
]
},
"Verify and Prepare Path1": {
"main": [
[
{
"node": "Open HTML Report1",
"type": "main",
"index": 0
}
]
]
},
"Calculate Conversion Time1": {
"main": [
[
{
"node": "Merge Data Before Verification1",
"type": "main",
"index": 1
}
]
]
},
"Complete File Verification1": {
"main": [
[
{
"node": "Generate HTML Report1",
"type": "main",
"index": 0
}
]
]
},
"Split Files for Processing1": {
"main": [
[
{
"node": "Create Output Directory1",
"type": "main",
"index": 0
},
{
"node": "Merge File Data1",
"type": "main",
"index": 0
}
]
]
},
"Capture Pipeline Start Time1": {
"main": [
[
{
"node": "Set Configuration Parameters1",
"type": "main",
"index": 0
},
{
"node": "Merge Pipeline Start with Config1",
"type": "main",
"index": 0
}
]
]
},
"Set Configuration Parameters1": {
"main": [
[
{
"node": "Merge Pipeline Start with Config1",
"type": "main",
"index": 1
}
]
]
},
"Merge Data Before Verification1": {
"main": [
[
{
"node": "Verify Output Files and Get Sizes1",
"type": "main",
"index": 0
}
]
]
},
"Merge Config with Search Results1": {
"main": [
[
{
"node": "Process File List1",
"type": "main",
"index": 0
}
]
]
},
"Merge Pipeline Start with Config1": {
"main": [
[
{
"node": "Find CAD Files1",
"type": "main",
"index": 0
},
{
"node": "Merge Config with Search Results1",
"type": "main",
"index": 0
}
]
]
},
"Verify Output Files and Get Sizes1": {
"main": [
[
{
"node": "Complete File Verification1",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 文档提取, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
n8n_7_Revit和IFC碳足迹CO2估算器
使用AI分类计算Revit/IFC模型的隐含碳(CO2)
If
Set
Code
+13
55 节点Artem Boiko
AI 摘要总结
n8n_6_使用LLM为Revit和IFC进行施工造价估算
使用GPT-4和Claude基于Revit/IFC模型估算施工成本
If
Set
Code
+13
55 节点Artem Boiko
AI 摘要总结
在可视化参考库中探索n8n节点
在可视化参考库中探索n8n节点
If
Ftp
Set
+93
113 节点I versus AI
其他
我的工作流11
根据Excel标准验证CAD-BIM文件(Revit/IFC/DWG/DGN)
If
Set
Code
+6
26 节点Artem
工程
AI-Deepseek-R1t 会议差旅审批与费用授权申请
通过Deepseek AI、Gmail和Google Sheets自动化会议差旅审批
If
Set
Code
+11
24 节点Cheng Siong Chin
文档提取
n8n_8_Revit_IFC_DWG转换_提取阶段_解析XLSX
提取并解析Revit模型数据至结构化Excel格式
If
Set
Manual Trigger
+4
13 节点Artem Boiko
工程
工作流信息
难度等级
高级
节点数量82
分类2
节点类型17
作者
Artem Boiko
@datadrivenconstructionFounder DataDrivenConstruction.io | AEC Tech Consultant & Automation Expert | Bridging Software and Construction
外部链接
在 n8n.io 查看 →
分享此工作流