| @@ -0,0 +1,6 @@ | |||||
| gopatterns | |||||
| gopatterns.exe | |||||
| /*.yaml | |||||
| *.svg | |||||
| @@ -0,0 +1,91 @@ | |||||
| --- | |||||
| linters: | |||||
| enable-all: true | |||||
| disable: | |||||
| - nakedret # naked returns are acceptable | |||||
| - nlreturn # covered by wsl cuddle rules | |||||
| - nonamedreturns # named returns are accepted | |||||
| - mnd | |||||
| - gomnd | |||||
| # deprecated | |||||
| - exhaustruct | |||||
| - execinquery | |||||
| severity: | |||||
| default-severity: major | |||||
| issues: | |||||
| exclude-use-default: false | |||||
| exclude-case-sensitive: true | |||||
| exclude-rules: | |||||
| - path: _test\.go | |||||
| linters: | |||||
| - funlen | |||||
| - gochecknoglobals | |||||
| - errchkjson | |||||
| max-same-issues: 0 | |||||
| max-issues-per-linter: 0 | |||||
| linters-settings: | |||||
| depguard: | |||||
| rules: | |||||
| main: | |||||
| allow: | |||||
| - $gostd | |||||
| - git.wtrh.nl/wouter/gopatterns/pkg/pattern | |||||
| - git.wtrh.nl/wouter/gopatterns/pkg/pattern/point | |||||
| - git.wtrh.nl/wouter/gopatterns/pkg/pattern/template | |||||
| - git.wtrh.nl/wouter/gopatterns/pkg/position | |||||
| - git.wtrh.nl/wouter/gopatterns/pkg/vector | |||||
| - github.com/stretchr/testify/assert | |||||
| - github.com/stretchr/testify/require | |||||
| - github.com/tdewolff/canvas | |||||
| - github.com/tdewolff/canvas/renderers | |||||
| - gitlab.com/Achilleshiel/gosplines | |||||
| gci: | |||||
| sections: | |||||
| - standard | |||||
| - default | |||||
| govet: | |||||
| enable-all: true | |||||
| disable: | |||||
| - fieldalignment # misalignment is accepted | |||||
| revive: | |||||
| # see https://github.com/mgechev/revive#recommended-configuration | |||||
| rules: | |||||
| - name: blank-imports | |||||
| - name: context-as-argument | |||||
| - name: context-keys-type | |||||
| - name: dot-imports | |||||
| - name: error-return | |||||
| - name: error-strings | |||||
| - name: error-naming | |||||
| - name: exported | |||||
| - name: if-return | |||||
| - name: increment-decrement | |||||
| - name: var-naming | |||||
| - name: var-declaration | |||||
| - name: package-comments | |||||
| - name: range | |||||
| - name: receiver-naming | |||||
| - name: time-naming | |||||
| - name: unexported-return | |||||
| - name: indent-error-flow | |||||
| - name: errorf | |||||
| - name: empty-block | |||||
| - name: superfluous-else | |||||
| - name: unused-parameter | |||||
| - name: unreachable-code | |||||
| - name: redefines-builtin-id | |||||
| stylecheck: | |||||
| checks: [all] | |||||
| varnamelen: | |||||
| min-name-length: 1 | |||||
| wsl: | |||||
| force-err-cuddling: true | |||||
| @@ -1,35 +1,46 @@ | |||||
| package main | package main | ||||
| import ( | import ( | ||||
| svg "github.com/ajstarks/svgo/float" | |||||
| "naaipatroon/pkg/patroon" | |||||
| "os" | |||||
| "git.wtrh.nl/wouter/gopatterns/internal/basispatroonbroek" | |||||
| "github.com/tdewolff/canvas" | |||||
| "github.com/tdewolff/canvas/renderers" | |||||
| ) | ) | ||||
| func main() { | func main() { | ||||
| broek := patroon.Basispatroonbroek{ | |||||
| Heupwijdte: 103, | |||||
| Taillewijdte: 85, | |||||
| Zithoogte: 31, | |||||
| Tussenbeenlengte: 83, | |||||
| Pijpbreedte: 25, | |||||
| Taillebandbreedte: 4, | |||||
| Eenheid: patroon.CentiMeter, | |||||
| broek := basispatroonbroek.Basispatroonbroek{ | |||||
| Heupwijdte: 1030, | |||||
| Taillewijdte: 850, | |||||
| Zithoogte: 310, | |||||
| Tussenbeenlengte: 830, | |||||
| Pijpbreedte: 250, | |||||
| Taillebandbreedte: 40, | |||||
| ExtraKniebreedte: 15, | |||||
| Eigenaar: "Wouter Horlings", | Eigenaar: "Wouter Horlings", | ||||
| } | } | ||||
| points := broek.GeneratePoints() | |||||
| f, err := os.OpenFile("broek.svg", os.O_RDWR|os.O_CREATE, 0o755) | |||||
| pat := broek.GenereerPatroon() | |||||
| c := canvas.New(200, 200) | |||||
| err := pat.ToCanvas(c) | |||||
| if err != nil { | if err != nil { | ||||
| panic(err) | panic(err) | ||||
| } | } | ||||
| c.Fit(10) | |||||
| points = points.Normalize() | |||||
| _, max := points.Box() | |||||
| canvas := svg.New(f) | |||||
| err = renderers.Write("broek2.pdf", c) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| canvas.Startview(max.X+40, max.Y+40, -20, -20, max.X+40, max.Y+40) | |||||
| broek.Voorbeen(canvas) | |||||
| canvas.End() | |||||
| //_, err := os.OpenFile("broek.svg", os.O_RDWR|os.O_CREATE, 0o755) | |||||
| //if err != nil { | |||||
| // panic(err) | |||||
| //} | |||||
| // | |||||
| //points = points.Normalize() | |||||
| //_, max := points.Box() | |||||
| //canvas := svg.New(f) | |||||
| // | |||||
| //canvas.Startview(max.X+40, max.Y+40, -20, -20, max.X+40, max.Y+40) | |||||
| //broek.Voorbeen(canvas) | |||||
| //canvas.End() | |||||
| } | } | ||||
| @@ -0,0 +1,7 @@ | |||||
| \documentclass{standalone} | |||||
| \usepackage{graphicx} | |||||
| \begin{document} | |||||
| {{ range $val := . }} | |||||
| \includegraphics[angle=270]{ {{- $val -}} } | |||||
| {{ end }} | |||||
| \end{document} | |||||
| @@ -0,0 +1,91 @@ | |||||
| package main | |||||
| import ( | |||||
| _ "embed" | |||||
| "flag" | |||||
| "fmt" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/template" | |||||
| "gitlab.com/slxh/go/env" | |||||
| "log/slog" | |||||
| "os" | |||||
| gotemplate "text/template" | |||||
| ) | |||||
| //go:embed combined_pdf.tex.template | |||||
| var texTemplate string | |||||
| func main() { | |||||
| var ( | |||||
| templateDir string | |||||
| outputDir string | |||||
| help bool | |||||
| ) | |||||
| flag.StringVar(&templateDir, "templates", "templates", "Directory with template files") | |||||
| flag.StringVar(&outputDir, "out", ".", "output directory") | |||||
| flag.BoolVar(&help, "help", false, "show help") | |||||
| if err := env.ParseWithFlags(); err != nil { | |||||
| slog.Error("parsing flags failed", "err", err) | |||||
| return | |||||
| } | |||||
| if help { | |||||
| fmt.Printf(` | |||||
| Render patterns. Usage | |||||
| gopatterns [-templates <template-dir>] [-out <output-dir>] input-file | |||||
| `) | |||||
| } | |||||
| args := flag.Args() | |||||
| if len(args) == 0 { | |||||
| slog.Error("at least one pattern is required") | |||||
| } | |||||
| os.MkdirAll(outputDir, 0o770) | |||||
| storage, err := template.NewStorage(templateDir) | |||||
| if err != nil { | |||||
| slog.Error("failed to open template directory", "err", err, "dir", templateDir) | |||||
| return | |||||
| } | |||||
| files := make([]string, 0) | |||||
| for _, arg := range args { | |||||
| pattern, err := template.LoadPattern(arg) | |||||
| if err != nil { | |||||
| slog.Error("failed to load pattern", "err", err) | |||||
| return | |||||
| } | |||||
| filenames, err := storage.RenderPatterns(pattern, outputDir) | |||||
| if err != nil { | |||||
| slog.Error("error occurred while creating pattern", "pattern", arg, "err", err) | |||||
| return | |||||
| } | |||||
| files = append(files, filenames...) | |||||
| } | |||||
| generateLatex(outputDir, files...) | |||||
| } | |||||
| func generateLatex(outDir string, filenames ...string) { | |||||
| open, err := os.OpenFile(outDir+"/combined_pdf.tex", os.O_RDWR+os.O_CREATE+os.O_TRUNC, 0o640) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| defer open.Close() | |||||
| parse, err := gotemplate.New("latex").Parse(texTemplate) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| err = parse.Execute(open, filenames) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| package main | |||||
| func main() { | |||||
| // pat := pattern.NewPatroon() | |||||
| // basePoint := point.NewAbsolutePoint(1, 1, 0, 0) | |||||
| // pat.AddPoint(basePoint) | |||||
| // pat.AddPoint(point.NewRelativePoint(basePoint, 5, 5, 1)) | |||||
| // pat.AddPoint(point.NewRelativePoint(&basePoint, 10, 0, 2)) | |||||
| // pat.AddPoint(point.NewRelativePoint(&basePoint, 7.5, 2.5, 4)) | |||||
| // pat.AddPoint(point.NewRelativePoint(&basePoint, 5, 0, 3)) | |||||
| // pat.AddLine(path.NewBezierCurve(pat.GetPoint(0), pat.GetPoint(1), pat.GetPoint(2))) | |||||
| // pat.AddLine(path.NewQBezierCurve(pat.GetPoint(0), pat.GetPoint(1), pat.GetPoint(3), pat.GetPoint(4))) | |||||
| // secondPoint := point.NewAbsolutePoint(5, 10, 0, 10) | |||||
| // pat.AddPoint(secondPoint) | |||||
| // pat.AddPoint(point.NewRelativePointBelow(secondPoint, 2, 11)) | |||||
| // pat.AddPoint(point.NewRelativePointBelow(secondPoint, 4, 12)) | |||||
| // pat.AddPoint(point.NewRelativePointBelow(secondPoint, 6, 13)) | |||||
| // pat.AddPoint(point.NewRelativePointBelow(secondPoint, 8, 14)) | |||||
| // pat.AddLine(path.NewMultiPointCurve(math.Pi/-8, math.Pi/8, pat.GetPoints(10, 11, 12, 13)...)) | |||||
| // pat.ToSVG("oefenbocht.svg", renderer.CentiMeter) | |||||
| } | |||||
| @@ -1,5 +1,38 @@ | |||||
| module naaipatroon | |||||
| module git.wtrh.nl/wouter/gopatterns | |||||
| go 1.17 | |||||
| go 1.22.0 | |||||
| require github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b | |||||
| toolchain go1.22.2 | |||||
| require ( | |||||
| github.com/stretchr/testify v1.9.0 | |||||
| github.com/tdewolff/canvas v0.0.0-20240404204646-eb921826d23b | |||||
| gitlab.com/Achilleshiel/gosplines v0.0.0-20240601225309-88d1e67b1066 | |||||
| gopkg.in/Knetic/govaluate.v3 v3.0.0 | |||||
| gopkg.in/yaml.v3 v3.0.1 | |||||
| ) | |||||
| require ( | |||||
| github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect | |||||
| github.com/andybalholm/brotli v1.1.0 // indirect | |||||
| github.com/benoitkugler/textlayout v0.3.0 // indirect | |||||
| github.com/benoitkugler/textprocessing v0.0.3 // indirect | |||||
| github.com/davecgh/go-spew v1.1.1 // indirect | |||||
| github.com/go-fonts/latin-modern v0.3.2 // indirect | |||||
| github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea // indirect | |||||
| github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect | |||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect | |||||
| github.com/pmezard/go-difflib v1.0.0 // indirect | |||||
| github.com/stoewer/go-strcase v1.3.0 // indirect | |||||
| github.com/tdewolff/font v0.0.0-20240404204409-be214eafe484 // indirect | |||||
| github.com/tdewolff/minify/v2 v2.20.5 // indirect | |||||
| github.com/tdewolff/parse/v2 v2.7.3 // indirect | |||||
| github.com/wcharczuk/go-chart/v2 v2.1.1 // indirect | |||||
| gitlab.com/slxh/go/env v1.0.0 // indirect | |||||
| golang.org/x/image v0.15.0 // indirect | |||||
| golang.org/x/net v0.17.0 // indirect | |||||
| golang.org/x/text v0.14.0 // indirect | |||||
| gonum.org/v1/gonum v0.15.0 // indirect | |||||
| gonum.org/v1/plot v0.14.0 // indirect | |||||
| star-tex.org/x/tex v0.4.0 // indirect | |||||
| ) | |||||
| @@ -1,29 +1,125 @@ | |||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |||||
| github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= | |||||
| github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= | |||||
| git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= | |||||
| git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= | |||||
| github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= | |||||
| github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= | |||||
| github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= | ||||
| github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | |||||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||||
| github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= | |||||
| github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= | |||||
| github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= | |||||
| github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= | |||||
| github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= | |||||
| github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= | |||||
| github.com/benoitkugler/textprocessing v0.0.3 h1:Q2X+Z6vxuW5Bxn1R9RaNt0qcprBfpc2hEUDeTlz90Ng= | |||||
| github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4= | |||||
| github.com/blend/go-sdk v1.20220411.3 h1:GFV4/FQX5UzXLPwWV03gP811pj7B8J2sbuq+GJQofXc= | |||||
| github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg09qsQqY1+ak= | |||||
| github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= | |||||
| github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= | |||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||||
| github.com/go-fonts/dejavu v0.3.2 h1:3XlHi0JBYX+Cp8n98c6qSoHrxPa4AUKDMKdrh/0sUdk= | |||||
| github.com/go-fonts/dejavu v0.3.2/go.mod h1:m+TzKY7ZEl09/a17t1593E4VYW8L1VaBXHzFZOIjGEY= | |||||
| github.com/go-fonts/latin-modern v0.3.2 h1:M+Sq24Dp0ZRPf3TctPnG1MZxRblqyWC/cRUL9WmdaFc= | |||||
| github.com/go-fonts/latin-modern v0.3.2/go.mod h1:9odJt4NbRrbdj4UAMuLVd4zEukf6aAEKnDaQga0whqQ= | |||||
| github.com/go-fonts/liberation v0.3.2 h1:XuwG0vGHFBPRRI8Qwbi5tIvR3cku9LUfZGq/Ar16wlQ= | |||||
| github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI= | |||||
| github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea h1:DfZQkvEbdmOe+JK2TMtBM+0I9GSdzE2y/L1/AmD8xKc= | |||||
| github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk= | |||||
| github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= | |||||
| github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= | |||||
| github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d h1:HrdwTlHVMdi9nOW7ZnYiLmIT1hJHvipIwM0aX3rKn8I= | |||||
| github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= | |||||
| github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= | |||||
| github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= | |||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= | |||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= | |||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |||||
| github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= | |||||
| github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= | |||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | |||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | |||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | |||||
| github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | |||||
| github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | |||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | |||||
| github.com/tdewolff/canvas v0.0.0-20240404204646-eb921826d23b h1:8wPwQtX2ulzB611fk+tPxLACiqb9VoJ9vwn8occWHD0= | |||||
| github.com/tdewolff/canvas v0.0.0-20240404204646-eb921826d23b/go.mod h1:dFEWjsGVQGviBNKYPqDbDhjfRg5QQjmEgoR6jo057bU= | |||||
| github.com/tdewolff/font v0.0.0-20240404204409-be214eafe484 h1:pS1QrGQdj4Qrwc26uU8wDHlGq1oe/64mFjwWhCuGdvo= | |||||
| github.com/tdewolff/font v0.0.0-20240404204409-be214eafe484/go.mod h1:S1ByajP+rzLFlhudtNTELNuhxoSZ19Coif+JE4kivAo= | |||||
| github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI= | |||||
| github.com/tdewolff/minify/v2 v2.20.5/go.mod h1:N78HtaitkDYAWXFbqhWX/LzgwylwudK0JvybGDVQ+Mw= | |||||
| github.com/tdewolff/parse/v2 v2.7.3 h1:SHj/ry85FdqniccvzJTG+Gt/mi/HNa1cJcTzYZnvc5U= | |||||
| github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= | |||||
| github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= | |||||
| github.com/tdewolff/test v1.0.11-0.20231121141655-2d5236e10ae4 h1:CmTImZFElFD07EUPqgMEraDMnJX1E5oJKeibjg0SC2c= | |||||
| github.com/tdewolff/test v1.0.11-0.20231121141655-2d5236e10ae4/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= | |||||
| github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= | |||||
| github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= | |||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | |||||
| gitlab.com/Achilleshiel/gosplines v0.0.0-20240601225309-88d1e67b1066 h1:v/CD9lHfrsY6V5JHyPEMiq2Meu+WDVNTXnktDuDQlGE= | |||||
| gitlab.com/Achilleshiel/gosplines v0.0.0-20240601225309-88d1e67b1066/go.mod h1:arpcB6wX3cMXszN2eA8phrMe7Z3OB1nks3Hata24tTM= | |||||
| gitlab.com/slxh/go/env v1.0.0 h1:MHjny7PNu5MAvPRHspQWy7tTDjjkZDsfhxAT8Eq3dbU= | |||||
| gitlab.com/slxh/go/env v1.0.0/go.mod h1:ApyfFy6Azu3faux6ekFESPHdHT8zAN0W8QrEGqD2ejw= | |||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | |||||
| golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= | |||||
| golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= | |||||
| golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | |||||
| golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= | |||||
| golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= | |||||
| golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= | |||||
| 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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | |||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | |||||
| golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | |||||
| golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | |||||
| golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= | |||||
| golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= | |||||
| 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-20201020160332-67f06af15bc9/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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
| golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | |||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | |||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | |||||
| golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | |||||
| golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | |||||
| golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | |||||
| 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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= | |||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | |||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | |||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||||
| honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= | |||||
| gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= | |||||
| gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= | |||||
| gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= | |||||
| gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= | |||||
| gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= | |||||
| gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= | |||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | |||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | |||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | |||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | |||||
| rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= | |||||
| rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= | |||||
| star-tex.org/x/tex v0.4.0 h1:AXUwgpnHLCxZUWW3qrmjv6ezNhH3PjUVBuLLejz2cgU= | |||||
| star-tex.org/x/tex v0.4.0/go.mod h1:w91ycsU/DkkCr7GWr60GPWqp3gn2U+6VX71T0o8k8qE= | |||||
| @@ -0,0 +1,168 @@ | |||||
| package basispatroonbroek | |||||
| import ( | |||||
| "math" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/path" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| ) | |||||
| type Basispatroonbroek struct { | |||||
| Heupwijdte float64 | |||||
| Taillewijdte float64 | |||||
| Zithoogte float64 | |||||
| Tussenbeenlengte float64 | |||||
| Pijpbreedte float64 | |||||
| Taillebandbreedte float64 | |||||
| ExtraKniebreedte float64 | |||||
| Eigenaar string | |||||
| } | |||||
| func (b *Basispatroonbroek) GenereerPatroon() *pattern.Pattern { | |||||
| p := pattern.NewPattern() | |||||
| b.generatePoints(p) | |||||
| b.generateLines(p) | |||||
| return p | |||||
| } | |||||
| func (b *Basispatroonbroek) generatePoints(p *pattern.Pattern) { | |||||
| //p0 := point.NewAbsolutePoint(0, 0, 0, "0") | |||||
| //p.AddPoint(p0) | |||||
| //p1 := p.RelativePoint(p0).WithYOffset(-(b.Zithoogte + 10 - b.Taillebandbreedte)).MarkWith("1").Done() | |||||
| //// p.AddPoint(point.NewRelativePointBelow(p.GetPoint(0), b.Zithoogte+1-b.Taillebandbreedte, 1)) | |||||
| //p2 := p.RelativePoint(p1).WithYOffset(-b.Tussenbeenlengte).MarkWith("2").Done() | |||||
| //// p.AddPoint(point.NewRelativePointBelow(p.GetPoint(1), b.Tussenbeenlengte, 2)) | |||||
| // | |||||
| //p3 := p.RelativePoint(p2).WithYOffset(p1.Vector().Distance(p2.Vector())/2 + 50). | |||||
| // MarkWith("3").Done() | |||||
| // | |||||
| //// p.AddPoint(point.NewRelativePointAbove(p.GetPoint(2), p.GetPoint(1).Vector().Distance(p.GetPoint(2).Vector())/2+5, 3)) | |||||
| //p4 := p.RelativePoint(p1).WithYOffset(b.Zithoogte / 4).MarkWith("4").Done() | |||||
| //// p.AddPoint(point.NewRelativePointAbove(p.GetPoint(1), b.Zithoogte/4, 4)) | |||||
| //p5 := p.RelativePoint(p1).WithXOffset(-b.Heupwijdte / 12).MarkWith("5").Done() | |||||
| //// p.AddPoint(point.NewRelativePointLeft(p.GetPoint(1), b.Heupwijdte/12, 5)) | |||||
| //p6 := p.RelativePoint(p4).WithXOffset(-b.Heupwijdte / 12).MarkWith("6").Done() | |||||
| //// p.AddPoint(point.NewRelativePointLeft(p.GetPoint(4), b.Heupwijdte/12, 6)) | |||||
| //p7 := p.RelativePoint(p0).WithXOffset(-b.Heupwijdte / 12).MarkWith("7").Done() | |||||
| //// p.AddPoint(point.NewRelativePointLeft(p.GetPoint(0), b.Heupwijdte/12, 7)) | |||||
| //p.RelativePoint(p6).WithXOffset(b.Heupwijdte/4 + 20).MarkWith("8").Done() | |||||
| //// p.AddPoint(point.NewRelativePointRight(p.GetPoint(6), b.Heupwijdte/4+2, 8)) | |||||
| //p9 := p.RelativePoint(p5).WithXOffset(-(b.Heupwijdte/16 + 5)).MarkWith("9").Done() | |||||
| //// p.AddPoint(point.NewRelativePointLeft(p.GetPoint(5), b.Heupwijdte/16+0.5, 9)) | |||||
| //p10 := p.RelativePoint(p7).WithXOffset(10).MarkWith("10").Done() | |||||
| //// p.AddPoint(point.NewRelativePointRight(p.GetPoint(7), 1, 10)) | |||||
| //p.RelativePoint(p10).WithXOffset(b.Taillewijdte/4 + 25).MarkWith("11").Done() | |||||
| //// p.AddPoint(point.NewRelativePointRight(p.GetPoint(10), b.Taillewijdte/4+2.5, 11)) | |||||
| //p12 := p.RelativePoint(p2).WithXOffset(b.Pijpbreedte / 2).MarkWith("12").Done() | |||||
| //// p.AddPoint(point.NewRelativePointRight(p.GetPoint(2), b.Pijpbreedte/2, 12)) | |||||
| //p13 := p.RelativePoint(p2).WithXOffset(-b.Pijpbreedte / 2).MarkWith("13").Done() | |||||
| //// p.AddPoint(point.NewRelativePointLeft(p.GetPoint(2), b.Pijpbreedte/2, 13)) | |||||
| //p14 := p.RelativePoint(p3).WithXOffset(b.Pijpbreedte/2 + b.ExtraKniebreedte).MarkWith("14").Done() | |||||
| //// p.AddPoint(point.NewRelativePointRight(p.GetPoint(3), b.Pijpbreedte/2+1.5, 14)) | |||||
| //p15 := p.RelativePoint(p3).WithXOffset(-(b.Pijpbreedte/2 + b.ExtraKniebreedte)).MarkWith("15").Done() | |||||
| //// p.AddPoint(point.NewRelativePointLeft(p.GetPoint(3), b.Pijpbreedte/2+1.5, 15)) | |||||
| //p1To5Distance := p1.Vector().Distance(p5.Vector()) | |||||
| //p16 := p.RelativePoint(p5).WithXOffset(p1To5Distance / 4).MarkWith("16").Done() | |||||
| //p17 := p.RelativePoint(p6).WithXOffset(p1To5Distance / 4).MarkWith("17").Done() | |||||
| //p18 := p.RelativePoint(p7).WithXOffset(p1To5Distance / 4).MarkWith("18").Done() | |||||
| //p.AddPoint(point.NewBetweenPoint(p16, p18, 0.5, "19")) | |||||
| //p20 := p.RelativePoint(p18).WithXOffset(20).MarkWith("20").Done() | |||||
| //distance20to21 := 10.0 | |||||
| //p21 := p.RelativePoint(p20).WithYOffset(distance20to21).MarkWith("21").Done() | |||||
| //p22 := p.RelativePoint(p9).WithXOffset(-(p5.Vector().Distance(p9.Vector())/2 + 5)).MarkWith("22").Done() | |||||
| //p.RelativePoint(p22).WithYOffset(-5).MarkWith("23").Done() | |||||
| //distance20to24 := math.Sqrt(math.Pow(b.Taillewijdte/4+45, 2) - math.Pow(distance20to21, 2)) | |||||
| //p24 := p.RelativePoint(p20).WithXOffset(distance20to24).MarkWith("24").Done() | |||||
| //p.AddPoint(point.NewBetweenPoint(p21, p24, 0.5, "25")) | |||||
| //p.RelativePoint(p17).WithXOffset(b.Heupwijdte/4 + 30).MarkWith("26").Done() | |||||
| //p.RelativePoint(p12).WithXOffset(20).MarkWith("27").Done() | |||||
| //p.RelativePoint(p13).WithXOffset(-20).MarkWith("28").Done() | |||||
| //p.RelativePoint(p14).WithXOffset(20).MarkWith("29").Done() | |||||
| //p.RelativePoint(p15).WithXOffset(-20).MarkWith("30").Done() | |||||
| } | |||||
| func (b *Basispatroonbroek) generateLines(p *pattern.Pattern) { | |||||
| p.AddLine(path.NewPath(p.GetPoints("15", "13", "12", "14")...)) | |||||
| p.AddLine(path.NewPath(p.GetPoints("6", "10", "11")...)) | |||||
| p15 := p.GetPoint("15") | |||||
| p9 := p.GetPoint("9") | |||||
| p13 := p.GetPoint("13") | |||||
| pstart15 := point.NewBetweenPoint(p13, p15, 1.5, "109") | |||||
| p.AddPoint(pstart15) | |||||
| p.AddLine(path.NewSpline(pstart15, nil, p15, p9)) | |||||
| hp5 := point.NewRelativePointWithVector(p.GetPoint("5"), vector.Vector{X: 30, Y: 0}.Rotate(3*math.Pi/4), "110") | |||||
| p.AddLine(path.NewSpline(nil, nil, p.GetPoint("6"), hp5, p9)) | |||||
| p25 := p.GetPoint("25") | |||||
| p21 := p.GetPoint("21") | |||||
| p24 := p.GetPoint("24") | |||||
| p19 := p.GetPoint("19") | |||||
| hp251 := point.NewRelativePoint(p25).WithYOffset(-25 / 2).Done() | |||||
| hp252 := point.NewRelativePoint(p25).WithYOffset(25 / 2).Done() | |||||
| hp253 := point.NewRelativePoint(p25).WithXOffset(120).Done() | |||||
| p.AddLine(path.NewPath(p19, p21, hp251, hp253, hp252, p24)) | |||||
| hp3 := point.NewBetweenPoint(p.GetPoint("14"), p.GetPoint("8"), 0.5, "17") | |||||
| hp4 := point.NewRelativePointLeft(hp3, 5, "21") | |||||
| hp7 := point.NewBetweenPoint(p.GetPoint("8"), p.GetPoint("11"), 0.5, "17") | |||||
| hp8 := point.NewRelativePointRight(hp7, 20, "22") | |||||
| hp10 := point.NewBetweenPoint(p.GetPoint("12"), p.GetPoint("14"), 1.3, "112") | |||||
| p.AddLine(path.NewSpline(hp10, hp8, p.GetPoint("14"), hp4, p.GetPoint("8"), p.GetPoint("11"))) | |||||
| hp16 := point.NewRelativePointWithVector(p.GetPoint("16"), vector.Vector{X: 45, Y: 0}.Rotate(3*math.Pi/4), "110") | |||||
| hp19 := point.NewBetweenPoint(p21, p19, 1.4, "0") | |||||
| p23 := p.GetPoint("23") | |||||
| hp23 := point.NewRelativePointWithVector(p23, vector.Vector{X: 70, Y: 0}.Rotate(-math.Pi/10), "111") | |||||
| p.AddLine(path.NewSpline(hp19, hp23, p19, hp16, p23)) | |||||
| p30 := p.GetPoint("30") | |||||
| p28 := p.GetPoint("28") | |||||
| hp30 := point.NewBetweenPoint(p28, p30, 1.5, "0") | |||||
| p.AddLine(path.NewSpline(nil, hp30, p23, p30)) | |||||
| p.AddLine(path.NewPath(p30, p28)) | |||||
| p29 := p.GetPoint("29") | |||||
| p27 := p.GetPoint("27") | |||||
| p.AddLine(path.NewPath(p29, p27)) | |||||
| hp28 := point.NewRelativePointRight(point.NewBetweenPoint(p28, p27, 0.5, "0"), 10, "0") | |||||
| p.AddLine(path.NewSpline(nil, nil, p27, hp28, p28)) | |||||
| p26 := p.GetPoint("26") | |||||
| hp13 := point.NewBetweenPoint(p29, p26, 0.5, "17") | |||||
| hp14 := point.NewRelativePointLeft(hp13, 5, "21") | |||||
| hp17 := point.NewBetweenPoint(p26, p24, 0.5, "17") | |||||
| hp18 := point.NewRelativePointRight(hp17, 20, "22") | |||||
| hp110 := point.NewBetweenPoint(p27, p29, 1.3, "112") | |||||
| p.AddLine(path.NewSpline(hp110, hp18, p29, hp14, p26, p24)) | |||||
| // p.AddLine(path.NewStraightLine(p.GetPoint(19), p.GetPoint(21))) | |||||
| // p.AddLine(path.NewStraightLine(hp251, p.GetPoint(21))) | |||||
| // p.AddLine(path.NewStraightLine(hp251, hp253)) | |||||
| // p.AddLine(path.NewStraightLine(hp252, hp253)) | |||||
| // p.AddLine(path.NewStraightLine(hp252, p.GetPoint(24))) | |||||
| // p19 := p.GetPoint(19) | |||||
| // p23 := p.GetPoint(23) | |||||
| // p.AddLine(path.NewMultiPointCurve(0.0, p.GetPoint(21).Position().Direction(p.GetPoint(19).Position()), p23, p19)) | |||||
| // p.AddLine(path.NewMultiPointCurve(-0.18, -math.Pi+0.18, p.GetPoints(28, 27)...)) | |||||
| // hp9 := point.NewBetweenPoint(p.GetPoint(29), p.GetPoint(26), 0.5, 0) | |||||
| // hp10 := point.NewRelativePointLeft(hp9, 0.3, 0) | |||||
| // p.AddLine(path.NewMultiPointCurve(math.Pi/2, -math.Pi/2, p.GetPoint(29), hp10, p.GetPoint(26), p.GetPoint(24))) | |||||
| // p.AddLine(path.NewMultiPointCurve(p.GetPoint(28).Position().Direction(p.GetPoint(30).Position()), -math.Pi/2, p.GetPoint(30), p.GetPoint(23))) | |||||
| // p.AddLine(path.NewStraightLine(p.GetPoint(28), p.GetPoint(30))) | |||||
| // p.AddLine(path.NewStraightLine(p.GetPoint(27), p.GetPoint(29))) | |||||
| } | |||||
| //func (b *Basispatroonbroek) Voorbeen(canvas *svg.SVG) { | |||||
| // p := b.GeneratePoints().Normalize() | |||||
| // p.Draw(canvas) | |||||
| // p.Line(canvas, 15, 13, 12, 14) | |||||
| // p.Line(canvas, 10, 11) | |||||
| // pa := p[14].Above(p[14].Distance(p[8]) / 3) | |||||
| // pb := p[8].Below(p[14].Distance(p[8]) / 3) | |||||
| // util.Bezier(canvas, p[14], pa, pb, p[8]) | |||||
| // p15p9half := p[9].Subtract(p[15]).Divide(2) | |||||
| // pc := p[15].Add(p15p9half).Add(p15p9half.Unit().Rotate(math.Pi / 2).Multiply(3 * float64(b.Eenheid))) | |||||
| // | |||||
| // util.Qbez(canvas, p[9], pc, p[15]) | |||||
| //} | |||||
| @@ -1,61 +0,0 @@ | |||||
| package patroon | |||||
| import ( | |||||
| svg "github.com/ajstarks/svgo/float" | |||||
| "math" | |||||
| "naaipatroon/pkg/util" | |||||
| ) | |||||
| const ( | |||||
| MilliMeter SvgScale = 3.7795 | |||||
| CentiMeter SvgScale = 37.795 | |||||
| ) | |||||
| type SvgScale float64 | |||||
| type Basispatroonbroek struct { | |||||
| Heupwijdte float64 | |||||
| Taillewijdte float64 | |||||
| Zithoogte float64 | |||||
| Tussenbeenlengte float64 | |||||
| Pijpbreedte float64 | |||||
| Taillebandbreedte float64 | |||||
| Eenheid SvgScale | |||||
| Eigenaar string | |||||
| } | |||||
| func (b *Basispatroonbroek) GeneratePoints() util.PointMap { | |||||
| p := make(util.PointMap, 30) | |||||
| p[0] = util.Point{} | |||||
| p[1] = p[0].Below(b.Zithoogte + 1 - b.Taillebandbreedte) | |||||
| p[2] = p[1].Below(b.Tussenbeenlengte) | |||||
| p[3] = p[2].Above(p[1].Distance(p[2])/2 + 5) | |||||
| p[4] = p[1].Above(b.Zithoogte / 4) | |||||
| p[5] = p[1].Left(b.Heupwijdte / 12) | |||||
| p[6] = p[4].Left(b.Heupwijdte / 12) | |||||
| p[7] = p[0].Left(b.Heupwijdte / 12) | |||||
| p[8] = p[6].Right(b.Heupwijdte/4 + 2) | |||||
| p[9] = p[5].Left(b.Heupwijdte/16 + 5) | |||||
| p[10] = p[7].Right(1) | |||||
| p[11] = p[10].Right(b.Taillewijdte/4 + 2.5) | |||||
| p[12] = p[2].Right(b.Pijpbreedte / 2) | |||||
| p[13] = p[2].Left(b.Pijpbreedte / 2) | |||||
| p[14] = p[3].Right(b.Pijpbreedte / 2) | |||||
| p[15] = p[3].Left(b.Pijpbreedte / 2) | |||||
| return p.Scale(float64(b.Eenheid)) | |||||
| } | |||||
| func (b *Basispatroonbroek) Voorbeen(canvas *svg.SVG) { | |||||
| p := b.GeneratePoints().Normalize() | |||||
| p.Draw(canvas) | |||||
| p.Line(canvas, 15, 13, 12, 14) | |||||
| p.Line(canvas, 10, 11) | |||||
| pa := p[14].Above(p[14].Distance(p[8]) / 3) | |||||
| pb := p[8].Below(p[14].Distance(p[8]) / 3) | |||||
| util.Bezier(canvas, p[14], pa, pb, p[8]) | |||||
| p15p9half := p[9].Subtract(p[15]).Divide(2) | |||||
| pc := p[15].Add(p15p9half).Add(p15p9half.Unit().Rotate(math.Pi / 2).Multiply(3 * float64(b.Eenheid))) | |||||
| util.Qbez(canvas, p[9], pc, p[15]) | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| package pattern | |||||
| import ( | |||||
| "fmt" | |||||
| "gopkg.in/yaml.v3" | |||||
| "io/fs" | |||||
| "math" | |||||
| "gopkg.in/Knetic/govaluate.v3" | |||||
| ) | |||||
| // DimensionID describe the ID of the dimension. | |||||
| type DimensionID string | |||||
| // Dimensions is a map with dimensions. | |||||
| type Dimensions map[DimensionID]Dimension | |||||
| // Parameters returns a govaluate.MapParameters object based on the dimensions. | |||||
| func (d Dimensions) Parameters() govaluate.MapParameters { | |||||
| parameters := govaluate.MapParameters{} | |||||
| parameters["pi"] = math.Pi | |||||
| for id, dimension := range d { | |||||
| parameters[string(id)] = dimension.Value | |||||
| } | |||||
| return parameters | |||||
| } | |||||
| func (d Dimensions) Names(templateDir fs.FS) (map[string]string, error) { | |||||
| f, err := templateDir.Open("dimension_names.yaml") | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("open \"dimension_names.yaml\": %w", err) | |||||
| } | |||||
| namedDimensions := Dimensions{} | |||||
| err = yaml.NewDecoder(f).Decode(&namedDimensions) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("decode yaml from \"dimension_names.yaml\": %w", err) | |||||
| } | |||||
| out := make(map[string]string) | |||||
| for id, dimension := range d { | |||||
| nd, ok := namedDimensions[id] | |||||
| if !ok { | |||||
| continue | |||||
| } | |||||
| value := math.Round(dimension.Value) / 10 | |||||
| out[nd.Name] = fmt.Sprintf("%.1f mm", value) | |||||
| } | |||||
| return out, nil | |||||
| } | |||||
| // Dimension is a combination of a name and a value. | |||||
| type Dimension struct { | |||||
| Name string | |||||
| Value float64 | |||||
| } | |||||
| // AddDimension adds a dimension to a pattern. | |||||
| func (p *Pattern) AddDimension(id DimensionID, dimension Dimension) { | |||||
| p.dimensions[id] = dimension | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| // Package path provides objects to define lines on a sewing pattern. | |||||
| package path | |||||
| import ( | |||||
| "image/color" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point" | |||||
| "github.com/tdewolff/canvas" | |||||
| ) | |||||
| // Path defines a set of straight lines through points. | |||||
| type Path struct { | |||||
| points []point.Point | |||||
| thickness float64 | |||||
| color color.RGBA | |||||
| } | |||||
| // NewPath returns a new [Path]. | |||||
| func NewPath(points ...point.Point) Path { | |||||
| black := canvas.Black | |||||
| return Path{points: points, color: black, thickness: 0.2} | |||||
| } | |||||
| // NewPathWithStyle returns a new [Path] with the specified thickness and color. | |||||
| func NewPathWithStyle(thickness float64, color color.RGBA, points ...point.Point) Path { | |||||
| return Path{points: points, color: color, thickness: thickness} | |||||
| } | |||||
| // 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(), | |||||
| canvas.Style{ | |||||
| Fill: canvas.Paint{}, | |||||
| Stroke: canvas.Paint{Color: p.color}, | |||||
| StrokeWidth: p.thickness, | |||||
| StrokeCapper: canvas.RoundCap, | |||||
| StrokeJoiner: canvas.BevelJoin, | |||||
| DashOffset: 0, | |||||
| Dashes: nil, | |||||
| FillRule: 0, | |||||
| }, canvas.Identity) | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,87 @@ | |||||
| package path | |||||
| import ( | |||||
| "fmt" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point" | |||||
| "github.com/tdewolff/canvas" | |||||
| splines "gitlab.com/Achilleshiel/gosplines" | |||||
| ) | |||||
| const resolution = 40 | |||||
| // Spline defines a smooth curved path through points. | |||||
| type Spline struct { | |||||
| Path | |||||
| start, end point.Point | |||||
| } | |||||
| // NewSpline returns a new spline through points. Start and end points can be provided as | |||||
| // the start and stop direction of the spline. When start or end point arguments are left nil there | |||||
| // are no constraints on the direction. | |||||
| func NewSpline(start, end point.Point, points ...point.Point) Spline { | |||||
| s := Spline{ | |||||
| Path: NewPath(points...), | |||||
| start: start, | |||||
| end: end, | |||||
| } | |||||
| if start == nil && len(points) > 1 { | |||||
| s.start = points[0] | |||||
| } | |||||
| if end == nil && len(points) > 1 { | |||||
| s.end = points[len(points)-1] | |||||
| } | |||||
| return s | |||||
| } | |||||
| // Draw the spline to the provided [canvas.Canvas]. | |||||
| func (p Spline) Draw(c *canvas.Canvas) error { | |||||
| if len(p.points) < 2 { | |||||
| return nil | |||||
| } | |||||
| x := make([]float64, len(p.points)) | |||||
| y := make([]float64, len(p.points)) | |||||
| for i, point := range p.points { | |||||
| x[i], y[i] = point.Vector().Values() | |||||
| } | |||||
| diffStart := p.start.Vector().Subtract(p.points[0].Vector()) | |||||
| diffEnd := p.points[len(p.points)-1].Vector().Subtract(p.end.Vector()) | |||||
| xCoefficient, err := splines.SolveSplineWithConstraint(x, diffStart.X, diffEnd.X) | |||||
| if err != nil { | |||||
| return fmt.Errorf("unable to calculate coefficients for x: %w", err) | |||||
| } | |||||
| yCoefficient, err := splines.SolveSplineWithConstraint(y, diffStart.Y, diffEnd.Y) | |||||
| if err != nil { | |||||
| return fmt.Errorf("unable to calculate coefficients for y: %w", err) | |||||
| } | |||||
| points := make([]point.Point, 0, len(x)*resolution) | |||||
| stepSize := 1.0 / float64(resolution-1) | |||||
| for i := range len(p.points) - 1 { | |||||
| points = append(points, p.points[i]) | |||||
| for t := stepSize; t < 1.0; t += stepSize { | |||||
| xCalculated := xCoefficient[i].Calculate(t) | |||||
| yCalculated := yCoefficient[i].Calculate(t) | |||||
| points = append(points, point.NewAbsolutePoint(xCalculated, yCalculated, 0, "0")) | |||||
| } | |||||
| } | |||||
| points = append(points, p.points[len(p.points)-1]) | |||||
| err = NewPath(points...).Draw(c) | |||||
| if err != nil { | |||||
| return fmt.Errorf("draw spline points to canvas: %w", err) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,98 @@ | |||||
| // Package pattern defines a pattern that can be drawn to a canvas. | |||||
| package pattern | |||||
| import ( | |||||
| "fmt" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/text" | |||||
| "github.com/tdewolff/canvas" | |||||
| "golang.org/x/image/font/gofont/goregular" | |||||
| "gopkg.in/Knetic/govaluate.v3" | |||||
| ) | |||||
| // Pattern contains all the points, lines and dimensions to draw a pattern to a canvas. | |||||
| type Pattern struct { | |||||
| points map[point.ID]point.Point | |||||
| lines []pathDrawer | |||||
| dimensions Dimensions | |||||
| texts []*text.Text | |||||
| } | |||||
| type pathDrawer interface { | |||||
| Draw(c *canvas.Canvas) error | |||||
| } | |||||
| // AddPoint adds a point.Point to the pattern. | |||||
| func (p *Pattern) AddPoint(point point.Point) { | |||||
| p.points[point.ID()] = point | |||||
| } | |||||
| // AddLine adds a drawable line to the pattern. | |||||
| func (p *Pattern) AddLine(line pathDrawer) { | |||||
| p.lines = append(p.lines, line) | |||||
| } | |||||
| // GetPoints returns a slice with points for the given IDs. | |||||
| func (p *Pattern) GetPoints(id ...point.ID) []point.Point { | |||||
| points := make([]point.Point, 0, len(id)) | |||||
| for _, i := range id { | |||||
| points = append(points, p.GetPoint(i)) | |||||
| } | |||||
| return points | |||||
| } | |||||
| // GetPoint returns the point for the given ID. | |||||
| func (p *Pattern) GetPoint(id point.ID) point.Point { //nolint:ireturn | |||||
| return p.points[id] | |||||
| } | |||||
| // NewPattern returns a new Pattern. | |||||
| func NewPattern() *Pattern { | |||||
| return &Pattern{ | |||||
| points: make(map[point.ID]point.Point, 32), | |||||
| lines: make([]pathDrawer, 0, 32), | |||||
| dimensions: make(Dimensions), | |||||
| texts: make([]*text.Text, 0), | |||||
| } | |||||
| } | |||||
| // ToCanvas draws the pattern on the provided [canvas.Canvas]. | |||||
| func (p *Pattern) ToCanvas(c *canvas.Canvas) error { | |||||
| fontDejaVu := canvas.NewFontFamily("latin") | |||||
| if err := fontDejaVu.LoadFont(goregular.TTF, 0, canvas.FontRegular); err != nil { | |||||
| panic(err) | |||||
| } | |||||
| face := fontDejaVu.Face(12.0, canvas.Black, canvas.FontRegular) | |||||
| for _, pp := range p.points { | |||||
| point.Draw(c, pp, face) | |||||
| } | |||||
| for _, path := range p.lines { | |||||
| err := path.Draw(c) | |||||
| if err != nil { | |||||
| return fmt.Errorf("draw path to canvas: %w", err) | |||||
| } | |||||
| } | |||||
| for _, t := range p.texts { | |||||
| t.ToCanvas(c, face) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // Parameters return the parameters based on the dimensions. | |||||
| func (p *Pattern) Parameters() govaluate.MapParameters { | |||||
| return p.dimensions.Parameters() | |||||
| } | |||||
| func (p *Pattern) AddText(t *text.Text) { | |||||
| p.texts = append(p.texts, t) | |||||
| } | |||||
| func (p *Pattern) SetDimensions(dimensions Dimensions) { | |||||
| p.dimensions = dimensions | |||||
| } | |||||
| @@ -0,0 +1,82 @@ | |||||
| package point | |||||
| import ( | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/position" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| "github.com/tdewolff/canvas" | |||||
| ) | |||||
| // AbsolutePoint implements Point and defines an absolute position. | |||||
| // All other points should eventually be relative to one or more absolute point(s). | |||||
| type AbsolutePoint struct { | |||||
| id ID | |||||
| position position.Position | |||||
| name string | |||||
| draw bool | |||||
| hide bool | |||||
| } | |||||
| // Matrix calculates and returns the [canvas.Matrix] of a point. | |||||
| func (a *AbsolutePoint) Matrix() canvas.Matrix { | |||||
| return canvas.Identity.Translate(a.position.Vector.Values()).Rotate(a.position.Rotation) | |||||
| } | |||||
| // ID returns the point ID. | |||||
| func (a *AbsolutePoint) ID() ID { | |||||
| return a.id | |||||
| } | |||||
| // Name returns the name of a point. | |||||
| func (a *AbsolutePoint) Name() string { | |||||
| return a.name | |||||
| } | |||||
| // Position calculates and returns the absolute [position.Position]. | |||||
| func (a *AbsolutePoint) Position() position.Position { | |||||
| return a.position | |||||
| } | |||||
| // Vector calculates and returns the absolute [vector.Vector]. | |||||
| func (a *AbsolutePoint) Vector() vector.Vector { | |||||
| return a.Position().Vector | |||||
| } | |||||
| // NewAbsolutePoint returns a new absolute point. | |||||
| func NewAbsolutePoint(x, y, r float64, id ID) *AbsolutePoint { | |||||
| return &AbsolutePoint{ | |||||
| position: position.Position{ | |||||
| Vector: vector.Vector{ | |||||
| X: x, | |||||
| Y: y, | |||||
| }, | |||||
| Rotation: r, | |||||
| }, | |||||
| id: id, | |||||
| name: string(id), | |||||
| } | |||||
| } | |||||
| // Draw returns if the point should be drawn. | |||||
| func (a *AbsolutePoint) Draw() bool { | |||||
| return a.draw | |||||
| } | |||||
| // SetDraw indicates that the point should be drawn. | |||||
| func (a *AbsolutePoint) SetDraw() { | |||||
| a.draw = true | |||||
| } | |||||
| // UnsetDraw indicates that the point should not be drawn. | |||||
| func (a *AbsolutePoint) UnsetDraw() { | |||||
| a.draw = true | |||||
| } | |||||
| // Hide returns if the point must remain hidden. | |||||
| func (a *AbsolutePoint) Hide() bool { | |||||
| return a.hide | |||||
| } | |||||
| // SetHide indicates that the must be hidden. | |||||
| func (a *AbsolutePoint) SetHide() { | |||||
| a.hide = true | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| package point | |||||
| import ( | |||||
| "math" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/position" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| "github.com/tdewolff/canvas" | |||||
| ) | |||||
| // BetweenPoint defines a point on the line between two other points. | |||||
| type BetweenPoint struct { | |||||
| id ID | |||||
| p Point | |||||
| q Point | |||||
| offset float64 | |||||
| name string | |||||
| draw bool | |||||
| hide bool | |||||
| } | |||||
| // NewBetweenPoint returns a new BetweenPoint relative to two other points p and q. | |||||
| // The given offset defines where the new point is. | |||||
| // With offset = 0 the new point is a p, offset = 0.5 results in a point exactly in the middle. | |||||
| // Offset can be <0 (extending from p side) or >1 (extending from the q side). | |||||
| func NewBetweenPoint(p, q Point, offset float64, id ID) *BetweenPoint { | |||||
| return &BetweenPoint{ | |||||
| id: id, | |||||
| p: p, | |||||
| q: q, | |||||
| offset: offset, | |||||
| name: string(id), | |||||
| } | |||||
| } | |||||
| // Position calculates and returns the absolute [position.Position]. | |||||
| func (b *BetweenPoint) Position() position.Position { | |||||
| return position.Position{ | |||||
| Vector: b.p.Vector().Add(b.inBetween()), | |||||
| Rotation: b.p.Vector().AngleBetween(b.q.Vector()) - math.Pi/2, | |||||
| } | |||||
| } | |||||
| func (b *BetweenPoint) inBetween() vector.Vector { | |||||
| return b.q.Vector().Subtract(b.p.Vector()).Multiply(b.offset) | |||||
| } | |||||
| // Vector calculates and returns the absolute [vector.Vector]. | |||||
| func (b *BetweenPoint) Vector() vector.Vector { | |||||
| return b.Position().Vector | |||||
| } | |||||
| // Matrix calculates and returns the [canvas.Matrix] of a point. | |||||
| func (b *BetweenPoint) Matrix() canvas.Matrix { | |||||
| return b.p.Matrix().Translate(b.inBetween().Values()). | |||||
| Rotate((b.p.Vector().AngleBetween(b.q.Vector()) - math.Pi/2) * 180 / math.Pi) | |||||
| } | |||||
| // ID returns the point ID. | |||||
| func (b *BetweenPoint) ID() ID { | |||||
| return b.id | |||||
| } | |||||
| // Name returns the name of a point. | |||||
| func (b *BetweenPoint) Name() string { | |||||
| return b.name | |||||
| } | |||||
| // Draw returns if the point should be drawn. | |||||
| func (b *BetweenPoint) Draw() bool { | |||||
| return b.draw | |||||
| } | |||||
| // SetDraw indicates that the point should be drawn. | |||||
| func (b *BetweenPoint) SetDraw() { | |||||
| b.draw = true | |||||
| } | |||||
| // UnsetDraw indicates that the point should not be drawn. | |||||
| func (b *BetweenPoint) UnsetDraw() { | |||||
| b.draw = true | |||||
| } | |||||
| // Hide returns if the point must remain hidden. | |||||
| func (b *BetweenPoint) Hide() bool { | |||||
| return b.hide | |||||
| } | |||||
| // SetHide indicates that the must be hidden. | |||||
| func (b *BetweenPoint) SetHide() { | |||||
| b.hide = true | |||||
| } | |||||
| @@ -0,0 +1,59 @@ | |||||
| // Package point contains different types of points for drawing patterns. | |||||
| // There are points that define an absolute position or a relative position. | |||||
| // Relative can be a specific distance one point or between two other points. | |||||
| package point | |||||
| import ( | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/position" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| "github.com/tdewolff/canvas" | |||||
| ) | |||||
| // ID defines a point id. | |||||
| type ID string | |||||
| // Point defines the interface for different types of points. | |||||
| type Point interface { | |||||
| // ID returns the point ID. | |||||
| ID() ID | |||||
| // Position calculates and returns the absolute [position.Position]. | |||||
| Position() position.Position | |||||
| // Vector calculates and returns the absolute [vector.Vector]. | |||||
| Vector() vector.Vector | |||||
| // Matrix calculates and returns the [canvas.Matrix] of a point. | |||||
| Matrix() canvas.Matrix | |||||
| // Name returns the name of a point. | |||||
| Name() string | |||||
| // SetDraw indicates that the point should be drawn. | |||||
| SetDraw() | |||||
| // Draw returns if the point should be drawn. | |||||
| Draw() bool | |||||
| // Hide returns if the point must remain hidden. | |||||
| Hide() bool | |||||
| // SetHide indicates that the must be hidden. | |||||
| SetHide() | |||||
| } | |||||
| // Draw draws a specific Point to the provided [canvas.Canvas]. | |||||
| // The point is only drawn when the point states that it should be drawn and not must be hidden. | |||||
| func Draw(c *canvas.Canvas, point Point, face *canvas.FontFace) { | |||||
| if !point.Draw() || point.Hide() { | |||||
| return | |||||
| } | |||||
| path := canvas.Circle(1.0) | |||||
| m := point.Matrix() | |||||
| style := canvas.Style{Fill: canvas.Paint{Color: canvas.Black}} | |||||
| c.RenderPath(path, style, m) | |||||
| text := canvas.NewTextLine(face, point.Name(), canvas.Bottom) | |||||
| c.RenderText(text, m.Translate(2, -4)) | |||||
| } | |||||
| @@ -0,0 +1,160 @@ | |||||
| package point | |||||
| import ( | |||||
| "math" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/position" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| "github.com/tdewolff/canvas" | |||||
| ) | |||||
| // RelativePoint implements Point and defines a position that is relative to a different Point. | |||||
| type RelativePoint struct { | |||||
| draw bool | |||||
| name string | |||||
| point Point | |||||
| relativeOffset position.Position | |||||
| id ID | |||||
| hide bool | |||||
| } | |||||
| // Name returns the name of a point. | |||||
| func (r *RelativePoint) Name() string { | |||||
| return r.name | |||||
| } | |||||
| // Done returns the relativePoint as interface. | |||||
| func (r *RelativePoint) Done() Point { //nolint:ireturn | |||||
| return r | |||||
| } | |||||
| // MarkWith sets the ID of the point. | |||||
| func (r *RelativePoint) MarkWith(n ID) *RelativePoint { | |||||
| r.id = n | |||||
| if r.name == "" { | |||||
| r.name = string(n) | |||||
| } | |||||
| return r | |||||
| } | |||||
| // Position calculates and returns the absolute [position.Position]. | |||||
| func (r *RelativePoint) Position() position.Position { | |||||
| p := r.point.Position() | |||||
| return p.Add(r.relativeOffset) | |||||
| } | |||||
| // Matrix calculates and returns the [canvas.Matrix] of a point. | |||||
| func (r *RelativePoint) Matrix() canvas.Matrix { | |||||
| return r.point.Matrix().Translate(r.relativeOffset.Vector.Values()).Rotate(r.relativeOffset.Rotation / math.Pi * 180) | |||||
| } | |||||
| // Vector calculates and returns the absolute [vector.Vector]. | |||||
| func (r *RelativePoint) Vector() vector.Vector { | |||||
| return r.Position().Vector | |||||
| } | |||||
| // ID returns the point ID. | |||||
| func (r *RelativePoint) ID() ID { | |||||
| return r.id | |||||
| } | |||||
| // NewRelativePointWithVector returns a new RelativePoint. | |||||
| func NewRelativePointWithVector(point Point, p vector.Vector, id ID) *RelativePoint { | |||||
| return &RelativePoint{ | |||||
| point: point, | |||||
| relativeOffset: position.Position{Vector: p}, | |||||
| id: id, | |||||
| name: string(id), | |||||
| } | |||||
| } | |||||
| // NewRelativePoint returns a new RelativePoint. | |||||
| func NewRelativePoint(point Point) *RelativePoint { | |||||
| return &RelativePoint{ | |||||
| point: point, | |||||
| } | |||||
| } | |||||
| // NewRotationPoint returns a new RelativePoint with a specific rotation. | |||||
| func NewRotationPoint(point Point, a float64, id ID) *RelativePoint { | |||||
| p := &RelativePoint{ | |||||
| point: point, | |||||
| relativeOffset: position.Position{ | |||||
| Vector: vector.Vector{}, | |||||
| Rotation: a, | |||||
| }, | |||||
| name: string(id), | |||||
| id: id, | |||||
| } | |||||
| return p | |||||
| } | |||||
| // WithXOffset sets the x value for the relative offset. | |||||
| func (r *RelativePoint) WithXOffset(value float64) *RelativePoint { | |||||
| r.relativeOffset.Vector.X = value | |||||
| return r | |||||
| } | |||||
| // WithYOffset sets the y value for the relative offset. | |||||
| func (r *RelativePoint) WithYOffset(value float64) *RelativePoint { | |||||
| r.relativeOffset.Vector.Y = value | |||||
| return r | |||||
| } | |||||
| // WithRotationOffset sets the angle for the relative offset. | |||||
| func (r *RelativePoint) WithRotationOffset(value float64) *RelativePoint { | |||||
| r.relativeOffset.Rotation = value | |||||
| return r | |||||
| } | |||||
| // NewRelativePointBelow returns a RelativePoint distance f below another Point. | |||||
| func NewRelativePointBelow(point Point, f float64, id ID) *RelativePoint { | |||||
| return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: -f}, id) | |||||
| } | |||||
| // NewRelativePointAbove returns a RelativePoint distance f above another Point. | |||||
| func NewRelativePointAbove(point Point, f float64, id ID) *RelativePoint { | |||||
| return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: f}, id) | |||||
| } | |||||
| // NewRelativePointLeft returns a RelativePoint distance f left of another Point. | |||||
| func NewRelativePointLeft(point Point, f float64, id ID) *RelativePoint { | |||||
| return NewRelativePointWithVector(point, vector.Vector{X: -f, Y: 0}, id) | |||||
| } | |||||
| // NewRelativePointRight returns a RelativePoint distance f right of another Point. | |||||
| func NewRelativePointRight(point Point, f float64, id ID) *RelativePoint { | |||||
| return NewRelativePointWithVector(point, vector.Vector{X: f, Y: 0}, id) | |||||
| } | |||||
| // Draw returns if the point should be drawn. | |||||
| func (r *RelativePoint) Draw() bool { | |||||
| return r.draw | |||||
| } | |||||
| // SetDraw indicates that the point should be drawn. | |||||
| func (r *RelativePoint) SetDraw() { | |||||
| r.draw = true | |||||
| } | |||||
| // UnsetDraw indicates that the point should not be drawn. | |||||
| func (r *RelativePoint) UnsetDraw() { | |||||
| r.draw = true | |||||
| } | |||||
| // Hide returns if the point must remain hidden. | |||||
| func (r *RelativePoint) Hide() bool { | |||||
| return r.hide | |||||
| } | |||||
| // SetHide indicates that the must be hidden. | |||||
| func (r *RelativePoint) SetHide() { | |||||
| r.hide = true | |||||
| } | |||||
| @@ -0,0 +1,119 @@ | |||||
| --- | |||||
| points: | |||||
| 0: | |||||
| position: | |||||
| 1: | |||||
| position: | |||||
| y: -(body_rise + 10) | |||||
| relativeTo: 0 | |||||
| 2: | |||||
| position: | |||||
| y: -inside_leg | |||||
| relativeTo: 1 | |||||
| 3: | |||||
| position: | |||||
| y: inside_leg/2+50 | |||||
| relativeTo: 2 | |||||
| 4: | |||||
| position: | |||||
| y: body_rise/4 | |||||
| relativeTo: 1 | |||||
| 5: | |||||
| position: | |||||
| x: -(seat/8 - 10) | |||||
| relativeTo: 1 | |||||
| 6: | |||||
| position: | |||||
| x: -(seat/8 - 10) | |||||
| relativeTo: 4 | |||||
| 7: | |||||
| position: | |||||
| x: -(seat/8 - 10) | |||||
| relativeTo: 0 | |||||
| 8: | |||||
| position: | |||||
| x: seat/4 + 20 | |||||
| relativeTo: 6 | |||||
| 9: | |||||
| position: | |||||
| x: -(seat/16 + 5) | |||||
| relativeTo: 5 | |||||
| 10: | |||||
| position: | |||||
| x: 10 | |||||
| relativeTo: 7 | |||||
| 11: | |||||
| position: | |||||
| x: trouser_waist/4 + 25 | |||||
| relativeTo: 10 | |||||
| 12: | |||||
| position: | |||||
| x: trouser_bottom_width/2 | |||||
| relativeTo: 2 | |||||
| 13: | |||||
| position: | |||||
| x: -trouser_bottom_width/2 | |||||
| relativeTo: 2 | |||||
| 14: | |||||
| position: | |||||
| x: trouser_bottom_width/2 + 15 | |||||
| relativeTo: 3 | |||||
| 15: | |||||
| position: | |||||
| x: -(trouser_bottom_width/2 + 15) | |||||
| relativeTo: 3 | |||||
| panels: | |||||
| front: | |||||
| points: | |||||
| extend12-14: | |||||
| between: | |||||
| from: 12 | |||||
| to: 14 | |||||
| offset: 1.3 | |||||
| between11-8: | |||||
| between: | |||||
| from: 8 | |||||
| to: 11 | |||||
| offset: 0.5 | |||||
| between9-15: | |||||
| between: | |||||
| from: 15 | |||||
| to: 9 | |||||
| offset: 0.5 | |||||
| offset_between11-8: | |||||
| position: | |||||
| x: 5 | |||||
| relativeTo: between11-8 | |||||
| offset_between9-15: | |||||
| position: | |||||
| x: 5 | |||||
| relativeTo: between9-15 | |||||
| extend13-15: | |||||
| between: | |||||
| from: 13 | |||||
| to: 15 | |||||
| offset: 1.5 | |||||
| r5: | |||||
| position: | |||||
| rotation: 3*pi/4 | |||||
| relativeTo: 5 | |||||
| h5: | |||||
| position: | |||||
| x: 30 | |||||
| relativeTo: r5 | |||||
| hide: true | |||||
| lines: | |||||
| - through: [0,4,1,3,2] | |||||
| - through: [14,12,13,15] | |||||
| - through: [14,8,11] | |||||
| curve: | |||||
| start: extend12-14 | |||||
| end: offset_between11-8 | |||||
| - through: [15,9] | |||||
| curve: | |||||
| start: extend13-15 | |||||
| - through: [9, h5, 6] | |||||
| curve: {} | |||||
| - through: [6, 10, 11] | |||||
| @@ -0,0 +1,9 @@ | |||||
| --- | |||||
| owner: Wouter | |||||
| sizes: | |||||
| body_rise: 281 | |||||
| inside_leg: 800 | |||||
| seat: 1020 | |||||
| trouser_waist: 900 | |||||
| trouser_bottom_width: 226 | |||||
| template: fixtures/classic_trouser_block.yaml | |||||
| @@ -0,0 +1,6 @@ | |||||
| package template | |||||
| type Information struct { | |||||
| Point `yaml:",inline"` | |||||
| Anchor string `yaml:"anchor"` | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| package template | |||||
| import ( | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/path" | |||||
| "git.wtrh.nl/wouter/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"` | |||||
| } | |||||
| // 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() | |||||
| } | |||||
| switch { | |||||
| case l.Curve != nil: | |||||
| startPoint := pat.GetPoint(l.Curve.Start) | |||||
| endPoint := pat.GetPoint(l.Curve.End) | |||||
| pat.AddLine(path.NewSpline(startPoint, endPoint, points...)) | |||||
| default: | |||||
| pat.AddLine(path.NewPath(points...)) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // Build adds all the lines to the provided [pattern.Pattern]. | |||||
| func (l Lines) Build(pat *pattern.Pattern) error { | |||||
| for _, line := range l { | |||||
| err := line.Build(pat) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package template | |||||
| // Panels contains a map with named panels. | |||||
| type Panels map[string]Panel | |||||
| // Panel contains all the lines and extra points to draw a panel. | |||||
| type Panel struct { | |||||
| Points Points `yaml:"points"` | |||||
| Lines Lines `yaml:"lines"` | |||||
| Allowances Allowances `yaml:"allowances"` | |||||
| Name string `yaml:"name"` | |||||
| Information Information `yaml:"information"` | |||||
| } | |||||
| type Allowances struct { | |||||
| Hem string `yaml:"hem"` | |||||
| Seam string `yaml:"seam"` | |||||
| } | |||||
| @@ -0,0 +1,247 @@ | |||||
| package template | |||||
| import ( | |||||
| "errors" | |||||
| "fmt" | |||||
| "gopkg.in/Knetic/govaluate.v3" | |||||
| "math" | |||||
| "strconv" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point" | |||||
| ) | |||||
| 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") | |||||
| ) | |||||
| // 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"` | |||||
| Hide bool `yaml:"hide"` | |||||
| } | |||||
| var ErrInvalidPointID = errors.New("type cannot be converted to a PointID") | |||||
| func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionFunction { | |||||
| return map[string]govaluate.ExpressionFunction{ | |||||
| "DistanceBetween": func(args ...interface{}) (interface{}, error) { | |||||
| id0, err := toPointID(args[0]) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("parsing args[0] to pointID: %w", err) | |||||
| } | |||||
| id1, err := toPointID(args[1]) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("parsing args[0] to pointID: %w", err) | |||||
| } | |||||
| p0, err := p.getOrCreate(id0, pat, 0) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("get or create point %q: %w", id0, err) | |||||
| } | |||||
| p1, err := p.getOrCreate(id1, pat, 0) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("get or create point %q: %w", id1, err) | |||||
| } | |||||
| return p0.Position().Distance(p1.Position()), nil | |||||
| }, | |||||
| "AngleBetween": func(args ...interface{}) (interface{}, error) { | |||||
| id0, err := toPointID(args[0]) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("parsing args[0] to pointID: %w", err) | |||||
| } | |||||
| id1, err := toPointID(args[1]) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("parsing args[0] to pointID: %w", err) | |||||
| } | |||||
| p0, err := p.getOrCreate(id0, pat, 0) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("get or create point %q: %w", id0, err) | |||||
| } | |||||
| p1, err := p.getOrCreate(id1, pat, 0) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("get or create point %q: %w", id1, err) | |||||
| } | |||||
| return p0.Vector().AngleBetween(p1.Vector()), 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) { | |||||
| return math.Acos(args[0].(float64)), nil | |||||
| }, | |||||
| "atan2": func(args ...interface{}) (interface{}, error) { | |||||
| return math.Atan2(args[0].(float64), args[1].(float64)), 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: | |||||
| 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 | |||||
| } | |||||
| 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 | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,28 @@ | |||||
| package template | |||||
| import "gopkg.in/Knetic/govaluate.v3" | |||||
| type Position struct { | |||||
| X *Value | |||||
| Y *Value | |||||
| Rotation *Value | |||||
| } | |||||
| func (p Position) evaluate(params govaluate.MapParameters, funcs map[string]govaluate.ExpressionFunction) (x, y, rotation float64, err error) { | |||||
| x, err = p.X.Evaluate(params, funcs) | |||||
| if err != nil { | |||||
| return 0, 0, 0, err | |||||
| } | |||||
| y, err = p.Y.Evaluate(params, funcs) | |||||
| if err != nil { | |||||
| return 0, 0, 0, err | |||||
| } | |||||
| rotation, err = p.Rotation.Evaluate(params, funcs) | |||||
| if err != nil { | |||||
| return 0, 0, 0, err | |||||
| } | |||||
| return | |||||
| } | |||||
| @@ -0,0 +1,115 @@ | |||||
| package template | |||||
| import ( | |||||
| "fmt" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/text" | |||||
| "github.com/stoewer/go-strcase" | |||||
| "github.com/tdewolff/canvas" | |||||
| "github.com/tdewolff/canvas/renderers" | |||||
| "path/filepath" | |||||
| "slices" | |||||
| "strings" | |||||
| ) | |||||
| // RenderPatterns loads a [Request] from yaml file and renders the pattern to an SVG. | |||||
| func (s Storage) RenderPatterns(request Request, outputDir string) ([]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) | |||||
| 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) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("write pattern to canvas: %w", err) | |||||
| } | |||||
| c.Fit(10) | |||||
| filename := filepath.Join(outputDir, strings.Join([]string{request.Template, name, | |||||
| strcase.SnakeCase(request.Owner)}, "_")+".pdf") | |||||
| filenames = append(filenames, filename) | |||||
| err = renderers.Write(filename, c) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("write canvas to file: %w", err) | |||||
| } | |||||
| } | |||||
| return filenames, nil | |||||
| } | |||||
| type Renderer struct { | |||||
| dimensions pattern.Dimensions | |||||
| owner string | |||||
| pattern string | |||||
| } | |||||
| // BuildPanel translates the panel to the provided [pattern.Pattern]. | |||||
| func (r Renderer) BuildPanel(panel Panel, pat *pattern.Pattern) error { | |||||
| err := panel.Points.AddToPattern(pat) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| err = panel.Lines.Build(pat) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| err = r.GenerateInformation(panel, pat) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (r Renderer) GenerateInformation(p Panel, pat *pattern.Pattern) error { | |||||
| err := Points{"_information": p.Information.Point}.AddToPattern(pat) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| dimensions := make([]string, 0, len(r.dimensions)) | |||||
| for _, dimension := range r.dimensions { | |||||
| dimensions = append(dimensions, | |||||
| fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10)) | |||||
| } | |||||
| slices.Sort(dimensions) | |||||
| dimensions = append([]string{ | |||||
| "For: " + r.owner, | |||||
| "Pattern: " + startCase(r.pattern), | |||||
| "Panel: " + p.Name, | |||||
| "Hem allowance: " + p.Allowances.Hem, | |||||
| "Seam allowance: " + p.Allowances.Seam, | |||||
| "\nMeasurements:", | |||||
| }, dimensions...) | |||||
| point := pat.GetPoint("_information") | |||||
| point.SetHide() | |||||
| pat.AddText(text.NewText(point, "", strings.Join(dimensions, "\n"))) | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,74 @@ | |||||
| package template | |||||
| import ( | |||||
| "fmt" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern" | |||||
| "gopkg.in/yaml.v3" | |||||
| "io/fs" | |||||
| "os" | |||||
| ) | |||||
| type Storage struct { | |||||
| dir fs.FS | |||||
| } | |||||
| func NewStorage(dir string) (Storage, error) { | |||||
| _, err := os.Stat(dir) | |||||
| if err != nil { | |||||
| return Storage{}, err | |||||
| } | |||||
| return Storage{dir: os.DirFS(dir)}, nil | |||||
| } | |||||
| func (s Storage) Dimensions(sizes Sizes) (pattern.Dimensions, error) { | |||||
| f, err := s.dir.Open("dimension_names.yaml") | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("open \"dimension_names.yaml\": %w", err) | |||||
| } | |||||
| namedDimensions := pattern.Dimensions{} | |||||
| err = yaml.NewDecoder(f).Decode(&namedDimensions) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("decode yaml from \"dimension_names.yaml\": %w", err) | |||||
| } | |||||
| for id, dimension := range namedDimensions { | |||||
| size, ok := sizes[string(id)] | |||||
| if !ok { | |||||
| delete(namedDimensions, id) | |||||
| continue | |||||
| } | |||||
| dimension.Value = size | |||||
| namedDimensions[id] = dimension | |||||
| } | |||||
| return namedDimensions, nil | |||||
| } | |||||
| // Request contains the information to draw a pattern for a specific owner, with corresponding | |||||
| // sizes and the template name. | |||||
| type Request struct { | |||||
| Sizes Sizes `yaml:"sizes,omitempty"` | |||||
| Owner string `yaml:"owner"` | |||||
| Template string `yaml:"template,omitempty"` | |||||
| } | |||||
| // LoadTemplate reads and decodes a [Template] from a yaml file. | |||||
| func (s Storage) LoadTemplate(name string) (Template, error) { | |||||
| fh, err := s.dir.Open(name + ".yaml") | |||||
| if err != nil { | |||||
| return Template{}, fmt.Errorf("open template file %q: %w", name, err) | |||||
| } | |||||
| template := Template{} | |||||
| err = yaml.NewDecoder(fh).Decode(&template) | |||||
| if err != nil { | |||||
| return Template{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err) | |||||
| } | |||||
| return template, nil | |||||
| } | |||||
| @@ -0,0 +1,56 @@ | |||||
| // 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) | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| package template_test | |||||
| import ( | |||||
| "testing" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/template" | |||||
| "github.com/stretchr/testify/require" | |||||
| ) | |||||
| func TestRenderPattern(t *testing.T) { | |||||
| t.Parallel() | |||||
| require.NoError(t, template.RenderPattern("fixtures/trouser.yaml")) | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package template | |||||
| import ( | |||||
| "errors" | |||||
| "fmt" | |||||
| "gopkg.in/Knetic/govaluate.v3" | |||||
| ) | |||||
| // ErrNonFloatValue is returned when the result of the expression is not a float64. | |||||
| var ErrNonFloatValue = errors.New("failed to cast expression result for float64") | |||||
| // Value describes a measurement or dimension in the templates. | |||||
| type Value string | |||||
| // Evaluate a Value as [govaluate.EvaluateExpression] in combination with the provided parameters. | |||||
| func (v *Value) Evaluate(parameters govaluate.MapParameters, funcs map[string]govaluate.ExpressionFunction) (float64, error) { | |||||
| if v == nil { | |||||
| return 0, nil | |||||
| } | |||||
| expression, err := govaluate.NewEvaluableExpressionWithFunctions(string(*v), funcs) | |||||
| if err != nil { | |||||
| return 0, fmt.Errorf("create new evaluable expression for %q: %w", *v, err) | |||||
| } | |||||
| result, err := expression.Evaluate(parameters) | |||||
| if err != nil { | |||||
| return 0, fmt.Errorf("evaluable expression for %q: %w", *v, err) | |||||
| } | |||||
| f, ok := result.(float64) | |||||
| if !ok { | |||||
| return 0, fmt.Errorf("cast %v to float: %w", result, ErrNonFloatValue) | |||||
| } | |||||
| return f, nil | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| package text | |||||
| import ( | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point" | |||||
| "github.com/tdewolff/canvas" | |||||
| ) | |||||
| type Text struct { | |||||
| point.Point | |||||
| anchor string | |||||
| text string | |||||
| } | |||||
| func NewText(point point.Point, anchor string, text string) *Text { | |||||
| return &Text{Point: point, anchor: anchor, text: text} | |||||
| } | |||||
| func (t Text) ToCanvas(c *canvas.Canvas, face *canvas.FontFace) { | |||||
| text := canvas.NewTextLine(face, t.text, canvas.Left) | |||||
| c.RenderText(text, t.Matrix()) | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| // Package position is a simple 2D position library. | |||||
| // Positions consist of a vector.Vector and a rotation. | |||||
| package position | |||||
| import ( | |||||
| "math" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| ) | |||||
| // Position contains a 2D vector and a rotation angle. | |||||
| type Position struct { | |||||
| Vector vector.Vector | |||||
| Rotation float64 | |||||
| } | |||||
| // Add a position. | |||||
| func (p Position) Add(q Position) Position { | |||||
| return Position{ | |||||
| Vector: p.Vector.Add(q.Vector.Rotate(p.Rotation)), | |||||
| Rotation: p.Rotation + q.Rotation, | |||||
| } | |||||
| } | |||||
| // 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) | |||||
| } | |||||
| // Distance returns the distance between two positions. | |||||
| func (p Position) Distance(q Position) float64 { | |||||
| return p.Vector.Distance(q.Vector) | |||||
| } | |||||
| // Direction returns the angle to another position. | |||||
| func (p Position) Direction(q Position) float64 { | |||||
| return math.Remainder(p.Vector.AngleBetween(q.Vector)-p.Rotation, 2*math.Pi) | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| package position_test | |||||
| import ( | |||||
| "math" | |||||
| "testing" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/position" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| "github.com/stretchr/testify/require" | |||||
| ) | |||||
| func TestPosition_Direction(t *testing.T) { | |||||
| t.Parallel() | |||||
| type testCase struct { | |||||
| A, B position.Position | |||||
| ExpA2B, ExpB2A float64 | |||||
| } | |||||
| testCases := []testCase{ | |||||
| { | |||||
| A: position.Position{ | |||||
| Vector: vector.Vector{X: 0, Y: 0}, | |||||
| Rotation: math.Pi / 4, | |||||
| }, | |||||
| B: position.Position{ | |||||
| Vector: vector.Vector{X: 2, Y: 0}, | |||||
| Rotation: 3 * math.Pi / 4, | |||||
| }, | |||||
| ExpA2B: -math.Pi / 4, | |||||
| ExpB2A: math.Pi / 4, | |||||
| }, | |||||
| { | |||||
| A: position.Position{ | |||||
| Vector: vector.Vector{X: 0, Y: 1}, | |||||
| Rotation: math.Pi, | |||||
| }, | |||||
| B: position.Position{ | |||||
| Vector: vector.Vector{X: 1, Y: 0}, | |||||
| Rotation: -math.Pi / 2, | |||||
| }, | |||||
| ExpA2B: 3 * math.Pi / 4, | |||||
| ExpB2A: -3 * math.Pi / 4, | |||||
| }, | |||||
| } | |||||
| for _, test := range testCases { | |||||
| actA2B := test.A.Direction(test.B) | |||||
| require.InDelta(t, test.ExpA2B, actA2B, 1e-12) | |||||
| actB2A := test.B.Direction(test.A) | |||||
| require.InDelta(t, test.ExpB2A, actB2A, 1e-12) | |||||
| } | |||||
| } | |||||
| func TestPosition_Add(t *testing.T) { | |||||
| t.Parallel() | |||||
| tests := map[string]struct { | |||||
| p, q position.Position | |||||
| want position.Position | |||||
| }{ | |||||
| "straight angle": { | |||||
| p: position.Position{ | |||||
| Vector: vector.Vector{X: 1, Y: 0}, | |||||
| Rotation: math.Pi / 2, | |||||
| }, | |||||
| q: position.Position{ | |||||
| Vector: vector.Vector{X: 1, Y: 0}, | |||||
| Rotation: 0, | |||||
| }, | |||||
| want: position.Position{ | |||||
| Vector: vector.Vector{X: 1, Y: 1}, | |||||
| Rotation: math.Pi / 2, | |||||
| }, | |||||
| }, | |||||
| "3-4-5 triangle": { | |||||
| p: position.Position{ | |||||
| Vector: vector.Vector{X: -2, Y: 0}, | |||||
| Rotation: math.Atan2(3, 4), | |||||
| }, | |||||
| q: position.Position{ | |||||
| Vector: vector.Vector{X: 5, Y: 0}, | |||||
| Rotation: -math.Atan2(3, 4), | |||||
| }, | |||||
| want: position.Position{ | |||||
| Vector: vector.Vector{X: 2, Y: 3}, | |||||
| Rotation: 0, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| for name, tt := range tests { | |||||
| t.Run(name, func(t *testing.T) { | |||||
| t.Parallel() | |||||
| require.Equal(t, tt.want, tt.p.Add(tt.q)) | |||||
| }) | |||||
| } | |||||
| } | |||||
| @@ -1,134 +0,0 @@ | |||||
| package util | |||||
| import ( | |||||
| "fmt" | |||||
| "math" | |||||
| ) | |||||
| // Point represents a 2 dimensional Point | |||||
| type Point struct { | |||||
| X, Y float64 | |||||
| } | |||||
| // Abs returns a Point with the absolute value of X and Y. | |||||
| // Should not be confused with Magnitude due to its notation ||A||. | |||||
| func (p Point) Abs() Point { | |||||
| return Point{ | |||||
| math.Abs(p.X), | |||||
| math.Abs(p.Y), | |||||
| } | |||||
| } | |||||
| // Add returns a Point with the value of Point p plus Point q. | |||||
| func (p Point) Add(q Point) Point { | |||||
| return Point{ | |||||
| p.X + q.X, | |||||
| p.Y + q.Y, | |||||
| } | |||||
| } | |||||
| // Subtract returns a Point with the value of Point p minus Point q. | |||||
| func (p Point) Subtract(q Point) Point { | |||||
| return Point{ | |||||
| X: p.X - q.X, | |||||
| Y: p.Y - q.Y, | |||||
| } | |||||
| } | |||||
| // Multiply returns a Point with the value of Point p multiplied by f. | |||||
| func (p Point) Multiply(f float64) Point { | |||||
| return Point{X: p.X * f, Y: p.Y * f} | |||||
| } | |||||
| // Divide returns a Point with the value of Point p divided by f. | |||||
| func (p Point) Divide(f float64) Point { | |||||
| return Point{X: p.X / f, Y: p.Y / f} | |||||
| } | |||||
| // Round returns a Point with the rounded value of Point p. | |||||
| func (p Point) Round() Point { | |||||
| return Point{X: math.Round(p.X), Y: math.Round(p.Y)} | |||||
| } | |||||
| // Floor returns a Point with the floored value of Point p. | |||||
| func (p Point) Floor() Point { | |||||
| return Point{X: math.Floor(p.X), Y: math.Floor(p.Y)} | |||||
| } | |||||
| // Ceil returns a Point with the rounded up value of Point p. | |||||
| func (p Point) Ceil() Point { | |||||
| return Point{X: math.Ceil(p.X), Y: math.Ceil(p.Y)} | |||||
| } | |||||
| // Scale returns a Point with the values of Point p scaled up by the values of Point q. | |||||
| func (p Point) Scale(q Point) Point { | |||||
| return Point{ | |||||
| X: p.X * q.X, | |||||
| Y: p.Y * q.Y, | |||||
| } | |||||
| } | |||||
| // ScaleDown returns a Point with the values of Point p scaled down by the values of Point q. | |||||
| func (p Point) ScaleDown(q Point) Point { | |||||
| return Point{ | |||||
| X: p.X / q.X, | |||||
| Y: p.Y / q.Y, | |||||
| } | |||||
| } | |||||
| // Min returns a Point with the smallest values of X and Y for Point p and q. | |||||
| func (p Point) Min(q Point) Point { | |||||
| return Point{ | |||||
| X: math.Min(p.X, q.X), | |||||
| Y: math.Min(p.Y, q.Y), | |||||
| } | |||||
| } | |||||
| // Max returns a Point with the largest values of X and Y for Point p and q. | |||||
| func (p Point) Max(q Point) Point { | |||||
| return Point{ | |||||
| X: math.Max(p.X, q.X), | |||||
| Y: math.Max(p.Y, q.Y), | |||||
| } | |||||
| } | |||||
| // String returns a string with comma separated values of Point p. | |||||
| func (p Point) String() string { | |||||
| return fmt.Sprintf("%v,%v", p.X, p.Y) | |||||
| } | |||||
| // Magnitude returns a float64 with the length/magnitude of Point p. | |||||
| func (p Point) Magnitude() float64 { | |||||
| return math.Sqrt(math.Pow(p.X, 2) + math.Pow(p.Y, 2)) | |||||
| } | |||||
| func (p Point) Unit() Point { | |||||
| return p.Divide(p.Magnitude()) | |||||
| } | |||||
| func (p Point) Rotate(a float64) Point { | |||||
| return Point{ | |||||
| X: math.Cos(a)*p.X - math.Sin(a)*p.Y, | |||||
| Y: math.Sin(a)*p.X + math.Cos(a)*p.Y, | |||||
| } | |||||
| } | |||||
| func (p Point) Distance(q Point) float64 { | |||||
| return p.Subtract(q).Magnitude() | |||||
| } | |||||
| func (p Point) Above(l float64) Point { | |||||
| return p.Subtract(Point{Y: l}) | |||||
| } | |||||
| func (p Point) Below(l float64) Point { | |||||
| return p.Add(Point{Y: l}) | |||||
| } | |||||
| func (p Point) Right(l float64) Point { | |||||
| return p.Add(Point{X: l}) | |||||
| } | |||||
| func (p Point) Left(l float64) Point { | |||||
| return p.Subtract(Point{X: l}) | |||||
| } | |||||
| @@ -1,73 +0,0 @@ | |||||
| package util | |||||
| import ( | |||||
| "fmt" | |||||
| "strconv" | |||||
| svg "github.com/ajstarks/svgo/float" | |||||
| ) | |||||
| type PointMap map[int]Point | |||||
| func (pm PointMap) Scale(s float64) PointMap { | |||||
| pointMap := make(PointMap, len(pm)) | |||||
| for i, p := range pm { | |||||
| pointMap[i] = p.Multiply(s) | |||||
| } | |||||
| return pointMap | |||||
| } | |||||
| func (pm PointMap) Box() (Point, Point) { | |||||
| min := Point{} | |||||
| max := Point{} | |||||
| for _, p := range pm { | |||||
| min = min.Min(p) | |||||
| max = max.Max(p) | |||||
| } | |||||
| return min, max | |||||
| } | |||||
| func (pm PointMap) Offset(point Point) PointMap { | |||||
| pointMap := make(PointMap, len(pm)) | |||||
| for i, p := range pm { | |||||
| pointMap[i] = p.Subtract(point) | |||||
| } | |||||
| return pointMap | |||||
| } | |||||
| func (pm PointMap) Normalize() PointMap { | |||||
| min, _ := pm.Box() | |||||
| return pm.Offset(min) | |||||
| } | |||||
| func (pm PointMap) Draw(canvas *svg.SVG) { | |||||
| for i, point := range pm.Normalize() { | |||||
| canvas.Circle(point.X, point.Y, 2.0, "stroke:black;fill:black") | |||||
| canvas.Text(point.X+2, point.Y+2, strconv.Itoa(i)) | |||||
| } | |||||
| } | |||||
| func (pm PointMap) Line(canvas *svg.SVG, lines ...int) { | |||||
| for i := 0; i < len(lines)-1; i++ { | |||||
| start := lines[i] | |||||
| end := lines[i+1] | |||||
| canvas.Line(pm[start].X, pm[start].Y, pm[end].X, pm[end].Y, "stroke:black;stroke-width:3") | |||||
| } | |||||
| } | |||||
| func (pm PointMap) Bezier(canvas *svg.SVG, lines ...int) error { | |||||
| if len(lines) != 4 { | |||||
| return fmt.Errorf("expected 4 line definitons, got %d instead", len(lines)) | |||||
| } | |||||
| canvas.Bezier(pm[lines[0]].X, pm[lines[0]].Y, pm[lines[1]].X, pm[lines[1]].Y, pm[lines[2]].X, pm[lines[2]].Y, pm[lines[3]].X, pm[lines[3]].Y, "stroke:black;fill:none") | |||||
| return nil | |||||
| } | |||||
| @@ -1,13 +0,0 @@ | |||||
| package util | |||||
| import ( | |||||
| svg "github.com/ajstarks/svgo/float" | |||||
| ) | |||||
| func Bezier(canvas *svg.SVG, start, c1, c2, end Point) { | |||||
| canvas.Bezier(start.X, start.Y, c1.X, c1.Y, c2.X, c2.Y, end.X, end.Y, "fill:none;stroke:black;stroke-width:3") | |||||
| } | |||||
| func Qbez(canvas *svg.SVG, start, control, end Point) { | |||||
| canvas.Qbez(start.X, start.Y, control.X, control.Y, end.X, end.Y, "fill:none;stroke:black;stroke-width:3") | |||||
| } | |||||
| @@ -0,0 +1,168 @@ | |||||
| // Package vector is a simple 2D vector math package. | |||||
| package vector | |||||
| import ( | |||||
| "fmt" | |||||
| "math" | |||||
| ) | |||||
| // Vector represents a 2 dimensional Vector. | |||||
| type Vector struct { | |||||
| X, Y float64 | |||||
| } | |||||
| // Values returns the x, y values of the Vector. | |||||
| func (v Vector) Values() (x, y float64) { | |||||
| return v.X, v.Y | |||||
| } | |||||
| // Abs returns a Vector with the absolute value of X and Y. | |||||
| // Should not be confused with Magnitude due to its notation ||A||. | |||||
| func (v Vector) Abs() Vector { | |||||
| return Vector{ | |||||
| math.Abs(v.X), | |||||
| math.Abs(v.Y), | |||||
| } | |||||
| } | |||||
| // Add returns a Vector with the value of Vector p plus Vector q. | |||||
| func (v Vector) Add(q Vector) Vector { | |||||
| return Vector{ | |||||
| v.X + q.X, | |||||
| v.Y + q.Y, | |||||
| } | |||||
| } | |||||
| // Subtract returns a Vector with the value of Vector p minus Vector q. | |||||
| func (v Vector) Subtract(q Vector) Vector { | |||||
| return Vector{ | |||||
| X: v.X - q.X, | |||||
| Y: v.Y - q.Y, | |||||
| } | |||||
| } | |||||
| // Multiply returns a Vector with the value of Vector p multiplied by f. | |||||
| func (v Vector) Multiply(f float64) Vector { | |||||
| return Vector{X: v.X * f, Y: v.Y * f} | |||||
| } | |||||
| // Divide returns a Vector with the value of Vector p divided by f. | |||||
| func (v Vector) Divide(f float64) Vector { | |||||
| return Vector{X: v.X / f, Y: v.Y / f} | |||||
| } | |||||
| // Round returns a Vector with the rounded value of Vector p. | |||||
| func (v Vector) Round() Vector { | |||||
| return Vector{X: math.Round(v.X), Y: math.Round(v.Y)} | |||||
| } | |||||
| // Floor returns a Vector with the floored value of Vector p. | |||||
| func (v Vector) Floor() Vector { | |||||
| return Vector{X: math.Floor(v.X), Y: math.Floor(v.Y)} | |||||
| } | |||||
| // Ceil returns a Vector with the rounded up value of Vector p. | |||||
| func (v Vector) Ceil() Vector { | |||||
| return Vector{X: math.Ceil(v.X), Y: math.Ceil(v.Y)} | |||||
| } | |||||
| // Scale returns a Vector with the values of Vector p scaled up by the values of Vector q. | |||||
| func (v Vector) Scale(q Vector) Vector { | |||||
| return Vector{ | |||||
| X: v.X * q.X, | |||||
| Y: v.Y * q.Y, | |||||
| } | |||||
| } | |||||
| // ScaleDown returns a Vector with the values of Vector p scaled down by the values of Vector q. | |||||
| func (v Vector) ScaleDown(q Vector) Vector { | |||||
| return Vector{ | |||||
| X: v.X / q.X, | |||||
| Y: v.Y / q.Y, | |||||
| } | |||||
| } | |||||
| // Min returns a Vector with the smallest values of X and Y for Vector p and q. | |||||
| func (v Vector) Min(q Vector) Vector { | |||||
| return Vector{ | |||||
| X: math.Min(v.X, q.X), | |||||
| Y: math.Min(v.Y, q.Y), | |||||
| } | |||||
| } | |||||
| // Max returns a Vector with the largest values of X and Y for Vector p and q. | |||||
| func (v Vector) Max(q Vector) Vector { | |||||
| return Vector{ | |||||
| X: math.Max(v.X, q.X), | |||||
| Y: math.Max(v.Y, q.Y), | |||||
| } | |||||
| } | |||||
| // String returns a string with comma separated values of Vector p. | |||||
| func (v Vector) String() string { | |||||
| return fmt.Sprintf("%v,%v", v.X, v.Y) | |||||
| } | |||||
| // Magnitude returns a float64 with the length/magnitude of Vector p. | |||||
| func (v Vector) Magnitude() float64 { | |||||
| return math.Sqrt(math.Pow(v.X, 2) + math.Pow(v.Y, 2)) | |||||
| } | |||||
| // Unit returns a vector with the same direction but with a length of 1. | |||||
| func (v Vector) Unit() Vector { | |||||
| m := v.Magnitude() | |||||
| if m == 0 { | |||||
| panic("Cannot create unit vector of a vector with length 0.") | |||||
| } | |||||
| return v.Divide(m) | |||||
| } | |||||
| // Rotate the vector with an angle in radians. | |||||
| func (v Vector) Rotate(a float64) Vector { | |||||
| return Vector{ | |||||
| X: math.Cos(a)*v.X - math.Sin(a)*v.Y, | |||||
| Y: math.Sin(a)*v.X + math.Cos(a)*v.Y, | |||||
| } | |||||
| } | |||||
| // Span returns the difference between a vector and the rotated version of the vector. | |||||
| func (v Vector) Span(a float64) Vector { | |||||
| return v.Subtract(v.Rotate(a)) | |||||
| } | |||||
| // Distance between two vectors. | |||||
| func (v Vector) Distance(q Vector) float64 { | |||||
| return v.Subtract(q).Magnitude() | |||||
| } | |||||
| // Angle of the vector. | |||||
| func (v Vector) Angle() float64 { | |||||
| return math.Atan2(v.Y, v.X) | |||||
| } | |||||
| // AngleBetween returns the angle of the line between to vector endpoints. | |||||
| func (v Vector) AngleBetween(q Vector) float64 { | |||||
| diff := q.Subtract(v) | |||||
| return diff.Angle() | |||||
| } | |||||
| // Above returns a vector that is length l above the existing vector. | |||||
| func (v Vector) Above(l float64) Vector { | |||||
| return v.Add(Vector{Y: l}) | |||||
| } | |||||
| // Below returns a vector that is length l below the existing vector. | |||||
| func (v Vector) Below(l float64) Vector { | |||||
| return v.Subtract(Vector{Y: l}) | |||||
| } | |||||
| // Right returns a vector that is length l right of the existing vector. | |||||
| func (v Vector) Right(l float64) Vector { | |||||
| return v.Add(Vector{X: l}) | |||||
| } | |||||
| // Left returns a vector that is length l left of the existing vector. | |||||
| func (v Vector) Left(l float64) Vector { | |||||
| return v.Subtract(Vector{X: l}) | |||||
| } | |||||
| @@ -0,0 +1,487 @@ | |||||
| package vector_test | |||||
| import ( | |||||
| "math" | |||||
| "testing" | |||||
| "git.wtrh.nl/wouter/gopatterns/pkg/vector" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func TestVector_Abs(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: -1, | |||||
| Y: 1, | |||||
| }.Abs() | |||||
| exp := vector.Vector{ | |||||
| X: 1, | |||||
| Y: 1, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Abs2(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 0, | |||||
| Y: -2, | |||||
| }.Abs() | |||||
| exp := vector.Vector{ | |||||
| X: 0, | |||||
| Y: 2, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Subtract(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 7, | |||||
| Y: -3, | |||||
| } | |||||
| act := v1.Subtract(v2) | |||||
| exp := vector.Vector{ | |||||
| X: 5.5, | |||||
| Y: 7, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Add(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 7, | |||||
| Y: -3, | |||||
| } | |||||
| act := v1.Add(v2) | |||||
| exp := vector.Vector{ | |||||
| X: 19.5, | |||||
| Y: 1, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Multiply(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4, | |||||
| } | |||||
| act := v1.Multiply(7) | |||||
| exp := vector.Vector{ | |||||
| X: 87.5, | |||||
| Y: 28, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Divide(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 15, | |||||
| Y: 3, | |||||
| } | |||||
| act := v1.Divide(3) | |||||
| exp := vector.Vector{ | |||||
| X: 5, | |||||
| Y: 1, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Divide2(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: -15, | |||||
| Y: 3, | |||||
| } | |||||
| act := v1.Divide(0) | |||||
| exp := vector.Vector{ | |||||
| X: math.Inf(-1), | |||||
| Y: math.Inf(1), | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Round(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4.3, | |||||
| } | |||||
| act := v1.Round() | |||||
| exp := vector.Vector{ | |||||
| X: 13, | |||||
| Y: 4, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Floor(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.9, | |||||
| Y: 4.3, | |||||
| } | |||||
| act := v1.Floor() | |||||
| exp := vector.Vector{ | |||||
| X: 12, | |||||
| Y: 4, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Ciel(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.9, | |||||
| Y: 4.3, | |||||
| } | |||||
| act := v1.Ceil() | |||||
| exp := vector.Vector{ | |||||
| X: 13, | |||||
| Y: 5, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Scale(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 7, | |||||
| Y: -3, | |||||
| } | |||||
| act := v1.Scale(v2) | |||||
| exp := vector.Vector{ | |||||
| X: 87.5, | |||||
| Y: -12, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_ScaleDown(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 5, | |||||
| Y: -2, | |||||
| } | |||||
| act := v1.ScaleDown(v2) | |||||
| exp := vector.Vector{ | |||||
| X: 2.5, | |||||
| Y: -2, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Max(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: -4, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 5, | |||||
| Y: 2, | |||||
| } | |||||
| act := v1.Max(v2) | |||||
| exp := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 2, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Min(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: -4, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 5, | |||||
| Y: 2, | |||||
| } | |||||
| act := v1.Min(v2) | |||||
| exp := vector.Vector{ | |||||
| X: 5, | |||||
| Y: -4, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_String(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 12.5, | |||||
| Y: 4, | |||||
| }.String() | |||||
| exp := "12.5,4" | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Magnitude(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 3, | |||||
| Y: 4, | |||||
| }.Magnitude() | |||||
| exp := 5.0 | |||||
| assert.InDelta(t, exp, act, 1e-15) | |||||
| } | |||||
| func TestVector_Unit_Panic(t *testing.T) { | |||||
| t.Parallel() | |||||
| emptyVector := vector.Vector{} | |||||
| assert.Panics(t, func() { emptyVector.Unit() }) | |||||
| } | |||||
| func TestVector_Unit(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 3, | |||||
| Y: 4, | |||||
| }.Unit() | |||||
| exp := vector.Vector{ | |||||
| X: 0.6, | |||||
| Y: 0.8, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Rotate(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 2, | |||||
| Y: 0, | |||||
| }.Rotate(math.Pi / 2) | |||||
| exp := vector.Vector{ | |||||
| X: 0, | |||||
| Y: 2, | |||||
| } | |||||
| assert.InDelta(t, exp.X, act.X, math.Pow10(-15)) | |||||
| assert.InDelta(t, exp.Y, act.Y, math.Pow10(-15)) | |||||
| } | |||||
| func TestVector_Rotate2(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 2, | |||||
| Y: 0, | |||||
| }.Rotate(math.Pi) | |||||
| exp := vector.Vector{ | |||||
| X: -2, | |||||
| Y: 0, | |||||
| } | |||||
| assert.InDelta(t, exp.X, act.X, math.Pow10(-15)) | |||||
| assert.InDelta(t, exp.Y, act.Y, math.Pow10(-15)) | |||||
| } | |||||
| func TestVector_Rotate3(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 0, | |||||
| Y: 2, | |||||
| }.Rotate(math.Pi / 2) | |||||
| exp := vector.Vector{ | |||||
| X: -2, | |||||
| Y: 0, | |||||
| } | |||||
| assert.InDelta(t, exp.X, act.X, math.Pow10(-15)) | |||||
| assert.InDelta(t, exp.Y, act.Y, math.Pow10(-15)) | |||||
| } | |||||
| func TestVector_Rotate4(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 0, | |||||
| Y: 2, | |||||
| }.Rotate(math.Pi) | |||||
| exp := vector.Vector{ | |||||
| X: 0, | |||||
| Y: -2, | |||||
| } | |||||
| assert.InDelta(t, exp.X, act.X, math.Pow10(-15)) | |||||
| assert.InDelta(t, exp.Y, act.Y, math.Pow10(-15)) | |||||
| } | |||||
| func TestVector_Chord(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 2, | |||||
| Y: 0, | |||||
| }.Span(math.Pi / 2) | |||||
| exp := vector.Vector{ | |||||
| X: 2, | |||||
| Y: -2, | |||||
| } | |||||
| assert.InDelta(t, exp.X, act.X, math.Pow10(-15)) | |||||
| assert.InDelta(t, exp.Y, act.Y, math.Pow10(-15)) | |||||
| } | |||||
| func TestVector_Distance(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: 3, | |||||
| Y: 0, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 0, | |||||
| Y: 4, | |||||
| } | |||||
| act := v1.Distance(v2) | |||||
| exp := 5.0 | |||||
| assert.InDelta(t, exp, act, 1e-15) | |||||
| } | |||||
| func TestVector_Angle(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: 3, | |||||
| Y: 3, | |||||
| }.Angle() | |||||
| exp := math.Pi / 4 | |||||
| assert.InDelta(t, exp, act, 1e-15) | |||||
| } | |||||
| func TestVector_Angle2(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{ | |||||
| X: -3, | |||||
| Y: 3, | |||||
| }.Angle() | |||||
| exp := 3 * math.Pi / 4 | |||||
| assert.InDelta(t, exp, act, 1e-15) | |||||
| } | |||||
| func TestVector_AngleBetween(t *testing.T) { | |||||
| t.Parallel() | |||||
| v1 := vector.Vector{ | |||||
| X: -1, | |||||
| Y: 3, | |||||
| } | |||||
| v2 := vector.Vector{ | |||||
| X: 2, | |||||
| Y: 6, | |||||
| } | |||||
| act := v1.AngleBetween(v2) | |||||
| exp := math.Pi / 4 | |||||
| assert.InDelta(t, exp, act, 1e-15) | |||||
| } | |||||
| func TestVector_Above(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{}.Above(1) | |||||
| exp := vector.Vector{ | |||||
| X: 0, | |||||
| Y: 1, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Below(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{}.Below(1) | |||||
| exp := vector.Vector{ | |||||
| X: 0, | |||||
| Y: -1, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Right(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{}.Right(1) | |||||
| exp := vector.Vector{ | |||||
| X: 1, | |||||
| Y: 0, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| func TestVector_Left(t *testing.T) { | |||||
| t.Parallel() | |||||
| act := vector.Vector{}.Left(1) | |||||
| exp := vector.Vector{ | |||||
| X: -1, | |||||
| Y: 0, | |||||
| } | |||||
| assert.Equal(t, exp, act) | |||||
| } | |||||
| @@ -0,0 +1,102 @@ | |||||
| --- | |||||
| $schema: "https://json-schema.org/draft-04/schema" | |||||
| id: "https://stsci.edu/schemas/yaml-schema/draft-01" | |||||
| title: | |||||
| YAML Schema | |||||
| type: object | |||||
| properties: | |||||
| points: | |||||
| $ref: '#/components/schemas/points' | |||||
| panels: | |||||
| type: object | |||||
| additionalProperties: | |||||
| type: object | |||||
| properties: | |||||
| allowances: | |||||
| type: object | |||||
| properties: | |||||
| hem: | |||||
| type: string | |||||
| seam: | |||||
| type: string | |||||
| points: | |||||
| $ref: '#/components/schemas/points' | |||||
| lines: | |||||
| type: array | |||||
| items: | |||||
| $ref: '#/components/schemas/line' | |||||
| 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' | |||||
| description: | |||||
| type: string | |||||
| between: | |||||
| type: object | |||||
| properties: | |||||
| from: | |||||
| $ref: '#/components/schemas/pointID' | |||||
| to: | |||||
| $ref: '#/components/schemas/pointID' | |||||
| offset: | |||||
| $ref: '#/components/schemas/expression' | |||||
| hide: | |||||
| type: bool | |||||
| points: | |||||
| type: object | |||||
| additionalProperties: | |||||
| $ref: '#/components/schemas/point' | |||||
| position: | |||||
| type: object | |||||
| properties: | |||||
| y: | |||||
| type: string | |||||
| x: | |||||
| type: string | |||||
| rotation: | |||||
| type: string | |||||
| line: | |||||
| type: object | |||||
| properties: | |||||
| through: | |||||
| type: array | |||||
| items: | |||||
| $ref: '#/components/schemas/pointID' | |||||
| curve: | |||||
| type: object | |||||
| properties: | |||||
| start: | |||||
| $ref: '#/components/schemas/pointID' | |||||
| end: | |||||
| type: | |||||
| $ref: '#/components/schemas/pointID' | |||||
| pointID: | |||||
| oneOf: | |||||
| - type: integer | |||||
| - type: string | |||||
| expression: | |||||
| oneOf: | |||||
| - type: integer | |||||
| - type: string | |||||
| @@ -0,0 +1,302 @@ | |||||
| --- | |||||
| name: Basic Trouser Block | |||||
| points: | |||||
| 0: | |||||
| position: {} | |||||
| 1: | |||||
| position: | |||||
| y: -(body_rise) | |||||
| relativeTo: 0 | |||||
| 2: | |||||
| position: | |||||
| y: -waist_to_hip | |||||
| relativeTo: 0 | |||||
| 3: | |||||
| position: | |||||
| y: -waist_to_floor | |||||
| relativeTo: 0 | |||||
| 4: | |||||
| position: | |||||
| y: -(((waist_to_floor - body_rise)/2) - 50) | |||||
| relativeTo: 1 | |||||
| 5: | |||||
| position: | |||||
| x: -(hips/12 + 20) | |||||
| relativeTo: 1 | |||||
| 6: | |||||
| position: | |||||
| x: -(hips/12 + 20) | |||||
| relativeTo: 2 | |||||
| 7: | |||||
| position: | |||||
| x: -(hips/12 + 20) | |||||
| relativeTo: 0 | |||||
| 8: | |||||
| position: | |||||
| x: hips/4 + 5 | |||||
| relativeTo: 6 | |||||
| 9: | |||||
| position: | |||||
| x: -(hips/16 + 10) | |||||
| relativeTo: 5 | |||||
| 10: | |||||
| position: | |||||
| x: 10 | |||||
| relativeTo: 7 | |||||
| 11: | |||||
| position: | |||||
| x: waist/4 + 5 | |||||
| relativeTo: 10 | |||||
| 12: | |||||
| position: | |||||
| x: trouser_bottom_width/2 - 5 | |||||
| relativeTo: 3 | |||||
| 13: | |||||
| position: | |||||
| x: trouser_bottom_width/2 + 12 | |||||
| relativeTo: 4 | |||||
| 14: | |||||
| position: | |||||
| x: -(trouser_bottom_width/2 - 5) | |||||
| relativeTo: 3 | |||||
| 15: | |||||
| position: | |||||
| x: -(trouser_bottom_width/2 + 12) | |||||
| relativeTo: 4 | |||||
| 16: | |||||
| position: | |||||
| x: DistanceBetween("1","5")/4 | |||||
| relativeTo: 5 | |||||
| 17: | |||||
| position: | |||||
| x: DistanceBetween("16","5") | |||||
| relativeTo: 6 | |||||
| 18: | |||||
| position: | |||||
| x: DistanceBetween("16","5") | |||||
| relativeTo: 7 | |||||
| 19: | |||||
| between: | |||||
| from: 16 | |||||
| to: 18 | |||||
| offset: 0.5 | |||||
| 20: | |||||
| position: | |||||
| x: 20 | |||||
| relativeTo: 18 | |||||
| 21: | |||||
| position: | |||||
| y: 20 | |||||
| relativeTo: 20 | |||||
| 22: | |||||
| position: | |||||
| x: (((waist/4 + 40)^2) - (DistanceBetween(20,21)^2))^(0.5) | |||||
| y: -DistanceBetween(20,21) | |||||
| relativeTo: 21 | |||||
| 23: | |||||
| position: | |||||
| x: -(DistanceBetween(5,9)/2+8) | |||||
| relativeTo: 9 | |||||
| 24: | |||||
| position: | |||||
| y: -5 | |||||
| relativeTo: 23 | |||||
| 25: | |||||
| relativeTo: 17 | |||||
| position: | |||||
| x: hips/4 + 15 | |||||
| 26: | |||||
| relativeTo: 12 | |||||
| position: | |||||
| x: 10 | |||||
| 27: | |||||
| relativeTo: 13 | |||||
| position: | |||||
| x: 10 | |||||
| 28: | |||||
| relativeTo: 14 | |||||
| position: | |||||
| x: -10 | |||||
| 29: | |||||
| relativeTo: 15 | |||||
| position: | |||||
| x: -10 | |||||
| 30: | |||||
| between: | |||||
| from: 21 | |||||
| to: 22 | |||||
| offset: 1/3 | |||||
| 31: | |||||
| between: | |||||
| from: 21 | |||||
| to: 22 | |||||
| offset: 2/3 | |||||
| panels: | |||||
| front: | |||||
| allowances: | |||||
| hem: none | |||||
| seam: none | |||||
| information: | |||||
| position: | |||||
| x: 10 | |||||
| y: -10 | |||||
| relativeTo: 1 | |||||
| points: | |||||
| 5r: | |||||
| position: | |||||
| rotation: pi/4 | |||||
| relativeTo: 5 | |||||
| 5d: | |||||
| position: | |||||
| y: 35 | |||||
| relativeTo: 5r | |||||
| hide: true | |||||
| 6e: | |||||
| between: | |||||
| from: 10 | |||||
| to: 6 | |||||
| offset: 1.3 | |||||
| 0l: | |||||
| position: | |||||
| x: -10 | |||||
| relativeTo: 0 | |||||
| 0r: | |||||
| position: | |||||
| x: 10 | |||||
| relativeTo: 0 | |||||
| 0b: | |||||
| position: | |||||
| y: -100 | |||||
| relativeTo: 0 | |||||
| 15e: | |||||
| between: | |||||
| from: 14 | |||||
| to: 15 | |||||
| offset: 1.8 | |||||
| 13e: | |||||
| between: | |||||
| from: 12 | |||||
| to: 13 | |||||
| offset: 1.8 | |||||
| lines: | |||||
| - through: [9,1] | |||||
| - through: [6,8] | |||||
| - through: [15,13] | |||||
| - through: [0,2,1,4,3] | |||||
| - through: [6,10,0l,0b,0r,11] | |||||
| - through: [9,5d,6] | |||||
| curve: | |||||
| start: 5 | |||||
| end: 6e | |||||
| - through: [15,14,12,13] | |||||
| - through: [15,9] | |||||
| curve: | |||||
| start: 15e | |||||
| - through: [13,8,11] | |||||
| curve: | |||||
| start: 13e | |||||
| back: | |||||
| name: Back | |||||
| information: | |||||
| position: | |||||
| x: 10 | |||||
| y: -10 | |||||
| relativeTo: 1 | |||||
| allowances: | |||||
| hem: none | |||||
| seam: none | |||||
| points: | |||||
| 30l: | |||||
| relativeTo: 30 | |||||
| position: | |||||
| y: -10 | |||||
| hide: true | |||||
| 30r: | |||||
| relativeTo: 30 | |||||
| position: | |||||
| y: 10 | |||||
| hide: true | |||||
| 30b: | |||||
| relativeTo: 30 | |||||
| position: | |||||
| x: 120 | |||||
| hide: true | |||||
| 31l: | |||||
| relativeTo: 31 | |||||
| position: | |||||
| y: -10 | |||||
| hide: true | |||||
| 31r: | |||||
| relativeTo: 31 | |||||
| position: | |||||
| y: 10 | |||||
| hide: true | |||||
| 31b: | |||||
| relativeTo: 31 | |||||
| position: | |||||
| x: 100 | |||||
| hide: true | |||||
| 16r: | |||||
| position: | |||||
| rotation: pi/4 | |||||
| relativeTo: 16 | |||||
| 16d: | |||||
| position: | |||||
| y: 47.5 | |||||
| relativeTo: 16r | |||||
| hide: true | |||||
| 23a: | |||||
| position: | |||||
| x: 160 | |||||
| y: -30 | |||||
| relativeTo: 24 | |||||
| 19e: | |||||
| between: | |||||
| from: 21 | |||||
| to: 19 | |||||
| offset: 1.8 | |||||
| 29extend: | |||||
| between: | |||||
| from: 28 | |||||
| to: 29 | |||||
| offset: 1.7 | |||||
| 27extend: | |||||
| between: | |||||
| from: 26 | |||||
| to: 27 | |||||
| offset: 1.7 | |||||
| 3down: | |||||
| position: | |||||
| y: -10 | |||||
| relativeTo: 3 | |||||
| hide: true | |||||
| lines: | |||||
| - through: [19,21,30l,30b,30r,31l,31b,31r,22] | |||||
| - through: [24,16d,19] | |||||
| curve: | |||||
| start: 23a | |||||
| end: 19e | |||||
| - through: [29,24] | |||||
| curve: | |||||
| start: 29extend | |||||
| - through: [27,25,22] | |||||
| curve: | |||||
| start: 27extend | |||||
| - through: [29,28] | |||||
| - through: [27,26] | |||||
| - through: [28,3down, 26] | |||||
| curve: {} | |||||
| - through: [23,1] | |||||
| - through: [6,25] | |||||
| - through: [29,27] | |||||
| - through: [0,2,1,4,3] | |||||
| @@ -0,0 +1,215 @@ | |||||
| --- | |||||
| points: | |||||
| A: | |||||
| position: {} | |||||
| B: | |||||
| position: | |||||
| x: bovenwijdte/2 + 40 | |||||
| relativeTo: A | |||||
| C: | |||||
| position: | |||||
| y: -(20 + ruglengte + heupdiepte) | |||||
| relativeTo: A | |||||
| D: | |||||
| position: | |||||
| x: bovenwijdte/2 + 40 | |||||
| relativeTo: C | |||||
| E: | |||||
| between: | |||||
| from: A | |||||
| to: B | |||||
| offset: 0.5 | |||||
| F: | |||||
| between: | |||||
| from: C | |||||
| to: D | |||||
| offset: 0.5 | |||||
| G: | |||||
| position: | |||||
| y: -(armsgatdiepte + 40) | |||||
| relativeTo: A | |||||
| H: | |||||
| position: | |||||
| y: -(armsgatdiepte + 40) | |||||
| relativeTo: B | |||||
| I: | |||||
| position: | |||||
| y: heupdiepte | |||||
| relativeTo: C | |||||
| J: | |||||
| position: | |||||
| y: heupdiepte | |||||
| relativeTo: D | |||||
| K: | |||||
| between: | |||||
| from: G | |||||
| to: H | |||||
| offset: 0.5 | |||||
| L: | |||||
| between: | |||||
| from: I | |||||
| to: J | |||||
| offset: 0.5 | |||||
| M: | |||||
| position: | |||||
| y: -rughals | |||||
| relativeTo: A | |||||
| N: | |||||
| position: | |||||
| x: rughals + 20 | |||||
| relativeTo: A | |||||
| O: | |||||
| position: | |||||
| x: borstbreedte/2 | |||||
| relativeTo: A | |||||
| P: | |||||
| position: | |||||
| x: borstbreedte/2 | |||||
| relativeTo: G | |||||
| Q: | |||||
| position: | |||||
| y: -40 | |||||
| relativeTo: O | |||||
| R: | |||||
| position: | |||||
| x: -rughals | |||||
| relativeTo: B | |||||
| S: | |||||
| position: | |||||
| y: -20 | |||||
| relativeTo: B | |||||
| T: | |||||
| position: | |||||
| x: -rugbreedte/2 | |||||
| y: -40 | |||||
| relativeTo: B | |||||
| U: | |||||
| position: | |||||
| x: -rugbreedte/2 | |||||
| relativeTo: H | |||||
| Nrotated: | |||||
| position: | |||||
| rotation: AngleBetween("N","Q") | |||||
| relativeTo: N | |||||
| V: | |||||
| position: | |||||
| x: DistanceBetween("R","W")-10 | |||||
| relativeTo: Nrotated | |||||
| Rrotated: | |||||
| position: | |||||
| rotation: AngleBetween("R","T") | |||||
| relativeTo: R | |||||
| W: | |||||
| position: | |||||
| x: DistanceBetween("R","T") + 20 | |||||
| relativeTo: Rrotated | |||||
| panels: | |||||
| basis: | |||||
| name: Basis | |||||
| information: | |||||
| position: | |||||
| x: 30 | |||||
| y: -30 | |||||
| relativeTo: M | |||||
| points: | |||||
| Tprime: | |||||
| position: | |||||
| y: 20 | |||||
| relativeTo: T | |||||
| hide: true | |||||
| Eprime: | |||||
| position: | |||||
| x: 20 | |||||
| relativeTo: E | |||||
| hide: true | |||||
| Rprime: | |||||
| position: | |||||
| y: -20 | |||||
| relativeTo: R | |||||
| hide: true | |||||
| Nprime: | |||||
| position: | |||||
| y: -rughals | |||||
| rotation: 3*pi/4 | |||||
| relativeTo: N | |||||
| hide: true | |||||
| Noffset: | |||||
| position: | |||||
| x: DistanceBetween("A","Nprime")/3.5 | |||||
| relativeTo: Nprime | |||||
| hide: true | |||||
| Roffset: | |||||
| between: | |||||
| from: S | |||||
| to: Rprime | |||||
| offset: 2 | |||||
| Arm1: | |||||
| between: | |||||
| from: U | |||||
| to: T | |||||
| offset: 0.5 | |||||
| hide: true | |||||
| Arm2: | |||||
| between: | |||||
| from: P | |||||
| to: Q | |||||
| offset: 2/5 | |||||
| hide: true | |||||
| Arm3: | |||||
| between: | |||||
| from: K | |||||
| to: P | |||||
| offset: 1.2 | |||||
| Arm4: | |||||
| between: | |||||
| from: K | |||||
| to: U | |||||
| offset: 0.8 | |||||
| Protated: | |||||
| position: | |||||
| rotation: pi/4 | |||||
| relativeTo: P | |||||
| Poffset: | |||||
| position: | |||||
| x: 22 | |||||
| relativeTo: Protated | |||||
| hide: true | |||||
| Urotated: | |||||
| position: | |||||
| rotation: 3*pi/4 | |||||
| relativeTo: U | |||||
| Uoffset: | |||||
| position: | |||||
| x: 30 | |||||
| relativeTo: Urotated | |||||
| hide: true | |||||
| 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] | |||||
| curve: | |||||
| start: Nprime | |||||
| - through: [S,R] | |||||
| curve: | |||||
| start: Roffset | |||||
| - through: [V,Arm2,Poffset,K] | |||||
| curve: | |||||
| end: Arm3 | |||||
| - through: [W,Arm1,Uoffset,K] | |||||
| curve: | |||||
| end: Arm4 | |||||
| # ooit stippellijnen | |||||
| - through: [G,H,J,I] | |||||
| - through: [Tprime,U] | |||||
| - through: [A,O,P] | |||||
| - through: [S,Eprime,K] | |||||
| - through: [R,Rprime] | |||||
| - through: [A,M,Nprime,N] | |||||
| @@ -0,0 +1,284 @@ | |||||
| --- | |||||
| name: Classic Trouser Block | |||||
| 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 | |||||
| 16: | |||||
| position: | |||||
| x: (seat/8 - 10) / 4 | |||||
| relativeTo: 5 | |||||
| 17: | |||||
| position: | |||||
| x: (seat/8 - 10) / 4 | |||||
| relativeTo: 6 | |||||
| 18: | |||||
| position: | |||||
| x: (seat/8 - 10) / 4 | |||||
| relativeTo: 7 | |||||
| 19: | |||||
| between: | |||||
| from: 16 | |||||
| to: 18 | |||||
| offset: 0.5 | |||||
| 21: | |||||
| position: | |||||
| x: 20 | |||||
| y: 10 | |||||
| relativeTo: 18 | |||||
| 22: | |||||
| position: | |||||
| x: -(seat/16 + 5)/2 + 5 | |||||
| relativeTo: 9 | |||||
| 23: | |||||
| position: | |||||
| y: -5 | |||||
| relativeTo: 22 | |||||
| 24: | |||||
| position: | |||||
| x: (((trouser_waist/4 + 45)^2) - (10^2))^(0.5) | |||||
| y: -10 | |||||
| relativeTo: 21 | |||||
| 25: | |||||
| between: | |||||
| from: 21 | |||||
| to: 24 | |||||
| offset: 0.5 | |||||
| 26: | |||||
| position: | |||||
| x: seat/4 + 30 | |||||
| relativeTo: 17 | |||||
| 27: | |||||
| position: | |||||
| x: 20 | |||||
| relativeTo: 12 | |||||
| 28: | |||||
| position: | |||||
| x: -20 | |||||
| relativeTo: 13 | |||||
| 29: | |||||
| position: | |||||
| x: 20 | |||||
| relativeTo: 14 | |||||
| 30: | |||||
| position: | |||||
| x: -20 | |||||
| relativeTo: 15 | |||||
| panels: | |||||
| front: | |||||
| name: Front | |||||
| information: | |||||
| position: | |||||
| x: 10 | |||||
| y: -10 | |||||
| relativeTo: 1 | |||||
| allowances: | |||||
| hem: none | |||||
| seam: 1cm | |||||
| 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 | |||||
| 1extend: | |||||
| position: | |||||
| y: -body_rise/4 | |||||
| relativeTo: 8 | |||||
| hide: true | |||||
| lines: | |||||
| - through: [0,4,1,3,2] | |||||
| - through: [6, 8] | |||||
| - through: [9, 1extend] | |||||
| - through: [14,12,13,15,14] | |||||
| - through: [14,8,11] | |||||
| curve: | |||||
| start: extend12-14 | |||||
| end: offset_between11-8 | |||||
| - through: [15,9] | |||||
| curve: | |||||
| start: extend13-15 | |||||
| - through: [9, h5, 6] | |||||
| curve: {} | |||||
| - through: [6, 10, 11] | |||||
| back: | |||||
| name: Back | |||||
| information: | |||||
| position: | |||||
| x: 10 | |||||
| y: -10 | |||||
| relativeTo: 1 | |||||
| allowances: | |||||
| hem: none | |||||
| seam: 1cm | |||||
| points: | |||||
| 25a: | |||||
| position: | |||||
| y: -12.5 | |||||
| relativeTo: 25 | |||||
| hide: true | |||||
| 25b: | |||||
| position: | |||||
| y: 12.5 | |||||
| relativeTo: 25 | |||||
| hide: true | |||||
| 25c: | |||||
| position: | |||||
| x: 120 | |||||
| relativeTo: 25 | |||||
| hide: true | |||||
| 2down: | |||||
| position: | |||||
| y: -10 | |||||
| relativeTo: 2 | |||||
| hide: true | |||||
| 16rotated: | |||||
| position: | |||||
| rotation: pi/4 | |||||
| relativeTo: 16 | |||||
| 16offset: | |||||
| position: | |||||
| y: 45 | |||||
| relativeTo: 16rotated | |||||
| hide: true | |||||
| 23a: | |||||
| position: | |||||
| x: 160 | |||||
| y: -30 | |||||
| relativeTo: 23 | |||||
| 30extend: | |||||
| between: | |||||
| from: 28 | |||||
| to: 30 | |||||
| offset: 1.7 | |||||
| 29extend: | |||||
| between: | |||||
| from: 27 | |||||
| to: 29 | |||||
| offset: 1.7 | |||||
| 16extend: | |||||
| position: | |||||
| x: seat/4 + 30 | |||||
| relativeTo: 16 | |||||
| hide: true | |||||
| 0extend: | |||||
| position: | |||||
| y: 10 | |||||
| relativeTo: 0 | |||||
| hide: true | |||||
| lines: | |||||
| - through: [0extend, 0, 4, 1, 3, 2] | |||||
| - through: [6, 17, 26] | |||||
| - through: [22, 16, 16extend] | |||||
| - through: [28, 30, 29, 27] | |||||
| - through: [21,25a,25c,25b,24] | |||||
| - through: [28,2down,27] | |||||
| curve: {} | |||||
| - through: [23,16offset,19,21] | |||||
| curve: | |||||
| start: 23a | |||||
| - through: [30,23] | |||||
| curve: | |||||
| start: 30extend | |||||
| - through: [29,26,24] | |||||
| curve: | |||||
| start: 29extend | |||||
| @@ -0,0 +1,38 @@ | |||||
| --- | |||||
| seat: | |||||
| name: Seat | |||||
| trouser_waist: | |||||
| name: Trouser waist | |||||
| body_rise: | |||||
| name: Body rise | |||||
| inside_leg: | |||||
| name: Inside leg | |||||
| trouser_bottom_width: | |||||
| name: Trouser bottom width | |||||
| waist: | |||||
| name: Waist | |||||
| hips: | |||||
| name: Hips | |||||
| waist_to_hip: | |||||
| name: Waist to hip | |||||
| waist_to_floor: | |||||
| name: Waist to floor | |||||
| bovenwijdte: | |||||
| name: Bovenwijdte | |||||
| rughals: | |||||
| name: Rughals | |||||
| rughoogte: | |||||
| name: Rughoogte | |||||
| rugbreedte: | |||||
| name: Rugbreedte | |||||
| borstbreedte: | |||||
| name: Borstbreedte | |||||
| schouderlengte: | |||||
| name: Schouderlengte | |||||
| armsgatdiepte: | |||||
| name: Armsgatdiepte | |||||
| ruglengte: | |||||
| name: Ruglengte | |||||
| heupdiepte: | |||||
| name: Heupdiepte | |||||