Преобразование дуги пути SVG в DXF

Я работаю над созданием собственного конвертера из SVG в DXF, и мне нужен математический мастер, который поможет мне выяснить, где моя логика ошибочна.

Вот мой текущий код:

function svgArcToLWPolyline(rx, ry, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2) {
    const scaleX = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) / (2 * rx);
    const scaleY = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) / (2 * ry);

    const arcInfo = svgArcToOvalArc(rx * scaleX, ry * scaleY, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2);
    const numPoints = calculateNumberOfPoints(rx * scaleX, ry * scaleY, arcInfo.startAngle, arcInfo.endAngle);

    // Calculate bulge factors
    const bulgeFactors = [];
    const angleIncrement = (arcInfo.endAngle - arcInfo.startAngle) / (numPoints - 1);
    let currentAngle = arcInfo.startAngle;
    for (let i = 0; i < numPoints - 1; i++) {
        const nextAngle = currentAngle + angleIncrement;
        const bulge = Math.tan((nextAngle - currentAngle) * Math.PI / 360);
        bulgeFactors.push(bulge);
        currentAngle = nextAngle;
    }
    bulgeFactors.push(0); // Last point has zero bulge

    // Construct LWPOLYLINE points
    const lwpolylinePoints = [];
    for (let i = 0; i < numPoints; i++) {
        const angle = arcInfo.startAngle + i * angleIncrement;
        const x = arcInfo.cx + rx * scaleX * Math.cos(angle * Math.PI / 180);
        const y = arcInfo.cy + ry * scaleY * Math.sin(angle * Math.PI / 180);
        lwpolylinePoints.push([x, y, bulgeFactors[i]]);
    }

    return lwpolylinePoints;
}

function svgArcToOvalArc(rx, ry, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2) {
    // Convert rotation angle to radians
    const angle = rotation * Math.PI / 180;

    // Calculate intermediate values
    const dx = (x1 - x2) / 2;
    const dy = (y1 - y2) / 2;
    const x1p = Math.cos(angle) * dx + Math.sin(angle) * dy;
    const y1p = -Math.sin(angle) * dx + Math.cos(angle) * dy;
    const rxSq = rx * rx;
    const rySq = ry * ry;
    const x1pSq = x1p * x1p;
    const y1pSq = y1p * y1p;
    let radicand = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);

    // Ensure non-negative radicand
    if (radicand < 0) {
        radicand = 0;
    }

    // Calculate root
    let root = Math.sqrt(radicand);
    if (largeArcFlag === sweepFlag) {
        root = -root;
    }
    const cxp = root * rx * y1p / ry;
    const cyp = -root * ry * x1p / rx;

    // Calculate center
    const cx = Math.cos(angle) * cxp - Math.sin(angle) * cyp + (x1 + x2) / 2;
    const cy = Math.sin(angle) * cxp + Math.cos(angle) * cyp + (y1 + y2) / 2;

    // Calculate start and end angles
    let startAngle = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
    let endAngle = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);

    // Convert angles to degrees
    startAngle *= 180 / Math.PI;
    endAngle *= 180 / Math.PI;

    // Adjust angles to be in the range [0, 360]
    if (startAngle < 0) {
        startAngle += 360;
    }
    if (endAngle < 0) {
        endAngle += 360;
    }

    return { cx: cx, cy: cy, startAngle: startAngle, endAngle: endAngle };
}

function calculateNumberOfPoints(rx, ry, startAngle, endAngle) {
    // Calculate arc length
    let arcLength;
    if (startAngle <= endAngle) {
        arcLength = (endAngle - startAngle) * Math.PI / 180;
    } else {
        arcLength = (360 - startAngle + endAngle) * Math.PI / 180;
    }
    arcLength *= (rx + ry) / 2;

    // Choose a fixed length for each segment
    const segmentLength = 1.0;  // Adjust as needed

    // Calculate the number of points
    const numPoints = Math.max(Math.floor(arcLength / segmentLength), 2);  // Minimum of 2 points

    return numPoints;
}

Вот как выглядит SVG при использовании
d = "M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z"

СВГ:

DXF:

Как видите, ry и rx не учитываются. И когда я меняю путь, чтобы добавить xRotationalAxis, мой dxf ломается еще больше: d = "M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z":

SVG с путем M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z

Преобразование Dxf M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z

