UNPKG

@linkiez/glory-star-calculator

Version:

Calculadora de tempo de corte para arquivos SVG da máquina GloryStar_GS3015

595 lines 25.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateDistance = calculateDistance; exports.getCuttingSpeed = getCuttingSpeed; exports.getPierceTime = getPierceTime; exports.calculateCuttingTime = calculateCuttingTime; exports.calculateCuttingTimeFromSvg = calculateCuttingTimeFromSvg; exports.calculateCuttingTimeFromDxf = calculateCuttingTimeFromDxf; const dxf_parser_1 = __importDefault(require("dxf-parser")); const constants_1 = require("./constants"); const svgProcessor_1 = require("./svgProcessor"); /** * Calcula a distância euclidiana entre dois pontos */ function calculateDistance(point1, point2) { return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)); } /** * Obtém a velocidade de corte para uma determinada espessura de material */ function getCuttingSpeed(thickness) { // Encontra os valores de espessura disponíveis na tabela const thicknesses = Object.keys(constants_1.CUTTING_SPEEDS).map(Number).sort((a, b) => a - b); // Se tivermos uma correspondência exata, retorna o valor if (constants_1.CUTTING_SPEEDS[thickness] !== undefined) { return constants_1.CUTTING_SPEEDS[thickness]; } // Espessura menor que a mínima disponível if (thickness < thicknesses[0]) { return constants_1.CUTTING_SPEEDS[thicknesses[0]]; } // Espessura maior que a máxima disponível if (thickness > thicknesses[thicknesses.length - 1]) { return constants_1.CUTTING_SPEEDS[thicknesses[thicknesses.length - 1]]; } // Interpolação linear entre os valores mais próximos for (let i = 0; i < thicknesses.length - 1; i++) { if (thickness > thicknesses[i] && thickness < thicknesses[i + 1]) { const lowerThickness = thicknesses[i]; const upperThickness = thicknesses[i + 1]; const lowerSpeed = constants_1.CUTTING_SPEEDS[lowerThickness]; const upperSpeed = constants_1.CUTTING_SPEEDS[upperThickness]; // Cálculo da interpolação linear const ratio = (thickness - lowerThickness) / (upperThickness - lowerThickness); return lowerSpeed - ratio * (lowerSpeed - upperSpeed); } } // Se por algum motivo não encontrou (não deveria ocorrer), retorna o valor para espessura mais próxima const closestThickness = thicknesses.reduce((prev, curr) => Math.abs(curr - thickness) < Math.abs(prev - thickness) ? curr : prev); return constants_1.CUTTING_SPEEDS[closestThickness]; } /** * Obtém o tempo de perfuração para uma determinada espessura de material */ function getPierceTime(thickness) { // Encontra os valores de espessura disponíveis na tabela const thicknesses = Object.keys(constants_1.PIERCE_TIMES).map(Number).sort((a, b) => a - b); if (constants_1.PIERCE_TIMES[thickness] !== undefined) { return constants_1.PIERCE_TIMES[thickness]; } // Espessura menor que a mínima disponível if (thickness < thicknesses[0]) { return constants_1.PIERCE_TIMES[thicknesses[0]]; } // Espessura maior que a máxima disponível if (thickness > thicknesses[thicknesses.length - 1]) { return constants_1.PIERCE_TIMES[thicknesses[thicknesses.length - 1]]; } // Interpolação linear entre os valores mais próximos for (let i = 0; i < thicknesses.length - 1; i++) { if (thickness > thicknesses[i] && thickness < thicknesses[i + 1]) { const lowerThickness = thicknesses[i]; const upperThickness = thicknesses[i + 1]; const lowerTime = constants_1.PIERCE_TIMES[lowerThickness]; const upperTime = constants_1.PIERCE_TIMES[upperThickness]; // Cálculo da interpolação linear const ratio = (thickness - lowerThickness) / (upperThickness - lowerThickness); return lowerTime + ratio * (upperTime - lowerTime); } } // Se por algum motivo não encontrou (não deveria ocorrer), retorna o valor para espessura mais próxima const closestThickness = thicknesses.reduce((prev, curr) => Math.abs(curr - thickness) < Math.abs(prev - thickness) ? curr : prev); return constants_1.PIERCE_TIMES[closestThickness]; } /** * Calcula o tempo de movimento baseado na distância e velocidade */ function calculateMovementTime(distance, speed) { if (distance <= 0 || speed <= 0) { return 0; } // Garante que não estamos usando valores NaN ou Infinity if (isNaN(distance) || !isFinite(distance) || isNaN(speed) || !isFinite(speed)) { return 0; } // Converte velocidade de mm/min para mm/s const speedInMmPerSec = speed / 60; // Se a distância for muito pequena, não considera aceleração if (distance < constants_1.MIN_DISTANCE_FOR_ACCELERATION) { return distance / speedInMmPerSec; } // Considera aceleração e desaceleração return distance / speedInMmPerSec + constants_1.ACCELERATION_TIME; } /** * Determina se um movimento deve ser feito com o cabeçote abaixado */ function shouldMoveWithHeadDown(distance) { return distance <= constants_1.MAX_DISTANCE_FOR_HEAD_DOWN; } /** * Determina se um movimento deve ser feito como um salto (jump) */ function shouldJump(distance) { return distance <= constants_1.MAX_DISTANCE_FOR_JUMP && distance > constants_1.MAX_DISTANCE_FOR_HEAD_DOWN; } /** * Calcula o tempo para um movimento específico */ function calculateTimeForMovement(movement, cuttingSpeed) { const distance = calculateDistance(movement.start, movement.end); // Garante que não estamos lidando com valores inválidos if (isNaN(distance) || !isFinite(distance)) { return { time: 0, distance: 0, isCutting: movement.isCutting }; } if (movement.isCutting) { // Movimento de corte const time = calculateMovementTime(distance, cuttingSpeed); return { time, distance, isCutting: true }; } else { // Movimento de posicionamento let speed = constants_1.RAPID_SPEED; if (shouldMoveWithHeadDown(distance)) { // Movimento curto com cabeçote abaixado speed = constants_1.RAPID_SPEED * 0.8; // 80% da velocidade rápida } else if (shouldJump(distance)) { // Movimento médio com salto speed = constants_1.RAPID_SPEED * 0.9; // 90% da velocidade rápida } const time = calculateMovementTime(distance, speed); return { time, distance, isCutting: false }; } } /** * Otimiza o caminho de corte para minimizar movimentos * (Implementação aprimorada de otimização) */ function optimizeMovements(movements) { if (movements.length <= 1) { return [...movements]; } // Separa movimentos de corte e posicionamento const cuttingSegments = []; let currentSegment = []; // Agrupa movimentos de corte contínuos OU conectados const EPSILON = 0.01; for (let i = 0; i < movements.length; i++) { if (movements[i].isCutting) { if (currentSegment.length === 0 || (Math.abs(movements[i].start.x - currentSegment[currentSegment.length - 1].end.x) < EPSILON && Math.abs(movements[i].start.y - currentSegment[currentSegment.length - 1].end.y) < EPSILON)) { currentSegment.push(movements[i]); } else { cuttingSegments.push([...currentSegment]); currentSegment = [movements[i]]; } } else if (currentSegment.length > 0) { cuttingSegments.push([...currentSegment]); currentSegment = []; } } if (currentSegment.length > 0) { cuttingSegments.push([...currentSegment]); } // Se não há segmentos de corte, retorna os movimentos originais if (cuttingSegments.length === 0) { return [...movements]; } // Reorganiza os segmentos para minimizar movimentos const optimizedMovements = []; let currentPoint = { x: 0, y: 0 }; // Ponto inicial while (cuttingSegments.length > 0) { // Encontra o segmento mais próximo do ponto atual let closestSegmentIndex = 0; let minDistance = Infinity; let useReverseOrder = false; for (let i = 0; i < cuttingSegments.length; i++) { const segment = cuttingSegments[i]; // Distância até o primeiro ponto do segmento const distToStart = calculateDistance(currentPoint, segment[0].start); if (distToStart < minDistance) { minDistance = distToStart; closestSegmentIndex = i; useReverseOrder = false; } // Distância até o último ponto do segmento (segmento invertido) if (segment.length > 1) { // só faz sentido inverter se houver mais de um movimento const lastMovement = segment[segment.length - 1]; const distToEnd = calculateDistance(currentPoint, lastMovement.end); if (distToEnd < minDistance) { minDistance = distToEnd; closestSegmentIndex = i; useReverseOrder = true; } } } // Adiciona movimento de posicionamento para o próximo segmento, se necessário const segment = cuttingSegments[closestSegmentIndex]; let segmentStart, segmentEnd; if (useReverseOrder && segment.length > 1) { segmentStart = segment[segment.length - 1].end; segmentEnd = segment[0].start; } else { segmentStart = segment[0].start; segmentEnd = segment[segment.length - 1].end; } // Só adiciona movimento de posicionamento se não estiver já conectado if (currentPoint.x !== segmentStart.x || currentPoint.y !== segmentStart.y) { optimizedMovements.push({ start: currentPoint, end: segmentStart, isCutting: false }); } // Adiciona os movimentos de corte if (useReverseOrder && segment.length > 1) { for (let i = segment.length - 1; i >= 0; i--) { optimizedMovements.push({ start: segment[i].end, end: segment[i].start, isCutting: true }); } currentPoint = segment[0].start; } else { optimizedMovements.push(...segment); currentPoint = segment[segment.length - 1].end; } // Remove o segmento processado cuttingSegments.splice(closestSegmentIndex, 1); } // Remove movimento de posicionamento inicial se for desnecessário if (optimizedMovements.length > 0 && !optimizedMovements[0].isCutting && optimizedMovements[1] && optimizedMovements[0].end.x === optimizedMovements[1].start.x && optimizedMovements[0].end.y === optimizedMovements[1].start.y) { optimizedMovements.shift(); } return optimizedMovements; } /** * Função utilitária para inserir movimentos de posicionamento entre segmentos desconectados */ function insertPositioningMovements(movements) { if (movements.length === 0) return []; const EPSILON = 0.01; const result = [movements[0]]; for (let i = 1; i < movements.length; i++) { const prev = result[result.length - 1]; const curr = movements[i]; if (Math.abs(prev.end.x - curr.start.x) > EPSILON || Math.abs(prev.end.y - curr.start.y) > EPSILON) { result.push({ start: prev.end, end: curr.start, isCutting: false }); } result.push(curr); } return result; } /** * Função utilitária para normalizar movimentos para a origem */ function normalizeMovementsToOrigin(movements) { if (movements.length === 0) return movements; let minX = Infinity, minY = Infinity; for (const m of movements) { minX = Math.min(minX, m.start.x, m.end.x); minY = Math.min(minY, m.start.y, m.end.y); } // Se já está na origem, não faz nada if (Math.abs(minX) < 1e-6 && Math.abs(minY) < 1e-6) return movements; return movements.map(m => (Object.assign(Object.assign({}, m), { start: { x: m.start.x - minX, y: m.start.y - minY }, end: { x: m.end.x - minX, y: m.end.y - minY } }))); } /** * Calcula o tempo de corte a partir de uma lista de movimentos */ function calculateCuttingTime(movements, options) { var _a, _b; // Valores iniciais para o resultado const result = { totalTimeSec: 0, cuttingTimeSec: 0, movementTimeSec: 0, piercingTimeSec: 0, setupTimeSec: 0, totalDistance: 0, cuttingDistance: 0, movementDistance: 0, pierceCount: 0, partCount: 0 }; if (movements.length === 0) { return result; } const scale = (_a = options.scaleFactor) !== null && _a !== void 0 ? _a : 1; // Determina o kerf a ser usado let kerf = options.kerf; if (kerf === undefined || kerf === null) { // Busca kerf padrão para a espessura // @ts-ignore const { KERF_DISTANCE } = require('./constants'); kerf = (_b = KERF_DISTANCE[options.materialThickness]) !== null && _b !== void 0 ? _b : 0; } kerf = Number(kerf) || 0; // Otimiza os movimentos se a opção estiver habilitada let processedMovements = movements; if (options.optimize) { // Normaliza para origem antes de otimizar processedMovements = optimizeMovements(normalizeMovementsToOrigin(insertPositioningMovements(movements))); } else { processedMovements = movements; } // Obtém a velocidade de corte baseada na espessura do material const cuttingSpeed = getCuttingSpeed(options.materialThickness); // Obtém o tempo de perfuração const pierceTime = getPierceTime(options.materialThickness); // Flag para rastrear se estamos em um segmento de corte contínuo let inCuttingSegment = false; // Processa cada movimento processedMovements.forEach(movement => { let { time, distance, isCutting } = calculateTimeForMovement(movement, cuttingSpeed); distance *= scale; time *= scale; // Aplica kerf apenas para movimentos de corte if (isCutting && kerf > 0 && options.materialThickness > 0) { distance *= (1 + kerf / options.materialThickness); } // Acumula distâncias result.totalDistance += distance; if (isCutting) { result.cuttingDistance += distance; result.cuttingTimeSec += time; // Se não estávamos cortando antes, adiciona tempo de perfuração if (!inCuttingSegment) { result.pierceCount++; result.piercingTimeSec += pierceTime; inCuttingSegment = true; } } else { result.movementDistance += distance; result.movementTimeSec += time; inCuttingSegment = false; } }); // Conta o número de peças (cada "true" seguido de "false" na sequência de isCutting) result.partCount = result.pierceCount; // Adiciona tempo de setup result.setupTimeSec = constants_1.SETUP_TIME * result.partCount; // Calcula tempo total result.totalTimeSec = result.cuttingTimeSec + result.movementTimeSec + result.piercingTimeSec + result.setupTimeSec; return result; } /** * Função principal que calcula o tempo de corte a partir de um arquivo SVG */ function calculateCuttingTimeFromSvg(svgContent, options) { // Processa o SVG para extrair elementos const elements = (0, svgProcessor_1.processSvg)(svgContent); // Converte os elementos em movimentos const movements = (0, svgProcessor_1.convertElementsToMovements)(elements); // Calcula bounding box let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (const el of elements) { for (const pt of el.points) { minX = Math.min(minX, pt.x); minY = Math.min(minY, pt.y); maxX = Math.max(maxX, pt.x); maxY = Math.max(maxY, pt.y); } } // Calcula o tempo de corte const result = calculateCuttingTime(movements, options); if (isFinite(minX) && isFinite(maxX) && isFinite(minY) && isFinite(maxY)) { result.cutAreaWidth = maxX - minX; result.cutAreaHeight = maxY - minY; } return result; } /** * Função principal que calcula o tempo de corte a partir de um arquivo DXF */ function calculateCuttingTimeFromDxf(dxfString, options) { const parser = new dxf_parser_1.default(); let dxf; try { dxf = parser.parseSync(dxfString); } catch (e) { throw new Error('Erro ao fazer o parse do DXF: ' + e); } const movements = []; const entityTypes = {}; let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; if (!dxf || !dxf.entities) return calculateCuttingTime([], options); for (const entity of dxf.entities) { entityTypes[entity.type] = (entityTypes[entity.type] || 0) + 1; let entityMinX = Infinity, entityMinY = Infinity, entityMaxX = -Infinity, entityMaxY = -Infinity; if (entity.type === 'INSERT' && dxf.blocks && dxf.blocks[entity.name]) { // Expande o bloco const block = dxf.blocks[entity.name]; const insertX = entity.x || 0; const insertY = entity.y || 0; for (const blockEntity of block.entities) { // Cria uma cópia da entidade com deslocamento const e = JSON.parse(JSON.stringify(blockEntity)); // Aplica deslocamento para entidades com pontos if (e.x !== undefined && e.y !== undefined) { e.x += insertX; e.y += insertY; } if (e.center) { e.center.x += insertX; e.center.y += insertY; } if (e.vertices) { for (const v of e.vertices) { v.x += insertX; v.y += insertY; } } // Processa a entidade expandida como se fosse do topo dxf.entities.push(e); } continue; } if (entity.type === 'LINE') { if (entity.vertices && entity.vertices.length === 2) { movements.push({ start: { x: entity.vertices[0].x, y: entity.vertices[0].y }, end: { x: entity.vertices[1].x, y: entity.vertices[1].y }, isCutting: true }); [entity.vertices[0], entity.vertices[1]].forEach((pt) => { entityMinX = Math.min(entityMinX, pt.x); entityMinY = Math.min(entityMinY, pt.y); entityMaxX = Math.max(entityMaxX, pt.x); entityMaxY = Math.max(entityMaxY, pt.y); }); } else { movements.push({ start: { x: entity.x1, y: entity.y1 }, end: { x: entity.x2, y: entity.y2 }, isCutting: true }); [ { x: entity.x1, y: entity.y1 }, { x: entity.x2, y: entity.y2 } ].forEach((pt) => { entityMinX = Math.min(entityMinX, pt.x); entityMinY = Math.min(entityMinY, pt.y); entityMaxX = Math.max(entityMaxX, pt.x); entityMaxY = Math.max(entityMaxY, pt.y); }); } } if (entity.type === 'CIRCLE') { const circle = entity; const steps = 32; const points = []; for (let i = 0; i < steps; i++) { const angle = (2 * Math.PI * i) / steps; points.push({ x: circle.center.x + circle.radius * Math.cos(angle), y: circle.center.y + circle.radius * Math.sin(angle) }); } for (let i = 0; i < steps; i++) { movements.push({ start: points[i], end: points[(i + 1) % steps], isCutting: true }); } entityMinX = circle.center.x - circle.radius; entityMaxX = circle.center.x + circle.radius; entityMinY = circle.center.y - circle.radius; entityMaxY = circle.center.y + circle.radius; } if (entity.type === 'ARC') { const arc = entity; const steps = 24; const startAngle = (arc.startAngle * Math.PI) / 180; const endAngle = (arc.endAngle * Math.PI) / 180; let sweep = endAngle - startAngle; if (sweep <= 0) sweep += 2 * Math.PI; let arcMinX = Infinity, arcMaxX = -Infinity, arcMinY = Infinity, arcMaxY = -Infinity; for (let i = 0; i < steps; i++) { const a1 = startAngle + (sweep * i) / steps; const a2 = startAngle + (sweep * (i + 1)) / steps; const pt1 = { x: arc.center.x + arc.radius * Math.cos(a1), y: arc.center.y + arc.radius * Math.sin(a1) }; const pt2 = { x: arc.center.x + arc.radius * Math.cos(a2), y: arc.center.y + arc.radius * Math.sin(a2) }; movements.push({ start: pt1, end: pt2, isCutting: true }); [pt1, pt2].forEach((pt) => { arcMinX = Math.min(arcMinX, pt.x); arcMinY = Math.min(arcMinY, pt.y); arcMaxX = Math.max(arcMaxX, pt.x); arcMaxY = Math.max(arcMaxY, pt.y); }); } // Envelope total do arco (círculo completo) arcMinX = Math.min(arcMinX, arc.center.x - arc.radius); arcMaxX = Math.max(arcMaxX, arc.center.x + arc.radius); arcMinY = Math.min(arcMinY, arc.center.y - arc.radius); arcMaxY = Math.max(arcMaxY, arc.center.y + arc.radius); entityMinX = arcMinX; entityMaxX = arcMaxX; entityMinY = arcMinY; entityMaxY = arcMaxY; } if (entity.type === 'LWPOLYLINE' || entity.type === 'POLYLINE') { const poly = entity; for (let i = 0; i < poly.vertices.length - 1; i++) { movements.push({ start: { x: poly.vertices[i].x, y: poly.vertices[i].y }, end: { x: poly.vertices[i + 1].x, y: poly.vertices[i + 1].y }, isCutting: true }); [poly.vertices[i], poly.vertices[i + 1]].forEach((pt) => { entityMinX = Math.min(entityMinX, pt.x); entityMinY = Math.min(entityMinY, pt.y); entityMaxX = Math.max(entityMaxX, pt.x); entityMaxY = Math.max(entityMaxY, pt.y); }); } if (poly.closed) { movements.push({ start: { x: poly.vertices[poly.vertices.length - 1].x, y: poly.vertices[poly.vertices.length - 1].y }, end: { x: poly.vertices[0].x, y: poly.vertices[0].y }, isCutting: true }); [poly.vertices[poly.vertices.length - 1], poly.vertices[0]].forEach((pt) => { entityMinX = Math.min(entityMinX, pt.x); entityMinY = Math.min(entityMinY, pt.y); entityMaxX = Math.max(entityMaxX, pt.x); entityMaxY = Math.max(entityMaxY, pt.y); }); } } // Atualiza o bounding box global minX = Math.min(minX, entityMinX); minY = Math.min(minY, entityMinY); maxX = Math.max(maxX, entityMaxX); maxY = Math.max(maxY, entityMaxY); } // Normaliza movimentos para enquadrar o desenho na origem const normalizedMovements = normalizeMovementsToOrigin(movements); console.log('Tipos de entidades encontrados no DXF:', entityTypes); console.log('Bounding box DXF: minX=', minX, 'maxX=', maxX, 'minY=', minY, 'maxY=', maxY, 'Largura:', maxX - minX, 'Altura:', maxY - minY); const result = calculateCuttingTime(normalizedMovements, options); if (isFinite(minX) && isFinite(maxX) && isFinite(minY) && isFinite(maxY)) { result.cutAreaWidth = maxX - minX; result.cutAreaHeight = maxY - minY; } return result; } //# sourceMappingURL=cuttingCalculator.js.map