Vue SVG. Приклад побудови живого параметричного креслення


17.06.2020

SVG drawing on Vue

Отже, на Vue можна робити параметричні креслення у форматі SVG, а потім зберегти як окремий SVG-файл. Що таке параметричне креслення і кому воно потрібно? Уявіть що Ви працюєте закрійником, всі викрійки типові, різні тільки розміри. Було б дуже зручно, якби Ви вбили основні розміри (параметри), а програма сама побудувала б креслення. Так само це стосується інших галузей, де треба завдати декілька параметрів і отримати креслення. Наприклад, Ви займаєтесь виготовленням коробок з картону, тощо. І, щоб скоротити час на креслення і вчасно виконати замовлення клієнта, Вам потрібна система для швидкої побудови креслення.

У цьому прикладі я буду "малювати" статор електричного двигуна.

Переглянути приклад SVG креслення на Vue

Основна компонента, яка займається саме кресленням drawing1 (дивись файл drawing1.js) створюється на базі mixins з назвою vuesvg (дивись файл app-mixins.js). Цей mixin містить (буде містити) всі властивості, методи, тощо, які необхідні всім компонентам - кресленням, які ми будемо створювати (поки-що у прикладі лише один drawing1).

Основна ідея побудови компоненти для SVG креслення така:

  • data містить всі параметри, які можуть змінюватись;
  • computed - містять всі необхідні розрахунки для побудови креслення. Основним є drawing. Він повертає набір об'єктів, які треба буде намалювати. В даному прикладі drawing повертає набір об'єктів: sectors, lines, circles, sizelines. Кожен з елементів має свої властивості, які використовуються для їх “малювання”.
  • зображення креслення будується у шаблоні (template). Також template містить форму для зміни необхідних розмірів.

Зверніть увагу, що для sizelines створений окремий компонент size-lines (дивись файл app-components.js). Саме цей компонент дозволяє простіше встановлювати розмірні лінії на кресленні. Таким чином у template для побудови креслення ви можете використовувати не тільки стандартні SVG теги, але і свої компоненти.

Наприклад, якщо Вам потрібно буде малювати дуже багато гайок, ви можете створити компонент, назвати його, скажімо screw-nuts, який буде малювати гайку певного розміру у певних координатах. У computed властивість drawing додати набір об'єктів типу screw-nuts і заповнити його даними. А у template додати рядок, який буде виводити ваші гайки у SVG, як це зроблено у прикладі з розмірними лініями.

Скачати приклад SVG креслення на Vue

Код самої компоненти:



"use strict";

