diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db2eee4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +gopatterns +gopatterns.exe +/*.yaml + +*.svg +*.pdf \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..7a708e0 --- /dev/null +++ b/.golangci.yml @@ -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 diff --git a/cmd/basispatroon_broek/basispatroon_broek.go b/cmd/basispatroon_broek/basispatroon_broek.go index 03eea79..c0ca21c 100644 --- a/cmd/basispatroon_broek/basispatroon_broek.go +++ b/cmd/basispatroon_broek/basispatroon_broek.go @@ -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() } diff --git a/cmd/gopatterns/combined_pdf.tex.template b/cmd/gopatterns/combined_pdf.tex.template new file mode 100644 index 0000000..0d92c42 --- /dev/null +++ b/cmd/gopatterns/combined_pdf.tex.template @@ -0,0 +1,7 @@ +\documentclass{standalone} +\usepackage{graphicx} +\begin{document} +{{ range $val := . }} + \includegraphics[angle=270]{ {{- $val -}} } +{{ end }} +\end{document} diff --git a/cmd/gopatterns/gopatterns.go b/cmd/gopatterns/gopatterns.go new file mode 100644 index 0000000..113c3c5 --- /dev/null +++ b/cmd/gopatterns/gopatterns.go @@ -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 ] [-out ] 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) + } +} diff --git a/cmd/oefenbocht/oefenbocht.go b/cmd/oefenbocht/oefenbocht.go new file mode 100644 index 0000000..0753e71 --- /dev/null +++ b/cmd/oefenbocht/oefenbocht.go @@ -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) +} diff --git a/go.mod b/go.mod index 43b0da7..502ea65 100644 --- a/go.mod +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum index 38027ba..b2fff3d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/basispatroonbroek/basispatroon_broek.go b/internal/basispatroonbroek/basispatroon_broek.go new file mode 100644 index 0000000..8095a63 --- /dev/null +++ b/internal/basispatroonbroek/basispatroon_broek.go @@ -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]) +//} diff --git a/pkg/patroon/basispatroon_broek.go b/pkg/patroon/basispatroon_broek.go deleted file mode 100644 index 880a75f..0000000 --- a/pkg/patroon/basispatroon_broek.go +++ /dev/null @@ -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]) -} diff --git a/pkg/pattern/dimensions.go b/pkg/pattern/dimensions.go new file mode 100644 index 0000000..985281a --- /dev/null +++ b/pkg/pattern/dimensions.go @@ -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 +} diff --git a/pkg/pattern/path/path.go b/pkg/pattern/path/path.go new file mode 100644 index 0000000..e06df04 --- /dev/null +++ b/pkg/pattern/path/path.go @@ -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 +} diff --git a/pkg/pattern/path/splines.go b/pkg/pattern/path/splines.go new file mode 100644 index 0000000..fbdddda --- /dev/null +++ b/pkg/pattern/path/splines.go @@ -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 +} diff --git a/pkg/pattern/pattern.go b/pkg/pattern/pattern.go new file mode 100644 index 0000000..926a232 --- /dev/null +++ b/pkg/pattern/pattern.go @@ -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 +} diff --git a/pkg/pattern/point/absolute_point.go b/pkg/pattern/point/absolute_point.go new file mode 100644 index 0000000..a86bb59 --- /dev/null +++ b/pkg/pattern/point/absolute_point.go @@ -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 +} diff --git a/pkg/pattern/point/between_point.go b/pkg/pattern/point/between_point.go new file mode 100644 index 0000000..9f0af83 --- /dev/null +++ b/pkg/pattern/point/between_point.go @@ -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 +} diff --git a/pkg/pattern/point/point.go b/pkg/pattern/point/point.go new file mode 100644 index 0000000..1cd3723 --- /dev/null +++ b/pkg/pattern/point/point.go @@ -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)) +} diff --git a/pkg/pattern/point/relative_point.go b/pkg/pattern/point/relative_point.go new file mode 100644 index 0000000..3c0e1ef --- /dev/null +++ b/pkg/pattern/point/relative_point.go @@ -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 +} diff --git a/pkg/pattern/template/fixtures/classic_trouser_block.yaml b/pkg/pattern/template/fixtures/classic_trouser_block.yaml new file mode 100644 index 0000000..3d645f4 --- /dev/null +++ b/pkg/pattern/template/fixtures/classic_trouser_block.yaml @@ -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] + diff --git a/pkg/pattern/template/fixtures/trouser.yaml b/pkg/pattern/template/fixtures/trouser.yaml new file mode 100644 index 0000000..2b35c53 --- /dev/null +++ b/pkg/pattern/template/fixtures/trouser.yaml @@ -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 \ No newline at end of file diff --git a/pkg/pattern/template/information.go b/pkg/pattern/template/information.go new file mode 100644 index 0000000..6457888 --- /dev/null +++ b/pkg/pattern/template/information.go @@ -0,0 +1,6 @@ +package template + +type Information struct { + Point `yaml:",inline"` + Anchor string `yaml:"anchor"` +} diff --git a/pkg/pattern/template/line.go b/pkg/pattern/template/line.go new file mode 100644 index 0000000..16c4828 --- /dev/null +++ b/pkg/pattern/template/line.go @@ -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 +} diff --git a/pkg/pattern/template/panel.go b/pkg/pattern/template/panel.go new file mode 100644 index 0000000..7ea0352 --- /dev/null +++ b/pkg/pattern/template/panel.go @@ -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"` +} diff --git a/pkg/pattern/template/point.go b/pkg/pattern/template/point.go new file mode 100644 index 0000000..552c0bb --- /dev/null +++ b/pkg/pattern/template/point.go @@ -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 +} diff --git a/pkg/pattern/template/position.go b/pkg/pattern/template/position.go new file mode 100644 index 0000000..d7b831a --- /dev/null +++ b/pkg/pattern/template/position.go @@ -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 +} diff --git a/pkg/pattern/template/renderer.go b/pkg/pattern/template/renderer.go new file mode 100644 index 0000000..26a7e39 --- /dev/null +++ b/pkg/pattern/template/renderer.go @@ -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 +} diff --git a/pkg/pattern/template/storage.go b/pkg/pattern/template/storage.go new file mode 100644 index 0000000..fb44517 --- /dev/null +++ b/pkg/pattern/template/storage.go @@ -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 +} diff --git a/pkg/pattern/template/template.go b/pkg/pattern/template/template.go new file mode 100644 index 0000000..2e6e671 --- /dev/null +++ b/pkg/pattern/template/template.go @@ -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) +} diff --git a/pkg/pattern/template/template_test.go b/pkg/pattern/template/template_test.go new file mode 100644 index 0000000..498d7cf --- /dev/null +++ b/pkg/pattern/template/template_test.go @@ -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")) +} diff --git a/pkg/pattern/template/value.go b/pkg/pattern/template/value.go new file mode 100644 index 0000000..f01f121 --- /dev/null +++ b/pkg/pattern/template/value.go @@ -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 +} diff --git a/pkg/pattern/text/text.go b/pkg/pattern/text/text.go new file mode 100644 index 0000000..258c689 --- /dev/null +++ b/pkg/pattern/text/text.go @@ -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()) +} diff --git a/pkg/position/position.go b/pkg/position/position.go new file mode 100644 index 0000000..de893d1 --- /dev/null +++ b/pkg/position/position.go @@ -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) +} diff --git a/pkg/position/position_test.go b/pkg/position/position_test.go new file mode 100644 index 0000000..17fc703 --- /dev/null +++ b/pkg/position/position_test.go @@ -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)) + }) + } +} diff --git a/pkg/util/point.go b/pkg/util/point.go deleted file mode 100644 index c75a7fb..0000000 --- a/pkg/util/point.go +++ /dev/null @@ -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}) -} diff --git a/pkg/util/point_list.go b/pkg/util/point_list.go deleted file mode 100644 index 786ee65..0000000 --- a/pkg/util/point_list.go +++ /dev/null @@ -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 -} diff --git a/pkg/util/svg_helper.go b/pkg/util/svg_helper.go deleted file mode 100644 index fa76411..0000000 --- a/pkg/util/svg_helper.go +++ /dev/null @@ -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") -} diff --git a/pkg/vector/vector.go b/pkg/vector/vector.go new file mode 100644 index 0000000..44d6647 --- /dev/null +++ b/pkg/vector/vector.go @@ -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}) +} diff --git a/pkg/vector/vector_test.go b/pkg/vector/vector_test.go new file mode 100644 index 0000000..612fb74 --- /dev/null +++ b/pkg/vector/vector_test.go @@ -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) +} diff --git a/spec/template.yaml b/spec/template.yaml new file mode 100644 index 0000000..6b705d4 --- /dev/null +++ b/spec/template.yaml @@ -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 + + + + diff --git a/templates/basic_trouser_block.yaml b/templates/basic_trouser_block.yaml new file mode 100644 index 0000000..a58a06b --- /dev/null +++ b/templates/basic_trouser_block.yaml @@ -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] + + + + + diff --git a/templates/basis_grondpatroon_heren.yaml b/templates/basis_grondpatroon_heren.yaml new file mode 100644 index 0000000..2092b57 --- /dev/null +++ b/templates/basis_grondpatroon_heren.yaml @@ -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] + diff --git a/templates/classic_trouser_block.yaml b/templates/classic_trouser_block.yaml new file mode 100644 index 0000000..4735aab --- /dev/null +++ b/templates/classic_trouser_block.yaml @@ -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 + + + + + diff --git a/templates/dimension_names.yaml b/templates/dimension_names.yaml new file mode 100644 index 0000000..b8c9069 --- /dev/null +++ b/templates/dimension_names.yaml @@ -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