Я потратил 12 часов, пытаясь настроить это и математически выяснить, как заставить это работать (с большой помощью Так что любая помощь, которую я могу получить по этому поводу, была бы очень полезна!

Полагаю, считается само собой разумеющимся, что big-arc-flag = 1. Вы можете попробовать перерисовать свои пути, убедившись, что big-arc-flag = 1. В приведенном выше примере попробуйте d = "M10,10L90,80V90L50,50A1,3,0,0,1,20,25L10,10Z"

enxaneta 09.05.2024 08:56

Я написал пост немного поздно и не проверял его до утра. Я обновил свой вопрос, чтобы он имел больше смысла. Я хочу, чтобы мой dxf полностью имитировал SVG. Итак, если в svg есть флаг большой дуги 0, я хочу, чтобы он это отражал. Вы хотите сказать, что моя логика предполагает, что bigArcFlag равен 1?

Andrew Giles 10.05.2024 17:56
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
99
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Боюсь, в помощнике вычислений есть несколько ошибок.
Самое главное, вам необходимо вычислить фактические значения rx и ry посредством параметризации.

Другими словами, A 1,3, 45,0,0,50,50 не содержит абсолютных значений для rx/ry – rx=1 и ry=3 являются относительными значениями.
Вот пример, основанный на ответе cuixiping «Вычислить центр дуги SVG»

// arc command values
let p0 = {
  x: 20,
  y: 25
}
let values = [1, 3, 45, 0, 0, 50, 50]
let vertices = 12;


let polylinePts = arcToPolyline(p0, values, vertices)
//render polyline
polyline.setAttribute('points', polylinePts.map(pt => {
  return `${pt.x} ${pt.y}`
}))


function arcToPolyline(p0, values, vertices = 12) {
  let [rxC, ryC, xAxisRotation, largeArc, sweep, x, y] = values;

  // parametrize arc command to get actual rx,ry and center
  let param = svgArcToCenterParam(p0.x, p0.y, rxC, ryC, xAxisRotation, largeArc, sweep, x, y);
  let {
    cx,
    cy,
    rx,
    ry,
    startAngle,
    deltaAngle,
    endAngle
  } = param;
  let splitAngle = deltaAngle / vertices;

  let pts = []
  for (let i = 0; i <= vertices; i++) {
    let angle = startAngle - splitAngle * i;
    let xAxisRotation_radian = xAxisRotation * Math.PI / 180;
    let pt = getEllipsePointForAngle(cx, cy, rx, ry, xAxisRotation_radian, deltaAngle + angle)
    pts.push(pt)
  }
  return pts;
}


/**
 * based on @cuixiping;
 * https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
 */
function svgArcToCenterParam(p0x, p0y, rx, ry, angle, largeArc, sweep, px, py) {
  const radian = (ux, uy, vx, vy) => {
    let dot = ux * vx + uy * vy;
    let mod = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
    let rad = Math.acos(dot / mod);
    if (ux * vy - uy * vx < 0) {
      rad = -rad;
    }
    return rad;
  };
  // degree to radian – if rx equals ry the x-axis rotation has no effect
  let phi = rx === ry ? 0 : (angle * Math.PI) / 180;
  let cx, cy, startAngle, deltaAngle, endAngle;
  let PI = Math.PI;
  //let PIpx = PI * 2;

  // invalid arguments
  if (rx == 0 || ry == 0) {
    throw Error("rx and ry can not be 0");
  }
  if (rx < 0) {
    rx = -rx;
  }
  if (ry < 0) {
    ry = -ry;
  }
  let s_phi = Math.sin(phi);
  let c_phi = Math.cos(phi);
  let hd_x = (p0x - px) / 2; // half diff of x
  let hd_y = (p0y - py) / 2; // half diff of y
  let hs_x = (p0x + px) / 2; // half sum of x
  let hs_y = (p0y + py) / 2; // half sum of y
  // F6.5.1
  let p0x_ = c_phi * hd_x + s_phi * hd_y;
  let p0y_ = c_phi * hd_y - s_phi * hd_x;
  // F.6.6 Correction of out-of-range radii
  //   Step 3: Ensure radii are large enough
  let lambda = (p0x_ * p0x_) / (rx * rx) + (p0y_ * p0y_) / (ry * ry);
  if (lambda > 1) {
    rx = rx * Math.sqrt(lambda);
    ry = ry * Math.sqrt(lambda);
  }
  let rxry = rx * ry;
  let rxp0y_ = rx * p0y_;
  let ryp0x_ = ry * p0x_;
  let sum_of_sq = rxp0y_ * rxp0y_ + ryp0x_ * ryp0x_; // sum of square
  if (!sum_of_sq) {
    console.info("start point can not be same as end point");
  }
  let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
  if (largeArc == sweep) {
    coe = -coe;
  }
  // F6.5.2
  let cx_ = (coe * rxp0y_) / ry;
  let cy_ = (-coe * ryp0x_) / rx;
  // F6.5.3
  cx = c_phi * cx_ - s_phi * cy_ + hs_x;
  cy = s_phi * cx_ + c_phi * cy_ + hs_y;
  let xcr1 = (p0x_ - cx_) / rx;
  let xcr2 = (p0x_ + cx_) / rx;
  let ycr1 = (p0y_ - cy_) / ry;
  let ycr2 = (p0y_ + cy_) / ry;
  // F6.5.5
  startAngle = radian(1, 0, xcr1, ycr1);
  // F6.5.6
  deltaAngle = radian(xcr1, ycr1, -xcr2, -ycr2);
  if (deltaAngle > PI * 2) {
    deltaAngle -= PI * 2;
  } else if (deltaAngle < 0) {
    deltaAngle += PI * 2;
  }
  if (sweep == false || sweep == 0) {
    deltaAngle -= PI * 2;
  }
  endAngle = startAngle + deltaAngle;
  if (endAngle > PI * 2) {
    endAngle -= PI * 2;
  } else if (endAngle < 0) {
    endAngle += PI * 2;
  }
  let outputObj = {
    cx: cx,
    cy: cy,
    rx: rx,
    ry: ry,
    startAngle: startAngle,
    deltaAngle: deltaAngle,
    endAngle: endAngle,
    sweep: sweep
  };
  return outputObj;
}



/*
 * based on
 * https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/modules/canvaskit/htmlcanvas/path2d.js
 * and https://observablehq.com/@toja/ellipse-and-elliptical-arc-conversion
 */

function getEllipsePointForAngle(cx, cy, rx, ry, rotation_angle, angle) {
  let M = Math.abs(rx) * Math.cos(angle),
    N = Math.abs(ry) * Math.sin(angle);
  return {
    x: cx + Math.cos(rotation_angle) * M - Math.sin(rotation_angle) * N,
    y: cy + Math.sin(rotation_angle) * M + Math.cos(rotation_angle) * N
  };
}
svg {
  overflow: visible;
  border: 1px solid #ccc;
}
<svg id = "svg" viewBox = "-9 0 100 100">
  <path id = "path" d = "M 10 10
          L 20 25
          A 1 3 45 0 0 50 50
          L 90 90
          V 80 
          Z" fill = "none" stroke = "#000" />
  <polyline id = "polyline" fill = "none" stroke = "red"></polyline>
</svg>

Как это работает

  1. svgArcToCenterParam(p0x, p0y, rx, ry, angle, largeArc, sweep, px, py) возвращает параметризованные параметры эллипса
  2. теперь мы можем вычислить несколько точек на эллипсе с помощью getEllipsePointForAngle(cx, cy, rx, ry, rotation_angle, angle)

Maker.js

Для более удобного преобразования вы можете просто использовать maker.js, поскольку он включает в себя готовый метод экспорта в dxf.

var m = require("makerjs");

var pathData = `M10,10L20,25 A1,3,0,0,0,50,50L90,90V80Z`;
var path =  m.importer.fromSVGPathData(pathData)

// convert to maker.js model object
var model = {models: {path}}

//export
var dxf = m.exporter.toDXF(model)
outputDxf.value=dxf;
textarea{
  display:block;
  width:100%;
  min-height:50vmin
}
<script src = "https://cdn.jsdelivr.net/npm/makerjs@0/target/js/browser.maker.js"></script>
<script src = "https://cdn.jsdelivr.net/npm/bezier-js@2/bezier.js"></script>


<h3>DXF</h3>
<textarea id = "outputDxf"></textarea>

Вы также можете почерпнуть вдохновение из моего помощника по созданию пути к многоугольнику (или простого примера). Он основан на специальном анализаторе данных пути и вычисляет длину каждого сегмента пути. Таким образом, мы можем вычислить вершины многоугольника с учетом команды on-path-points.

Большое спасибо за работу, которую вы вложили в этот ответ. Это было очень полезно и именно то, что мне было нужно. Мне нужно будет еще немного изучить maker.js, если он принесет мне пользу. При беглом взгляде кажется, что у меня могут возникнуть проблемы с моими шаблонами штриховки от svg до dxf. Но просто получение точных баллов — это именно то, что мне нужно.

Andrew Giles 14.05.2024 03:58

Другие вопросы по теме