产品草图的JS库

huangapple go评论83阅读模式
英文:

JS libraries for product sketching

问题

我需要帮助找一个好的JS库,可以创建像这样的产品草图:Partition sketch。我正在使用Next.js,但大多数库都有问题,因为Next.js使用SSR,而大多数图形库在客户端运行。我不需要向用户显示草图,它应该只是一个基于用户在网页上输入的可下载的SVG文件。

理想情况下,我希望它是一个PDF,可以直接发送给客户,包含所有必要的信息和草图。这可行吗?

我已经尝试使用Rhino3dm.js和P5.js,但在Next.js中实现起来都很困难。

英文:

I need help finding a good JS library that can create a product sketch like this:
Partition sketch. I am using Next.js, and are having toruble with most libraries since Next.js is using SSR and most graphical libraries run client side. I dont need to show the sketch (neccessarrily) to the user, it should just be a downloadble SVG file that are based on the users input from the webpage.

Idealy i want it to be a PDF, that can be send directly to a customer, with all the requireing information and the sketch. Is this possible?

I have tried messing around with Rhino3dm.js and P5.js, but both seem hard to implement with Next.js.

答案1

得分: 1

你可以尝试使用p5.svg渲染器,在p5中绘制相同的测量/线条,但直接渲染为SVG而不是画布(完全绕过.3dm格式)。该库最近似乎缺乏更新,但你可以从旧版本/示例(例如这个)开始,并复制/修改以满足你的原型需求。

如果.3dm更好,你根本不需要使用p5.js:rhino3dm js库应该可以处理绘制线条/曲线/文本(如果你有用户的输入)并保存为.3dm。

你可以查看SampleSketch2d的实时示例(源代码)。

产品草图的JS库

你也可以运行下面的示例:

