| @@ -32,7 +32,7 @@ linters-settings: | |||||
| allow: | allow: | ||||
| - $gostd | - $gostd | ||||
| - git.wtrh.nl/patterns/gopatterns/pkg/pattern | - 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/pattern/template | ||||
| - git.wtrh.nl/patterns/gopatterns/pkg/position | - git.wtrh.nl/patterns/gopatterns/pkg/position | ||||
| - git.wtrh.nl/patterns/gopatterns/pkg/vector | - git.wtrh.nl/patterns/gopatterns/pkg/vector | ||||
| @@ -8,7 +8,9 @@ import ( | |||||
| "os" | "os" | ||||
| gotemplate "text/template" | 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" | "gitlab.com/slxh/go/env" | ||||
| ) | ) | ||||
| @@ -40,6 +42,10 @@ gopatterns [-templates <template-dir>] [-out <output-dir>] input-file | |||||
| `) | `) | ||||
| } | } | ||||
| if debug { | |||||
| slog.SetLogLoggerLevel(slog.LevelDebug) | |||||
| } | |||||
| args := flag.Args() | args := flag.Args() | ||||
| if len(args) == 0 { | if len(args) == 0 { | ||||
| slog.Error("at least one pattern is required") | slog.Error("at least one pattern is required") | ||||
| @@ -47,7 +53,7 @@ gopatterns [-templates <template-dir>] [-out <output-dir>] input-file | |||||
| os.MkdirAll(outputDir, 0o770) | os.MkdirAll(outputDir, 0o770) | ||||
| storage, err := template.NewStorage(templateDir) | |||||
| storage, err := storage2.NewStorage(templateDir) | |||||
| if err != nil { | if err != nil { | ||||
| slog.Error("failed to open template directory", "err", err, "dir", templateDir) | slog.Error("failed to open template directory", "err", err, "dir", templateDir) | ||||
| return | return | ||||
| @@ -56,13 +62,13 @@ gopatterns [-templates <template-dir>] [-out <output-dir>] input-file | |||||
| files := make([]string, 0) | files := make([]string, 0) | ||||
| for _, arg := range args { | for _, arg := range args { | ||||
| pattern, err := template.LoadPattern(arg) | |||||
| pattern, err := config.LoadConfig(arg) | |||||
| if err != nil { | if err != nil { | ||||
| slog.Error("failed to load pattern", "err", err) | slog.Error("failed to load pattern", "err", err) | ||||
| return | return | ||||
| } | } | ||||
| filenames, err := storage.RenderPatterns(pattern, outputDir, debug) | |||||
| filenames, err := renderer.RenderPatterns(storage, pattern, outputDir, debug) | |||||
| if err != nil { | if err != nil { | ||||
| slog.Error("error occurred while creating pattern", "pattern", arg, "err", err) | slog.Error("error occurred while creating pattern", "pattern", arg, "err", err) | ||||
| return | return | ||||
| @@ -1,20 +1,20 @@ | |||||
| module git.wtrh.nl/patterns/gopatterns | module git.wtrh.nl/patterns/gopatterns | ||||
| go 1.24.5 | |||||
| go 1.25.1 | |||||
| require ( | require ( | ||||
| github.com/stoewer/go-strcase v1.3.1 | 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/Achilleshiel/gosplines v0.0.0-20240602125710-c93b87aea1ee | ||||
| gitlab.com/slxh/go/env v1.2.0 | 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/Knetic/govaluate.v3 v3.0.0 | ||||
| gopkg.in/yaml.v3 v3.0.1 | gopkg.in/yaml.v3 v3.0.1 | ||||
| ) | ) | ||||
| require ( | 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 | codeberg.org/go-pdf/fpdf v0.11.1 // indirect | ||||
| github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect | github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect | ||||
| github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // 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/pmezard/go-difflib v1.0.0 // indirect | ||||
| github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect | ||||
| github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // 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 | 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/gonum v0.16.0 // indirect | ||||
| gonum.org/v1/plot v0.16.0 // indirect | gonum.org/v1/plot v0.16.0 // indirect | ||||
| modernc.org/knuth v0.5.5 // indirect | modernc.org/knuth v0.5.5 // indirect | ||||
| @@ -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-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 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= | ||||
| codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= | 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 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs= | ||||
| codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= | 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 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= | ||||
| git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= | 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 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= | ||||
| git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= | 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 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= | ||||
| github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= | 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= | 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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | 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.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 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-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 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-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 h1:tvjHzRer46kwOfpdCBCWsDblCw3QtnLJRd61pTVkyZ8= | ||||
| github.com/tdewolff/minify/v2 v2.23.8/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno= | 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 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po= | ||||
| github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= | 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 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= | ||||
| github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= | github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= | ||||
| github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= | 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.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= | ||||
| golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= | 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.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.12.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.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= | ||||
| golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= | 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.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-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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.1.0/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.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= | ||||
| golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= | 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.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-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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| @@ -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 | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| package pattern | |||||
| package dimensions | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| @@ -15,7 +15,7 @@ type DimensionID string | |||||
| // Dimensions is a map with dimensions. | // Dimensions is a map with dimensions. | ||||
| type Dimensions map[DimensionID]Dimension | 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 { | func (d Dimensions) Parameters() govaluate.MapParameters { | ||||
| parameters := govaluate.MapParameters{} | parameters := govaluate.MapParameters{} | ||||
| parameters["pi"] = math.Pi | parameters["pi"] = math.Pi | ||||
| @@ -60,8 +60,3 @@ type Dimension struct { | |||||
| Name string | Name string | ||||
| Value float64 | Value float64 | ||||
| } | } | ||||
| // AddDimension adds a dimension to a pattern. | |||||
| func (p *Pattern) AddDimension(id DimensionID, dimension Dimension) { | |||||
| p.dimensions[id] = dimension | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -2,7 +2,10 @@ package path | |||||
| import ( | import ( | ||||
| "fmt" | "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" | "github.com/tdewolff/canvas" | ||||
| splines "gitlab.com/Achilleshiel/gosplines" | splines "gitlab.com/Achilleshiel/gosplines" | ||||
| ) | ) | ||||
| @@ -11,7 +14,7 @@ const resolution = 40 | |||||
| // Spline defines a smooth curved path through points. | // Spline defines a smooth curved path through points. | ||||
| type Spline struct { | type Spline struct { | ||||
| *Path | |||||
| *Polygon | |||||
| start, end point.Point | start, end point.Point | ||||
| } | } | ||||
| @@ -19,6 +22,7 @@ type SplineOpts struct { | |||||
| Start, End point.Point | Start, End point.Point | ||||
| Points []point.Point | Points []point.Point | ||||
| Style Style | Style Style | ||||
| ID util.ID | |||||
| } | } | ||||
| // NewSpline returns a new spline through points. Start and end points can be provided as | // NewSpline returns a new spline through points. Start and end points can be provided as | ||||
| @@ -26,7 +30,7 @@ type SplineOpts struct { | |||||
| // are no constraints on the direction. | // are no constraints on the direction. | ||||
| func NewSpline(opts SplineOpts) Spline { | func NewSpline(opts SplineOpts) Spline { | ||||
| s := Spline{ | s := Spline{ | ||||
| Path: NewPath(opts.Points, opts.Style), | |||||
| Polygon: NewPolygon(opts.Points, opts.Style, opts.ID), | |||||
| start: opts.Start, | start: opts.Start, | ||||
| end: opts.End, | end: opts.End, | ||||
| } | } | ||||
| @@ -44,8 +48,41 @@ func NewSpline(opts SplineOpts) Spline { | |||||
| // Draw the spline to the provided [canvas.Canvas]. | // Draw the spline to the provided [canvas.Canvas]. | ||||
| func (s Spline) Draw(c *canvas.Canvas) error { | func (s Spline) Draw(c *canvas.Canvas) error { | ||||
| if len(s.points) < 2 { | |||||
| 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 | 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 s.points, nil | |||||
| } | } | ||||
| x := make([]float64, len(s.points)) | 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) | xCoefficient, err := splines.SolveSplineWithConstraint(x, diffStart.X, diffEnd.X) | ||||
| if err != nil { | 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) | yCoefficient, err := splines.SolveSplineWithConstraint(y, diffStart.Y, diffEnd.Y) | ||||
| if err != nil { | 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) | stepSize := 1.0 / float64(resolution-1) | ||||
| for i := range len(s.points) - 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]) | 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 | |||||
| } | } | ||||
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -4,8 +4,10 @@ package pattern | |||||
| import ( | import ( | ||||
| "fmt" | "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/pattern/text" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/point" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/util" | |||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| "golang.org/x/image/font/gofont/goregular" | "golang.org/x/image/font/gofont/goregular" | ||||
| "gopkg.in/Knetic/govaluate.v3" | "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. | // The Pattern contains all the points, lines and dimensions to draw a pattern to a canvas. | ||||
| type Pattern struct { | type Pattern struct { | ||||
| points map[point.ID]point.Point | |||||
| points map[util.ID]point.Point | |||||
| lines []pathDrawer | lines []pathDrawer | ||||
| dimensions Dimensions | |||||
| dimensions dimensions.Dimensions | |||||
| texts []*text.Text | texts []*text.Text | ||||
| } | } | ||||
| type pathDrawer interface { | type pathDrawer interface { | ||||
| Length() (float64, error) | |||||
| Draw(c *canvas.Canvas) 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. | // 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)) | points := make([]point.Point, 0, len(id)) | ||||
| for _, i := range id { | for _, i := range id { | ||||
| points = append(points, p.GetPoint(i)) | 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. | // 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] | return p.points[id] | ||||
| } | } | ||||
| // NewPattern returns a new Pattern. | // NewPattern returns a new Pattern. | ||||
| func NewPattern() *Pattern { | func NewPattern() *Pattern { | ||||
| return &Pattern{ | return &Pattern{ | ||||
| points: make(map[point.ID]point.Point, 32), | |||||
| points: make(map[util.ID]point.Point, 32), | |||||
| lines: make([]pathDrawer, 0, 32), | lines: make([]pathDrawer, 0, 32), | ||||
| dimensions: make(Dimensions), | |||||
| dimensions: make(dimensions.Dimensions), | |||||
| texts: make([]*text.Text, 0), | texts: make([]*text.Text, 0), | ||||
| } | } | ||||
| } | } | ||||
| @@ -94,6 +97,11 @@ func (p *Pattern) AddText(t *text.Text) { | |||||
| p.texts = append(p.texts, t) | p.texts = append(p.texts, t) | ||||
| } | } | ||||
| func (p *Pattern) SetDimensions(dimensions Dimensions) { | |||||
| func (p *Pattern) SetDimensions(dimensions dimensions.Dimensions) { | |||||
| p.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 | |||||
| } | |||||
| @@ -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] | |||||
| @@ -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 | |||||
| @@ -1,6 +0,0 @@ | |||||
| package template | |||||
| type Information struct { | |||||
| Point `yaml:",inline"` | |||||
| Anchor string `yaml:"anchor"` | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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) | |||||
| } | |||||
| @@ -1,21 +1,22 @@ | |||||
| package text | package text | ||||
| import ( | import ( | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/position" | |||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| ) | ) | ||||
| type Text struct { | 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) { | 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) | |||||
| } | } | ||||
| @@ -2,6 +2,7 @@ package point | |||||
| import ( | import ( | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/position" | "git.wtrh.nl/patterns/gopatterns/pkg/position" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/util" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/vector" | "git.wtrh.nl/patterns/gopatterns/pkg/vector" | ||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| ) | ) | ||||
| @@ -9,7 +10,7 @@ import ( | |||||
| // AbsolutePoint implements Point and defines an absolute position. | // AbsolutePoint implements Point and defines an absolute position. | ||||
| // All other points should eventually be relative to one or more absolute point(s). | // All other points should eventually be relative to one or more absolute point(s). | ||||
| type AbsolutePoint struct { | type AbsolutePoint struct { | ||||
| id ID | |||||
| id util.ID | |||||
| position position.Position | position position.Position | ||||
| name string | name string | ||||
| draw bool | draw bool | ||||
| @@ -18,11 +19,11 @@ type AbsolutePoint struct { | |||||
| // Matrix calculates and returns the [canvas.Matrix] of a point. | // Matrix calculates and returns the [canvas.Matrix] of a point. | ||||
| func (a *AbsolutePoint) Matrix() canvas.Matrix { | 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. | // ID returns the point ID. | ||||
| func (a *AbsolutePoint) ID() ID { | |||||
| func (a *AbsolutePoint) ID() util.ID { | |||||
| return a.id | return a.id | ||||
| } | } | ||||
| @@ -42,7 +43,7 @@ func (a *AbsolutePoint) Vector() vector.Vector { | |||||
| } | } | ||||
| // NewAbsolutePoint returns a new absolute point. | // 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{ | return &AbsolutePoint{ | ||||
| position: position.Position{ | position: position.Position{ | ||||
| Vector: vector.Vector{ | Vector: vector.Vector{ | ||||
| @@ -68,7 +69,7 @@ func (a *AbsolutePoint) SetDraw() { | |||||
| // UnsetDraw indicates that the point should not be drawn. | // UnsetDraw indicates that the point should not be drawn. | ||||
| func (a *AbsolutePoint) UnsetDraw() { | func (a *AbsolutePoint) UnsetDraw() { | ||||
| a.draw = true | |||||
| a.draw = false | |||||
| } | } | ||||
| // Hide returns if the point must remain hidden. | // Hide returns if the point must remain hidden. | ||||
| @@ -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()) | |||||
| } | |||||
| @@ -4,13 +4,14 @@ import ( | |||||
| "math" | "math" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/position" | "git.wtrh.nl/patterns/gopatterns/pkg/position" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/util" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/vector" | "git.wtrh.nl/patterns/gopatterns/pkg/vector" | ||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| ) | ) | ||||
| // BetweenPoint defines a point on the line between two other points. | // BetweenPoint defines a point on the line between two other points. | ||||
| type BetweenPoint struct { | type BetweenPoint struct { | ||||
| id ID | |||||
| id util.ID | |||||
| p Point | p Point | ||||
| q Point | q Point | ||||
| offset float64 | offset float64 | ||||
| @@ -23,7 +24,7 @@ type BetweenPoint struct { | |||||
| // The given offset defines where the new point is. | // 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. | // 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). | // 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{ | return &BetweenPoint{ | ||||
| id: id, | id: id, | ||||
| p: p, | p: p, | ||||
| @@ -52,12 +53,11 @@ func (b *BetweenPoint) Vector() vector.Vector { | |||||
| // Matrix calculates and returns the [canvas.Matrix] of a point. | // Matrix calculates and returns the [canvas.Matrix] of a point. | ||||
| func (b *BetweenPoint) Matrix() canvas.Matrix { | 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. | // ID returns the point ID. | ||||
| func (b *BetweenPoint) ID() ID { | |||||
| func (b *BetweenPoint) ID() util.ID { | |||||
| return b.id | return b.id | ||||
| } | } | ||||
| @@ -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) | |||||
| } | |||||
| @@ -4,13 +4,14 @@ import ( | |||||
| "math" | "math" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/position" | "git.wtrh.nl/patterns/gopatterns/pkg/position" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/util" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/vector" | "git.wtrh.nl/patterns/gopatterns/pkg/vector" | ||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| ) | ) | ||||
| // ExtendPoint defines a point on the line between two other points. | // ExtendPoint defines a point on the line between two other points. | ||||
| type ExtendPoint struct { | type ExtendPoint struct { | ||||
| id ID | |||||
| id util.ID | |||||
| from Point | from Point | ||||
| to Point | to Point | ||||
| extend float64 | extend float64 | ||||
| @@ -22,7 +23,7 @@ type ExtendPoint struct { | |||||
| // NewExtendPoint returns a new ExtendPoint relative to two other points from and to. | // NewExtendPoint returns a new ExtendPoint relative to two other points from and to. | ||||
| // The given offset defines where the new point is. | // 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. | // 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{ | return &ExtendPoint{ | ||||
| id: id, | id: id, | ||||
| from: from, | from: from, | ||||
| @@ -36,7 +37,7 @@ func NewExtendPoint(from, to Point, extend float64, id ID) *ExtendPoint { | |||||
| func (b *ExtendPoint) Position() position.Position { | func (b *ExtendPoint) Position() position.Position { | ||||
| return position.Position{ | return position.Position{ | ||||
| Vector: b.to.Vector().Add(b.extendedVector()), | 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. | // Matrix calculates and returns the [canvas.Matrix] of a point. | ||||
| func (b *ExtendPoint) Matrix() canvas.Matrix { | 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. | // ID returns the point ID. | ||||
| func (b *ExtendPoint) ID() ID { | |||||
| func (b *ExtendPoint) ID() util.ID { | |||||
| return b.id | return b.id | ||||
| } | } | ||||
| @@ -5,17 +5,15 @@ package point | |||||
| import ( | import ( | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/position" | "git.wtrh.nl/patterns/gopatterns/pkg/position" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/util" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/vector" | "git.wtrh.nl/patterns/gopatterns/pkg/vector" | ||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| ) | ) | ||||
| // ID defines a point id. | |||||
| type ID string | |||||
| // Point defines the interface for different types of points. | // Point defines the interface for different types of points. | ||||
| type Point interface { | type Point interface { | ||||
| // ID returns the point ID. | // ID returns the point ID. | ||||
| ID() ID | |||||
| ID() util.ID | |||||
| // Position calculates and returns the absolute [position.Position]. | // Position calculates and returns the absolute [position.Position]. | ||||
| Position() 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) | c.RenderPath(path, style, m) | ||||
| text := canvas.NewTextLine(face, point.Name(), canvas.Bottom) | 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 { | if debug { | ||||
| yStyle := canvas.Style{ | yStyle := canvas.Style{ | ||||
| @@ -4,6 +4,7 @@ import ( | |||||
| "math" | "math" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/position" | "git.wtrh.nl/patterns/gopatterns/pkg/position" | ||||
| "git.wtrh.nl/patterns/gopatterns/pkg/util" | |||||
| "git.wtrh.nl/patterns/gopatterns/pkg/vector" | "git.wtrh.nl/patterns/gopatterns/pkg/vector" | ||||
| "github.com/tdewolff/canvas" | "github.com/tdewolff/canvas" | ||||
| ) | ) | ||||
| @@ -14,7 +15,7 @@ type RelativePoint struct { | |||||
| name string | name string | ||||
| point Point | point Point | ||||
| relativeOffset position.Position | relativeOffset position.Position | ||||
| id ID | |||||
| id util.ID | |||||
| hide bool | hide bool | ||||
| } | } | ||||
| @@ -29,7 +30,7 @@ func (r *RelativePoint) Done() Point { //nolint:ireturn | |||||
| } | } | ||||
| // MarkWith sets the ID of the point. | // 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 | r.id = n | ||||
| if r.name == "" { | if r.name == "" { | ||||
| @@ -56,12 +57,12 @@ func (r *RelativePoint) Vector() vector.Vector { | |||||
| } | } | ||||
| // ID returns the point ID. | // ID returns the point ID. | ||||
| func (r *RelativePoint) ID() ID { | |||||
| func (r *RelativePoint) ID() util.ID { | |||||
| return r.id | return r.id | ||||
| } | } | ||||
| // NewRelativePointWithVector returns a new RelativePoint. | // 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{ | return &RelativePoint{ | ||||
| point: point, | point: point, | ||||
| relativeOffset: position.Position{Vector: p}, | relativeOffset: position.Position{Vector: p}, | ||||
| @@ -78,7 +79,7 @@ func NewRelativePoint(point Point) *RelativePoint { | |||||
| } | } | ||||
| // NewRotationPoint returns a new RelativePoint with a specific rotation. | // 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{ | p := &RelativePoint{ | ||||
| point: point, | point: point, | ||||
| relativeOffset: position.Position{ | relativeOffset: position.Position{ | ||||
| @@ -115,22 +116,22 @@ func (r *RelativePoint) WithRotationOffset(value float64) *RelativePoint { | |||||
| } | } | ||||
| // NewRelativePointBelow returns a RelativePoint distance f below another Point. | // 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) | return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: -f}, id) | ||||
| } | } | ||||
| // NewRelativePointAbove returns a RelativePoint distance f above another Point. | // 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) | return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: f}, id) | ||||
| } | } | ||||
| // NewRelativePointLeft returns a RelativePoint distance f left of another Point. | // 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) | return NewRelativePointWithVector(point, vector.Vector{X: -f, Y: 0}, id) | ||||
| } | } | ||||
| // NewRelativePointRight returns a RelativePoint distance f right of another Point. | // 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) | return NewRelativePointWithVector(point, vector.Vector{X: f, Y: 0}, id) | ||||
| } | } | ||||
| @@ -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. | // Distance returns the distance between two positions. | ||||
| @@ -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) | |||||
| }) | |||||
| } | |||||
| } | |||||
| @@ -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) | |||||
| } | |||||
| @@ -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 | |||||
| //} | |||||
| @@ -1,11 +1,13 @@ | |||||
| package template | |||||
| package storage | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "io/fs" | "io/fs" | ||||
| "os" | "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" | "gopkg.in/yaml.v3" | ||||
| ) | ) | ||||
| @@ -22,13 +24,13 @@ func NewStorage(dir string) (Storage, error) { | |||||
| return Storage{dir: os.DirFS(dir)}, nil | 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") | f, err := s.dir.Open("dimension_names.yaml") | ||||
| if err != nil { | if err != nil { | ||||
| return nil, fmt.Errorf("open \"dimension_names.yaml\": %w", err) | return nil, fmt.Errorf("open \"dimension_names.yaml\": %w", err) | ||||
| } | } | ||||
| namedDimensions := pattern.Dimensions{} | |||||
| namedDimensions := dimensions.Dimensions{} | |||||
| err = yaml.NewDecoder(f).Decode(&namedDimensions) | err = yaml.NewDecoder(f).Decode(&namedDimensions) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -49,27 +51,19 @@ func (s Storage) Dimensions(sizes Sizes) (pattern.Dimensions, error) { | |||||
| return namedDimensions, nil | 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. | // 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") | fh, err := s.dir.Open(name + ".yaml") | ||||
| if err != nil { | 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 { | 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 | |||||
| } | } | ||||
| @@ -0,0 +1,13 @@ | |||||
| --- | |||||
| points: | |||||
| 1: | |||||
| position: | |||||
| x: test | |||||
| y: 14.3 | |||||
| panels: | |||||
| body: | |||||
| points: | |||||
| 2: | |||||
| position: | |||||
| x: test*2 | |||||
| y: -3 | |||||
| @@ -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 | |||||
| @@ -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: {} | |||||
| @@ -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 | |||||
| @@ -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] | |||||
| @@ -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 | |||||
| @@ -0,0 +1,12 @@ | |||||
| --- | |||||
| points: | |||||
| 1: {} | |||||
| 3: | |||||
| relativeTo: body.2 | |||||
| panels: | |||||
| body: | |||||
| points: | |||||
| 2: | |||||
| relativeTo: 1 | |||||
| position: | |||||
| x: 3 | |||||
| @@ -0,0 +1,15 @@ | |||||
| --- | |||||
| points: | |||||
| 1: | |||||
| position: | |||||
| x: test | |||||
| y: 14.3 | |||||
| panels: | |||||
| body: | |||||
| points: | |||||
| 2: | |||||
| relativeTo: | |||||
| 1 | |||||
| position: | |||||
| x: test*2 | |||||
| y: -3 | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -1,9 +1,15 @@ | |||||
| package template | package template | ||||
| import ( | |||||
| "errors" | |||||
| ) | |||||
| var ErrPanelNotFound = errors.New("panel does not exist") | |||||
| // Panels contains a map with named panels. | // Panels contains a map with named panels. | ||||
| type Panels map[string]Panel | 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 { | type Panel struct { | ||||
| Points Points `yaml:"points"` | Points Points `yaml:"points"` | ||||
| Lines Lines `yaml:"lines"` | Lines Lines `yaml:"lines"` | ||||
| @@ -16,3 +22,12 @@ type Allowances struct { | |||||
| Hem string `yaml:"hem"` | Hem string `yaml:"hem"` | ||||
| Seam string `yaml:"seam"` | 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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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) | |||||
| } | |||||
| @@ -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()) | |||||
| } | |||||
| @@ -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()) | |||||
| }) | |||||
| } | |||||
| } | |||||
| @@ -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 | |||||
| @@ -6,6 +6,8 @@ title: | |||||
| type: object | type: object | ||||
| properties: | properties: | ||||
| version: | |||||
| type: string | |||||
| points: | points: | ||||
| $ref: '#/components/schemas/points' | $ref: '#/components/schemas/points' | ||||
| panels: | panels: | ||||
| @@ -102,6 +104,8 @@ components: | |||||
| properties: | properties: | ||||
| thickness: | thickness: | ||||
| type: number | type: number | ||||
| reference: | |||||
| type: string | |||||
| pointID: | pointID: | ||||
| oneOf: | oneOf: | ||||
| - type: integer | - type: integer | ||||
| @@ -80,7 +80,7 @@ points: | |||||
| between: | between: | ||||
| from: 16 | from: 16 | ||||
| to: 18 | to: 18 | ||||
| absolute: 0.5 | |||||
| offset: 0.5 | |||||
| 20: | 20: | ||||
| position: | position: | ||||
| x: 20 | x: 20 | ||||
| @@ -144,14 +144,11 @@ panels: | |||||
| y: -10 | y: -10 | ||||
| relativeTo: 1 | relativeTo: 1 | ||||
| points: | points: | ||||
| 5r: | |||||
| position: | |||||
| rotation: pi/4 | |||||
| relativeTo: 5 | |||||
| 5d: | 5d: | ||||
| position: | |||||
| y: 35 | |||||
| relativeTo: 5r | |||||
| polar: | |||||
| length: (waist-640)*0.015625+27.5 | |||||
| rotation: 3*pi/4 | |||||
| relativeTo: 5 | |||||
| hide: true | hide: true | ||||
| 6e: | 6e: | ||||
| between: | between: | ||||
| @@ -182,29 +179,48 @@ panels: | |||||
| offset: 1.8 | offset: 1.8 | ||||
| lines: | 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: | curve: | ||||
| start: 5 | start: 5 | ||||
| end: 6e | 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: | curve: | ||||
| start: 15e | start: 15e | ||||
| - through: [13,8,11] | |||||
| style: | |||||
| thickness: 1 | |||||
| 9: | |||||
| through: [13,8,11] | |||||
| curve: | curve: | ||||
| start: 13e | start: 13e | ||||
| style: | |||||
| thickness: 1 | |||||
| back: | back: | ||||
| name: Back | name: Back | ||||
| information: | information: | ||||
| position: | position: | ||||
| x: 10 | x: 10 | ||||
| y: -10 | y: -10 | ||||
| relativeTo: 1 | |||||
| relativeTo: 2 | |||||
| allowances: | allowances: | ||||
| hem: none | hem: none | ||||
| seam: none | seam: none | ||||
| @@ -276,25 +292,50 @@ panels: | |||||
| lines: | 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: | curve: | ||||
| start: 23a | start: 23a | ||||
| end: 19e | end: 19e | ||||
| - through: [29,24] | |||||
| style: | |||||
| thickness: 1 | |||||
| 3: | |||||
| through: [29,24] | |||||
| curve: | curve: | ||||
| start: 29extend | start: 29extend | ||||
| - through: [27,25,22] | |||||
| style: | |||||
| thickness: 1 | |||||
| 4: | |||||
| through: [27,25,22] | |||||
| curve: | curve: | ||||
| start: 27extend | 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: {} | 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] | |||||
| @@ -181,28 +181,42 @@ panels: | |||||
| lines: | 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: | curve: | ||||
| start: Nprime | start: Nprime | ||||
| - through: [S,R] | |||||
| 6: | |||||
| through: [S,R] | |||||
| curve: | curve: | ||||
| start: Roffset | start: Roffset | ||||
| - through: [V,Arm2,Poffset,K] | |||||
| 7: | |||||
| through: [V,Arm2,Poffset,K] | |||||
| curve: | curve: | ||||
| end: Arm3 | end: Arm3 | ||||
| - through: [W,Arm1,Uoffset,K] | |||||
| 8: | |||||
| through: [W,Arm1,Uoffset,K] | |||||
| curve: | curve: | ||||
| end: Arm4 | end: Arm4 | ||||
| # ooit stippellijnen | # 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] | |||||
| @@ -176,20 +176,39 @@ panels: | |||||
| relativeTo: 8 | relativeTo: 8 | ||||
| hide: true | hide: true | ||||
| lines: | 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: | curve: | ||||
| start: extend12-14 | start: extend12-14 | ||||
| end: offset_between11-8 | |||||
| - through: [15,9] | |||||
| style: | |||||
| thickness: 1 | |||||
| 6: | |||||
| through: [15,9] | |||||
| curve: | curve: | ||||
| start: extend13-15 | start: extend13-15 | ||||
| - through: [9, h5, 6] | |||||
| style: | |||||
| thickness: 1 | |||||
| 7: | |||||
| through: [9, h5, 6] | |||||
| curve: {} | curve: {} | ||||
| - through: [6, 10, 11] | |||||
| style: | |||||
| thickness: 1 | |||||
| 8: | |||||
| through: [6, 10, 11] | |||||
| style: | |||||
| thickness: 1 | |||||
| 9: | |||||
| through: [15,14] | |||||
| back: | back: | ||||
| name: Back | name: Back | ||||
| @@ -258,22 +277,49 @@ panels: | |||||
| hide: true | hide: true | ||||
| lines: | 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: {} | curve: {} | ||||
| - through: [23,16offset,19,21] | |||||
| style: | |||||
| thickness: 1 | |||||
| 7: | |||||
| through: [23,16offset,19,21] | |||||
| curve: | curve: | ||||
| start: 23a | start: 23a | ||||
| - through: [30,23] | |||||
| style: | |||||
| thickness: 1 | |||||
| 8: | |||||
| through: [30,23] | |||||
| curve: | curve: | ||||
| start: 30extend | start: 30extend | ||||
| - through: [29,26,24] | |||||
| style: | |||||
| thickness: 1 | |||||
| 9: | |||||
| through: [29,26,24] | |||||
| curve: | curve: | ||||
| start: 29extend | start: 29extend | ||||
| style: | |||||
| thickness: 1 | |||||
| @@ -5,6 +5,8 @@ trouser_waist: | |||||
| name: Trouser waist | name: Trouser waist | ||||
| body_rise: | body_rise: | ||||
| name: Body rise | name: Body rise | ||||
| button_stand: | |||||
| name: Button stand | |||||
| inside_leg: | inside_leg: | ||||
| name: Inside leg | name: Inside leg | ||||
| trouser_bottom_width: | trouser_bottom_width: | ||||
| @@ -33,6 +35,10 @@ sleeve_length_shirt: | |||||
| name: Sleeve length for shirts | name: Sleeve length for shirts | ||||
| cuff_size: | cuff_size: | ||||
| name: Cuff size | name: Cuff size | ||||
| cuff_depth: | |||||
| name: Cuff Depth | |||||
| collar_depth: | |||||
| name: Collar Depth | |||||
| bovenwijdte: | bovenwijdte: | ||||
| name: Bovenwijdte | name: Bovenwijdte | ||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -10,76 +10,107 @@ panels: | |||||
| y: -10 | y: -10 | ||||
| x: 10 | x: 10 | ||||
| lines: | lines: | ||||
| - through: [14,8] | |||||
| 1: | |||||
| through: [14,8] | |||||
| style: | style: | ||||
| thickness: 1 | 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] | |||||
| 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: | curve: | ||||
| start: 10 | start: 10 | ||||
| - through: [0,7a,8] | |||||
| 7: | |||||
| through: [0,7a,8] | |||||
| curve: | curve: | ||||
| start: 7 | start: 7 | ||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [17,18,19] | |||||
| - through: [24,21] | |||||
| 8: | |||||
| through: [17,18,19] | |||||
| 9: | |||||
| through: [24,21] | |||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [21,22] | |||||
| 10: | |||||
| through: [21,22] | |||||
| curve: | curve: | ||||
| start: 20b | start: 20b | ||||
| end: 20a | end: 20a | ||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [14,10,11a,17,25a,26,27a,24] | |||||
| 11: | |||||
| through: [14,10,11a,17,25a,26,27a,24] | |||||
| curve: | curve: | ||||
| start: 14 | start: 14 | ||||
| style: | style: | ||||
| thickness: 1 | 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: | curve: | ||||
| start: 19 | start: 19 | ||||
| end: 34a | end: 34a | ||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [33,36] | |||||
| 15: | |||||
| through: [33,36] | |||||
| curve: | curve: | ||||
| start: 33a | start: 33a | ||||
| end: 36a | end: 36a | ||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [17,31,34] | |||||
| 16: | |||||
| through: [17,31,34] | |||||
| curve: {} | curve: {} | ||||
| style: | style: | ||||
| thickness: 0.6 | thickness: 0.6 | ||||
| - through: [17,30,33] | |||||
| 17: | |||||
| through: [17,30,33] | |||||
| curve: {} | curve: {} | ||||
| style: | style: | ||||
| thickness: 0.6 | thickness: 0.6 | ||||
| - through: [36,29b] | |||||
| - through: [34,33] | |||||
| 18: | |||||
| through: [36,29b] | |||||
| 19: | |||||
| through: [34,33] | |||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [39,43,41] | |||||
| 20: | |||||
| through: [39,43,41] | |||||
| curve: {} | curve: {} | ||||
| style: | style: | ||||
| thickness: 0.6 | thickness: 0.6 | ||||
| - through: [39,42,41] | |||||
| 21: | |||||
| through: [39,42,41] | |||||
| curve: {} | curve: {} | ||||
| style: | style: | ||||
| thickness: 0.6 | thickness: 0.6 | ||||
| - through: [0,3,37] | |||||
| 22: | |||||
| through: [0,3,37] | |||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| - through: [22,29,29b,36] | |||||
| 23: | |||||
| through: [22,29,29b,36] | |||||
| style: | style: | ||||
| thickness: 1 | thickness: 1 | ||||
| 00: | |||||
| relativeTo: 17 | |||||
| 01: | |||||
| relativeTo: 25 | |||||
| 02: | |||||
| relativeTo: 01 | |||||
| position: | |||||
| y: (LineLength("11")-70)/3 | |||||
| points: | points: | ||||
| 0: | 0: | ||||
| @@ -128,10 +159,13 @@ panels: | |||||
| relativeTo: 0 | relativeTo: 0 | ||||
| position: | position: | ||||
| x: half_back + 40 | x: half_back + 40 | ||||
| 14: | |||||
| 13: | |||||
| relativeTo: 12 | relativeTo: 12 | ||||
| position: | position: | ||||
| x: 15 | x: 15 | ||||
| 14: | |||||
| relativeTo: 13 | |||||
| position: | |||||
| y: 20 | y: 20 | ||||
| 15: | 15: | ||||
| relativeTo: 10 | relativeTo: 10 | ||||
| @@ -172,8 +206,8 @@ panels: | |||||
| 24: | 24: | ||||
| relativeTo: 21 | relativeTo: 21 | ||||
| polar: | 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: | 25: | ||||
| relativeTo: 1 | relativeTo: 1 | ||||
| position: | position: | ||||
| @@ -194,7 +228,7 @@ panels: | |||||
| 28a: | 28a: | ||||
| relativeTo: 28 | relativeTo: 28 | ||||
| position: | position: | ||||
| y: -YDistanceBetween("28","3") | |||||
| y: YDistanceBetween("28","3") | |||||
| hide: true | hide: true | ||||
| 29: | 29: | ||||
| relativeTo: 28 | relativeTo: 28 | ||||
| @@ -203,7 +237,7 @@ panels: | |||||
| 29a: | 29a: | ||||
| relativeTo: 29 | relativeTo: 29 | ||||
| position: | position: | ||||
| y: -YDistanceBetween("29","3") | |||||
| y: YDistanceBetween("29","3") | |||||
| hide: true | hide: true | ||||
| 29b: | 29b: | ||||
| relativeTo: 29a | relativeTo: 29a | ||||
| @@ -309,3 +343,195 @@ panels: | |||||
| position: | position: | ||||
| y: 7 | y: 7 | ||||
| x: -30 | x: -30 | ||||
| 00: | |||||
| relativeTo: 17 | |||||
| 01: | |||||
| relativeTo: 25 | |||||
| 02: | |||||
| relativeTo: 01 | |||||
| position: | |||||
| y: (LineLength("11")-70)/3 | |||||
| 03: | |||||
| between: | |||||
| from: 01 | |||||
| to: 02 | |||||
| offset: 0.5 | |||||
| 06: | |||||
| relativeTo: 02 | |||||
| position: | |||||
| x: ((DistanceBetween("24","26")^2 - (DistanceBetween("02","26")^2))^0.5) | |||||
| sleeve: | |||||
| information: | |||||
| relativeTo: A | |||||
| position: | |||||
| x: 10 | |||||
| y: -10 | |||||
| allowances: | |||||
| hem: none | |||||
| seam: 1cm | |||||
| points: | |||||
| 0: {} | |||||
| 1: | |||||
| relativeTo: 0 | |||||
| position: | |||||
| y: -(502.6 / 4 + 15) | |||||
| 2: | |||||
| relativeTo: 0 | |||||
| position: | |||||
| y: -(sleeve_length_shirt+60-cuff_depth - DistanceBetween("body.0","body.13")) | |||||
| 3: | |||||
| between: | |||||
| from: 2 | |||||
| to: 1 | |||||
| offset: 0.5 | |||||
| 4: | |||||
| relativeTo: 1 | |||||
| position: | |||||
| x: -DiagonalTo("0","1",502.6/2 -5) | |||||
| 5: | |||||
| relativeTo: 4 | |||||
| position: | |||||
| y: -DistanceBetween("1","2") | |||||
| 6: | |||||
| relativeTo: 1 | |||||
| position: | |||||
| x: DiagonalTo("0","1",502.6/2 -5) | |||||
| 7: | |||||
| relativeTo: 6 | |||||
| position: | |||||
| y: -DistanceBetween("1","2") | |||||
| 8a: | |||||
| between: | |||||
| from: 4 | |||||
| to: 9a | |||||
| offset: 0.5 | |||||
| 8: | |||||
| relativeTo: 8a | |||||
| position: | |||||
| x: 5 | |||||
| 9a: | |||||
| between: | |||||
| from: 4 | |||||
| to: 0 | |||||
| offset: 0.5 | |||||
| 9: | |||||
| relativeTo: 9a | |||||
| position: | |||||
| x: -12.5 | |||||
| 10a: | |||||
| between: | |||||
| from: 9a | |||||
| to: 0 | |||||
| offset: 0.5 | |||||
| 10: | |||||
| relativeTo: 10a | |||||
| position: | |||||
| x: -22.5 | |||||
| 11a: | |||||
| between: | |||||
| from: 0 | |||||
| to: 6 | |||||
| offset: 0.25 | |||||
| 11: | |||||
| relativeTo: 11a | |||||
| position: | |||||
| x: -15 | |||||
| 12: | |||||
| between: | |||||
| from: 0 | |||||
| to: 6 | |||||
| offset: 0.5 | |||||
| 13a: | |||||
| between: | |||||
| from: 0 | |||||
| to: 6 | |||||
| offset: 0.75 | |||||
| 13: | |||||
| relativeTo: 13a | |||||
| position: | |||||
| x: 12.5 | |||||
| 14: | |||||
| relativeTo: 5 | |||||
| position: | |||||
| x: DistanceBetween("5","2")/3+7.5 | |||||
| 15: | |||||
| relativeTo: 7 | |||||
| position: | |||||
| x: -DistanceBetween("5","14") | |||||
| 3a: | |||||
| between: | |||||
| from: 14 | |||||
| to: 4 | |||||
| offset: 0.5 | |||||
| 3aa: | |||||
| relativeTo: 3a | |||||
| position: | |||||
| x: -7 | |||||
| 3b: | |||||
| between: | |||||
| from: 15 | |||||
| to: 6 | |||||
| offset: 0.5 | |||||
| 3bb: | |||||
| relativeTo: 3b | |||||
| position: | |||||
| x: 7 | |||||
| A: | |||||
| relativeTo: 0 | |||||
| position: | |||||
| y: -250 | |||||
| B: | |||||
| relativeTo: 4 | |||||
| position: | |||||
| y: -DistanceBetween("1","A") | |||||
| C: | |||||
| relativeTo: 6 | |||||
| position: | |||||
| y: -DistanceBetween("1","A") | |||||
| 16a: | |||||
| between: | |||||
| from: 14 | |||||
| to: 2 | |||||
| offset: 0.5 | |||||
| hide: true | |||||
| 16: | |||||
| relativeTo: 16a | |||||
| position: | |||||
| rotation: pi/2 | |||||
| 17: | |||||
| relativeTo: 16 | |||||
| position: | |||||
| y: 150 | |||||
| lines: | |||||
| scye: | |||||
| through: [4,8,9,10,0,11,12,13,6] | |||||
| curve: {} | |||||
| style: | |||||
| thickness: 1 | |||||
| 1: | |||||
| through: [4,3aa,14] | |||||
| curve: {} | |||||
| style: | |||||
| thickness: 1 | |||||
| 2: | |||||
| through: [14,2,15] | |||||
| style: | |||||
| thickness: 1 | |||||
| 3: | |||||
| through: [6,3bb,15] | |||||
| curve: {} | |||||
| style: | |||||
| thickness: 1 | |||||
| 4: | |||||
| through: [0,1,3,2] | |||||
| 0: | |||||
| through: [14,4,0,6,15] | |||||
| abc: | |||||
| through: [B,A,C] | |||||
| 5: | |||||
| through: [16,17] | |||||