diff --git a/.golangci.yml b/.golangci.yml index e29c38b..3a361f3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -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 diff --git a/cmd/gopatterns/gopatterns.go b/cmd/gopatterns/gopatterns.go index 38d3379..c841936 100644 --- a/cmd/gopatterns/gopatterns.go +++ b/cmd/gopatterns/gopatterns.go @@ -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 ] [-out ] 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 ] [-out ] 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 ] [-out ] 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 diff --git a/go.mod b/go.mod index 947b7e6..3fe3266 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 319e41a..8a99c30 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..aadd536 --- /dev/null +++ b/pkg/config/config.go @@ -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 +} diff --git a/pkg/pattern/dimensions.go b/pkg/dimensions/dimensions.go similarity index 83% rename from pkg/pattern/dimensions.go rename to pkg/dimensions/dimensions.go index c967574..895906f 100644 --- a/pkg/pattern/dimensions.go +++ b/pkg/dimensions/dimensions.go @@ -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 -} diff --git a/pkg/path/path.go b/pkg/path/path.go new file mode 100644 index 0000000..2a96310 --- /dev/null +++ b/pkg/path/path.go @@ -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 +} diff --git a/pkg/pattern/path/splines.go b/pkg/path/splines.go similarity index 63% rename from pkg/pattern/path/splines.go rename to pkg/path/splines.go index 8f1f1fa..6c5445b 100644 --- a/pkg/pattern/path/splines.go +++ b/pkg/path/splines.go @@ -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 } diff --git a/pkg/pattern/path/style.go b/pkg/path/style.go similarity index 100% rename from pkg/pattern/path/style.go rename to pkg/path/style.go diff --git a/pkg/pattern/panel/panel.go b/pkg/pattern/panel/panel.go new file mode 100644 index 0000000..b1489d3 --- /dev/null +++ b/pkg/pattern/panel/panel.go @@ -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 +} diff --git a/pkg/pattern/path/path.go b/pkg/pattern/path/path.go deleted file mode 100644 index 647723e..0000000 --- a/pkg/pattern/path/path.go +++ /dev/null @@ -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 -} diff --git a/pkg/pattern/pattern.go b/pkg/pattern/pattern.go index e066893..d11f211 100644 --- a/pkg/pattern/pattern.go +++ b/pkg/pattern/pattern.go @@ -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 +} diff --git a/pkg/pattern/template/fixtures/classic_trouser_block.yaml b/pkg/pattern/template/fixtures/classic_trouser_block.yaml deleted file mode 100644 index 3d645f4..0000000 --- a/pkg/pattern/template/fixtures/classic_trouser_block.yaml +++ /dev/null @@ -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] - diff --git a/pkg/pattern/template/fixtures/trouser.yaml b/pkg/pattern/template/fixtures/trouser.yaml deleted file mode 100644 index 2b35c53..0000000 --- a/pkg/pattern/template/fixtures/trouser.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/pkg/pattern/template/information.go b/pkg/pattern/template/information.go deleted file mode 100644 index 6457888..0000000 --- a/pkg/pattern/template/information.go +++ /dev/null @@ -1,6 +0,0 @@ -package template - -type Information struct { - Point `yaml:",inline"` - Anchor string `yaml:"anchor"` -} diff --git a/pkg/pattern/template/line.go b/pkg/pattern/template/line.go deleted file mode 100644 index 1c7c21d..0000000 --- a/pkg/pattern/template/line.go +++ /dev/null @@ -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 -} diff --git a/pkg/pattern/template/point.go b/pkg/pattern/template/point.go deleted file mode 100644 index 3621f7b..0000000 --- a/pkg/pattern/template/point.go +++ /dev/null @@ -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 -} diff --git a/pkg/pattern/template/renderer.go b/pkg/pattern/template/renderer.go deleted file mode 100644 index 27328db..0000000 --- a/pkg/pattern/template/renderer.go +++ /dev/null @@ -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 -} diff --git a/pkg/pattern/template/template.go b/pkg/pattern/template/template.go deleted file mode 100644 index 2e6e671..0000000 --- a/pkg/pattern/template/template.go +++ /dev/null @@ -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) -} diff --git a/pkg/pattern/text/text.go b/pkg/pattern/text/text.go index 3d92c3e..66d0b38 100644 --- a/pkg/pattern/text/text.go +++ b/pkg/pattern/text/text.go @@ -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) } diff --git a/pkg/pattern/point/absolute_point.go b/pkg/point/absolute_point.go similarity index 89% rename from pkg/pattern/point/absolute_point.go rename to pkg/point/absolute_point.go index 5b9a587..998621b 100644 --- a/pkg/pattern/point/absolute_point.go +++ b/pkg/point/absolute_point.go @@ -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. diff --git a/pkg/point/absolute_point_test.go b/pkg/point/absolute_point_test.go new file mode 100644 index 0000000..052d631 --- /dev/null +++ b/pkg/point/absolute_point_test.go @@ -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()) +} diff --git a/pkg/pattern/point/between_point.go b/pkg/point/between_point.go similarity index 88% rename from pkg/pattern/point/between_point.go rename to pkg/point/between_point.go index a4551c6..f585dab 100644 --- a/pkg/pattern/point/between_point.go +++ b/pkg/point/between_point.go @@ -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 } diff --git a/pkg/point/between_point_test.go b/pkg/point/between_point_test.go new file mode 100644 index 0000000..c12292d --- /dev/null +++ b/pkg/point/between_point_test.go @@ -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) +} diff --git a/pkg/pattern/point/extend_point.go b/pkg/point/extend_point.go similarity index 79% rename from pkg/pattern/point/extend_point.go rename to pkg/point/extend_point.go index e6b903b..caf476d 100644 --- a/pkg/pattern/point/extend_point.go +++ b/pkg/point/extend_point.go @@ -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 } diff --git a/pkg/pattern/point/point.go b/pkg/point/point.go similarity index 94% rename from pkg/pattern/point/point.go rename to pkg/point/point.go index d77eb86..0e03e1f 100644 --- a/pkg/pattern/point/point.go +++ b/pkg/point/point.go @@ -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{ diff --git a/pkg/pattern/point/relative_point.go b/pkg/point/relative_point.go similarity index 86% rename from pkg/pattern/point/relative_point.go rename to pkg/point/relative_point.go index d570ba7..9762fa5 100644 --- a/pkg/pattern/point/relative_point.go +++ b/pkg/point/relative_point.go @@ -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) } diff --git a/pkg/position/position.go b/pkg/position/position.go index 78a5fb4..3050ed8 100644 --- a/pkg/position/position.go +++ b/pkg/position/position.go @@ -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. diff --git a/pkg/position/position_test.go b/pkg/position/position_test.go index 4e7cf74..b95900a 100644 --- a/pkg/position/position_test.go +++ b/pkg/position/position_test.go @@ -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) + }) + } +} diff --git a/pkg/position/testutil/testutil.go b/pkg/position/testutil/testutil.go new file mode 100644 index 0000000..71e161b --- /dev/null +++ b/pkg/position/testutil/testutil.go @@ -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) +} diff --git a/pkg/renderer/renderer.go b/pkg/renderer/renderer.go new file mode 100644 index 0000000..f45a484 --- /dev/null +++ b/pkg/renderer/renderer.go @@ -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 +//} diff --git a/pkg/pattern/template/storage.go b/pkg/storage/storage.go similarity index 54% rename from pkg/pattern/template/storage.go rename to pkg/storage/storage.go index df8e7d7..23f0837 100644 --- a/pkg/pattern/template/storage.go +++ b/pkg/storage/storage.go @@ -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 } diff --git a/pkg/template/fixtures/absolute_points.yaml b/pkg/template/fixtures/absolute_points.yaml new file mode 100644 index 0000000..709114e --- /dev/null +++ b/pkg/template/fixtures/absolute_points.yaml @@ -0,0 +1,13 @@ +--- +points: + 1: + position: + x: test + y: 14.3 +panels: + body: + points: + 2: + position: + x: test*2 + y: -3 diff --git a/pkg/template/fixtures/between_points.yaml b/pkg/template/fixtures/between_points.yaml new file mode 100644 index 0000000..3c144a0 --- /dev/null +++ b/pkg/template/fixtures/between_points.yaml @@ -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 diff --git a/pkg/template/fixtures/evaluation_functions.yaml b/pkg/template/fixtures/evaluation_functions.yaml new file mode 100644 index 0000000..29e795c --- /dev/null +++ b/pkg/template/fixtures/evaluation_functions.yaml @@ -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: {} \ No newline at end of file diff --git a/pkg/template/fixtures/extend_points.yaml b/pkg/template/fixtures/extend_points.yaml new file mode 100644 index 0000000..ce1cbfe --- /dev/null +++ b/pkg/template/fixtures/extend_points.yaml @@ -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 + diff --git a/pkg/template/fixtures/functions.yaml b/pkg/template/fixtures/functions.yaml new file mode 100644 index 0000000..efd48b7 --- /dev/null +++ b/pkg/template/fixtures/functions.yaml @@ -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] + diff --git a/pkg/template/fixtures/polar_points.yaml b/pkg/template/fixtures/polar_points.yaml new file mode 100644 index 0000000..b579297 --- /dev/null +++ b/pkg/template/fixtures/polar_points.yaml @@ -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 + diff --git a/pkg/template/fixtures/references.yaml b/pkg/template/fixtures/references.yaml new file mode 100644 index 0000000..04d82f2 --- /dev/null +++ b/pkg/template/fixtures/references.yaml @@ -0,0 +1,12 @@ +--- +points: + 1: {} + 3: + relativeTo: body.2 +panels: + body: + points: + 2: + relativeTo: 1 + position: + x: 3 diff --git a/pkg/template/fixtures/relative_points.yaml b/pkg/template/fixtures/relative_points.yaml new file mode 100644 index 0000000..52eae9a --- /dev/null +++ b/pkg/template/fixtures/relative_points.yaml @@ -0,0 +1,15 @@ +--- +points: + 1: + position: + x: test + y: 14.3 +panels: + body: + points: + 2: + relativeTo: + 1 + position: + x: test*2 + y: -3 diff --git a/pkg/template/information.go b/pkg/template/information.go new file mode 100644 index 0000000..d365541 --- /dev/null +++ b/pkg/template/information.go @@ -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 +} diff --git a/pkg/template/line.go b/pkg/template/line.go new file mode 100644 index 0000000..ff7993d --- /dev/null +++ b/pkg/template/line.go @@ -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 +} diff --git a/pkg/pattern/template/panel.go b/pkg/template/panel.go similarity index 57% rename from pkg/pattern/template/panel.go rename to pkg/template/panel.go index 7ea0352..b6701b0 100644 --- a/pkg/pattern/template/panel.go +++ b/pkg/template/panel.go @@ -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 +} diff --git a/pkg/template/point.go b/pkg/template/point.go new file mode 100644 index 0000000..8100487 --- /dev/null +++ b/pkg/template/point.go @@ -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 +} diff --git a/pkg/template/point_test.go b/pkg/template/point_test.go new file mode 100644 index 0000000..a20a589 --- /dev/null +++ b/pkg/template/point_test.go @@ -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 +} diff --git a/pkg/pattern/template/position.go b/pkg/template/position.go similarity index 100% rename from pkg/pattern/template/position.go rename to pkg/template/position.go diff --git a/pkg/template/template.go b/pkg/template/template.go new file mode 100644 index 0000000..27fb2d4 --- /dev/null +++ b/pkg/template/template.go @@ -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) +} diff --git a/pkg/pattern/template/value.go b/pkg/template/value.go similarity index 100% rename from pkg/pattern/template/value.go rename to pkg/template/value.go diff --git a/pkg/util/id.go b/pkg/util/id.go new file mode 100644 index 0000000..3a7fe4f --- /dev/null +++ b/pkg/util/id.go @@ -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()) +} diff --git a/pkg/util/id_test.go b/pkg/util/id_test.go new file mode 100644 index 0000000..0f9dd1f --- /dev/null +++ b/pkg/util/id_test.go @@ -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()) + }) + } +} diff --git a/spec/pattern.v2.yaml b/spec/pattern.v2.yaml new file mode 100644 index 0000000..76fc030 --- /dev/null +++ b/spec/pattern.v2.yaml @@ -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 + + + diff --git a/spec/pattern.yaml b/spec/pattern.yaml index 0737295..8d9d296 100644 --- a/spec/pattern.yaml +++ b/spec/pattern.yaml @@ -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 diff --git a/templates/basic_trouser_block.yaml b/templates/basic_trouser_block.yaml index 018781e..49c7c4a 100644 --- a/templates/basic_trouser_block.yaml +++ b/templates/basic_trouser_block.yaml @@ -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] diff --git a/templates/basis_grondpatroon_heren.yaml b/templates/basis_grondpatroon_heren.yaml index 7078d76..9ea508d 100644 --- a/templates/basis_grondpatroon_heren.yaml +++ b/templates/basis_grondpatroon_heren.yaml @@ -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] diff --git a/templates/classic_trouser_block.yaml b/templates/classic_trouser_block.yaml index 4d54aa7..20e0163 100644 --- a/templates/classic_trouser_block.yaml +++ b/templates/classic_trouser_block.yaml @@ -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 diff --git a/templates/dimension_names.yaml b/templates/dimension_names.yaml index e7dee77..c290cd8 100644 --- a/templates/dimension_names.yaml +++ b/templates/dimension_names.yaml @@ -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 diff --git a/templates/shirt_collar.yaml b/templates/shirt_collar.yaml new file mode 100644 index 0000000..372ec80 --- /dev/null +++ b/templates/shirt_collar.yaml @@ -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 + diff --git a/templates/standing_collars.yaml b/templates/standing_collars.yaml new file mode 100644 index 0000000..7e6b811 --- /dev/null +++ b/templates/standing_collars.yaml @@ -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 + + diff --git a/templates/tailored_shirt_block.yaml b/templates/tailored_shirt_block.yaml index 5461ef6..2e45a8e 100644 --- a/templates/tailored_shirt_block.yaml +++ b/templates/tailored_shirt_block.yaml @@ -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 \ No newline at end of file + 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] + +