| @@ -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 | |||
| 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() { | |||
| 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", | |||
| } | |||
| 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 { | |||
| 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/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-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-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-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-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.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-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-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 | |||