<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->

    const downloadButton = document.getElementById("downloadButton")
    downloadButton.onclick = download

    // global variables
    let _model = {
      // saved nurbs curves
      curves: [],
      // new nurbs curve
      points: null,
      // viewport for canvas
      viewport: null,
    }


    // wait for the rhino3dm web assembly to load asynchronously
    let rhino
    rhino3dm().then(async m => {
      console.log('Loaded rhino3dm.')
      rhino = m // global
      run()
    })
    /**/

    // initialize canvas and model
    function run() {
      let canvas = getCanvas()
      canvas.addEventListener('mousedown', onMouseDown)
      canvas.addEventListener('mousemove', onMouseMove)
      window.addEventListener('keyup', onKeyUp)
      _model.points = new rhino.Point3dList()
      _model.viewport = new rhino.ViewportInfo()
      _model.viewport.screenPort = [0, 0, canvas.clientWidth, canvas.clientHeight]
      _model.viewport.setFrustum(-30,30,-30,30,1,1000)
      draw()
    }

    function download() {
      if(_model.curves.length<1){
        console.log('no curves')
        return
      }

      let doc = new rhino.File3dm()
      for(let i=0; i<_model.curves.length;i++) {
        doc.objects().add(_model.curves[i], null)
      }

      let options = new rhino.File3dmWriteOptions()
      options.version = 7
      let buffer = doc.toByteArray(options)
      saveByteArray("sketch2d"+ options.version +".3dm", buffer)
      doc.delete()
    }

    function saveByteArray(fileName, byte) {
      let blob = new Blob([byte], {type: "application/octect-stream"})
      let link = document.createElement('a')
      link.href = window.URL.createObjectURL(blob)
      link.download = fileName
      link.click()
    }

    /* * * * * * * * * * * * * * * *  interaction   * * * * * * * * * * * * * * * */

    // handles mouse down events
    // adds a new control point at the location of the mouse
    function onMouseDown(event) {
      // get the location of the mouse on the canvas
      let [x,y] = getXY(event)

      // if this is a brand new curve, add the first control point
      if (_model.points.count === 0) {
        _model.points.add(x, y, 0)
      }

      // add a new control point that will be saved on the next mouse click
      // (the location of the previous control point is now frozen)
      _model.points.add(x, y, 0)
      draw()
    }

    // handles mouse move events
    // the last control point in the list follows the mouse
    function onMouseMove(event) {
      let index = _model.points.count - 1
      if (index >= 0) {
        let [x,y] = getXY(event)
        _model.points.set(index, [x, y, 0])
        draw()
      }
    }

    // handles key up events
    function onKeyUp( event ) {
      switch ( event.key ) {
        // when the enter key is pressed, save the new nurbs curve
        case "Enter":
          if (_model.points.count < 4) { // 3 pts (min.) + next pt
            console.error('Not enough points!')
          } else {
            // remove the last point in the list (a.k.a. next)
            let index = _model.points.count - 1
            _model.points.removeAt(index)

            // construct a curve from the points list
            let degree = _model.points.count - 1
            if (degree > 3)
              degree = 3

            // construct a nurbs curve
            // (first arg == true to create a closed periodic uniform curve)
            _model.curves.push(rhino.NurbsCurve.create(true, degree, _model.points))
          }

          // clear points list
          _model.points.clear()
          
          // enable download button
          downloadButton.disabled = false
          break
      }
      draw()
    }

    /* * * * * * * * * * * * * * * * *  helpers   * * * * * * * * * * * * * * * */

    // gets the canvas
    function getCanvas() {
      return document.getElementById('canvas')
    }

    // gets the [x, y] location of the mouse in world coordinates
    function getXY(evt) {
      let canvas = getCanvas()
      let rect = canvas.getBoundingClientRect()
      let x = evt.clientX - rect.left
      let y = evt.clientY - rect.top
      let s2w = _model.viewport.getXform(rhino.CoordinateSystem.Screen, rhino.CoordinateSystem.World)
      let world_point = rhino.Point3d.transform([x,y,0], s2w)
      s2w.delete()
      return [world_point[0],world_point[1]]
    }

    /* * * * * * * * * * * * * * * * *  drawing   * * * * * * * * * * * * * * * */

    // clears the canvas and draws the model
    // for some reason removing semicolons causes an error in this method
    function draw() {
      // get canvas' 2d context
      let canvas = getCanvas();
      let ctx = canvas.getContext('2d');
      let w2s = _model.viewport.getXform(rhino.CoordinateSystem.World, rhino.CoordinateSystem.Screen);

      // clear and draw a grid
      ctx.beginPath();
      ctx.lineWidth = 0.5;
      ctx.strokeStyle = 'rgb(130,130,130)';
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for(let i=0; i<50; i+=1){
        let [x,y] = rhino.Point3d.transform([i,-50,0], w2s);
        let [x1,y1] = rhino.Point3d.transform([i,50,0], w2s);
        ctx.moveTo(x,y);
        ctx.lineTo(x1,y1);
        [x,y] = rhino.Point3d.transform([-i,-50,0], w2s);
        [x1,y1] = rhino.Point3d.transform([-i,50,0], w2s);
        ctx.moveTo(x,y);
        ctx.lineTo(x1,y1);

        [x,y] = rhino.Point3d.transform([-50, i, 0], w2s);
        [x1,y1] = rhino.Point3d.transform([50, i, 0], w2s);
        ctx.moveTo(x,y);
        ctx.lineTo(x1,y1);
        [x,y] = rhino.Point3d.transform([-50, -i, 0], w2s);
        [x1,y1] = rhino.Point3d.transform([50, -i, 0], w2s);
        ctx.moveTo(x,y);
        ctx.lineTo(x1,y1);
      }
      ctx.stroke();

      ctx.lineWidth = 2;
      ctx.strokeStyle = 'rgb(150,75,75)';
      let [x,y] = rhino.Point3d.transform([0,0,0], w2s);
      let [x1,y1] = rhino.Point3d.transform([50,0,0], w2s);
      ctx.beginPath();
      ctx.moveTo(x,y);
      ctx.lineTo(x1,y1);
      ctx.stroke();
      ctx.beginPath();
      ctx.strokeStyle = 'rgb(75,150,75)';
      [x1,y1] = rhino.Point3d.transform([0,50,0], w2s);
      ctx.moveTo(x,y);
      ctx.lineTo(x1,y1);
      ctx.stroke();

      // draw saved nurbs curves
      for (let i=0; i<_model.curves.length; i++)
        drawNurbsCurve(ctx, _model.curves[i], w2s);

      // create a temporary curve from the points and draw it
      if (_model.points !== null && _model.points.count > 0) {
        let degree = _model.points.count - 1;
        if (degree > 3)
          degree = 3;
        let curve = rhino.NurbsCurve.create(true, degree, _model.points);
        drawNurbsCurve(ctx, curve, w2s);

        // draw control polygon from the temp curve's control points
        //drawControlPolygon(ctx, curve.points());
        drawControlPolygon(ctx, _model.points);

        // delete the temp curve when we're done using it
        // (webassembly memory management isn't great)
        curve.delete();
      }

      w2s.delete();
    }

    // draws a nurbs curve
    function drawNurbsCurve(ctx, curve, w2s) {
      ctx.lineWidth = 1
      ctx.strokeStyle = 'black'

      const divisions = 200 // TODO: dynamic
      ctx.beginPath()

      let [t0,t1] = curve.domain
      let world_point = curve.pointAt(t0)
      let screen_point = rhino.Point3d.transform(world_point, w2s)
      ctx.moveTo(screen_point[0],screen_point[1])
      for (let j=1; j<=divisions; j++) {
        let t = t0 + j / divisions * (t1-t0)
        world_point = curve.pointAt(t)
        let screen_point = rhino.Point3d.transform(world_point, w2s)
        ctx.lineTo(screen_point[0],screen_point[1])
      }
      ctx.stroke()
    }

    // draws a control polygon
    function drawControlPolygon(ctx, points) {
      // draw dashed lines between control points
      ctx.strokestyle = 'darkgray'
      ctx.setLineDash([4,4])
      ctx.beginPath()

      let w2s = _model.viewport.getXform(rhino.CoordinateSystem.World, rhino.CoordinateSystem.Screen)
      for (let i=0; i<points.count; i++) {
        let world_point = points.get(i)
        let screen_point = rhino.Point3d.transform(world_point, w2s)
        if (0 === i)
          ctx.moveTo(screen_point[0], screen_point[1])
        else
          ctx.lineTo(screen_point[0], screen_point[1])
      }
      if( points.count > 2 ){
        let world_point = points.get(0)
        let screen_point = rhino.Point3d.transform(world_point, w2s)
        ctx.lineTo(screen_point[0], screen_point[1])
      }

      ctx.stroke()

      // draw control points
      ctx.setLineDash([])
      ctx.fillStyle = 'white'
      ctx.strokeStyle = 'black'
      for (let i=0; i<points.count; i++) {
        let world_point = points.get(i)
        let screen_point = rhino.Point3d.transform(world_point, w2s)
        let [x,y,z] = screen_point
        ctx.fillRect(x-1,y-1, 3, 3)
        ctx.strokeRect(x-2, y-2, 5, 5)
      }
      w2s.delete()
    }