Vue.component('drawing1', {
  mixins:[vuesvg],
  data: function () {
    return {
      pageW: this.pageW,
      pageH: this.pageH,

      statorDiameter: this.statorDiameter,
      statorCentralHoleDiameter: this.statorCentralHoleDiameter,

      toothCount: this.toothCount,
      toothHeight: this.toothHeight,
      toothWidth: this.toothWidth,
      toothHatHeight: this.toothHatHeight,
      toothBetween: this.toothBetween,
    }
  },
  computed: {
    pageCenter: function () {
      return {'x':this.pageW/2, 'y': this.pageH/2}
    },
    /* calculate limits for each parameters. optional */
    limits: function () {
      return {
        statorDiameter: {
          min: parseFloat(this.statorCentralHoleDiameter) + this.toothHeight*2,
          max: this.pageW
        },
        statorCentralHoleDiameter: {
          min: 0,
          max: parseFloat(this.statorDiameter) - this.toothHeight*2
        },
        toothCount: {
          min: 3,
          max: 57
        },
        toothHeight: {
          min: this.toothHatHeight * 3,
          max: (this.statorDiameter - (this.toothWidth * this.toothCount / Math.PI)) / 2
        },
        toothWidth: {
          min: 1,
          max: (this.statorDiameter/2 - this.toothHeight)*2*Math.PI / this.toothCount
        },
        toothHatHeight: {
          min: 1,
          max: this.toothHeight/3
        },
        toothBetween: {
          min: 1,
          max: ((Math.PI*this.statorDiameter/this.toothCount - this.toothWidth)/2)
        }
      }
    },
    drawing: function () {
      let result = {'sectors':[],'lines':[],'circles':[],'sizelines':[]}

      let radius = this.statorDiameter/2
      let sectorAngle = 2*Math.PI/this.toothCount;
      let sectorAngleBetween = this.getSectorAngle(radius, this.toothBetween)

      let radiusSmall = this.statorDiameter/2 - this.toothHeight
      let toothWidthAngle = this.getSectorAngle(radiusSmall, this.toothWidth)

      let radiusUnderHat = this.statorDiameter/2 - this.toothHatHeight
      let toothWidthAngleHat = this.getSectorAngle(radiusUnderHat, this.toothWidth)

      let so_interesting_size_line = {'x1':0,'y1':0,'x2':0,'y2':0}

      for (let i = 0; i < this.toothCount; i++) {
        let p1 = this.getPointOnCircle(radius, i*sectorAngle + sectorAngleBetween, this.pageCenter)
        let p2 = this.getPointOnCircle(radius, (i+1)*sectorAngle - sectorAngleBetween, this.pageCenter)
        let p3 = this.getPointOnCircle(radiusUnderHat, i*sectorAngle + sectorAngleBetween, this.pageCenter)
        let p4 = this.getPointOnCircle(radiusUnderHat, i*sectorAngle + sectorAngle/2 - toothWidthAngleHat/2, this.pageCenter)
        let p5 = this.getPointOnCircle(radiusUnderHat, (i+1)*sectorAngle - sectorAngle/2 + toothWidthAngleHat/2, this.pageCenter)
        let p6 = this.getPointOnCircle(radiusUnderHat, (i+1)*sectorAngle - sectorAngleBetween, this.pageCenter)
        let p7 =  this.getPointOnCircle(radiusSmall, i*sectorAngle, this.pageCenter)
        let p8 = this.getPointOnCircle(radiusSmall, i*sectorAngle + sectorAngle/2 - toothWidthAngle/2, this.pageCenter)
        let p9 = this.getPointOnCircle(radiusSmall, (i+1)*sectorAngle - sectorAngle/2 + toothWidthAngle/2, this.pageCenter)
        let p10 = this.getPointOnCircle(radiusSmall, (i+1)*sectorAngle, this.pageCenter)

        result.sectors.push({'p1': p1, 'p2': p2, 'radius': radius})
        result.sectors.push({'p1': p7, 'p2': p8, 'radius': radiusSmall})
        result.sectors.push({'p1': p9, 'p2': p10, 'radius': radiusSmall})
        result.sectors.push({'p1': p3, 'p2': p4, 'radius': radiusUnderHat})
        result.sectors.push({'p1': p5, 'p2': p6, 'radius': radiusUnderHat})

        result.lines.push({'p1': p1, 'p2': p3})
        result.lines.push({'p1': p2, 'p2': p6})
        result.lines.push({'p1': p4, 'p2': p8})
        result.lines.push({'p1': p5, 'p2': p9})

        if (i==this.toothCount-2) { // First tooth
          so_interesting_size_line.x1 = p2.x
          so_interesting_size_line.y1 = p2.y
        }
        if (i==this.toothCount-1) { // Last tooth
          so_interesting_size_line.x2 = p1.x
          so_interesting_size_line.y2 = p1.y
        }
      }

      result.circles.push({'p': this.pageCenter, 'r': this.statorCentralHoleDiameter/2})

      /* Size lines */
      // Between tooths (so interesting size line)
      result.sizelines.push({ 'x1': so_interesting_size_line.x1, 'y1': so_interesting_size_line.y1, 'x2': so_interesting_size_line.x2, 'y2': so_interesting_size_line.y2, 'offset': 5, 'text': this.toothBetween })
      // Stator Diameter
      result.sizelines.push({ 'x1':this.pageCenter.x - radius, 'y1': this.pageCenter.y, 'x2': this.pageCenter.x + radius, 'y2': this.pageCenter.y, 'offset': (radius * 1.1) + 7, 'text': this.statorDiameter })
      // Central Hole Diameter
      result.sizelines.push({ 'x1':this.pageCenter.x - this.statorCentralHoleDiameter/2, 'y1': this.pageCenter.y, 'x2': this.pageCenter.x + this.statorCentralHoleDiameter/2, 'y2': this.pageCenter.y, 'offset': 0, 'text': this.statorCentralHoleDiameter })

      return result
    }
  },
  mounted: function() {
    /* Init values */
    this.pageW = 210
    this.pageH = 297
    
    this.statorDiameter = 180
    this.statorCentralHoleDiameter = 40

    this.toothCount = 12
    this.toothHeight = 40
    this.toothWidth = 20
    this.toothBetween = 5
    this.toothHatHeight = 3

    this.draw()
  },
  methods: {
    getPointOnCircle: function (radius, angle, offset = {'x':0,'y':0}) {
      return {'x': offset.x + radius*Math.cos(angle),'y': offset.y + radius*Math.sin(angle)}
    },
    getSectorAngle: function (radius, length) {
      return length / radius
    }
  },
  template: `
<div>
  <v-style>
    form div {
      margin-bottom: 0.5em;
    }
    form label {
      min-width: 15em;
      display: inline-block;
    }
    .drawing {
      border: 1px solid silver;
    }
  </v-style>
  <h2>Form:</h2>
  <div>
    <form>
      <div><label>Stator Diameter:</label><input v-model="statorDiameter" type="number" :min="limits.statorDiameter.min" :max="limits.statorDiameter.max"></div>
      <div><label>Central Hole's Diameter:</label><input v-model="statorCentralHoleDiameter" type="number" :min="limits.statorCentralHoleDiameter.min" :max="limits.statorCentralHoleDiameter.max"></div>
      <div><label>Tooth Count:</label><input v-model="toothCount" type="number" :min="limits.toothCount.min" :max="limits.toothCount.max"></div>

      <div><label>Tooth Height:</label><input v-model="toothHeight" type="number" :min="limits.toothHeight.min" :max="limits.toothHeight.max"></div>
      <div><label>Tooth Width:</label><input v-model="toothWidth" type="number" :min="limits.toothWidth.min" :max="limits.toothWidth.max"></div>
      <div><label>Tooth's Hat Height:</label><input v-model="toothHatHeight" type="number" :min="limits.toothHatHeight.min" :max="limits.toothHatHeight.max"></div>
      <div><label>Between Tooths:</label><input v-model="toothBetween" type="number" :min="limits.toothBetween.min" :max="limits.toothBetween.max"></div>
    </form>
  </div>

  <h2>Drawing: <button type="button" @click="saveSvg('vue.svg')">Save SVG-file</button></h2>
  <div>
    <svg ref="svg" v-if="ready" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" :width="pageW+'mm'" :height="pageH+'mm'" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" :viewBox="'0 0 ' + pageW + ' ' + pageH" xmlns:xlink="http://www.w3.org/1999/xlink" class="drawing">
      <defs>
        <marker id="DimPoint1" viewBox="-2 -12 29 24" markerWidth="22" markerHeight="18" orient="auto">
            <path fill="black" stroke="black" d="M0,0 L20,-4 16,0 20,4 z M0,-10 L0,10 M0,0 L27,0"/>
        </marker>
        <marker id="DimPoint2" viewBox="-27 -12 29 24" markerWidth="22" markerHeight="18" orient="auto">
            <path fill="black" stroke="black" d="M0,0 L-20,-4 -16,0 -20,4 z M0,-10 L0,10 M0,0 L-27,0"/>
        </marker>
        <v-style type="text/css">
          .sizeline { fill: none; stroke-width: 0.2; }
          .sizetext { fill: black; stroke: none; font-weight:normal; font-family:'Arial'; font-size: 0.3em; }
        </v-style>
      </defs>

      <g fill="none" stroke="black">
        <path v-for="sector in drawing.sectors" :d="'M'+sector.p1.x+','+sector.p1.y+' A'+sector.radius+','+sector.radius+' 0 0,1 ' + sector.p2.x+','+sector.p2.y" />
        <line v-for="line in drawing.lines" :x1="line.p1.x" :y1="line.p1.y" :x2="line.p2.x" :y2="line.p2.y" />
        <circle v-for="circle in drawing.circles" :cx="circle.p.x" :cy="circle.p.y" :r="circle.r" />
      </g>

      <size-lines v-for="sizeline in drawing.sizelines" :sizeline="sizeline" />

    </svg>
  </div>
</div>`
})



Смотри также:

Web-dev склерозник
Коментарі:
Додати коментар
Code
* - обов'язкові поля

Архіви