function parseStringIfNumber(input) { if (typeof input === "string" && !isNaN(input) && !isNaN(parseFloat(input))) { return parseFloat(input); } return input; } function convertToCamelCase(obj) { const result = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const value = obj[key]; // Convert the key to camelCase let camelKey = key // First handle special cases with spaces, brackets and other characters .replace(/\s*\[.*?\]\s*/g, "") // Remove brackets and their contents .replace(/\s+/g, " ") // Normalize spaces .trim() // Split by common separators (space, underscore, hyphen) .split(/[\s_-]/) // Convert to camelCase .map((word, index) => { // Remove any non-alphanumeric characters word = word.replace(/[^a-zA-Z0-9]/g, ""); // Lowercase first word, uppercase others return index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); }) .join(""); // Handle values that are objects recursively if ( value !== null && typeof value === "object" && !Array.isArray(value) ) { result[camelKey] = convertToCamelCase(value); } else { result[camelKey] = value; } } } return result; } function extractConfigBlock(fileContent, useCamelCase = true) { const configObject = {}; // Extract header information const headerBlockRegex = /; HEADER_BLOCK_START([\s\S]*?)(?:; HEADER_BLOCK_END|$)/; const headerBlockMatch = fileContent.match(headerBlockRegex); if (headerBlockMatch && headerBlockMatch[1]) { const headerLines = headerBlockMatch[1].split("\n"); headerLines.forEach((line) => { // Match lines with info after semicolon const headerLineRegex = /^\s*;\s*([^:]+?):\s*(.*?)\s*$/; const keyValueRegex = /^\s*;\s*([^:]+?):\s*(.*?)\s*$/; const simpleValueRegex = /^\s*;\s*(.*?)\s*$/; // Try key-value format first let match = line.match(keyValueRegex); if (match) { const key = match[1].trim(); let value = match[2].trim(); // Try to convert value to appropriate type if (!isNaN(value) && value !== "") { value = Number(value); } configObject[key] = value; } else { // Try the simple format like "; generated by OrcaSlicer 2.1.1 on 2025-04-28 at 13:30:11" match = line.match(simpleValueRegex); if (match && match[1] && !match[1].includes("HEADER_BLOCK")) { const text = match[1].trim(); // Extract slicer info const slicerMatch = text.match( /generated by (.*?) on (.*?) at (.*?)$/, ); if (slicerMatch) { configObject["slicer"] = slicerMatch[1].trim(); configObject["date"] = slicerMatch[2].trim(); configObject["time"] = slicerMatch[3].trim(); } else { // Just add as a general header entry if it doesn't match any specific pattern const key = `header_${Object.keys(configObject).length}`; configObject[key] = text; } } } }); } // Extract thumbnail data const thumbnailBlockRegex = /; THUMBNAIL_BLOCK_START([\s\S]*?)(?:; THUMBNAIL_BLOCK_END|$)/; const thumbnailBlockMatch = fileContent.match(thumbnailBlockRegex); if (thumbnailBlockMatch && thumbnailBlockMatch[1]) { const thumbnailLines = thumbnailBlockMatch[1].split("\n"); let base64Data = ""; let thumbnailInfo = {}; thumbnailLines.forEach((line) => { // Extract thumbnail dimensions and size from the line "thumbnail begin 640x640 27540" const thumbnailHeaderRegex = /^\s*;\s*thumbnail begin (\d+)x(\d+) (\d+)/; const match = line.match(thumbnailHeaderRegex); if (match) { thumbnailInfo.width = parseInt(match[1], 10); thumbnailInfo.height = parseInt(match[2], 10); thumbnailInfo.size = parseInt(match[3], 10); } else if ( line.trim().startsWith("; ") && !line.includes("THUMBNAIL_BLOCK") ) { // Collect base64 data (remove the leading semicolon and space and thumbnail end) const dataLine = line.trim().substring(2); if (dataLine && dataLine != "thumbnail end") { base64Data += dataLine; } } }); // Add thumbnail data to config object if (base64Data) { configObject.thumbnail = { data: base64Data, ...thumbnailInfo, }; } } // Extract CONFIG_BLOCK const configBlockRegex = /; CONFIG_BLOCK_START([\s\S]*?)(?:; CONFIG_BLOCK_END|$)/; const configBlockMatch = fileContent.match(configBlockRegex); if (configBlockMatch && configBlockMatch[1]) { // Extract each config line const configLines = configBlockMatch[1].split("\n"); // Process each line configLines.forEach((line) => { // Check if the line starts with a semicolon and has an equals sign const configLineRegex = /^\s*;\s*([^=]+?)\s*=\s*(.*?)\s*$/; const match = line.match(configLineRegex); if (match) { const key = match[1].trim(); let value = match[2].trim(); // Try to convert value to appropriate type if (value === "true" || value === "false") { value = value === "true"; } else if (!isNaN(value) && value !== "") { // Check if it's a number (but not a percentage) if (!value.includes("%")) { value = Number(value); } } configObject[key] = value; } }); } // Extract additional variables that appear after EXECUTABLE_BLOCK_END const additionalVarsRegex = /; EXECUTABLE_BLOCK_(?:START|END)([\s\S]*?)(?:; CONFIG_BLOCK_START|$)/i; const additionalVarsMatch = fileContent.match(additionalVarsRegex); if (additionalVarsMatch && additionalVarsMatch[1]) { const additionalLines = additionalVarsMatch[1].split("\n"); additionalLines.forEach((line) => { // Match both standard format and the special case for "total filament cost" const varRegex = /^\s*;\s*((?:filament used|filament cost|total filament used|total filament cost|total layers count|estimated printing time)[^=]*?)\s*=\s*(.*?)\s*$/; const match = line.match(varRegex); if (match) { const key = match[1].replace(/\[([^\]]+)\]/g, "$1").trim(); let value = match[2].trim(); // Clean up values - remove units in brackets and handle special cases if (key.includes("filament used")) { // Extract just the numeric value, ignoring units in brackets const numMatch = value.match(/(\d+\.\d+)/); if (numMatch) { value = parseFloat(numMatch[1]); } } else if (key.includes("filament cost")) { // Extract just the numeric value const numMatch = value.match(/(\d+\.\d+)/); if (numMatch) { value = parseFloat(numMatch[1]); } } else if (key.includes("total layers count")) { value = parseInt(value, 10); } else if (key.includes("estimated printing time")) { // Keep as string but trim any additional whitespace value = value.trim(); } configObject[key] = value; } }); } // Also extract extrusion width settings const extrusionWidthRegex = /;\s*(.*?)\s*extrusion width\s*=\s*(.*?)mm/g; let extrusionMatch; while ((extrusionMatch = extrusionWidthRegex.exec(fileContent)) !== null) { const settingName = extrusionMatch[1].trim(); const settingValue = parseFloat(extrusionMatch[2].trim()); configObject[`${settingName} extrusion width`] = settingValue; } // Extract additional parameters after CONFIG_BLOCK_END if they exist const postConfigParams = /; CONFIG_BLOCK_END\s*\n([\s\S]*?)$/; const postConfigMatch = fileContent.match(postConfigParams); if (postConfigMatch && postConfigMatch[1]) { const postConfigLines = postConfigMatch[1].split("\n"); postConfigLines.forEach((line) => { // Match lines with format "; parameter_name = value" const paramRegex = /^\s*;\s*([^=]+?)\s*=\s*(.*?)\s*$/; const match = line.match(paramRegex); if (match) { const key = match[1].trim(); let value = match[2].trim(); // Try to convert value to appropriate type if (value === "true" || value === "false") { value = value === "true"; } else if (!isNaN(value) && value !== "") { // Check if it's a number (but not a percentage) if (!value.includes("%")) { value = Number(value); } } // Add to config object if not already present if (!configObject[key]) { configObject[key] = value; } } }); } // Apply camelCase conversion if requested return useCamelCase ? convertToCamelCase(configObject) : configObject; } export { parseStringIfNumber, convertToCamelCase, extractConfigBlock };