<!-- language: lang-html -->

    <div id="description">
      点击画布创建一个NURBS曲线,按Enter键保存并开始新的曲线!
    </div>
    <button id="downloadButton" disabled>下载</button> 
    <div>
      <canvas class="rhino3dm" id="canvas" width="500" height="500"></canvas>
    </div>
    <script src="https://unpkg.com/rhino3dm@7.15.0/rhino3dm.js" type="application/javascript"></script>

<!-- end snippet -->

在上面的示例中,下载按钮将被禁用,但在本地Web服务器上运行应该没问题。

(最后一点说明,如果出于某种原因你需要同时使用p5和rhino3dm(尽管性能可能有所不同),你可以访问p5的drawingContext来获取草图的底层Canvas渲染(当不使用SVG渲染器时)以在草图的画布中绘制与上述示例相同的内容)。

英文:

There is an p5.svg renderer you could try to essentially draw the same measurements/lines in p5, but render to SVG directly instead of canvas (completely bypassing the .3dm format). The library seems to lack updates recently, but you can start with an older version/example like this one and copy it/modify to prototype what you need.

If .3dm is even better, you shouldn't need p5.js at all: the rhino3dm js library should handle both drawing lines/curves/text (if you have the input from the user) and save to .3dm.

You can check out the SampleSketch2d live example. (source)

产品草图的JS库

