Kaynağa Gözat

Merge pull request 'implement_v2' (#8) from implement_v2 into master

master
Wouter Horlings 2 ay önce
ebeveyn
işleme
9d67b61b9a
59 değiştirilmiş dosya ile 2850 ekleme ve 1047 silme
  1. +1
    -1
      .golangci.yml
  2. +10
    -4
      cmd/gopatterns/gopatterns.go
  3. +10
    -10
      go.mod
  4. +19
    -0
      go.sum
  5. +36
    -0
      pkg/config/config.go
  6. +2
    -7
      pkg/dimensions/dimensions.go
  7. +62
    -0
      pkg/path/path.go
  8. +47
    -16
      pkg/path/splines.go
  9. +0
    -0
      pkg/path/style.go
  10. +41
    -0
      pkg/pattern/panel/panel.go
  11. +0
    -36
      pkg/pattern/path/path.go
  12. +16
    -8
      pkg/pattern/pattern.go
  13. +0
    -119
      pkg/pattern/template/fixtures/classic_trouser_block.yaml
  14. +0
    -9
      pkg/pattern/template/fixtures/trouser.yaml
  15. +0
    -6
      pkg/pattern/template/information.go
  16. +0
    -69
      pkg/pattern/template/line.go
  17. +0
    -423
      pkg/pattern/template/point.go
  18. +0
    -124
      pkg/pattern/template/renderer.go
  19. +0
    -56
      pkg/pattern/template/template.go
  20. +9
    -8
      pkg/pattern/text/text.go
  21. +6
    -5
      pkg/point/absolute_point.go
  22. +71
    -0
      pkg/point/absolute_point_test.go
  23. +5
    -5
      pkg/point/between_point.go
  24. +48
    -0
      pkg/point/between_point_test.go
  25. +8
    -6
      pkg/point/extend_point.go
  26. +3
    -5
      pkg/point/point.go
  27. +10
    -9
      pkg/point/relative_point.go
  28. +3
    -4
      pkg/position/position.go
  29. +41
    -0
      pkg/position/position_test.go
  30. +15
    -0
      pkg/position/testutil/testutil.go
  31. +143
    -0
      pkg/renderer/renderer.go
  32. +12
    -18
      pkg/storage/storage.go
  33. +13
    -0
      pkg/template/fixtures/absolute_points.yaml
  34. +20
    -0
      pkg/template/fixtures/between_points.yaml
  35. +19
    -0
      pkg/template/fixtures/evaluation_functions.yaml
  36. +18
    -0
      pkg/template/fixtures/extend_points.yaml
  37. +52
    -0
      pkg/template/fixtures/functions.yaml
  38. +17
    -0
      pkg/template/fixtures/polar_points.yaml
  39. +12
    -0
      pkg/template/fixtures/references.yaml
  40. +15
    -0
      pkg/template/fixtures/relative_points.yaml
  41. +45
    -0
      pkg/template/information.go
  42. +157
    -0
      pkg/template/line.go
  43. +16
    -1
      pkg/template/panel.go
  44. +491
    -0
      pkg/template/point.go
  45. +289
    -0
      pkg/template/point_test.go
  46. +0
    -0
      pkg/template/position.go
  47. +109
    -0
      pkg/template/template.go
  48. +0
    -0
      pkg/template/value.go
  49. +28
    -0
      pkg/util/id.go
  50. +34
    -0
      pkg/util/id_test.go
  51. +125
    -0
      spec/pattern.v2.yaml
  52. +4
    -0
      spec/pattern.yaml
  53. +70
    -29
      templates/basic_trouser_block.yaml
  54. +28
    -14
      templates/basis_grondpatroon_heren.yaml
  55. +64
    -18
      templates/classic_trouser_block.yaml
  56. +6
    -0
      templates/dimension_names.yaml
  57. +217
    -0
      templates/shirt_collar.yaml
  58. +120
    -0
      templates/standing_collars.yaml
  59. +263
    -37
      templates/tailored_shirt_block.yaml

+ 1
- 1
.golangci.yml Dosyayı Görüntüle

@@ -32,7 +32,7 @@ linters-settings:
allow:
- $gostd
- git.wtrh.nl/patterns/gopatterns/pkg/pattern
- git.wtrh.nl/patterns/gopatterns/pkg/pattern/point
- git.wtrh.nl/patterns/gopatterns/pkg/point
- git.wtrh.nl/patterns/gopatterns/pkg/pattern/template
- git.wtrh.nl/patterns/gopatterns/pkg/position
- git.wtrh.nl/patterns/gopatterns/pkg/vector


+ 10
- 4
cmd/gopatterns/gopatterns.go Dosyayı Görüntüle

@@ -8,7 +8,9 @@ import (
"os"
gotemplate "text/template"

"git.wtrh.nl/patterns/gopatterns/pkg/pattern/template"
"git.wtrh.nl/patterns/gopatterns/pkg/config"
"git.wtrh.nl/patterns/gopatterns/pkg/renderer"
storage2 "git.wtrh.nl/patterns/gopatterns/pkg/storage"
"gitlab.com/slxh/go/env"
)

@@ -40,6 +42,10 @@ gopatterns [-templates <template-dir>] [-out <output-dir>] input-file
`)
}

if debug {
slog.SetLogLoggerLevel(slog.LevelDebug)
}

args := flag.Args()
if len(args) == 0 {
slog.Error("at least one pattern is required")
@@ -47,7 +53,7 @@ gopatterns [-templates <template-dir>] [-out <output-dir>] input-file

os.MkdirAll(outputDir, 0o770)

storage, err := template.NewStorage(templateDir)
storage, err := storage2.NewStorage(templateDir)
if err != nil {
slog.Error("failed to open template directory", "err", err, "dir", templateDir)
return
@@ -56,13 +62,13 @@ gopatterns [-templates <template-dir>] [-out <output-dir>] input-file
files := make([]string, 0)

for _, arg := range args {
pattern, err := template.LoadPattern(arg)
pattern, err := config.LoadConfig(arg)
if err != nil {
slog.Error("failed to load pattern", "err", err)
return
}

filenames, err := storage.RenderPatterns(pattern, outputDir, debug)
filenames, err := renderer.RenderPatterns(storage, pattern, outputDir, debug)
if err != nil {
slog.Error("error occurred while creating pattern", "pattern", arg, "err", err)
return


+ 10
- 10
go.mod Dosyayı Görüntüle

@@ -1,20 +1,20 @@
module git.wtrh.nl/patterns/gopatterns

go 1.24.5
go 1.25.1

require (
github.com/stoewer/go-strcase v1.3.1
github.com/stretchr/testify v1.10.0
github.com/tdewolff/canvas v0.0.0-20250508181010-75987a1ae9cc
github.com/stretchr/testify v1.11.1
github.com/tdewolff/canvas v0.0.0-20250923071733-b2b2ba99a987
gitlab.com/Achilleshiel/gosplines v0.0.0-20240602125710-c93b87aea1ee
gitlab.com/slxh/go/env v1.2.0
golang.org/x/image v0.29.0
golang.org/x/image v0.31.0
gopkg.in/Knetic/govaluate.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)

require (
codeberg.org/go-latex/latex v0.1.0 // indirect
codeberg.org/go-latex/latex v0.2.0 // indirect
codeberg.org/go-pdf/fpdf v0.11.1 // indirect
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
@@ -33,12 +33,12 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // indirect
github.com/tdewolff/font v0.0.0-20250602165824-bf05faa75fda // indirect
github.com/tdewolff/minify/v2 v2.23.8 // indirect
github.com/tdewolff/parse/v2 v2.8.1 // indirect
github.com/tdewolff/font v0.0.0-20250902141222-fb72ecc1bc0a // indirect
github.com/tdewolff/minify/v2 v2.24.3 // indirect
github.com/tdewolff/parse/v2 v2.8.4-0.20250902141113-be7b6b11bb1b // indirect
github.com/wcharczuk/go-chart/v2 v2.1.2 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/text v0.29.0 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
gonum.org/v1/plot v0.16.0 // indirect
modernc.org/knuth v0.5.5 // indirect


+ 19
- 0
go.sum Dosyayı Görüntüle

@@ -4,12 +4,15 @@ codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOL
codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU=
codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c=
codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw=
codeberg.org/go-latex/latex v0.2.0 h1:Ol/a6VHY06N+5gPfewswymoRb5ZcKDXWVaVegcx4hbI=
codeberg.org/go-latex/latex v0.2.0/go.mod h1:VJAwQir7/T8LZxj7xAPivISKiVOwkMpQ8bTuPQ31X0Y=
codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs=
codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU=
git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE=
git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38=
git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94=
git.sr.ht/~sbinet/gg v0.7.0 h1:YmNf7YKd7diDMTPm86hZa1EM3pbkOyD/zzjl0LZUdNM=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
@@ -70,14 +73,24 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tdewolff/canvas v0.0.0-20250508181010-75987a1ae9cc h1:hZ/uFsNQuNRaJWk/IVvIogyBfmfhRg2CLOhKpfeNGK0=
github.com/tdewolff/canvas v0.0.0-20250508181010-75987a1ae9cc/go.mod h1:xXkALI8c2qLMmoMWPRhDHkc1AtNSW/OGPPxp7lBfycU=
github.com/tdewolff/canvas v0.0.0-20250923071733-b2b2ba99a987 h1:tzQqRIECH8fEHpkG16gD7uOadYfgSgAuzxq6GaHk8v0=
github.com/tdewolff/canvas v0.0.0-20250923071733-b2b2ba99a987/go.mod h1:r5O5UHm7WMj6o9mbY1gdBHkg308r0EcfS/10YBbBLHI=
github.com/tdewolff/font v0.0.0-20250602165824-bf05faa75fda h1:WB5DpyaMFc/Y+n/neEg8o1lRUQgaj53FVK2H7mTT5zs=
github.com/tdewolff/font v0.0.0-20250602165824-bf05faa75fda/go.mod h1:eDnkgh2pt95UFXk0GsUv2JNj5gumg78c02QX0TdcwTA=
github.com/tdewolff/font v0.0.0-20250902141222-fb72ecc1bc0a h1:IuR6wFg9mSxhxcCogXcG5bte813psi1PE4KTjMAkM6k=
github.com/tdewolff/font v0.0.0-20250902141222-fb72ecc1bc0a/go.mod h1:lGIMHKyJnHCmJeb9MqdWnudFoPDVz8COuALmILs95xY=
github.com/tdewolff/minify/v2 v2.23.8 h1:tvjHzRer46kwOfpdCBCWsDblCw3QtnLJRd61pTVkyZ8=
github.com/tdewolff/minify/v2 v2.23.8/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno=
github.com/tdewolff/minify/v2 v2.24.3 h1:BaKgWSFLKbKDiUskbeRgbe2n5d1Ci1x3cN/eXna8zOA=
github.com/tdewolff/minify/v2 v2.24.3/go.mod h1:1JrCtoZXaDbqioQZfk3Jdmr0GPJKiU7c1Apmb+7tCeE=
github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po=
github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/parse/v2 v2.8.4-0.20250902141113-be7b6b11bb1b h1:ltRewarE+mA/m3nJrYJVfFFUUFP+RXOx8V1g5tVsU64=
github.com/tdewolff/parse/v2 v2.8.4-0.20250902141113-be7b6b11bb1b/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
@@ -98,6 +111,8 @@ golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -114,6 +129,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -151,6 +168,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=


+ 36
- 0
pkg/config/config.go Dosyayı Görüntüle

@@ -0,0 +1,36 @@
package config

import (
"fmt"
"os"

"gopkg.in/yaml.v3"
)

// Request contains the information to draw a pattern for a specific owner, with corresponding
// sizes and the template name.
type Request struct {
Sizes Sizes `yaml:"sizes,omitempty"`
Owner string `yaml:"owner"`
Template string `yaml:"template,omitempty"`
}

// Sizes defines a map with the size name and the size value.
type Sizes map[string]float64

// LoadConfig reads and decodes a [Request] from a yaml file.
func LoadConfig(name string) (Request, error) {
fh, err := os.Open(name)
if err != nil {
return Request{}, fmt.Errorf("open pattern file %q: %w", name, err)
}

pat := Request{}

err = yaml.NewDecoder(fh).Decode(&pat)
if err != nil {
return Request{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err)
}

return pat, nil
}

pkg/pattern/dimensions.go → pkg/dimensions/dimensions.go Dosyayı Görüntüle

@@ -1,4 +1,4 @@
package pattern
package dimensions

import (
"fmt"
@@ -15,7 +15,7 @@ type DimensionID string
// Dimensions is a map with dimensions.
type Dimensions map[DimensionID]Dimension

// Parameters returns a govaluate.MapParameters object based on the dimensions.
// Parameters return a govaluate.MapParameters object based on the dimensions.
func (d Dimensions) Parameters() govaluate.MapParameters {
parameters := govaluate.MapParameters{}
parameters["pi"] = math.Pi
@@ -60,8 +60,3 @@ type Dimension struct {
Name string
Value float64
}

// AddDimension adds a dimension to a pattern.
func (p *Pattern) AddDimension(id DimensionID, dimension Dimension) {
p.dimensions[id] = dimension
}

+ 62
- 0
pkg/path/path.go Dosyayı Görüntüle

@@ -0,0 +1,62 @@
// Package path provides objects to define lines on a sewing pattern.
package path

import (
"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"github.com/tdewolff/canvas"
)

// Polygon defines a set of straight lines through points.
type Polygon struct {
points []point.Point
style Style
id util.ID
}

func (p *Polygon) Through() []point.Point {
return p.points
}

// NewPolygon returns a new [Polygon].
func NewPolygon(points []point.Point, style Style, id util.ID) *Polygon {
return &Polygon{points: points, style: style, id: id}
}

// WithStyle updates the style of the Polygon.
func (p *Polygon) WithStyle(Style Style) *Polygon {
p.style = Style
return p
}

// Draw the path to the provided [canvas.Canvas].
func (p *Polygon) Draw(c *canvas.Canvas) error {
polyline := canvas.Polyline{}
for _, next := range p.points {
polyline.Add(next.Vector().Values())
}

c.RenderPath(polyline.ToPath(), p.style.ToCanvas(), canvas.Identity)

return nil
}

func (p *Polygon) Length() (float64, error) {
length := 0.0

for i := range p.points[1:] {
length += p.points[i].Position().Distance(p.points[i+1].Position())
}

return length, nil
}

func (p *Polygon) ID() util.ID {
return p.id
}

type Path interface {
Draw(c *canvas.Canvas) error
Length() (float64, error)
Through() []point.Point
}

pkg/pattern/path/splines.go → pkg/path/splines.go Dosyayı Görüntüle

@@ -2,7 +2,10 @@ package path

import (
"fmt"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
"log/slog"

"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"github.com/tdewolff/canvas"
splines "gitlab.com/Achilleshiel/gosplines"
)
@@ -11,7 +14,7 @@ const resolution = 40

// Spline defines a smooth curved path through points.
type Spline struct {
*Path
*Polygon
start, end point.Point
}

@@ -19,6 +22,7 @@ type SplineOpts struct {
Start, End point.Point
Points []point.Point
Style Style
ID util.ID
}

// NewSpline returns a new spline through points. Start and end points can be provided as
@@ -26,9 +30,9 @@ type SplineOpts struct {
// are no constraints on the direction.
func NewSpline(opts SplineOpts) Spline {
s := Spline{
Path: NewPath(opts.Points, opts.Style),
start: opts.Start,
end: opts.End,
Polygon: NewPolygon(opts.Points, opts.Style, opts.ID),
start: opts.Start,
end: opts.End,
}

if opts.Start == nil && len(opts.Points) > 1 {
@@ -44,8 +48,41 @@ func NewSpline(opts SplineOpts) Spline {

// Draw the spline to the provided [canvas.Canvas].
func (s Spline) Draw(c *canvas.Canvas) error {
points, err := s.build()
if err != nil {
return fmt.Errorf("generating spline: %w", err)
}

path := NewPolygon(points, s.style, s.id)

if err = path.Draw(c); err != nil {
return fmt.Errorf("draw spline points to canvas: %w", err)
}

length, _ := s.Length()
slog.Debug("Draw spline", "length", length, "from", points[0].Name(), "to", points[len(points)-1].Name())

return nil
}

func (s Spline) Length() (float64, error) {
points, err := s.build()
if err != nil {
return 0.0, fmt.Errorf("generating spline: %w", err)
}

length := 0.0

for i := range points[1:] {
length += points[i].Position().Distance(points[i+1].Position())
}

return length, nil
}

func (s Spline) build() (points []point.Point, err error) {
if len(s.points) < 2 {
return nil
return s.points, nil
}

x := make([]float64, len(s.points))
@@ -60,15 +97,15 @@ func (s Spline) Draw(c *canvas.Canvas) error {

xCoefficient, err := splines.SolveSplineWithConstraint(x, diffStart.X, diffEnd.X)
if err != nil {
return fmt.Errorf("unable to calculate coefficients for x: %w", err)
return nil, fmt.Errorf("unable to calculate coefficients for x: %w", err)
}

yCoefficient, err := splines.SolveSplineWithConstraint(y, diffStart.Y, diffEnd.Y)
if err != nil {
return fmt.Errorf("unable to calculate coefficients for y: %w", err)
return nil, fmt.Errorf("unable to calculate coefficients for y: %w", err)
}

points := make([]point.Point, 0, len(x)*resolution)
points = make([]point.Point, 0, len(x)*resolution)
stepSize := 1.0 / float64(resolution-1)

for i := range len(s.points) - 1 {
@@ -83,11 +120,5 @@ func (s Spline) Draw(c *canvas.Canvas) error {

points = append(points, s.points[len(s.points)-1])

path := NewPath(points, s.style)

if err = path.Draw(c); err != nil {
return fmt.Errorf("draw spline points to canvas: %w", err)
}

return nil
return points, nil
}

pkg/pattern/path/style.go → pkg/path/style.go Dosyayı Görüntüle


+ 41
- 0
pkg/pattern/panel/panel.go Dosyayı Görüntüle

@@ -0,0 +1,41 @@
package panel

import (
"git.wtrh.nl/patterns/gopatterns/pkg/dimensions"
"git.wtrh.nl/patterns/gopatterns/pkg/path"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/text"
"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"github.com/tdewolff/canvas"
)

type Panel struct {
Name string
Lines map[util.ID]path.Path
Points map[util.ID]point.Point
Dimensions dimensions.Dimensions
Texts []text.Text
}

func (p Panel) Draw(c *canvas.Canvas, face *canvas.FontFace, debug bool) error {
for _, line := range p.Lines {
err := line.Draw(c)
if err != nil {
return err
}

for _, throughPoint := range line.Through() {
throughPoint.SetDraw()
}
}

for _, drawPoints := range p.Points {
point.Draw(c, drawPoints, face, debug)
}

for _, t := range p.Texts {
t.ToCanvas(c, face)
}

return nil
}

+ 0
- 36
pkg/pattern/path/path.go Dosyayı Görüntüle

@@ -1,36 +0,0 @@
// Package path provides objects to define lines on a sewing pattern.
package path

import (
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
"github.com/tdewolff/canvas"
)

// Path defines a set of straight lines through points.
type Path struct {
points []point.Point
style Style
}

// NewPath returns a new [Path].
func NewPath(points []point.Point, style Style) *Path {
return &Path{points: points, style: style}
}

// WithStyle updates the style of the Path.
func (p *Path) WithStyle(Style Style) *Path {
p.style = Style
return p
}

// Draw the path to the provided [canvas.Canvas].
func (p *Path) Draw(c *canvas.Canvas) error {
polyline := canvas.Polyline{}
for _, next := range p.points {
polyline.Add(next.Vector().Values())
}

c.RenderPath(polyline.ToPath(), p.style.ToCanvas(), canvas.Identity)

return nil
}

+ 16
- 8
pkg/pattern/pattern.go Dosyayı Görüntüle

@@ -4,8 +4,10 @@ package pattern
import (
"fmt"

"git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
"git.wtrh.nl/patterns/gopatterns/pkg/dimensions"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/text"
"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"github.com/tdewolff/canvas"
"golang.org/x/image/font/gofont/goregular"
"gopkg.in/Knetic/govaluate.v3"
@@ -13,13 +15,14 @@ import (

// The Pattern contains all the points, lines and dimensions to draw a pattern to a canvas.
type Pattern struct {
points map[point.ID]point.Point
points map[util.ID]point.Point
lines []pathDrawer
dimensions Dimensions
dimensions dimensions.Dimensions
texts []*text.Text
}

type pathDrawer interface {
Length() (float64, error)
Draw(c *canvas.Canvas) error
}

@@ -34,7 +37,7 @@ func (p *Pattern) AddLine(line pathDrawer) {
}

// GetPoints returns a slice with points for the given IDs.
func (p *Pattern) GetPoints(id []point.ID) []point.Point {
func (p *Pattern) GetPoints(id []util.ID) []point.Point {
points := make([]point.Point, 0, len(id))
for _, i := range id {
points = append(points, p.GetPoint(i))
@@ -44,16 +47,16 @@ func (p *Pattern) GetPoints(id []point.ID) []point.Point {
}

// GetPoint returns the point for the given ID.
func (p *Pattern) GetPoint(id point.ID) point.Point { //nolint:ireturn
func (p *Pattern) GetPoint(id util.ID) point.Point { //nolint:ireturn
return p.points[id]
}

// NewPattern returns a new Pattern.
func NewPattern() *Pattern {
return &Pattern{
points: make(map[point.ID]point.Point, 32),
points: make(map[util.ID]point.Point, 32),
lines: make([]pathDrawer, 0, 32),
dimensions: make(Dimensions),
dimensions: make(dimensions.Dimensions),
texts: make([]*text.Text, 0),
}
}
@@ -94,6 +97,11 @@ func (p *Pattern) AddText(t *text.Text) {
p.texts = append(p.texts, t)
}

func (p *Pattern) SetDimensions(dimensions Dimensions) {
func (p *Pattern) SetDimensions(dimensions dimensions.Dimensions) {
p.dimensions = dimensions
}

// AddDimension adds a dimension to a pattern.
func (p *Pattern) AddDimension(id dimensions.DimensionID, dimension dimensions.Dimension) {
p.dimensions[id] = dimension
}

+ 0
- 119
pkg/pattern/template/fixtures/classic_trouser_block.yaml Dosyayı Görüntüle

@@ -1,119 +0,0 @@
---
points:
0:
position:
1:
position:
y: -(body_rise + 10)
relativeTo: 0
2:
position:
y: -inside_leg
relativeTo: 1
3:
position:
y: inside_leg/2+50
relativeTo: 2
4:
position:
y: body_rise/4
relativeTo: 1
5:
position:
x: -(seat/8 - 10)
relativeTo: 1
6:
position:
x: -(seat/8 - 10)
relativeTo: 4
7:
position:
x: -(seat/8 - 10)
relativeTo: 0
8:
position:
x: seat/4 + 20
relativeTo: 6
9:
position:
x: -(seat/16 + 5)
relativeTo: 5
10:
position:
x: 10
relativeTo: 7
11:
position:
x: trouser_waist/4 + 25
relativeTo: 10
12:
position:
x: trouser_bottom_width/2
relativeTo: 2
13:
position:
x: -trouser_bottom_width/2
relativeTo: 2
14:
position:
x: trouser_bottom_width/2 + 15
relativeTo: 3
15:
position:
x: -(trouser_bottom_width/2 + 15)
relativeTo: 3

panels:
front:
points:
extend12-14:
between:
from: 12
to: 14
offset: 1.3
between11-8:
between:
from: 8
to: 11
offset: 0.5
between9-15:
between:
from: 15
to: 9
offset: 0.5
offset_between11-8:
position:
x: 5
relativeTo: between11-8
offset_between9-15:
position:
x: 5
relativeTo: between9-15
extend13-15:
between:
from: 13
to: 15
offset: 1.5
r5:
position:
rotation: 3*pi/4
relativeTo: 5
h5:
position:
x: 30
relativeTo: r5
hide: true
lines:
- through: [0,4,1,3,2]
- through: [14,12,13,15]
- through: [14,8,11]
curve:
start: extend12-14
end: offset_between11-8
- through: [15,9]
curve:
start: extend13-15
- through: [9, h5, 6]
curve: {}
- through: [6, 10, 11]


+ 0
- 9
pkg/pattern/template/fixtures/trouser.yaml Dosyayı Görüntüle

@@ -1,9 +0,0 @@
---
owner: Wouter
sizes:
body_rise: 281
inside_leg: 800
seat: 1020
trouser_waist: 900
trouser_bottom_width: 226
template: fixtures/classic_trouser_block.yaml

+ 0
- 6
pkg/pattern/template/information.go Dosyayı Görüntüle

@@ -1,6 +0,0 @@
package template

type Information struct {
Point `yaml:",inline"`
Anchor string `yaml:"anchor"`
}

+ 0
- 69
pkg/pattern/template/line.go Dosyayı Görüntüle

@@ -1,69 +0,0 @@
package template

import (
"git.wtrh.nl/patterns/gopatterns/pkg/pattern"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/path"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
)

// Lines contains one Line or more in a slice.
type Lines []Line

// Line describes a pattern line.
type Line struct {
Through []point.ID `yaml:"through"`
Curve *Curve `yaml:"curve,omitempty"`
Style *Style `yaml:"style,omitempty"`
}

type Style struct {
Thickness *float64 `yaml:"thickness,omitempty"`
}

// Curve describes if a Line curves and if it has start and end constraints.
type Curve struct {
Start point.ID `yaml:"start,omitempty"`
End point.ID `yaml:"end,omitempty"`
}

// Build adds the line to the provided [pattern.Pattern].
func (l Line) Build(pat *pattern.Pattern) error {
points := pat.GetPoints(l.Through)
for _, p := range points {
p.SetDraw()
}

style := path.NewDefaultStyle()

if l.Style != nil && l.Style.Thickness != nil {
style.Thickness = *l.Style.Thickness
}

switch {
case l.Curve != nil:
pat.AddLine(
path.NewSpline(path.SplineOpts{
Start: pat.GetPoint(l.Curve.Start),
End: pat.GetPoint(l.Curve.End),
Points: points,
Style: style,
}),
)
default:
pat.AddLine(path.NewPath(points, style))
}

return nil
}

// Build adds all the lines to the provided [pattern.Pattern].
func (l Lines) Build(pat *pattern.Pattern) error {
for _, line := range l {
err := line.Build(pat)
if err != nil {
return err
}
}

return nil
}

+ 0
- 423
pkg/pattern/template/point.go Dosyayı Görüntüle

@@ -1,423 +0,0 @@
package template

import (
"errors"
"fmt"
"maps"
"math"
"strconv"

"git.wtrh.nl/patterns/gopatterns/pkg/pattern"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
"gopkg.in/Knetic/govaluate.v3"
)

const maxRecursionDepth = 100

var (
// ErrPointNotFound is returned when a required point is not defined.
ErrPointNotFound = errors.New("required point not found")

// ErrRelativePointRecursion is returned when a points are relative to itself.
ErrRelativePointRecursion = errors.New("point cannot be relative to itself")

ErrInvalidArguments = errors.New("invalid arguments to call function")
)

// Points contains a map with points.
type Points map[point.ID]Point

// Point contains the template information for a point.
type Point struct {
Position Position `yaml:"position"`
RelativeTo *point.ID `yaml:"relativeTo,omitempty"`
Description string `yaml:"description"`
Between *BetweenPoint `yaml:"between"`
Extend *ExtendPoint `yaml:"extend"`
Hide bool `yaml:"hide"`
Polar *PolarPoint `yaml:"polar"`
}

var ErrInvalidPointID = errors.New("type cannot be converted to a PointID")

func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionFunction {
functions := p.evaluationFunctions()

maps.Copy(functions, map[string]govaluate.ExpressionFunction{
"DistanceBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := p.getOrCreateFromArgs(pat, args...)
if err != nil {
return nil, err
}
return points[0].Position().Distance(points[1].Position()), nil
},
"AngleBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function AngleBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := p.getOrCreateFromArgs(pat, args...)
if err != nil {
return nil, err
}

return points[0].Vector().AngleBetween(points[1].Vector()), nil
},
"YDistanceBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := p.getOrCreateFromArgs(pat, args...)
if err != nil {
return nil, err
}

return points[0].Vector().Y - points[1].Vector().Y, nil
},
"XDistanceBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := p.getOrCreateFromArgs(pat, args...)
if err != nil {
return nil, err
}

return points[0].Vector().X - points[1].Vector().X, nil
},
})

return functions
}

func (p Points) getOrCreateFromArgs(pat *pattern.Pattern, args ...interface{}) ([]point.Point, error) {
points := make([]point.Point, 0, len(args))

for i, arg := range args {
id, err := toPointID(arg)
if err != nil {
return nil, fmt.Errorf("parsing args[%d] to pointID: %w", i, err)
}

newPoint, err := p.getOrCreate(id, pat, 0)
if err != nil {
return nil, fmt.Errorf("get or create point %q: %w", id, err)
}

points = append(points, newPoint)
}

return points, nil
}

func toPointID(arg interface{}) (point.ID, error) {
v1, ok := arg.(string)
if !ok {
f, ok := arg.(float64)
if !ok {
return "", fmt.Errorf("parsing %v as PointID: %w", arg, ErrInvalidPointID)
}

v1 = strconv.FormatFloat(f, 'f', -1, 64)
}

id1 := point.ID(v1)

return id1, nil
}

// BetweenPoint contains the template information for a point in between two other points.
type BetweenPoint struct {
From point.ID `yaml:"from"`
To point.ID `yaml:"to"`
Offset *Value `yaml:"offset"`
}

func (p Points) evaluationFunctions() map[string]govaluate.ExpressionFunction {
return map[string]govaluate.ExpressionFunction{
"acos": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function acos() requires 1 argument: %w",
ErrInvalidArguments)
}

x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate acos(): parsing %q as float64: %w", args[0],
ErrInvalidArguments)
}

return math.Acos(x), nil
},
"asin": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function asin() requires 1 argument: %w",
ErrInvalidArguments)
}

x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate asin(): parsing %q as float64: %w", args[0],
ErrInvalidArguments)
}

return math.Asin(x), nil
},
"atan2": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function atan2() requires 2 arguments: %w",
ErrInvalidArguments)
}
x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0],
ErrInvalidArguments)
}

y, ok := args[1].(float64)
if !ok {
return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0],
ErrInvalidArguments)
}

return math.Atan2(x, y), nil
},
}
}

// AddToPattern will add all points to the provided [pattern.Pattern].
func (p Points) AddToPattern(pat *pattern.Pattern) error {
for id := range p {
if pat.GetPoint(id) == nil {
err := p.addSingleToPattern(id, pat, 0)
if err != nil {
return err
}
}
}

return nil
}

func (p Points) addSingleToPattern(id point.ID, pat *pattern.Pattern, depth int) (err error) {
templatePoint, ok := p[id]
if !ok {
return ErrPointNotFound
}

var newPoint point.Point

switch {
case templatePoint.RelativeTo != nil && templatePoint.Polar != nil:
newPoint, err = p.createPolar(id, pat, depth)
if err != nil {
return err
}
case templatePoint.RelativeTo != nil:
newPoint, err = p.createRelative(id, pat, depth)
if err != nil {
return err
}
case templatePoint.Between != nil:
newPoint, err = p.createBetween(id, pat, depth)
if err != nil {
return err
}
case templatePoint.Extend != nil:
newPoint, err = p.createExtend(id, pat, depth)
if err != nil {
return err
}
default:
x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat))
if err != nil {
return err
}

newPoint = point.NewAbsolutePoint(x, y, r, id)
}

if templatePoint.Hide {
newPoint.SetHide()
}

pat.AddPoint(newPoint)

return nil
}

func (p Points) createRelative(
id point.ID,
pat *pattern.Pattern,
depth int,
) (*point.RelativePoint, error) {
templatePoint, ok := p[id]
if !ok {
return nil, ErrPointNotFound
}

relativePointID := *templatePoint.RelativeTo
if relativePointID == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

relativePoint, err := p.getOrCreate(relativePointID, pat, depth)
if err != nil {
return nil, err
}

x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat))
if err != nil {
return nil, err
}

return point.NewRelativePoint(relativePoint).
WithXOffset(x).WithYOffset(y).WithRotationOffset(r).
MarkWith(id), nil
}

//nolint:ireturn,dupl
func (p Points) createBetween(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
newPoint, ok := p[id]
if !ok {
return nil, ErrPointNotFound
}

if newPoint.Between.To == id || newPoint.Between.From == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

fromPoint, err := p.getOrCreate(newPoint.Between.From, pat, depth)
if err != nil {
return nil, err
}

toPoint, err := p.getOrCreate(newPoint.Between.To, pat, depth)
if err != nil {
return nil, err
}

params := pat.Parameters()

offset, err := newPoint.Between.Offset.Evaluate(params, p.Functions(pat))
if err != nil {
return nil, err
}

return point.NewBetweenPoint(fromPoint, toPoint, offset, id), nil
}

//nolint:ireturn
func (p Points) getOrCreate(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
if pat.GetPoint(id) == nil {
err := p.addSingleToPattern(id, pat, depth+1)
if err != nil {
return nil, err
}
}

createdPoint := pat.GetPoint(id)
if createdPoint == nil {
panic("getPoint cannot be nil")
}

return createdPoint, nil
}

//nolint:ireturn,dupl
func (p Points) createExtend(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
newPoint, ok := p[id]
if !ok {
return nil, ErrPointNotFound
}

if newPoint.Extend.To == id || newPoint.Extend.From == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

fromPoint, err := p.getOrCreate(newPoint.Extend.From, pat, depth)
if err != nil {
return nil, err
}

toPoint, err := p.getOrCreate(newPoint.Extend.To, pat, depth)
if err != nil {
return nil, err
}

params := pat.Parameters()

offset, err := newPoint.Extend.Offset.Evaluate(params, p.Functions(pat))
if err != nil {
return nil, err
}

return point.NewExtendPoint(fromPoint, toPoint, offset, id), nil
}

//nolint:ireturn
func (p Points) createPolar(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
templatePoint, ok := p[id]
if !ok {
return nil, ErrPointNotFound
}

relativePointID := *templatePoint.RelativeTo
if relativePointID == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

relativePoint, err := p.getOrCreate(relativePointID, pat, depth)
if err != nil {
return nil, err
}

x, y, err := templatePoint.Polar.evaluate(pat.Parameters(), p.Functions(pat))
if err != nil {
return nil, err
}

return point.NewRelativePoint(relativePoint).
WithXOffset(x).WithYOffset(y).MarkWith(id), nil
}

// ExtendPoint describes how to draw a new point that extends in line with two points.
type ExtendPoint struct {
From point.ID `yaml:"from"`
To point.ID `yaml:"to"`
Offset *Value `yaml:"offset"`
}

// PolarPoint describes how to draw a new point with a direction and a distance from the current
// position.
type PolarPoint struct {
Length *Value `yaml:"length"`
Rotation *Value `yaml:"rotation"`
}

func (p PolarPoint) evaluate(
params govaluate.MapParameters,
funcs map[string]govaluate.ExpressionFunction,
) (x, y float64, err error) {
rotation, err := p.Rotation.Evaluate(params, funcs)
if err != nil {
return 0, 0, err
}

length, err := p.Length.Evaluate(params, funcs)
if err != nil {
return 0, 0, err
}

return math.Sin(rotation) * -length, math.Cos(rotation) * -length, nil
}

+ 0
- 124
pkg/pattern/template/renderer.go Dosyayı Görüntüle

@@ -1,124 +0,0 @@
package template

import (
"fmt"
"path/filepath"
"slices"
"strings"

"git.wtrh.nl/patterns/gopatterns/pkg/pattern"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/text"
"github.com/stoewer/go-strcase"
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers"
)

// RenderPatterns loads a [Request] from yaml file and renders the pattern to an SVG.
func (s Storage) RenderPatterns(request Request, outputDir string, debug bool) ([]string, error) {
template, err := s.LoadTemplate(request.Template)
if err != nil {
return nil, fmt.Errorf("load pattern %q: %w", request.Template, err)
}

filenames := make([]string, 0, len(template.Panels))

dim, err := s.Dimensions(request.Sizes)
if err != nil {
return nil, fmt.Errorf("load dimensions: %w", err)
}

renderer := Renderer{dimensions: dim, owner: request.Owner, pattern: request.Template}

for name, panel := range template.Panels {
pat := pattern.NewPattern()
pat.SetDimensions(dim)

err = template.Points.AddToPattern(pat)
if err != nil {
return nil, fmt.Errorf("add generic points to pattern: %w", err)
}

err = renderer.BuildPanel(panel, pat)
if err != nil {
return nil, fmt.Errorf("constructing %s panel: %w", name, err)
}

c := canvas.New(200, 200)

err = pat.ToCanvas(c, debug)
if err != nil {
return nil, fmt.Errorf("write pattern to canvas: %w", err)
}

c.Fit(10)

filename := filepath.Join(outputDir, strings.Join([]string{
request.Template, name,
strcase.SnakeCase(request.Owner),
}, "_")+".pdf")

filenames = append(filenames, filename)

err = renderers.Write(filename, c)
if err != nil {
return nil, fmt.Errorf("write canvas to file: %w", err)
}
}

return filenames, nil
}

type Renderer struct {
dimensions pattern.Dimensions
owner string
pattern string
}

// BuildPanel translates the panel to the provided [pattern.Pattern].
func (r Renderer) BuildPanel(panel Panel, pat *pattern.Pattern) error {
err := panel.Points.AddToPattern(pat)
if err != nil {
return err
}

err = panel.Lines.Build(pat)
if err != nil {
return err
}

err = r.GenerateInformation(panel, pat)
if err != nil {
return err
}

return nil
}

func (r Renderer) GenerateInformation(p Panel, pat *pattern.Pattern) error {
err := Points{"_information": p.Information.Point}.AddToPattern(pat)
if err != nil {
return err
}

dimensions := make([]string, 0, len(r.dimensions))
for _, dimension := range r.dimensions {
dimensions = append(dimensions,
fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10))
}

slices.Sort(dimensions)
dimensions = append([]string{
"For: " + r.owner,
"Pattern: " + startCase(r.pattern),
"Panel: " + p.Name,
"Hem allowance: " + p.Allowances.Hem,
"Seam allowance: " + p.Allowances.Seam,
"\nMeasurements:",
}, dimensions...)

point := pat.GetPoint("_information")
point.SetHide()
pat.AddText(text.NewText(point, "", strings.Join(dimensions, "\n")))

return nil
}

+ 0
- 56
pkg/pattern/template/template.go Dosyayı Görüntüle

@@ -1,56 +0,0 @@
// Package template makes it possible to describe templates and patterns in yaml files and
// to render them to SVG.
package template

import (
"fmt"
"os"
"unicode"

"gopkg.in/yaml.v3"
)

// Sizes defines a map with the size name and the size value.
type Sizes map[string]float64

// Template contains the generic information to draw a specific clothing pattern.
type Template struct {
Points `yaml:"points"`
Panels `yaml:"panels"`
}

// LoadPattern reads and decodes a [Request] from a yaml file.
func LoadPattern(name string) (Request, error) {
fh, err := os.Open(name)
if err != nil {
return Request{}, fmt.Errorf("open pattern file %q: %w", name, err)
}

pat := Request{}

err = yaml.NewDecoder(fh).Decode(&pat)
if err != nil {
return Request{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err)
}

return pat, nil
}

func startCase(text string) string {
output := make([]rune, len(text))

for i, val := range text {
switch {
case i == 0:
output[i] = unicode.ToUpper(val)
case val == '_':
output[i] = ' '
case output[i-1] == ' ':
output[i] = unicode.ToUpper(val)
default:
output[i] = val
}
}

return string(output)
}

+ 9
- 8
pkg/pattern/text/text.go Dosyayı Görüntüle

@@ -1,21 +1,22 @@
package text

import (
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
"git.wtrh.nl/patterns/gopatterns/pkg/position"
"github.com/tdewolff/canvas"
)

type Text struct {
point.Point
anchor string
text string
Position position.Position
Anchor string
Text string
}

func NewText(point point.Point, anchor string, text string) *Text {
return &Text{Point: point, anchor: anchor, text: text}
func NewText(position position.Position, anchor string, text string) Text {
return Text{Position: position, Anchor: anchor, Text: text}
}

func (t Text) ToCanvas(c *canvas.Canvas, face *canvas.FontFace) {
text := canvas.NewTextLine(face, t.text, canvas.Left)
c.RenderText(text, t.Matrix())
text := canvas.NewTextLine(face, t.Text, canvas.Left)
matrix := canvas.Identity.Translate(t.Position.Vector.X, t.Position.Vector.Y).Rotate(t.Position.RotationD())
c.RenderText(text, matrix)
}

pkg/pattern/point/absolute_point.go → pkg/point/absolute_point.go Dosyayı Görüntüle

@@ -2,6 +2,7 @@ package point

import (
"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/tdewolff/canvas"
)
@@ -9,7 +10,7 @@ import (
// AbsolutePoint implements Point and defines an absolute position.
// All other points should eventually be relative to one or more absolute point(s).
type AbsolutePoint struct {
id ID
id util.ID
position position.Position
name string
draw bool
@@ -18,11 +19,11 @@ type AbsolutePoint struct {

// Matrix calculates and returns the [canvas.Matrix] of a point.
func (a *AbsolutePoint) Matrix() canvas.Matrix {
return canvas.Identity.Translate(a.position.Vector.Values()).Rotate(a.position.Rotation)
return canvas.Identity.Translate(a.position.Vector.Values()).Rotate(a.position.RotationD())
}

// ID returns the point ID.
func (a *AbsolutePoint) ID() ID {
func (a *AbsolutePoint) ID() util.ID {
return a.id
}

@@ -42,7 +43,7 @@ func (a *AbsolutePoint) Vector() vector.Vector {
}

// NewAbsolutePoint returns a new absolute point.
func NewAbsolutePoint(x, y, r float64, id ID) *AbsolutePoint {
func NewAbsolutePoint(x, y, r float64, id util.ID) *AbsolutePoint {
return &AbsolutePoint{
position: position.Position{
Vector: vector.Vector{
@@ -68,7 +69,7 @@ func (a *AbsolutePoint) SetDraw() {

// UnsetDraw indicates that the point should not be drawn.
func (a *AbsolutePoint) UnsetDraw() {
a.draw = true
a.draw = false
}

// Hide returns if the point must remain hidden.

+ 71
- 0
pkg/point/absolute_point_test.go Dosyayı Görüntüle

@@ -0,0 +1,71 @@
package point_test

import (
"math"
"testing"

"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/stretchr/testify/require"
"github.com/tdewolff/canvas"
)

func EqualMatrix(t *testing.T, expected, actual canvas.Matrix, delta float64) {
t.Helper()

require.InDeltaSlice(t, expected[0][:], actual[0][:], delta)
require.InDeltaSlice(t, expected[1][:], actual[1][:], delta)
}

func TestAbsolutePoint_Matrix(t *testing.T) {
rotation := math.Pi/2 + 2
newPoint := point.NewAbsolutePoint(1, 2, rotation, "1")
actual := newPoint.Matrix()

expected := canvas.Matrix{
{math.Cos(rotation), -math.Sin(rotation), 1.0},
{math.Sin(rotation), math.Cos(rotation), 2.0},
}

EqualMatrix(t, actual, expected, 1e-10)
}

func TestAbsolutePoint_ID(t *testing.T) {
newPoint := point.NewAbsolutePoint(1, 2, 3, "this is a test value")
require.Equal(t, util.ID("this is a test value"), newPoint.ID())
}

func TestAbsolutePoint_Name(t *testing.T) {
require.Equal(t, "this is a test value", point.NewAbsolutePoint(1, 2, 3, "this is a test value").Name())
}

func TestAbsolutePoint_Position(t *testing.T) {
actual := point.NewAbsolutePoint(1, 2, 3, "this is a test value").Position()
expected := position.Position{
Vector: vector.Vector{X: 1, Y: 2},
Rotation: 3,
}

require.Equal(t, expected, actual)
}

func TestAbsolutePoint_Draw(t *testing.T) {
newPoint := point.NewAbsolutePoint(1, 2, 3, "this is a test value")
require.False(t, newPoint.Draw())

newPoint.SetDraw()
require.True(t, newPoint.Draw())

newPoint.UnsetDraw()
require.False(t, newPoint.Draw())
}

func TestAbsolutePoint_Hide(t *testing.T) {
newPoint := point.NewAbsolutePoint(1, 2, 3, "this is a test value")
require.False(t, newPoint.Hide())

newPoint.SetHide()
require.True(t, newPoint.Hide())
}

pkg/pattern/point/between_point.go → pkg/point/between_point.go Dosyayı Görüntüle

@@ -4,13 +4,14 @@ import (
"math"

"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/tdewolff/canvas"
)

// BetweenPoint defines a point on the line between two other points.
type BetweenPoint struct {
id ID
id util.ID
p Point
q Point
offset float64
@@ -23,7 +24,7 @@ type BetweenPoint struct {
// The given offset defines where the new point is.
// With offset = 0 the new point is a p, offset = 0.5 results in a point exactly in the middle.
// Offset can be <0 (extending from p side) or >1 (extending from the q side).
func NewBetweenPoint(p, q Point, offset float64, id ID) *BetweenPoint {
func NewBetweenPoint(p, q Point, offset float64, id util.ID) *BetweenPoint {
return &BetweenPoint{
id: id,
p: p,
@@ -52,12 +53,11 @@ func (b *BetweenPoint) Vector() vector.Vector {

// Matrix calculates and returns the [canvas.Matrix] of a point.
func (b *BetweenPoint) Matrix() canvas.Matrix {
return b.p.Matrix().Translate(b.inBetween().Values()).
Rotate((b.p.Vector().AngleBetween(b.q.Vector()) - math.Pi/2) * 180 / math.Pi)
return canvas.Identity.Translate(b.Vector().Values()).Rotate(b.Position().RotationD())
}

// ID returns the point ID.
func (b *BetweenPoint) ID() ID {
func (b *BetweenPoint) ID() util.ID {
return b.id
}


+ 48
- 0
pkg/point/between_point_test.go Dosyayı Görüntüle

@@ -0,0 +1,48 @@
package point_test

import (
"math"
"testing"

"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/position/testutil"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/tdewolff/canvas"
)

func TestBetweenPoint_Position(t *testing.T) {
p1 := point.NewAbsolutePoint(0, 0, 0, "1")
p2 := point.NewAbsolutePoint(1, 1, 0, "2")
p3 := point.NewBetweenPoint(p1, p2, 0.5, "3")
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 0.5,
Y: 0.5,
},
Rotation: -math.Pi / 4,
}, p3.Position(), 1e-10)

matrix := p3.Matrix()
EqualMatrix(t, canvas.Matrix{
{math.Cos(-math.Pi / 4), -math.Sin(-math.Pi / 4), 0.5},
{math.Sin(-math.Pi / 4), math.Cos(-math.Pi / 4), 0.5},
}, matrix, 1e-10)

p4 := point.NewAbsolutePoint(0, 1, 0, "4")
p5 := point.NewBetweenPoint(p3, p4, 0.5, "5")

testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 0.25,
Y: 0.75,
},
Rotation: math.Pi / 4,
}, p5.Position(), 1e-10)

matrix2 := p5.Matrix()
EqualMatrix(t, canvas.Matrix{
{math.Cos(math.Pi / 4), -math.Sin(math.Pi / 4), 0.25},
{math.Sin(math.Pi / 4), math.Cos(math.Pi / 4), 0.75},
}, matrix2, 1e-10)
}

pkg/pattern/point/extend_point.go → pkg/point/extend_point.go Dosyayı Görüntüle

@@ -4,13 +4,14 @@ import (
"math"

"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/tdewolff/canvas"
)

// ExtendPoint defines a point on the line between two other points.
type ExtendPoint struct {
id ID
id util.ID
from Point
to Point
extend float64
@@ -22,7 +23,7 @@ type ExtendPoint struct {
// NewExtendPoint returns a new ExtendPoint relative to two other points from and to.
// The given offset defines where the new point is.
// With offset = 0 the new point is a from, offset = 0.5 results in a point exactly in the middle.
func NewExtendPoint(from, to Point, extend float64, id ID) *ExtendPoint {
func NewExtendPoint(from, to Point, extend float64, id util.ID) *ExtendPoint {
return &ExtendPoint{
id: id,
from: from,
@@ -36,7 +37,7 @@ func NewExtendPoint(from, to Point, extend float64, id ID) *ExtendPoint {
func (b *ExtendPoint) Position() position.Position {
return position.Position{
Vector: b.to.Vector().Add(b.extendedVector()),
Rotation: b.to.Vector().AngleBetween(b.to.Vector()) - math.Pi/2,
Rotation: b.from.Vector().AngleBetween(b.to.Vector()) - math.Pi/2,
}
}

@@ -51,12 +52,13 @@ func (b *ExtendPoint) Vector() vector.Vector {

// Matrix calculates and returns the [canvas.Matrix] of a point.
func (b *ExtendPoint) Matrix() canvas.Matrix {
return b.to.Matrix().Translate(b.extendedVector().Values()).
Rotate((b.from.Vector().AngleBetween(b.to.Vector()) - math.Pi/2) * 180 / math.Pi)
return canvas.Identity.Translate(b.Position().Vector.Values()).Rotate(b.Position().RotationD())
//return b.to.Matrix().Translate(b.extendedVector().Values()).
// Rotate((b.from.Vector().AngleBetween(b.to.Vector()) - math.Pi/2) * 180 / math.Pi)
}

// ID returns the point ID.
func (b *ExtendPoint) ID() ID {
func (b *ExtendPoint) ID() util.ID {
return b.id
}


pkg/pattern/point/point.go → pkg/point/point.go Dosyayı Görüntüle

@@ -5,17 +5,15 @@ package point

import (
"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/tdewolff/canvas"
)

// ID defines a point id.
type ID string

// Point defines the interface for different types of points.
type Point interface {
// ID returns the point ID.
ID() ID
ID() util.ID

// Position calculates and returns the absolute [position.Position].
Position() position.Position
@@ -55,7 +53,7 @@ func Draw(c *canvas.Canvas, point Point, face *canvas.FontFace, debug bool) {
c.RenderPath(path, style, m)

text := canvas.NewTextLine(face, point.Name(), canvas.Bottom)
c.RenderText(text, m.Translate(2, -4))
c.RenderText(text, m.Translate(2, -4).Rotate(-point.Position().RotationD()))

if debug {
yStyle := canvas.Style{

pkg/pattern/point/relative_point.go → pkg/point/relative_point.go Dosyayı Görüntüle

@@ -4,6 +4,7 @@ import (
"math"

"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/tdewolff/canvas"
)
@@ -14,7 +15,7 @@ type RelativePoint struct {
name string
point Point
relativeOffset position.Position
id ID
id util.ID
hide bool
}

@@ -29,7 +30,7 @@ func (r *RelativePoint) Done() Point { //nolint:ireturn
}

// MarkWith sets the ID of the point.
func (r *RelativePoint) MarkWith(n ID) *RelativePoint {
func (r *RelativePoint) MarkWith(n util.ID) *RelativePoint {
r.id = n

if r.name == "" {
@@ -56,12 +57,12 @@ func (r *RelativePoint) Vector() vector.Vector {
}

// ID returns the point ID.
func (r *RelativePoint) ID() ID {
func (r *RelativePoint) ID() util.ID {
return r.id
}

// NewRelativePointWithVector returns a new RelativePoint.
func NewRelativePointWithVector(point Point, p vector.Vector, id ID) *RelativePoint {
func NewRelativePointWithVector(point Point, p vector.Vector, id util.ID) *RelativePoint {
return &RelativePoint{
point: point,
relativeOffset: position.Position{Vector: p},
@@ -78,7 +79,7 @@ func NewRelativePoint(point Point) *RelativePoint {
}

// NewRotationPoint returns a new RelativePoint with a specific rotation.
func NewRotationPoint(point Point, a float64, id ID) *RelativePoint {
func NewRotationPoint(point Point, a float64, id util.ID) *RelativePoint {
p := &RelativePoint{
point: point,
relativeOffset: position.Position{
@@ -115,22 +116,22 @@ func (r *RelativePoint) WithRotationOffset(value float64) *RelativePoint {
}

// NewRelativePointBelow returns a RelativePoint distance f below another Point.
func NewRelativePointBelow(point Point, f float64, id ID) *RelativePoint {
func NewRelativePointBelow(point Point, f float64, id util.ID) *RelativePoint {
return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: -f}, id)
}

// NewRelativePointAbove returns a RelativePoint distance f above another Point.
func NewRelativePointAbove(point Point, f float64, id ID) *RelativePoint {
func NewRelativePointAbove(point Point, f float64, id util.ID) *RelativePoint {
return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: f}, id)
}

// NewRelativePointLeft returns a RelativePoint distance f left of another Point.
func NewRelativePointLeft(point Point, f float64, id ID) *RelativePoint {
func NewRelativePointLeft(point Point, f float64, id util.ID) *RelativePoint {
return NewRelativePointWithVector(point, vector.Vector{X: -f, Y: 0}, id)
}

// NewRelativePointRight returns a RelativePoint distance f right of another Point.
func NewRelativePointRight(point Point, f float64, id ID) *RelativePoint {
func NewRelativePointRight(point Point, f float64, id util.ID) *RelativePoint {
return NewRelativePointWithVector(point, vector.Vector{X: f, Y: 0}, id)
}


+ 3
- 4
pkg/position/position.go Dosyayı Görüntüle

@@ -22,10 +22,9 @@ func (p Position) Add(q Position) Position {
}
}

// Translate the position to a new reference frame.
func (p Position) Translate(q Position) Position {
q.Vector = q.Vector.Span(q.Rotation)
return p.Add(q)
// RotationD returns the rotation angle of the position in degrees.
func (p Position) RotationD() float64 {
return p.Rotation * 180 / math.Pi
}

// Distance returns the distance between two positions.


+ 41
- 0
pkg/position/position_test.go Dosyayı Görüntüle

@@ -98,3 +98,44 @@ func TestPosition_Add(t *testing.T) {
})
}
}

func TestPosition_Translate(t *testing.T) {
tests := map[string]struct {
p, q position.Position
result float64
}{
"origin to 1,1": {
p: position.Position{
Vector: vector.Vector{
X: 1, Y: 1,
},
},
q: position.Position{
Vector: vector.Vector{
X: 0, Y: 0,
},
},
result: math.Sqrt(2),
},
"0,2 to 4,5 -> 5": {
p: position.Position{
Vector: vector.Vector{
X: 0, Y: 2,
},
},
q: position.Position{
Vector: vector.Vector{
X: 4, Y: 5,
},
},
result: 5,
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
translateResult := tt.p.Distance(tt.q)
require.InDelta(t, tt.result, translateResult, 1e-10)
})
}
}

+ 15
- 0
pkg/position/testutil/testutil.go Dosyayı Görüntüle

@@ -0,0 +1,15 @@
package testutil

import (
"testing"

"git.wtrh.nl/patterns/gopatterns/pkg/position"
"github.com/stretchr/testify/require"
)

func EqualPosition(tb testing.TB, expected, actual position.Position, delta float64, msgAndArgs ...interface{}) {
tb.Helper()
require.InDelta(tb, expected.Vector.X, actual.Vector.X, delta, msgAndArgs)
require.InDelta(tb, expected.Vector.Y, actual.Vector.Y, delta, msgAndArgs)
require.InDelta(tb, expected.Rotation, actual.Rotation, delta, msgAndArgs)
}

+ 143
- 0
pkg/renderer/renderer.go Dosyayı Görüntüle

@@ -0,0 +1,143 @@
package renderer

import (
"fmt"
"git.wtrh.nl/patterns/gopatterns/pkg/config"
"git.wtrh.nl/patterns/gopatterns/pkg/dimensions"
"git.wtrh.nl/patterns/gopatterns/pkg/template"
"github.com/stoewer/go-strcase"
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers"
"golang.org/x/image/font/gofont/goregular"
"path/filepath"
"strings"
)

type Storage interface {
LoadDimensions(sizes config.Sizes) (dimensions.Dimensions, error)
LoadTemplate(name string) (template.Template, error)
}

// RenderPatterns loads a [Request] from yaml file and renders the pattern to an SVG.
func RenderPatterns(s Storage, request config.Request, outputDir string, debug bool) ([]string, error) {
loadedTemplate, err := s.LoadTemplate(request.Template)
if err != nil {
return nil, fmt.Errorf("load pattern %q: %w", request.Template, err)
}

filenames := make([]string, 0, len(loadedTemplate.Panels))

dim, err := s.LoadDimensions(request.Sizes)
if err != nil {
return nil, fmt.Errorf("load dimensions: %w", err)
}

// renderer := Renderer{dimensions: dim, owner: request.Owner, pattern: request.Template}

for name := range loadedTemplate.Panels {
newPanel, err := loadedTemplate.GetPanel(template.Request{Dims: dim, Panel: name, Owner: request.Owner})
if err != nil {
return nil, err
}
//
//pat := pattern.NewPattern()
//pat.SetDimensions(dim)
//
//err = loadedTemplate.Points.AddToPattern(pat)
//if err != nil {
// return nil, fmt.Errorf("add generic points to pattern: %w", err)
//}
//
//err = renderer.BuildPanel(panel, pat)
//if err != nil {
// return nil, fmt.Errorf("constructing %s panel: %w", name, err)
//}

c := canvas.New(200, 200)

err = newPanel.Draw(c, loadFont(), debug)
if err != nil {
return nil, fmt.Errorf("write pattern to canvas: %w", err)
}

c.Fit(10)

filename := filepath.Join(outputDir, strings.Join([]string{
request.Template, name,
strcase.SnakeCase(request.Owner),
}, "_")+".pdf")

filenames = append(filenames, filename)

err = renderers.Write(filename, c)
if err != nil {
return nil, fmt.Errorf("write canvas to file: %w", err)
}
}

return filenames, nil
}

func loadFont() *canvas.FontFace {
fontDejaVu := canvas.NewFontFamily("latin")

if err := fontDejaVu.LoadFont(goregular.TTF, 0, canvas.FontRegular); err != nil {
panic(err)
}

return fontDejaVu.Face(12.0, canvas.Black, canvas.FontRegular)
}

type Renderer struct {
dimensions dimensions.Dimensions
owner string
pattern string
}

//// BuildPanel translates the panel to the provided [pattern.Pattern].
//func (r Renderer) BuildPanel(panel template.Panel, pat *pattern.Pattern) error {
// err := panel.Points.AddToPattern(pat)
// if err != nil {
// return err
// }
//
// err = panel.Lines.Build(pat)
// if err != nil {
// return err
// }
//
// err = r.GenerateInformation(panel, pat)
// if err != nil {
// return err
// }
//
// return nil
//}

//func (r Renderer) GenerateInformation(p panel.Panel, pat *pattern.Pattern) error {
// err := template.Points{"_information": p.Information.Point}
// if err != nil {
// return err
// }
//
// dims := make([]string, 0, len(r.dimensions))
// for _, dimension := range r.dimensions {
// dims = append(dims, fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10))
// }
//
// slices.Sort(dims)
// dims = append([]string{
// "For: " + r.owner,
// "Pattern: " + startCase(r.pattern),
// "Panel: " + p.Name,
// "Hem allowance: " + p.Allowances.Hem,
// "Seam allowance: " + p.Allowances.Seam,
// "\nMeasurements:",
// }, dims...)
//
// point := pat.GetPoint("_information")
// point.SetHide()
// pat.AddText(text.NewText(point, "", strings.Join(dims, "\n")))
//
// return nil
//}

pkg/pattern/template/storage.go → pkg/storage/storage.go Dosyayı Görüntüle

@@ -1,11 +1,13 @@
package template
package storage

import (
"fmt"
"io/fs"
"os"

"git.wtrh.nl/patterns/gopatterns/pkg/pattern"
"git.wtrh.nl/patterns/gopatterns/pkg/config"
"git.wtrh.nl/patterns/gopatterns/pkg/dimensions"
"git.wtrh.nl/patterns/gopatterns/pkg/template"
"gopkg.in/yaml.v3"
)

@@ -22,13 +24,13 @@ func NewStorage(dir string) (Storage, error) {
return Storage{dir: os.DirFS(dir)}, nil
}

func (s Storage) Dimensions(sizes Sizes) (pattern.Dimensions, error) {
func (s Storage) LoadDimensions(sizes config.Sizes) (dimensions.Dimensions, error) {
f, err := s.dir.Open("dimension_names.yaml")
if err != nil {
return nil, fmt.Errorf("open \"dimension_names.yaml\": %w", err)
}

namedDimensions := pattern.Dimensions{}
namedDimensions := dimensions.Dimensions{}

err = yaml.NewDecoder(f).Decode(&namedDimensions)
if err != nil {
@@ -49,27 +51,19 @@ func (s Storage) Dimensions(sizes Sizes) (pattern.Dimensions, error) {
return namedDimensions, nil
}

// Request contains the information to draw a pattern for a specific owner, with corresponding
// sizes and the template name.
type Request struct {
Sizes Sizes `yaml:"sizes,omitempty"`
Owner string `yaml:"owner"`
Template string `yaml:"template,omitempty"`
}

// LoadTemplate reads and decodes a [Template] from a yaml file.
func (s Storage) LoadTemplate(name string) (Template, error) {
func (s Storage) LoadTemplate(name string) (template.Template, error) {
fh, err := s.dir.Open(name + ".yaml")
if err != nil {
return Template{}, fmt.Errorf("open template file %q: %w", name, err)
return template.Template{}, fmt.Errorf("open template file %q: %w", name, err)
}

template := Template{}
t := template.Template{}

err = yaml.NewDecoder(fh).Decode(&template)
err = yaml.NewDecoder(fh).Decode(&t)
if err != nil {
return Template{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err)
return template.Template{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err)
}

return template, nil
return t, nil
}

+ 13
- 0
pkg/template/fixtures/absolute_points.yaml Dosyayı Görüntüle

@@ -0,0 +1,13 @@
---
points:
1:
position:
x: test
y: 14.3
panels:
body:
points:
2:
position:
x: test*2
y: -3

+ 20
- 0
pkg/template/fixtures/between_points.yaml Dosyayı Görüntüle

@@ -0,0 +1,20 @@
---
points:
1:
position:
x: 4
y: 14.3
panels:
body:
points:
2:
relativeTo:
1
position:
x: 2
y: -3
3:
between:
from: 1
to: 2
offset: test

+ 19
- 0
pkg/template/fixtures/evaluation_functions.yaml Dosyayı Görüntüle

@@ -0,0 +1,19 @@
---
points:
acos:
position:
x: acos(1/2)
asin:
position:
x: asin(pi/2)
atan2:
position:
x: atan2(1,1)
abs1:
position:
x: abs(-1.4)
abs2:
position:
x: abs(3.5)
panels:
test: {}

+ 18
- 0
pkg/template/fixtures/extend_points.yaml Dosyayı Görüntüle

@@ -0,0 +1,18 @@
---
points:
1: {}
panels:
body:
points:
2:
relativeTo:
1
position:
x: 4
y: 3
3:
extend:
from: 1
to: 2
offset: 5


+ 52
- 0
pkg/template/fixtures/functions.yaml Dosyayı Görüntüle

@@ -0,0 +1,52 @@
---
points:
1: {}
2:
position:
x: 4
y: 3
distance:
position:
x: DistanceBetween("1","2")
angle:
position:
x: AngleBetween("1","2")
yDistance:
position:
x: YDistanceBetween("1","2")
xDistance:
position:
x: XDistanceBetween("1","2")
lineLength:
position:
x: LineLength("test.1")
lineLength2:
position:
x: LineLength("test.2")
panels:
test:
points:
3:
relativeTo: 1
position:
y: 1
4:
relativeTo: 2
position:
y: 1
5:
position:
x: 1
6:
position:
y: 1
x: 1
7:
position:
y: 1
lines:
1:
through: [1,2,4,3,1]
2:
through: [1,5,6,7,1]


+ 17
- 0
pkg/template/fixtures/polar_points.yaml Dosyayı Görüntüle

@@ -0,0 +1,17 @@
---
points:
1: {}
panels:
body:
points:
2:
relativeTo: 1
polar:
rotation: 0
length: 1
3:
relativeTo: 2
polar:
rotation: pi/2
length: 1


+ 12
- 0
pkg/template/fixtures/references.yaml Dosyayı Görüntüle

@@ -0,0 +1,12 @@
---
points:
1: {}
3:
relativeTo: body.2
panels:
body:
points:
2:
relativeTo: 1
position:
x: 3

+ 15
- 0
pkg/template/fixtures/relative_points.yaml Dosyayı Görüntüle

@@ -0,0 +1,15 @@
---
points:
1:
position:
x: test
y: 14.3
panels:
body:
points:
2:
relativeTo:
1
position:
x: test*2
y: -3

+ 45
- 0
pkg/template/information.go Dosyayı Görüntüle

@@ -0,0 +1,45 @@
package template

import (
"fmt"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/text"
"slices"
"strings"
)

type Information struct {
Point `yaml:",inline"`
Anchor string `yaml:"anchor"`
}

func (t Template) createInformation(req request) (text.Text, error) {
templatePanel, ok := t.Panels[req.panel]
if !ok {
return text.Text{}, ErrPanelNotFound
}

dims := make([]string, 0, len(req.dimensions))
for _, dimension := range req.dimensions {
dims = append(dims, fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10))
}

slices.Sort(dims)
dims = append([]string{
"For: " + req.owner,
"Pattern: " + startCase(t.Name),
"Panel: " + startCase(req.panel),
"Hem allowance: " + templatePanel.Allowances.Hem,
"Seam allowance: " + templatePanel.Allowances.Seam,
"\nMeasurements:",
}, dims...)

templatePanel.Points["_information"] = templatePanel.Information.Point
point, err := t.getOrCreatePoint("_information", req, 0)
if err != nil {
return text.Text{}, err
}

info := text.NewText(point.Position(), templatePanel.Information.Anchor, strings.Join(dims, "\n"))

return info, nil
}

+ 157
- 0
pkg/template/line.go Dosyayı Görüntüle

@@ -0,0 +1,157 @@
package template

import (
"errors"

"git.wtrh.nl/patterns/gopatterns/pkg/path"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern"
"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
)

var ErrLineNotFound = errors.New("required path not found")

// Lines contain named lines.
type Lines map[util.ID]Line

// Line describes a pattern line.
type Line struct {
Through []util.ID `yaml:"through"`
Curve *Curve `yaml:"curve,omitempty"`
Style *Style `yaml:"style,omitempty"`
}

type Style struct {
Thickness *float64 `yaml:"thickness,omitempty"`
}

// Curve describes if a Line curves and if it has start and end constraints.
type Curve struct {
Start util.ID `yaml:"start,omitempty"`
End util.ID `yaml:"end,omitempty"`
}

func (t Template) templateLine(panelName string, id util.ID) (Line, error) {
if id.Panel() != "" {
panelName = id.Panel()
}

panel, err := t.Panel(panelName)
if !errors.Is(err, ErrPanelNotFound) {
l, ok := panel.Lines[id.Deref()]
if ok {
return l, nil
}
}

line, ok := t.Lines[id.Deref()]
if !ok {
return Line{}, ErrLineNotFound
}

return line, nil
}

func (t Template) getOrCreateLine(id util.ID, req request, depth int) (path.Path, error) {
l, ok := req.lines[id]
if ok {
return l, nil
}

newLine, err := t.createLine(id, req, depth+1)
if err != nil {
return nil, err
}

req.lines[util.ID(id)] = newLine

return newLine, nil
}

func (t Template) createLine(id util.ID, req request, depth int) (path.Path, error) {
line, err := t.templateLine(req.panel, id)
if err != nil {
return nil, err
}

throughPoints, err := t.getOrCreatePoints(line.Through, req, depth+1)
if err != nil {
return nil, err
}

style := path.NewDefaultStyle()

if line.Style != nil && line.Style.Thickness != nil {
style.Thickness = *line.Style.Thickness
}

switch {
case line.Curve != nil:
var start, end point.Point
if line.Curve.Start != "" {
start, err = t.getOrCreatePoint(line.Curve.Start, req, depth+1)
if err != nil {
return nil, err
}
}

if line.Curve.End != "" {
end, err = t.getOrCreatePoint(line.Curve.End, req, depth+1)
if err != nil {
return nil, err
}
}

return path.NewSpline(path.SplineOpts{
Start: start,
End: end,
Points: throughPoints,
Style: style,
ID: util.ID(id),
}), nil
default:
return path.NewPolygon(throughPoints, style, util.ID(id)), nil
}
}

// Build adds the line to the provided [pattern.Pattern].
func (l Line) Build(pat *pattern.Pattern) error {
points := pat.GetPoints(l.Through)
for _, p := range points {
p.SetDraw()
}

style := path.NewDefaultStyle()

if l.Style != nil && l.Style.Thickness != nil {
style.Thickness = *l.Style.Thickness
}

switch {
case l.Curve != nil:
pat.AddLine(
path.NewSpline(path.SplineOpts{
Start: pat.GetPoint(l.Curve.Start),
End: pat.GetPoint(l.Curve.End),
Points: points,
Style: style,
}),
)
default:
pat.AddLine(path.NewPolygon(points, style, ""))
}

return nil
}

// Build adds all the lines to the provided [pattern.Pattern].
func (l Lines) Build(pat *pattern.Pattern) error {
for _, line := range l {
err := line.Build(pat)
if err != nil {
return err
}
}

return nil
}

pkg/pattern/template/panel.go → pkg/template/panel.go Dosyayı Görüntüle

@@ -1,9 +1,15 @@
package template

import (
"errors"
)

var ErrPanelNotFound = errors.New("panel does not exist")

// Panels contains a map with named panels.
type Panels map[string]Panel

// Panel contains all the lines and extra points to draw a panel.
// The Panel contains all the lines and extra points to draw a panel.
type Panel struct {
Points Points `yaml:"points"`
Lines Lines `yaml:"lines"`
@@ -16,3 +22,12 @@ type Allowances struct {
Hem string `yaml:"hem"`
Seam string `yaml:"seam"`
}

func (t Template) Panel(name string) (Panel, error) {
p, ok := t.Panels[name]
if !ok {
return Panel{}, ErrPanelNotFound
}

return p, nil
}

+ 491
- 0
pkg/template/point.go Dosyayı Görüntüle

@@ -0,0 +1,491 @@
package template

import (
"errors"
"fmt"
"maps"
"math"
"strconv"

"git.wtrh.nl/patterns/gopatterns/pkg/path"
"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"gopkg.in/Knetic/govaluate.v3"
)

const maxRecursionDepth = 100

var (
// ErrPointNotFound is returned when a required point is not defined.
ErrPointNotFound = errors.New("required point not found")

// ErrRelativePointRecursion is returned when a points are relative to itself.
ErrRelativePointRecursion = errors.New("point cannot be relative to itself")

ErrInvalidArguments = errors.New("invalid arguments to call function")
)

// Points contains a map with points.
type Points map[util.ID]Point

// Point contains the template information for a point.
type Point struct {
Position Position `yaml:"position"`
RelativeTo *util.ID `yaml:"relativeTo,omitempty"`
Description string `yaml:"description"`
Between *BetweenPoint `yaml:"between"`
Extend *ExtendPoint `yaml:"extend"`
Hide bool `yaml:"hide"`
Polar *PolarPoint `yaml:"polar"`
}

func (t Template) templatePoint(panelName string, id util.ID) (Point, error) {
if id.Panel() != "" {
panelName = id.Panel()
}

panel, err := t.Panel(panelName)
if !errors.Is(err, ErrPanelNotFound) {
p, ok := panel.Points[id.Deref()]
if ok {
return p, nil
}
}

p, ok := t.Points[id.Deref()]
if !ok {
return Point{}, ErrPointNotFound
}

return p, nil
}

var ErrInvalidPointID = errors.New("type cannot be converted to a PointID")

func (t Template) functions(req request) map[string]govaluate.ExpressionFunction {
functions := t.evaluationFunctions()

maps.Copy(functions, map[string]govaluate.ExpressionFunction{
"DistanceBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := t.getOrCreatePointsFromArgs(req, args...)
if err != nil {
return nil, err
}
return points[0].Position().Distance(points[1].Position()), nil
},
"AngleBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function AngleBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := t.getOrCreatePointsFromArgs(req, args...)
if err != nil {
return nil, err
}

return points[0].Vector().AngleBetween(points[1].Vector()), nil
},
"YDistanceBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := t.getOrCreatePointsFromArgs(req, args...)
if err != nil {
return nil, err
}

return points[1].Vector().Y - points[0].Vector().Y, nil
},
"XDistanceBetween": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
ErrInvalidArguments)
}

points, err := t.getOrCreatePointsFromArgs(req, args...)
if err != nil {
return nil, err
}

return points[1].Vector().X - points[0].Vector().X, nil
},
"LineLength": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function LineLength() requires 2 arguments: %w", ErrInvalidArguments)
}

line, err := t.getOrCreateLinesFromArgs(req, args...)
if err != nil {
return nil, err
}

return line[0].Length()
},
"DiagonalTo": func(args ...interface{}) (interface{}, error) {
if len(args) != 3 {
return nil, fmt.Errorf("function DiagonalTo() requires 3 arguments: %w", ErrInvalidArguments)
}

points, err := t.getOrCreatePointsFromArgs(req, args[0:2]...)
if err != nil {
return nil, err
}

f, ok := args[2].(float64)
if !ok {
return nil, fmt.Errorf("function DiagonalTo() requires the third argument to be a float: %w",
ErrInvalidArguments)
}

return math.Sqrt(math.Pow(f, 2) - math.Pow(points[0].Position().Distance(points[1].Position()), 2)), nil
},
})

return functions
}

func (t Template) getOrCreatePointsFromArgs(req request, args ...interface{}) ([]point.Point, error) {
points := make([]point.Point, 0, len(args))

for i, arg := range args {
id, err := toID(arg)
if err != nil {
return nil, fmt.Errorf("parsing args[%d] to ID: %w", i, err)
}

newPoint, err := t.getOrCreatePoint(id, req, 0)
if err != nil {
return nil, fmt.Errorf("get or create point %q: %w", id, err)
}

points = append(points, newPoint)
}

return points, nil
}

func (t Template) getOrCreateLinesFromArgs(req request, args ...interface{}) ([]path.Path, error) {
points := make([]path.Path, 0, len(args))

for i, arg := range args {
id, err := toID(arg)
if err != nil {
return nil, fmt.Errorf("parsing args[%d] to ID: %w", i, err)
}

newPoint, err := t.getOrCreateLine(id, req, 0)
if err != nil {
return nil, fmt.Errorf("get or create line %q: %w", id, err)
}

points = append(points, newPoint)
}

return points, nil
}

func toID(arg interface{}) (util.ID, error) {
v1, ok := arg.(string)
if !ok {
f, ok := arg.(float64)
if !ok {
return "", fmt.Errorf("parsing %v as PointID: %w", arg, ErrInvalidPointID)
}

v1 = strconv.FormatFloat(f, 'f', -1, 64)
}

return util.ID(v1), nil
}

// BetweenPoint contains the template information for a point in between two other points.
type BetweenPoint struct {
From util.ID `yaml:"from"`
To util.ID `yaml:"to"`
Offset *Value `yaml:"offset"`
}

func (t Template) evaluationFunctions() map[string]govaluate.ExpressionFunction {
return map[string]govaluate.ExpressionFunction{
"acos": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function acos() requires 1 argument: %w", ErrInvalidArguments)
}

x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate acos(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
}

return math.Acos(x), nil
},
"asin": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function asin() requires 1 argument: %w", ErrInvalidArguments)
}

x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate asin(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
}

return math.Asin(x), nil
},
"abs": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function abs() requires 1 argument: %w", ErrInvalidArguments)
}

x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate abs(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
}

return math.Abs(x), nil
},
"atan2": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("function atan2() requires 2 arguments: %w", ErrInvalidArguments)
}
x, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
}

y, ok := args[1].(float64)
if !ok {
return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
}

return math.Atan2(x, y), nil
},
}
}

func (t Template) createPoint(id util.ID, req request, depth int) (p point.Point, err error) {
templatePoint, err := t.templatePoint(req.panel, id)
if err != nil {
return nil, fmt.Errorf("creating point: %w", err)
}

var newPoint point.Point

switch {
case templatePoint.RelativeTo != nil && templatePoint.Polar != nil:
newPoint, err = t.createPolar(id, req, depth)
if err != nil {
return nil, err
}
case templatePoint.RelativeTo != nil:
newPoint, err = t.createRelative(id, req, depth)
if err != nil {
return nil, err
}
case templatePoint.Between != nil:
newPoint, err = t.createBetween(id, req, depth)
if err != nil {
return nil, err
}
case templatePoint.Extend != nil:
newPoint, err = t.createExtend(id, req, depth)
if err != nil {
return nil, err
}
default:
x, y, r, err := templatePoint.Position.evaluate(req.dimensions.Parameters(), t.functions(req))
if err != nil {
return nil, err
}

newPoint = point.NewAbsolutePoint(x, y, r, id)
}

if templatePoint.Hide {
newPoint.SetHide()
}

return newPoint, nil
}

func (t Template) createRelative(id util.ID, req request, depth int) (*point.RelativePoint, error) {
templatePoint, err := t.templatePoint(req.panel, id)
if err != nil {
return nil, err
}

relativePointID := *templatePoint.RelativeTo
if relativePointID == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

relativePoint, err := t.getOrCreatePoint(relativePointID, req, depth)
if err != nil {
return nil, err
}

x, y, r, err := templatePoint.Position.evaluate(req.dimensions.Parameters(), t.functions(req))
if err != nil {
return nil, err
}

return point.NewRelativePoint(relativePoint).
WithXOffset(x).WithYOffset(y).WithRotationOffset(r).
MarkWith(id), nil
}

//nolint:ireturn,dupl
func (t Template) createBetween(id util.ID, req request, depth int) (point.Point, error) {
newPoint, err := t.templatePoint(req.panel, id)
if err != nil {
return nil, err
}

if newPoint.Between.To == id || newPoint.Between.From == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

fromPoint, err := t.getOrCreatePoint(newPoint.Between.From, req, depth)
if err != nil {
return nil, err
}

toPoint, err := t.getOrCreatePoint(newPoint.Between.To, req, depth)
if err != nil {
return nil, err
}

params := req.dimensions.Parameters()

offset, err := newPoint.Between.Offset.Evaluate(params, t.functions(req))
if err != nil {
return nil, err
}

return point.NewBetweenPoint(fromPoint, toPoint, offset, id), nil
}

func (t Template) getOrCreatePoints(ids []util.ID, req request, depth int) ([]point.Point, error) {
points := make([]point.Point, 0, len(ids))

for _, id := range ids {
createPoint, err := t.getOrCreatePoint(id, req, depth)
if err != nil {
return nil, err
}

points = append(points, createPoint)
}

return points, nil
}

//nolint:ireturn
func (t Template) getOrCreatePoint(id util.ID, req request, depth int) (point.Point, error) {
p, ok := req.points[id]
if ok {
return p, nil
}

newPoint, err := t.createPoint(id, req, depth+1)
if err != nil {
return nil, fmt.Errorf("creating point %q: %w", id, err)
}

req.points[id] = newPoint

return newPoint, nil
}

//nolint:ireturn,dupl
func (t Template) createExtend(id util.ID, req request, depth int) (point.Point, error) {
newPoint, err := t.templatePoint(req.panel, id)
if err != nil {
return nil, err
}

if newPoint.Extend.To == id || newPoint.Extend.From == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

fromPoint, err := t.getOrCreatePoint(newPoint.Extend.From, req, depth)
if err != nil {
return nil, err
}

toPoint, err := t.getOrCreatePoint(newPoint.Extend.To, req, depth)
if err != nil {
return nil, err
}

offset, err := newPoint.Extend.Offset.Evaluate(req.dimensions.Parameters(), t.functions(req))
if err != nil {
return nil, err
}

return point.NewExtendPoint(fromPoint, toPoint, offset, id), nil
}

//nolint:ireturn
func (t Template) createPolar(id util.ID, req request, depth int) (point.Point, error) {
templatePoint, err := t.templatePoint(req.panel, id)
if err != nil {
return nil, err
}

relativePointID := *templatePoint.RelativeTo
if relativePointID == id || depth > maxRecursionDepth {
return nil, ErrRelativePointRecursion
}

relativePoint, err := t.getOrCreatePoint(relativePointID, req, depth)
if err != nil {
return nil, err
}

x, y, r, err := templatePoint.Polar.evaluate(req.dimensions.Parameters(), t.functions(req))
if err != nil {
return nil, err
}

return point.NewRelativePoint(relativePoint).
WithXOffset(x).WithYOffset(y).MarkWith(id).WithRotationOffset(r), nil
}

// ExtendPoint describes how to draw a new point that extends in line with two points.
type ExtendPoint struct {
From util.ID `yaml:"from"`
To util.ID `yaml:"to"`
Offset *Value `yaml:"offset"`
}

// PolarPoint describes how to draw a new point with a direction and a distance from the current
// position.
type PolarPoint struct {
Length *Value `yaml:"length"`
Rotation *Value `yaml:"rotation"`
}

func (p PolarPoint) evaluate(
params govaluate.MapParameters,
funcs map[string]govaluate.ExpressionFunction,
) (x, y, r float64, err error) {
rotation, err := p.Rotation.Evaluate(params, funcs)
if err != nil {
return 0, 0, 0, err
}

length, err := p.Length.Evaluate(params, funcs)
if err != nil {
return 0, 0, 0, err
}

return math.Cos(rotation) * length, math.Sin(rotation) * length, rotation, nil
}

+ 289
- 0
pkg/template/point_test.go Dosyayı Görüntüle

@@ -0,0 +1,289 @@
package template_test

import (
_ "embed"
"math"
"testing"

"git.wtrh.nl/patterns/gopatterns/pkg/dimensions"
"git.wtrh.nl/patterns/gopatterns/pkg/position"
"git.wtrh.nl/patterns/gopatterns/pkg/position/testutil"
"git.wtrh.nl/patterns/gopatterns/pkg/template"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"git.wtrh.nl/patterns/gopatterns/pkg/vector"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

//go:embed fixtures/absolute_points.yaml
var absolutePoints []byte

//go:embed fixtures/relative_points.yaml
var relativePoints []byte

//go:embed fixtures/between_points.yaml
var betweenPoints []byte

//go:embed fixtures/extend_points.yaml
var extendPoints []byte

//go:embed fixtures/polar_points.yaml
var polarPoints []byte

//go:embed fixtures/functions.yaml
var functions []byte

//go:embed fixtures/evaluation_functions.yaml
var evaluationFunctions []byte

//go:embed fixtures/references.yaml
var references []byte

func TestAbsolutePoint(t *testing.T) {
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(absolutePoints, temp))

panel, err := temp.GetPanel(template.Request{
Dims: dimensions.Dimensions{"test": dimensions.Dimension{
Name: "Test",
Value: 1.3,
}},
Panel: "body",
})
require.NoError(t, err)

p1, ok := panel.Points["1"]
require.True(t, ok)
require.Equal(t, position.Position{
Vector: vector.Vector{
X: 1.3,
Y: 14.3,
},
Rotation: 0,
}, p1.Position())

p2, ok := panel.Points["2"]
require.True(t, ok)
require.Equal(t, position.Position{
Vector: vector.Vector{
X: 2.6,
Y: -3,
},
Rotation: 0,
}, p2.Position())
}

func TestBetweenPoints(t *testing.T) {
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(betweenPoints, temp))

panel, err := temp.GetPanel(template.Request{
Dims: dimensions.Dimensions{"test": dimensions.Dimension{
Name: "Test",
Value: 0.5,
}},
Panel: "body",
})
require.NoError(t, err)

p1, ok := panel.Points["1"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 4,
Y: 14.3,
},
Rotation: 0,
}, p1.Position(), 1e-10)

p2, ok := panel.Points["2"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 6,
Y: 11.3,
},
Rotation: 0,
}, p2.Position(), 1e-10)

p3, ok := panel.Points["3"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 5,
Y: 12.8,
},
Rotation: math.Atan2(-3, 2) - math.Pi/2,
}, p3.Position(), 1e-10)
}

func TestRelativePoints(t *testing.T) {
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(relativePoints, temp))

panel, err := temp.GetPanel(template.Request{
Dims: dimensions.Dimensions{"test": dimensions.Dimension{
Name: "Test",
Value: 1.3,
}},
Panel: "body",
})
require.NoError(t, err)

p1, ok := panel.Points["1"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 1.3,
Y: 14.3,
},
Rotation: 0,
}, p1.Position(), 1e-10)

p2, ok := panel.Points["2"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 3.9,
Y: 11.3,
},
Rotation: 0,
}, p2.Position(), 1e-10)
}

func TestExtendPoints(t *testing.T) {
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(extendPoints, temp))

panel, err := temp.GetPanel(template.Request{
Dims: dimensions.Dimensions{"test": dimensions.Dimension{
Name: "Test",
Value: 1.3,
}},
Panel: "body",
})
require.NoError(t, err)

p1, ok := panel.Points["1"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{}, p1.Position(), 1e-10)

p2, ok := panel.Points["2"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 4,
Y: 3,
},
Rotation: 0,
}, p2.Position(), 1e-10)

p3, ok := panel.Points["3"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 8,
Y: 6,
},
Rotation: math.Atan2(3, 4) - math.Pi/2,
}, p3.Position(), 1e-10)
}

func TestPolarPoints(t *testing.T) {
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(polarPoints, temp))

panel, err := temp.GetPanel(template.Request{
Dims: dimensions.Dimensions{"test": dimensions.Dimension{
Name: "Test",
Value: 1.3,
}},
Panel: "body",
})
require.NoError(t, err)

p1, ok := panel.Points["1"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{}, p1.Position(), 1e-10)

p2, ok := panel.Points["2"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 1,
Y: 0,
},
Rotation: 0,
}, p2.Position(), 1e-10)

p3, ok := panel.Points["3"]
require.True(t, ok)
testutil.EqualPosition(t, position.Position{
Vector: vector.Vector{
X: 1,
Y: 1,
},
Rotation: math.Pi / 2,
}, p3.Position(), 1e-10)
}

func TestFunctions(t *testing.T) {
tests := map[string]struct {
result float64
}{
"distance": {result: 5},
"angle": {result: math.Atan2(3, 4)},
"yDistance": {result: 3},
"xDistance": {result: 4},
"lineLength": {result: 12},
"lineLength2": {result: 4},
}
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(functions, temp))

panel, err := temp.GetPanel(template.Request{Panel: "test"})
require.NoError(t, err)

for name, test := range tests {
t.Run(name, func(t *testing.T) {
p, ok := panel.Points[util.ID(name)]
require.True(t, ok)
require.InDelta(t, test.result, p.Vector().X, 1e-10)
})
}
}

func TestEvaluationFunctions(t *testing.T) {
tests := map[string]struct {
result float64
}{
"acos": {result: math.Acos(0.5)},
"asin": {result: math.Asin(math.Pi / 2)},
"atan2": {result: math.Atan2(1, 1)},
"abs1": {result: math.Abs(-1.4)},
"abs2": {result: math.Abs(3.5)},
}
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(evaluationFunctions, temp))

panel, err := temp.GetPanel(template.Request{Panel: "test"})
require.NoError(t, err)

for name, test := range tests {
t.Run(name, func(t *testing.T) {
p, ok := panel.Points[util.ID(name)]
require.True(t, ok)
require.InDelta(t, test.result, p.Vector().X, 1e-10)
})
}
}

func TestReferences(t *testing.T) {
temp := &template.Template{}
require.NoError(t, yaml.Unmarshal(references, temp))

panel, err := temp.GetPanel(template.Request{Panel: "body"})
require.NoError(t, err)

_ = panel
}

pkg/pattern/template/position.go → pkg/template/position.go Dosyayı Görüntüle


+ 109
- 0
pkg/template/template.go Dosyayı Görüntüle

@@ -0,0 +1,109 @@
package template

import (
"git.wtrh.nl/patterns/gopatterns/pkg/dimensions"
"git.wtrh.nl/patterns/gopatterns/pkg/path"
"git.wtrh.nl/patterns/gopatterns/pkg/pattern/panel"
"git.wtrh.nl/patterns/gopatterns/pkg/point"
"git.wtrh.nl/patterns/gopatterns/pkg/util"
"unicode"
)

type Template struct {
Name string `yaml:"name"`
Points Points `yaml:"points"`
Lines Lines `yaml:"lines"`
Panels Panels `yaml:"panels"`
Version string `yaml:"version"`
}

type Request struct {
Owner string
Dims dimensions.Dimensions
Panel string
}

type request struct {
dimensions dimensions.Dimensions
panel string
owner string
lines map[util.ID]path.Path
points map[util.ID]point.Point
}

func (t Template) GetPanel(req Request) (panel.Panel, error) {
p, ok := t.Panels[req.Panel]
if !ok {
return panel.Panel{}, ErrPanelNotFound
}

r := request{
dimensions: req.Dims,
panel: req.Panel,
lines: make(map[util.ID]path.Path),
points: make(map[util.ID]point.Point),
owner: req.Owner,
}

result := panel.Panel{
Name: req.Panel,
Lines: make(map[util.ID]path.Path),
Points: make(map[util.ID]point.Point),
Dimensions: req.Dims,
}

for id := range p.Lines {
line, err := t.getOrCreateLine(id, r, 0)
if err != nil {
return panel.Panel{}, err
}

result.Lines[id] = line
}

for id := range p.Points {
newPoint, err := t.getOrCreatePoint(id, r, 0)
if err != nil {
return panel.Panel{}, err
}

result.Points[id] = newPoint
}

for id := range t.Points {
newPoint, err := t.getOrCreatePoint(id, r, 0)
if err != nil {
return panel.Panel{}, err
}

result.Points[id] = newPoint
}

text, err := t.createInformation(r)
if err != nil {
return panel.Panel{}, err
}

result.Texts = append(result.Texts, text)

return result, nil
}

func startCase(text string) string {
output := make([]rune, len(text))

for i, val := range text {
switch {
case i == 0:
output[i] = unicode.ToUpper(val)
case val == '_':
output[i] = ' '
case output[i-1] == ' ':
output[i] = unicode.ToUpper(val)
default:
output[i] = val
}
}

return string(output)
}

pkg/pattern/template/value.go → pkg/template/value.go Dosyayı Görüntüle


+ 28
- 0
pkg/util/id.go Dosyayı Görüntüle

@@ -0,0 +1,28 @@
package util

import "strings"

// ID defines a point id.
type ID string

func (i ID) Panel() string {
split := strings.Split(string(i), ".")
if len(split) < 2 {
return ""
}

return split[0]
}

func (i ID) Name() string {
split := strings.Split(string(i), ".")
if len(split) < 2 {
return split[0]
}

return split[1]
}

func (i ID) Deref() ID {
return ID(i.Name())
}

+ 34
- 0
pkg/util/id_test.go Dosyayı Görüntüle

@@ -0,0 +1,34 @@
package util_test

import (
"testing"

"git.wtrh.nl/patterns/gopatterns/pkg/util"
"github.com/stretchr/testify/require"
)

func TestID(t *testing.T) {
tests := map[string]struct {
panel, name string
}{
"body.2": {
panel: "body",
name: "2",
},
"2": {
panel: "",
name: "2",
},
"1.test": {
panel: "1",
name: "test",
},
}
for testName, tt := range tests {
t.Run(testName, func(t *testing.T) {
id := util.ID(testName)
require.Equal(t, tt.panel, id.Panel())
require.Equal(t, tt.name, id.Name())
})
}
}

+ 125
- 0
spec/pattern.v2.yaml Dosyayı Görüntüle

@@ -0,0 +1,125 @@
---
$schema: "https://json-schema.org/draft-04/schema"
id: "https://stsci.edu/schemas/yaml-schema/draft-01"
title:
YAML Schema

type: object
properties:
version:
type: string
points:
$ref: '#/components/schemas/points'
lines:
$ref: '#/components/schemas/lines'
panels:
type: object
additionalProperties:
type: object
properties:
allowances:
type: object
properties:
hem:
type: string
seam:
type: string
points:
$ref: '#/components/schemas/points'
lines:
$ref: '#/components/schemas/lines'
information:
allOf:
- $ref: '#/components/schemas/point'
- type: object
properties:
anchor:
type: string
enum: [top, center, bottom, left, right]

components:
schemas:
point:
type: object
properties:
position:
$ref: '#/components/schemas/position'
relativeTo:
$ref: '#/components/schemas/pointID'
polar:
$ref: '#/components/schemas/polar'
description:
type: string
between:
$ref: '#/components/schemas/between'
hide:
type: bool
extend:
$ref: '#/components/schemas/between'
points:
type: object
additionalProperties:
$ref: '#/components/schemas/point'
position:
type: object
properties:
y:
$ref: '#/components/schemas/expression'
x:
$ref: '#/components/schemas/expression'
rotation:
$ref: '#/components/schemas/expression'
polar:
type: object
properties:
length:
$ref: '#/components/schemas/expression'
rotation:
$ref: '#/components/schemas/expression'
between:
type: object
properties:
from:
$ref: '#/components/schemas/pointID'
to:
$ref: '#/components/schemas/pointID'
offset:
$ref: '#/components/schemas/expression'
line:
type: object
properties:
through:
type: array
items:
$ref: '#/components/schemas/pointID'
curve:
type: object
properties:
start:
$ref: '#/components/schemas/pointID'
end:
$ref: '#/components/schemas/pointID'
style:
type: object
properties:
thickness:
type: number
hide:
type: bool
reference:
type: string
lines:
type: object
additionalProperties:
$ref: '#/components/schemas/line'
pointID:
oneOf:
- type: integer
- type: string
expression:
oneOf:
- type: number
- type: string




+ 4
- 0
spec/pattern.yaml Dosyayı Görüntüle

@@ -6,6 +6,8 @@ title:

type: object
properties:
version:
type: string
points:
$ref: '#/components/schemas/points'
panels:
@@ -102,6 +104,8 @@ components:
properties:
thickness:
type: number
reference:
type: string
pointID:
oneOf:
- type: integer


+ 70
- 29
templates/basic_trouser_block.yaml Dosyayı Görüntüle

@@ -80,7 +80,7 @@ points:
between:
from: 16
to: 18
absolute: 0.5
offset: 0.5
20:
position:
x: 20
@@ -144,14 +144,11 @@ panels:
y: -10
relativeTo: 1
points:
5r:
position:
rotation: pi/4
relativeTo: 5
5d:
position:
y: 35
relativeTo: 5r
polar:
length: (waist-640)*0.015625+27.5
rotation: 3*pi/4
relativeTo: 5
hide: true
6e:
between:
@@ -182,29 +179,48 @@ panels:
offset: 1.8

lines:
- through: [9,1]
- through: [6,8]
- through: [15,13]
- through: [0,2,1,4,3]
- through: [6,10,0l,0b,0r,11]
- through: [9,5d,6]
1:
through: [9,1]
2:
through: [6,8]
3:
through: [15,13]
4:
through: [0,2,1,4,3]
5:
through: [6,10,0l,0b,0r,11]
style:
thickness: 1
6:
through: [9,5d,6]
curve:
start: 5
end: 6e
- through: [15,14,12,13]
- through: [15,9]
style:
thickness: 1
7:
through: [15,14,12,13]
style:
thickness: 1
8:
through: [15,9]
curve:
start: 15e
- through: [13,8,11]
style:
thickness: 1
9:
through: [13,8,11]
curve:
start: 13e
style:
thickness: 1
back:
name: Back
information:
position:
x: 10
y: -10
relativeTo: 1
relativeTo: 2
allowances:
hem: none
seam: none
@@ -276,25 +292,50 @@ panels:


lines:
- through: [19,21,30l,30b,30r,31l,31b,31r,22]
- through: [24,16d,19]
1:
through: [19,21,30l,30b,30r,31l,31b,31r,22]
style:
thickness: 1
2:
through: [24,16d,19]
curve:
start: 23a
end: 19e
- through: [29,24]
style:
thickness: 1
3:
through: [29,24]
curve:
start: 29extend
- through: [27,25,22]
style:
thickness: 1
4:
through: [27,25,22]
curve:
start: 27extend
- through: [29,28]
- through: [27,26]
- through: [28,3down, 26]
style:
thickness: 1
5:
through: [29,28]
style:
thickness: 1
6:
through: [27,26]
style:
thickness: 1
7:
through: [28,3down,26]
curve: {}
- through: [23,1]
- through: [6,25]
- through: [29,27]
- through: [0,2,1,4,3]
style:
thickness: 1
8:
through: [23,1]
9:
through: [6,25]
10:
through: [29,27]
11:
through: [0,2,1,4,3]





+ 28
- 14
templates/basis_grondpatroon_heren.yaml Dosyayı Görüntüle

@@ -181,28 +181,42 @@ panels:


lines:
- through: [N,V]
- through: [R,W]
- through: [M,G,I,C,F,L,K]
- through: [S,H,J,D,F]
- through: [M,Noffset,N]
1:
through: [N,V]
2:
through: [R,W]
3:
through: [M,G,I,C,F,L,K]
4:
through: [S,H,J,D,F]
5:
through: [M,Noffset,N]
curve:
start: Nprime
- through: [S,R]
6:
through: [S,R]
curve:
start: Roffset
- through: [V,Arm2,Poffset,K]
7:
through: [V,Arm2,Poffset,K]
curve:
end: Arm3
- through: [W,Arm1,Uoffset,K]
8:
through: [W,Arm1,Uoffset,K]
curve:
end: Arm4

# ooit stippellijnen
- through: [G,H,J,I]
- through: [Tprime,U]
- through: [A,O,P]
- through: [S,Eprime,K]
- through: [R,Rprime]
- through: [A,M,Nprime,N]
9:
through: [G,H,J,I]
10:
through: [Tprime,U]
11:
through: [A,O,P]
12:
through: [S,Eprime,K]
13:
through: [R,Rprime]
14:
through: [A,M,Nprime,N]


+ 64
- 18
templates/classic_trouser_block.yaml Dosyayı Görüntüle

@@ -176,20 +176,39 @@ panels:
relativeTo: 8
hide: true
lines:
- through: [0,4,1,3,2]
- through: [6, 8]
- through: [9, 1extend]
- through: [14,12,13,15,14]
- through: [14,8,11]
1:
through: [0,4,1,3,2]
2:
through: [6, 8]
3:
through: [9, 1extend]
4:
through: [14,12,13,15]
style:
thickness: 1
5:
through: [14,8,11]
curve:
start: extend12-14
end: offset_between11-8
- through: [15,9]
style:
thickness: 1
6:
through: [15,9]
curve:
start: extend13-15
- through: [9, h5, 6]
style:
thickness: 1
7:
through: [9, h5, 6]
curve: {}
- through: [6, 10, 11]
style:
thickness: 1
8:
through: [6, 10, 11]
style:
thickness: 1
9:
through: [15,14]

back:
name: Back
@@ -258,22 +277,49 @@ panels:
hide: true

lines:
- through: [0extend, 0, 4, 1, 3, 2]
- through: [6, 17, 26]
- through: [22, 16, 16extend]
- through: [28, 30, 29, 27]
- through: [21,25a,25c,25b,24]
- through: [28,2down,27]
1:
through: [0extend, 0, 4, 1, 3, 2]
2:
through: [6, 17, 26]
3:
through: [22, 16, 16extend]
4:
through: [28, 30]
style:
thickness: 1
4a:
through: [30, 29]
4b:
through: [29, 27]
style:
thickness: 1
5:
through: [21,25a,25c,25b,24]
style:
thickness: 1
6:
through: [28,2down,27]
curve: {}
- through: [23,16offset,19,21]
style:
thickness: 1
7:
through: [23,16offset,19,21]
curve:
start: 23a
- through: [30,23]
style:
thickness: 1
8:
through: [30,23]
curve:
start: 30extend
- through: [29,26,24]
style:
thickness: 1
9:
through: [29,26,24]
curve:
start: 29extend
style:
thickness: 1





+ 6
- 0
templates/dimension_names.yaml Dosyayı Görüntüle

@@ -5,6 +5,8 @@ trouser_waist:
name: Trouser waist
body_rise:
name: Body rise
button_stand:
name: Button stand
inside_leg:
name: Inside leg
trouser_bottom_width:
@@ -33,6 +35,10 @@ sleeve_length_shirt:
name: Sleeve length for shirts
cuff_size:
name: Cuff size
cuff_depth:
name: Cuff Depth
collar_depth:
name: Collar Depth

bovenwijdte:
name: Bovenwijdte


+ 217
- 0
templates/shirt_collar.yaml Dosyayı Görüntüle

@@ -0,0 +1,217 @@
---
panels:
Shirt collar:
points:
1: {}
2:
relativeTo: 1
position:
x: neck_size /2
3:
relativeTo: 2
position:
x: button_stand + 12.5
4:
relativeTo: 1
position:
x: DistanceBetween("1","2") / 4*3
5:
relativeTo: 1
position:
y: collar_depth + button_stand + 2
6:
between:
from: 1
to: 5
offset: 0.5
7:
relativeTo: 2
position:
y: DistanceBetween("1","6")
8:
relativeTo: 3
position:
y: DistanceBetween("1","6")
9:
relativeTo: 8
position:
x: -10
10:
extend:
from: 9
to: 3
offset: -7.5
11:
extend:
from: 3
to: 9
offset: -7.5
12:
relativeTo: 1
position:
y: 5
13:
relativeTo: 5
position:
x: DistanceBetween("1","4")/5 *4
hide: true
14:
relativeTo: 2
position:
y: DistanceBetween("1","5")
14a:
relativeTo: 14
polar:
length: 15
rotation: pi/4
hide: true
14b:
relativeTo: 14
position:
x: 40
lines:
1:
through: [12,4,10]
curve: {}
style:
thickness: 1
2:
through: [10,11,7,14a]
style:
thickness: 1
3:
through: [12,6,5,13]
style:
thickness: 1
4:
through: [13,14a]
curve:
start: 14b
style:
thickness: 1
5:
through: [13,14,7,2]
6:
through: [12,1,4,2,3,8,7,6]
7:
through: [3,10,11,9]
Shirt collar with stand:
points:
1: {}
2:
relativeTo: 1
position:
x: neck_size /2
3:
relativeTo: 2
position:
x: button_stand + 12.5
4:
relativeTo: 1
position:
x: DistanceBetween("1","2") / 4*3
5:
relativeTo: 1
position:
y: collar_depth + button_stand + 2
6:
between:
from: 1
to: 5
offset: 0.5
7:
relativeTo: 2
position:
y: DistanceBetween("1","6")
8:
relativeTo: 3
position:
y: DistanceBetween("1","6")
9:
relativeTo: 8
position:
x: -10
10:
extend:
from: 9
to: 3
offset: -7.5
11:
extend:
from: 3
to: 9
offset: -7.5
12:
relativeTo: 1
position:
y: 5
13:
between:
from: 6
to: 7
offset: 0.5
14:
relativeTo: 7
position:
y: -10
15:
extend:
from: 10
to: 11
offset: -10
16:
relativeTo: 2
position:
y: DistanceBetween("1","5")
16a:
relativeTo: 16
polar:
length: 15
rotation: pi/4
hide: true
16b:
relativeTo: 16
position:
x: 40
17:
relativeTo: 5
position:
x: DistanceBetween("1","4")/5 *4
hide: true
lines:
1:
through: [12,4,10]
curve: {}
style:
thickness: 1
2:
through: [14,16a]
style:
thickness: 1
3:
through: [13,14,15]
style:
thickness: 1
curve:
start: 8
4:
through: [6,13]
style:
thickness: 1
5:
through: [12,6,5,17]
style:
thickness: 1
6:
through: [12,1,4,2,3,8,7,6]
7:
through: [10,15]
style:
thickness: 1
8:
through: [17,16a]
curve:
start: 16b
style:
thickness: 1


+ 120
- 0
templates/standing_collars.yaml Dosyayı Görüntüle

@@ -0,0 +1,120 @@
---
panels:
1a Standard straight collar:
points:
1: {}
2:
relativeTo: 1
position:
x: neck_size/2
3:
relativeTo: 2
position:
x: button_stand + 10
4:
relativeTo: 1
position:
y: collar_depth + 20
5:
relativeTo: 3
position:
y: collar_depth + 20
6:
relativeTo: 5
position:
x: -15
7:
relativeTo: 6
position:
x: -DistanceBetween("2","3")
8:
relativeTo: 1
position:
x: DistanceBetween("1","2") * 3 / 4
9:
relativeTo: 3
position:
y: 15
lines:
1:
through: [9,6,7,4,1,8]
style:
thickness: 1
2:
through: [8,3,5,7,2]
3:
through: [8,9]
curve:
start: 3
style:
thickness: 1
1b Standard straight collar with stand:
points:
1: {}
2:
relativeTo: 1
position:
x: neck_size/2
3:
relativeTo: 2
position:
x: button_stand + 10
4:
relativeTo: 1
position:
y: button_stand + 20
5:
relativeTo: 3
position:
y: collar_depth + 20
6:
relativeTo: 5
position:
x: -15
7:
relativeTo: 6
position:
x: -DistanceBetween("2","3")
8:
relativeTo: 1
position:
x: DistanceBetween("1","2") * 3 / 4
9:
relativeTo: 3
position:
y: 15
10:
relativeTo: 4
position:
y: 20
11:
relativeTo: 6
position:
y: 20
12:
relativeTo: 10
position:
y: collar_depth + 20
13:
relativeTo: 11
position:
y: collar_depth + 20
14:
relativeTo: 7
position:
y: 20
lines:
1:
through: [9,6,7,4,1,8]
style:
thickness: 1
2:
through: [8,3,5,7,2]
3:
through: [8,9]
curve:
start: 3
style:
thickness: 1



+ 263
- 37
templates/tailored_shirt_block.yaml Dosyayı Görüntüle

@@ -10,76 +10,107 @@ panels:
y: -10
x: 10
lines:
- through: [14,8]
style:
thickness: 1
- through: [8, 7, 0, 1, 2, 3, 37, 19, 35, 6, 22]
- through: [1,11,17,4]
- through: [7,12,10]
- through: [9,15,10,23,11]
- through: [15,16]
curve:
start: 10
- through: [0,7a,8]
curve:
start: 7
style:
thickness: 1
- through: [17,18,19]
- through: [24,21]
1:
through: [14,8]
style:
thickness: 1
2:
through: [8, 7, 0, 1, 2, 3, 37, 19, 35, 6, 22]
3:
through: [1,11,17,4]
4:
through: [7,12,10]
5:
through: [9,15,10,23,11]
6:
through: [15,16]
curve:
start: 10
7:
through: [0,7a,8]
curve:
start: 7
style:
thickness: 1
8:
through: [17,18,19]
9:
through: [24,21]
style:
thickness: 1
- through: [21,22]
10:
through: [21,22]
curve:
start: 20b
end: 20a
style:
thickness: 1
- through: [14,10,11a,17,25a,26,27a,24]
11:
through: [14,10,11a,17,25a,26,27a,24]
curve:
start: 14
style:
thickness: 1
- through: [28,28a]
- through: [22,29,29a]
- through: [37,34]
12:
through: [28,28a]
13:
through: [22,29,29a]
14:
through: [37,34]
curve:
start: 19
end: 34a
style:
thickness: 1
- through: [33,36]
15:
through: [33,36]
curve:
start: 33a
end: 36a
style:
thickness: 1
- through: [17,31,34]
16:
through: [17,31,34]
curve: {}
style:
thickness: 0.6
- through: [17,30,33]
17:
through: [17,30,33]
curve: {}
style:
thickness: 0.6
- through: [36,29b]
- through: [34,33]
18:
through: [36,29b]
19:
through: [34,33]
style:
thickness: 1
- through: [39,43,41]
20:
through: [39,43,41]
curve: {}
style:
thickness: 0.6
- through: [39,42,41]
21:
through: [39,42,41]
curve: {}
style:
thickness: 0.6
- through: [0,3,37]
22:
through: [0,3,37]
style:
thickness: 1
- through: [22,29,29b,36]
23:
through: [22,29,29b,36]
style:
thickness: 1
00:
relativeTo: 17
01:
relativeTo: 25
02:
relativeTo: 01
position:
y: (LineLength("11")-70)/3

points:
0:
@@ -128,10 +159,13 @@ panels:
relativeTo: 0
position:
x: half_back + 40
14:
13:
relativeTo: 12
position:
x: 15
14:
relativeTo: 13
position:
y: 20
15:
relativeTo: 10
@@ -172,8 +206,8 @@ panels:
24:
relativeTo: 21
polar:
length: DistanceBetween("8","14")
rotation: acos(YDistanceBetween("21","23")/DistanceBetween("8","14"))
length: -DistanceBetween("8","14")
rotation: asin(abs(YDistanceBetween("21","23"))/abs(DistanceBetween("8","14")))
25:
relativeTo: 1
position:
@@ -194,7 +228,7 @@ panels:
28a:
relativeTo: 28
position:
y: -YDistanceBetween("28","3")
y: YDistanceBetween("28","3")
hide: true
29:
relativeTo: 28
@@ -203,7 +237,7 @@ panels:
29a:
relativeTo: 29
position:
y: -YDistanceBetween("29","3")
y: YDistanceBetween("29","3")
hide: true
29b:
relativeTo: 29a
@@ -308,4 +342,196 @@ panels:
relativeTo: 25
position:
y: 7
x: -30
x: -30
00:
relativeTo: 17
01:
relativeTo: 25
02:
relativeTo: 01
position:
y: (LineLength("11")-70)/3
03:
between:
from: 01
to: 02
offset: 0.5
06:
relativeTo: 02
position:
x: ((DistanceBetween("24","26")^2 - (DistanceBetween("02","26")^2))^0.5)


sleeve:
information:
relativeTo: A
position:
x: 10
y: -10
allowances:
hem: none
seam: 1cm
points:
0: {}
1:
relativeTo: 0
position:
y: -(502.6 / 4 + 15)
2:
relativeTo: 0
position:
y: -(sleeve_length_shirt+60-cuff_depth - DistanceBetween("body.0","body.13"))
3:
between:
from: 2
to: 1
offset: 0.5
4:
relativeTo: 1
position:
x: -DiagonalTo("0","1",502.6/2 -5)
5:
relativeTo: 4
position:
y: -DistanceBetween("1","2")
6:
relativeTo: 1
position:
x: DiagonalTo("0","1",502.6/2 -5)
7:
relativeTo: 6
position:
y: -DistanceBetween("1","2")
8a:
between:
from: 4
to: 9a
offset: 0.5
8:
relativeTo: 8a
position:
x: 5
9a:
between:
from: 4
to: 0
offset: 0.5
9:
relativeTo: 9a
position:
x: -12.5
10a:
between:
from: 9a
to: 0
offset: 0.5
10:
relativeTo: 10a
position:
x: -22.5
11a:
between:
from: 0
to: 6
offset: 0.25
11:
relativeTo: 11a
position:
x: -15
12:
between:
from: 0
to: 6
offset: 0.5
13a:
between:
from: 0
to: 6
offset: 0.75
13:
relativeTo: 13a
position:
x: 12.5
14:
relativeTo: 5
position:
x: DistanceBetween("5","2")/3+7.5
15:
relativeTo: 7
position:
x: -DistanceBetween("5","14")
3a:
between:
from: 14
to: 4
offset: 0.5
3aa:
relativeTo: 3a
position:
x: -7
3b:
between:
from: 15
to: 6
offset: 0.5
3bb:
relativeTo: 3b
position:
x: 7
A:
relativeTo: 0
position:
y: -250
B:
relativeTo: 4
position:
y: -DistanceBetween("1","A")
C:
relativeTo: 6
position:
y: -DistanceBetween("1","A")
16a:
between:
from: 14
to: 2
offset: 0.5
hide: true
16:
relativeTo: 16a
position:
rotation: pi/2
17:
relativeTo: 16
position:
y: 150

lines:
scye:
through: [4,8,9,10,0,11,12,13,6]
curve: {}
style:
thickness: 1
1:
through: [4,3aa,14]
curve: {}
style:
thickness: 1
2:
through: [14,2,15]
style:
thickness: 1
3:
through: [6,3bb,15]
curve: {}
style:
thickness: 1
4:
through: [0,1,3,2]
0:
through: [14,4,0,6,15]
abc:
through: [B,A,C]
5:
through: [16,17]



Yükleniyor…
İptal
Kaydet