You can run the same example bellow as well)

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const downloadButton = document.getElementById(&quot;downloadButton&quot;)
downloadButton.onclick = download
// global variables
let _model = {
// saved nurbs curves
curves: [],
// new nurbs curve
points: null,
// viewport for canvas
viewport: null,
}
// wait for the rhino3dm web assembly to load asynchronously
let rhino
rhino3dm().then(async m =&gt; {
console.log(&#39;Loaded rhino3dm.&#39;)
rhino = m // global
run()
})
/**/
// initialize canvas and model
function run() {
let canvas = getCanvas()
canvas.addEventListener(&#39;mousedown&#39;, onMouseDown)
canvas.addEventListener(&#39;mousemove&#39;, onMouseMove)
window.addEventListener(&#39;keyup&#39;, onKeyUp)
_model.points = new rhino.Point3dList()
_model.viewport = new rhino.ViewportInfo()
_model.viewport.screenPort = [0, 0, canvas.clientWidth, canvas.clientHeight]
_model.viewport.setFrustum(-30,30,-30,30,1,1000)
draw()
}
function download() {
if(_model.curves.length&lt;1){
console.log(&#39;no curves&#39;)
return
}
let doc = new rhino.File3dm()
for(let i=0; i&lt;_model.curves.length;i++) {
doc.objects().add(_model.curves[i], null)
}
let options = new rhino.File3dmWriteOptions()
options.version = 7
let buffer = doc.toByteArray(options)
saveByteArray(&quot;sketch2d&quot;+ options.version +&quot;.3dm&quot;, buffer)
doc.delete()
}
function saveByteArray(fileName, byte) {
let blob = new Blob([byte], {type: &quot;application/octect-stream&quot;})
let link = document.createElement(&#39;a&#39;)
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
}
/* * * * * * * * * * * * * * * *  interaction   * * * * * * * * * * * * * * * */
// handles mouse down events
// adds a new control point at the location of the mouse
function onMouseDown(event) {
// get the location of the mouse on the canvas
let [x,y] = getXY(event)
// if this is a brand new curve, add the first control point
if (_model.points.count === 0) {
_model.points.add(x, y, 0)
}
// add a new control point that will be saved on the next mouse click
// (the location of the previous control point is now frozen)
_model.points.add(x, y, 0)
draw()
}
// handles mouse move events
// the last control point in the list follows the mouse
function onMouseMove(event) {
let index = _model.points.count - 1
if (index &gt;= 0) {
let [x,y] = getXY(event)
_model.points.set(index, [x, y, 0])
draw()
}
}
// handles key up events
function onKeyUp( event ) {
switch ( event.key ) {
// when the enter key is pressed, save the new nurbs curve
case &quot;Enter&quot;:
if (_model.points.count &lt; 4) { // 3 pts (min.) + next pt
console.error(&#39;Not enough points!&#39;)
} else {
// remove the last point in the list (a.k.a. next)
let index = _model.points.count - 1
_model.points.removeAt(index)
// construct a curve from the points list
let degree = _model.points.count - 1
if (degree &gt; 3)
degree = 3
// construct a nurbs curve
// (first arg == true to create a closed periodic uniform curve)
_model.curves.push(rhino.NurbsCurve.create(true, degree, _model.points))
}
// clear points list
_model.points.clear()
// enable download button
downloadButton.disabled = false
break
}
draw()
}
/* * * * * * * * * * * * * * * * *  helpers   * * * * * * * * * * * * * * * * */
// gets the canvas
function getCanvas() {
return document.getElementById(&#39;canvas&#39;)
}
// gets the [x, y] location of the mouse in world coordinates
function getXY(evt) {
let canvas = getCanvas()
let rect = canvas.getBoundingClientRect()
let x = evt.clientX - rect.left
let y = evt.clientY - rect.top
let s2w = _model.viewport.getXform(rhino.CoordinateSystem.Screen, rhino.CoordinateSystem.World)
let world_point = rhino.Point3d.transform([x,y,0], s2w)
s2w.delete()
return [world_point[0],world_point[1]]
}
/* * * * * * * * * * * * * * * * *  drawing   * * * * * * * * * * * * * * * * */
// clears the canvas and draws the model
// for some reason removing semicolons causes an error in this method
function draw() {
// get canvas&#39; 2d context
let canvas = getCanvas();
let ctx = canvas.getContext(&#39;2d&#39;);
let w2s = _model.viewport.getXform(rhino.CoordinateSystem.World, rhino.CoordinateSystem.Screen);
// clear and draw a grid
ctx.beginPath();
ctx.lineWidth = 0.5;
ctx.strokeStyle = &#39;rgb(130,130,130)&#39;;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(let i=0; i&lt;50; i+=1){
let [x,y] = rhino.Point3d.transform([i,-50,0], w2s);
let [x1,y1] = rhino.Point3d.transform([i,50,0], w2s);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
[x,y] = rhino.Point3d.transform([-i,-50,0], w2s);
[x1,y1] = rhino.Point3d.transform([-i,50,0], w2s);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
[x,y] = rhino.Point3d.transform([-50, i, 0], w2s);
[x1,y1] = rhino.Point3d.transform([50, i, 0], w2s);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
[x,y] = rhino.Point3d.transform([-50, -i, 0], w2s);
[x1,y1] = rhino.Point3d.transform([50, -i, 0], w2s);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
}
ctx.stroke();
ctx.lineWidth = 2;
ctx.strokeStyle = &#39;rgb(150,75,75)&#39;;
let [x,y] = rhino.Point3d.transform([0,0,0], w2s);
let [x1,y1] = rhino.Point3d.transform([50,0,0], w2s);
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = &#39;rgb(75,150,75)&#39;;
[x1,y1] = rhino.Point3d.transform([0,50,0], w2s);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
ctx.stroke();
// draw saved nurbs curves
for (let i=0; i&lt;_model.curves.length; i++)
drawNurbsCurve(ctx, _model.curves[i], w2s);
// create a temporary curve from the points and draw it
if (_model.points !== null &amp;&amp; _model.points.count &gt; 0) {
let degree = _model.points.count - 1;
if (degree &gt; 3)
degree = 3;
let curve = rhino.NurbsCurve.create(true, degree, _model.points);
drawNurbsCurve(ctx, curve, w2s);
// draw control polygon from the temp curve&#39;s control points
//drawControlPolygon(ctx, curve.points());
drawControlPolygon(ctx, _model.points);
// delete the temp curve when we&#39;re done using it
// (webassembly memory management isn&#39;t great)
curve.delete();
}
w2s.delete();
}
// draws a nurbs curve
function drawNurbsCurve(ctx, curve, w2s) {
ctx.lineWidth = 1
ctx.strokeStyle = &#39;black&#39;
const divisions = 200 // TODO: dynamic
ctx.beginPath()
let [t0,t1] = curve.domain
let world_point = curve.pointAt(t0)
let screen_point = rhino.Point3d.transform(world_point, w2s)
ctx.moveTo(screen_point[0],screen_point[1])
for (let j=1; j&lt;=divisions; j++) {
let t = t0 + j / divisions * (t1-t0)
world_point = curve.pointAt(t)
let screen_point = rhino.Point3d.transform(world_point, w2s)
ctx.lineTo(screen_point[0],screen_point[1])
}
ctx.stroke()
}
// draws a control polygon
function drawControlPolygon(ctx, points) {
// draw dashed lines between control points
ctx.strokestyle = &#39;darkgray&#39;
ctx.setLineDash([4,4])
ctx.beginPath()
let w2s = _model.viewport.getXform(rhino.CoordinateSystem.World, rhino.CoordinateSystem.Screen)
for (let i=0; i&lt;points.count; i++) {
let world_point = points.get(i)
let screen_point = rhino.Point3d.transform(world_point, w2s)
if (0 === i)
ctx.moveTo(screen_point[0], screen_point[1])
else
ctx.lineTo(screen_point[0], screen_point[1])
}
if( points.count &gt; 2 ){
let world_point = points.get(0)
let screen_point = rhino.Point3d.transform(world_point, w2s)
ctx.lineTo(screen_point[0], screen_point[1])
}
ctx.stroke()
// draw control points
ctx.setLineDash([])
ctx.fillStyle = &#39;white&#39;
ctx.strokeStyle = &#39;black&#39;
for (let i=0; i&lt;points.count; i++) {
let world_point = points.get(i)
let screen_point = rhino.Point3d.transform(world_point, w2s)
let [x,y,z] = screen_point
ctx.fillRect(x-1,y-1, 3, 3)
ctx.strokeRect(x-2, y-2, 5, 5)
}
w2s.delete()
}

<!-- language: lang-html -->

&lt;div id=&quot;description&quot;&gt;
Click on the canvas to create a NURBS curve and hit &lt;kbd&gt;Enter&lt;/kbd&gt; to save and start a new one!
&lt;/div&gt;
&lt;button id=&quot;downloadButton&quot; disabled&gt;Download&lt;/button&gt; 
&lt;div&gt;
&lt;canvas class=&quot;rhino3dm&quot; id=&quot;canvas&quot; width=&quot;500&quot; height=&quot;500&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script src=&quot;https://unpkg.com/rhino3dm@7.15.0/rhino3dm.js&quot; type=&quot;application/javascript&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

In the above example the download button will be sanboxed, but running on a local webserver should be fine.

(One final note, if for some reason you need to use p5 and rhino3dm (though performance may vary), you can access p5's drawingContext to get the the sketch's underlying Canvas render (when not using the SVG renderer) to draw the same as the above in a sketch's canvas).

huangapple
  • 本文由 发表于 2023年7月31日 18:14:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76802625.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定