From 929177a1c5520cd49a4b29c7b6020c11b7222544 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Sun, 20 Jul 2025 17:02:02 +0200 Subject: [PATCH 1/6] Add debug line for spline drawing --- cmd/gopatterns/gopatterns.go | 4 +++ pkg/pattern/path/splines.go | 50 ++++++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/cmd/gopatterns/gopatterns.go b/cmd/gopatterns/gopatterns.go index 38d3379..89913ab 100644 --- a/cmd/gopatterns/gopatterns.go +++ b/cmd/gopatterns/gopatterns.go @@ -40,6 +40,10 @@ gopatterns [-templates ] [-out ] input-file `) } + if debug { + slog.SetLogLoggerLevel(slog.LevelDebug) + } + args := flag.Args() if len(args) == 0 { slog.Error("at least one pattern is required") diff --git a/pkg/pattern/path/splines.go b/pkg/pattern/path/splines.go index 8f1f1fa..ecf1c67 100644 --- a/pkg/pattern/path/splines.go +++ b/pkg/pattern/path/splines.go @@ -5,6 +5,7 @@ import ( "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" "github.com/tdewolff/canvas" splines "gitlab.com/Achilleshiel/gosplines" + "log/slog" ) const resolution = 40 @@ -44,8 +45,41 @@ func NewSpline(opts SplineOpts) Spline { // Draw the spline to the provided [canvas.Canvas]. func (s Spline) Draw(c *canvas.Canvas) error { + points, err := s.build() + if err != nil { + return fmt.Errorf("generating spline: %w", err) + } + + path := NewPath(points, s.style) + + if err = path.Draw(c); err != nil { + return fmt.Errorf("draw spline points to canvas: %w", err) + } + + length, _ := s.Length() + slog.Debug("Draw spline", "length", length, "from", points[0].Name(), "to", points[len(points)-1].Name()) + + return nil +} + +func (s Spline) Length() (float64, error) { + points, err := s.build() + if err != nil { + return 0.0, fmt.Errorf("generating spline: %w", err) + } + + length := 0.0 + + for i := range points[1:] { + length += points[i].Position().Distance(points[i+1].Position()) + } + + return length, nil +} + +func (s Spline) build() (points []point.Point, err error) { if len(s.points) < 2 { - return nil + return s.points, nil } x := make([]float64, len(s.points)) @@ -60,15 +94,15 @@ func (s Spline) Draw(c *canvas.Canvas) error { xCoefficient, err := splines.SolveSplineWithConstraint(x, diffStart.X, diffEnd.X) if err != nil { - return fmt.Errorf("unable to calculate coefficients for x: %w", err) + return nil, fmt.Errorf("unable to calculate coefficients for x: %w", err) } yCoefficient, err := splines.SolveSplineWithConstraint(y, diffStart.Y, diffEnd.Y) if err != nil { - return fmt.Errorf("unable to calculate coefficients for y: %w", err) + return nil, fmt.Errorf("unable to calculate coefficients for y: %w", err) } - points := make([]point.Point, 0, len(x)*resolution) + points = make([]point.Point, 0, len(x)*resolution) stepSize := 1.0 / float64(resolution-1) for i := range len(s.points) - 1 { @@ -83,11 +117,5 @@ func (s Spline) Draw(c *canvas.Canvas) error { points = append(points, s.points[len(s.points)-1]) - path := NewPath(points, s.style) - - if err = path.Draw(c); err != nil { - return fmt.Errorf("draw spline points to canvas: %w", err) - } - - return nil + return points, nil } From be84172729bde902cfb6efec130b4f268a2954a9 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Sun, 27 Jul 2025 17:31:11 +0200 Subject: [PATCH 2/6] Build v2 and refactor code base --- .golangci.yml | 2 +- cmd/gopatterns/gopatterns.go | 10 +- pkg/config/config.go | 36 ++ pkg/{pattern => dimensions}/dimensions.go | 9 +- pkg/path/path.go | 62 +++ pkg/{pattern => }/path/splines.go | 17 +- pkg/{pattern => }/path/style.go | 0 pkg/pattern/panel/panel.go | 35 ++ pkg/pattern/path/path.go | 36 -- pkg/pattern/pattern.go | 24 +- .../fixtures/classic_trouser_block.yaml | 119 ----- pkg/pattern/template/fixtures/trouser.yaml | 9 - pkg/pattern/template/line.go | 69 --- pkg/pattern/template/renderer.go | 124 ----- pkg/pattern/template/template.go | 56 -- pkg/pattern/text/text.go | 2 +- pkg/{pattern => }/point/absolute_point.go | 7 +- pkg/{pattern => }/point/between_point.go | 7 +- pkg/{pattern => }/point/extend_point.go | 9 +- pkg/{pattern => }/point/point.go | 6 +- pkg/{pattern => }/point/relative_point.go | 19 +- pkg/position/position.go | 6 - pkg/position/position_test.go | 41 ++ pkg/position/testutil/testutil.go | 15 + pkg/renderer/renderer.go | 164 ++++++ pkg/{pattern/template => storage}/storage.go | 30 +- pkg/template/fixtures/absolute_points.yaml | 13 + pkg/template/fixtures/between_points.yaml | 20 + .../fixtures/evaluation_functions.yaml | 19 + pkg/template/fixtures/extend_points.yaml | 18 + pkg/template/fixtures/functions.yaml | 52 ++ pkg/template/fixtures/polar_points.yaml | 17 + pkg/template/fixtures/references.yaml | 12 + pkg/template/fixtures/relative_points.yaml | 15 + pkg/{pattern => }/template/information.go | 0 pkg/template/line.go | 157 ++++++ pkg/{pattern => }/template/panel.go | 17 +- pkg/{pattern => }/template/point.go | 286 +++++----- pkg/template/point_test.go | 289 ++++++++++ pkg/{pattern => }/template/position.go | 0 pkg/template/template.go | 79 +++ pkg/{pattern => }/template/value.go | 0 pkg/util/id.go | 28 + pkg/util/id_test.go | 33 ++ spec/pattern.v2.yaml | 125 +++++ spec/pattern.yaml | 4 + templates/dimension_names.yaml | 2 + templates/tailored_shirt_block.v2.yaml | 498 ++++++++++++++++++ 48 files changed, 1991 insertions(+), 607 deletions(-) create mode 100644 pkg/config/config.go rename pkg/{pattern => dimensions}/dimensions.go (83%) create mode 100644 pkg/path/path.go rename pkg/{pattern => }/path/splines.go (91%) rename pkg/{pattern => }/path/style.go (100%) create mode 100644 pkg/pattern/panel/panel.go delete mode 100644 pkg/pattern/path/path.go delete mode 100644 pkg/pattern/template/fixtures/classic_trouser_block.yaml delete mode 100644 pkg/pattern/template/fixtures/trouser.yaml delete mode 100644 pkg/pattern/template/line.go delete mode 100644 pkg/pattern/template/renderer.go delete mode 100644 pkg/pattern/template/template.go rename pkg/{pattern => }/point/absolute_point.go (91%) rename pkg/{pattern => }/point/between_point.go (92%) rename pkg/{pattern => }/point/extend_point.go (89%) rename pkg/{pattern => }/point/point.go (97%) rename pkg/{pattern => }/point/relative_point.go (86%) create mode 100644 pkg/position/testutil/testutil.go create mode 100644 pkg/renderer/renderer.go rename pkg/{pattern/template => storage}/storage.go (54%) create mode 100644 pkg/template/fixtures/absolute_points.yaml create mode 100644 pkg/template/fixtures/between_points.yaml create mode 100644 pkg/template/fixtures/evaluation_functions.yaml create mode 100644 pkg/template/fixtures/extend_points.yaml create mode 100644 pkg/template/fixtures/functions.yaml create mode 100644 pkg/template/fixtures/polar_points.yaml create mode 100644 pkg/template/fixtures/references.yaml create mode 100644 pkg/template/fixtures/relative_points.yaml rename pkg/{pattern => }/template/information.go (100%) create mode 100644 pkg/template/line.go rename pkg/{pattern => }/template/panel.go (57%) rename pkg/{pattern => }/template/point.go (51%) create mode 100644 pkg/template/point_test.go rename pkg/{pattern => }/template/position.go (100%) create mode 100644 pkg/template/template.go rename pkg/{pattern => }/template/value.go (100%) create mode 100644 pkg/util/id.go create mode 100644 pkg/util/id_test.go create mode 100644 spec/pattern.v2.yaml create mode 100644 templates/tailored_shirt_block.v2.yaml diff --git a/.golangci.yml b/.golangci.yml index e29c38b..3a361f3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,7 +32,7 @@ linters-settings: allow: - $gostd - git.wtrh.nl/patterns/gopatterns/pkg/pattern - - git.wtrh.nl/patterns/gopatterns/pkg/pattern/point + - git.wtrh.nl/patterns/gopatterns/pkg/point - git.wtrh.nl/patterns/gopatterns/pkg/pattern/template - git.wtrh.nl/patterns/gopatterns/pkg/position - git.wtrh.nl/patterns/gopatterns/pkg/vector diff --git a/cmd/gopatterns/gopatterns.go b/cmd/gopatterns/gopatterns.go index 89913ab..c841936 100644 --- a/cmd/gopatterns/gopatterns.go +++ b/cmd/gopatterns/gopatterns.go @@ -8,7 +8,9 @@ import ( "os" gotemplate "text/template" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/template" + "git.wtrh.nl/patterns/gopatterns/pkg/config" + "git.wtrh.nl/patterns/gopatterns/pkg/renderer" + storage2 "git.wtrh.nl/patterns/gopatterns/pkg/storage" "gitlab.com/slxh/go/env" ) @@ -51,7 +53,7 @@ gopatterns [-templates ] [-out ] input-file os.MkdirAll(outputDir, 0o770) - storage, err := template.NewStorage(templateDir) + storage, err := storage2.NewStorage(templateDir) if err != nil { slog.Error("failed to open template directory", "err", err, "dir", templateDir) return @@ -60,13 +62,13 @@ gopatterns [-templates ] [-out ] input-file files := make([]string, 0) for _, arg := range args { - pattern, err := template.LoadPattern(arg) + pattern, err := config.LoadConfig(arg) if err != nil { slog.Error("failed to load pattern", "err", err) return } - filenames, err := storage.RenderPatterns(pattern, outputDir, debug) + filenames, err := renderer.RenderPatterns(storage, pattern, outputDir, debug) if err != nil { slog.Error("error occurred while creating pattern", "pattern", arg, "err", err) return diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..aadd536 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,36 @@ +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// Request contains the information to draw a pattern for a specific owner, with corresponding +// sizes and the template name. +type Request struct { + Sizes Sizes `yaml:"sizes,omitempty"` + Owner string `yaml:"owner"` + Template string `yaml:"template,omitempty"` +} + +// Sizes defines a map with the size name and the size value. +type Sizes map[string]float64 + +// LoadConfig reads and decodes a [Request] from a yaml file. +func LoadConfig(name string) (Request, error) { + fh, err := os.Open(name) + if err != nil { + return Request{}, fmt.Errorf("open pattern file %q: %w", name, err) + } + + pat := Request{} + + err = yaml.NewDecoder(fh).Decode(&pat) + if err != nil { + return Request{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err) + } + + return pat, nil +} diff --git a/pkg/pattern/dimensions.go b/pkg/dimensions/dimensions.go similarity index 83% rename from pkg/pattern/dimensions.go rename to pkg/dimensions/dimensions.go index c967574..895906f 100644 --- a/pkg/pattern/dimensions.go +++ b/pkg/dimensions/dimensions.go @@ -1,4 +1,4 @@ -package pattern +package dimensions import ( "fmt" @@ -15,7 +15,7 @@ type DimensionID string // Dimensions is a map with dimensions. type Dimensions map[DimensionID]Dimension -// Parameters returns a govaluate.MapParameters object based on the dimensions. +// Parameters return a govaluate.MapParameters object based on the dimensions. func (d Dimensions) Parameters() govaluate.MapParameters { parameters := govaluate.MapParameters{} parameters["pi"] = math.Pi @@ -60,8 +60,3 @@ type Dimension struct { Name string Value float64 } - -// AddDimension adds a dimension to a pattern. -func (p *Pattern) AddDimension(id DimensionID, dimension Dimension) { - p.dimensions[id] = dimension -} diff --git a/pkg/path/path.go b/pkg/path/path.go new file mode 100644 index 0000000..e1be8c3 --- /dev/null +++ b/pkg/path/path.go @@ -0,0 +1,62 @@ +// Package path provides objects to define lines on a sewing pattern. +package path + +import ( + "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" + "github.com/tdewolff/canvas" +) + +// Polygon defines a set of straight lines through points. +type Polygon struct { + points []point.Point + style Style + id util.ID +} + +func (p *Polygon) Through() []point.Point { + return p.points +} + +// NewPolygon returns a new [Polygon]. +func NewPolygon(points []point.Point, style Style, id util.ID) *Polygon { + return &Polygon{points: points, style: style} +} + +// WithStyle updates the style of the Polygon. +func (p *Polygon) WithStyle(Style Style) *Polygon { + p.style = Style + return p +} + +// Draw the path to the provided [canvas.Canvas]. +func (p *Polygon) Draw(c *canvas.Canvas) error { + polyline := canvas.Polyline{} + for _, next := range p.points { + polyline.Add(next.Vector().Values()) + } + + c.RenderPath(polyline.ToPath(), p.style.ToCanvas(), canvas.Identity) + + return nil +} + +func (p *Polygon) Length() (float64, error) { + length := 0.0 + + for i := range p.points[1:] { + length += p.points[i].Position().Distance(p.points[i+1].Position()) + } + + return length, nil +} + +func (p *Polygon) ID() util.ID { + return p.id +} + +type Path interface { + Draw(c *canvas.Canvas) error + Length() (float64, error) + Through() []point.Point +} diff --git a/pkg/pattern/path/splines.go b/pkg/path/splines.go similarity index 91% rename from pkg/pattern/path/splines.go rename to pkg/path/splines.go index ecf1c67..aa80281 100644 --- a/pkg/pattern/path/splines.go +++ b/pkg/path/splines.go @@ -2,17 +2,19 @@ package path import ( "fmt" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" + "log/slog" + + "git.wtrh.nl/patterns/gopatterns/pkg/point" "github.com/tdewolff/canvas" splines "gitlab.com/Achilleshiel/gosplines" - "log/slog" ) const resolution = 40 // Spline defines a smooth curved path through points. type Spline struct { - *Path + *Polygon start, end point.Point } @@ -20,6 +22,7 @@ type SplineOpts struct { Start, End point.Point Points []point.Point Style Style + ID util.ID } // NewSpline returns a new spline through points. Start and end points can be provided as @@ -27,9 +30,9 @@ type SplineOpts struct { // are no constraints on the direction. func NewSpline(opts SplineOpts) Spline { s := Spline{ - Path: NewPath(opts.Points, opts.Style), - start: opts.Start, - end: opts.End, + Polygon: NewPolygon(opts.Points, opts.Style, opts.ID), + start: opts.Start, + end: opts.End, } if opts.Start == nil && len(opts.Points) > 1 { @@ -50,7 +53,7 @@ func (s Spline) Draw(c *canvas.Canvas) error { return fmt.Errorf("generating spline: %w", err) } - path := NewPath(points, s.style) + path := NewPolygon(points, s.style, s.id) if err = path.Draw(c); err != nil { return fmt.Errorf("draw spline points to canvas: %w", err) diff --git a/pkg/pattern/path/style.go b/pkg/path/style.go similarity index 100% rename from pkg/pattern/path/style.go rename to pkg/path/style.go diff --git a/pkg/pattern/panel/panel.go b/pkg/pattern/panel/panel.go new file mode 100644 index 0000000..c186d57 --- /dev/null +++ b/pkg/pattern/panel/panel.go @@ -0,0 +1,35 @@ +package panel + +import ( + "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" + "git.wtrh.nl/patterns/gopatterns/pkg/path" + "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" + "github.com/tdewolff/canvas" +) + +type Panel struct { + Name string + Lines map[util.ID]path.Path + Points map[util.ID]point.Point + Dimensions dimensions.Dimensions +} + +func (p Panel) Draw(c *canvas.Canvas, face *canvas.FontFace, debug bool) error { + for _, line := range p.Lines { + err := line.Draw(c) + if err != nil { + return err + } + + for _, throughPoint := range line.Through() { + throughPoint.SetDraw() + } + } + + for _, drawPoints := range p.Points { + point.Draw(c, drawPoints, face, debug) + } + + return nil +} diff --git a/pkg/pattern/path/path.go b/pkg/pattern/path/path.go deleted file mode 100644 index 647723e..0000000 --- a/pkg/pattern/path/path.go +++ /dev/null @@ -1,36 +0,0 @@ -// Package path provides objects to define lines on a sewing pattern. -package path - -import ( - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" - "github.com/tdewolff/canvas" -) - -// Path defines a set of straight lines through points. -type Path struct { - points []point.Point - style Style -} - -// NewPath returns a new [Path]. -func NewPath(points []point.Point, style Style) *Path { - return &Path{points: points, style: style} -} - -// WithStyle updates the style of the Path. -func (p *Path) WithStyle(Style Style) *Path { - p.style = Style - return p -} - -// Draw the path to the provided [canvas.Canvas]. -func (p *Path) Draw(c *canvas.Canvas) error { - polyline := canvas.Polyline{} - for _, next := range p.points { - polyline.Add(next.Vector().Values()) - } - - c.RenderPath(polyline.ToPath(), p.style.ToCanvas(), canvas.Identity) - - return nil -} diff --git a/pkg/pattern/pattern.go b/pkg/pattern/pattern.go index e066893..b3ee4d3 100644 --- a/pkg/pattern/pattern.go +++ b/pkg/pattern/pattern.go @@ -3,9 +3,11 @@ package pattern import ( "fmt" + "git.wtrh.nl/patterns/gopatterns/pkg/util" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" + "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" "git.wtrh.nl/patterns/gopatterns/pkg/pattern/text" + "git.wtrh.nl/patterns/gopatterns/pkg/point" "github.com/tdewolff/canvas" "golang.org/x/image/font/gofont/goregular" "gopkg.in/Knetic/govaluate.v3" @@ -13,13 +15,14 @@ import ( // The Pattern contains all the points, lines and dimensions to draw a pattern to a canvas. type Pattern struct { - points map[point.ID]point.Point + points map[util.ID]point.Point lines []pathDrawer - dimensions Dimensions + dimensions dimensions.Dimensions texts []*text.Text } type pathDrawer interface { + Length() (float64, error) Draw(c *canvas.Canvas) error } @@ -34,7 +37,7 @@ func (p *Pattern) AddLine(line pathDrawer) { } // GetPoints returns a slice with points for the given IDs. -func (p *Pattern) GetPoints(id []point.ID) []point.Point { +func (p *Pattern) GetPoints(id []util.ID) []point.Point { points := make([]point.Point, 0, len(id)) for _, i := range id { points = append(points, p.GetPoint(i)) @@ -44,16 +47,16 @@ func (p *Pattern) GetPoints(id []point.ID) []point.Point { } // GetPoint returns the point for the given ID. -func (p *Pattern) GetPoint(id point.ID) point.Point { //nolint:ireturn +func (p *Pattern) GetPoint(id util.ID) point.Point { //nolint:ireturn return p.points[id] } // NewPattern returns a new Pattern. func NewPattern() *Pattern { return &Pattern{ - points: make(map[point.ID]point.Point, 32), + points: make(map[util.ID]point.Point, 32), lines: make([]pathDrawer, 0, 32), - dimensions: make(Dimensions), + dimensions: make(dimensions.Dimensions), texts: make([]*text.Text, 0), } } @@ -94,6 +97,11 @@ func (p *Pattern) AddText(t *text.Text) { p.texts = append(p.texts, t) } -func (p *Pattern) SetDimensions(dimensions Dimensions) { +func (p *Pattern) SetDimensions(dimensions dimensions.Dimensions) { p.dimensions = dimensions } + +// AddDimension adds a dimension to a pattern. +func (p *Pattern) AddDimension(id dimensions.DimensionID, dimension dimensions.Dimension) { + p.dimensions[id] = dimension +} diff --git a/pkg/pattern/template/fixtures/classic_trouser_block.yaml b/pkg/pattern/template/fixtures/classic_trouser_block.yaml deleted file mode 100644 index 3d645f4..0000000 --- a/pkg/pattern/template/fixtures/classic_trouser_block.yaml +++ /dev/null @@ -1,119 +0,0 @@ ---- -points: - 0: - position: - 1: - position: - y: -(body_rise + 10) - relativeTo: 0 - 2: - position: - y: -inside_leg - relativeTo: 1 - 3: - position: - y: inside_leg/2+50 - relativeTo: 2 - 4: - position: - y: body_rise/4 - relativeTo: 1 - 5: - position: - x: -(seat/8 - 10) - relativeTo: 1 - 6: - position: - x: -(seat/8 - 10) - relativeTo: 4 - 7: - position: - x: -(seat/8 - 10) - relativeTo: 0 - 8: - position: - x: seat/4 + 20 - relativeTo: 6 - 9: - position: - x: -(seat/16 + 5) - relativeTo: 5 - 10: - position: - x: 10 - relativeTo: 7 - 11: - position: - x: trouser_waist/4 + 25 - relativeTo: 10 - 12: - position: - x: trouser_bottom_width/2 - relativeTo: 2 - 13: - position: - x: -trouser_bottom_width/2 - relativeTo: 2 - 14: - position: - x: trouser_bottom_width/2 + 15 - relativeTo: 3 - 15: - position: - x: -(trouser_bottom_width/2 + 15) - relativeTo: 3 - -panels: - front: - points: - extend12-14: - between: - from: 12 - to: 14 - offset: 1.3 - between11-8: - between: - from: 8 - to: 11 - offset: 0.5 - between9-15: - between: - from: 15 - to: 9 - offset: 0.5 - offset_between11-8: - position: - x: 5 - relativeTo: between11-8 - offset_between9-15: - position: - x: 5 - relativeTo: between9-15 - extend13-15: - between: - from: 13 - to: 15 - offset: 1.5 - r5: - position: - rotation: 3*pi/4 - relativeTo: 5 - h5: - position: - x: 30 - relativeTo: r5 - hide: true - lines: - - through: [0,4,1,3,2] - - through: [14,12,13,15] - - through: [14,8,11] - curve: - start: extend12-14 - end: offset_between11-8 - - through: [15,9] - curve: - start: extend13-15 - - through: [9, h5, 6] - curve: {} - - through: [6, 10, 11] - diff --git a/pkg/pattern/template/fixtures/trouser.yaml b/pkg/pattern/template/fixtures/trouser.yaml deleted file mode 100644 index 2b35c53..0000000 --- a/pkg/pattern/template/fixtures/trouser.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -owner: Wouter -sizes: - body_rise: 281 - inside_leg: 800 - seat: 1020 - trouser_waist: 900 - trouser_bottom_width: 226 -template: fixtures/classic_trouser_block.yaml \ No newline at end of file diff --git a/pkg/pattern/template/line.go b/pkg/pattern/template/line.go deleted file mode 100644 index 1c7c21d..0000000 --- a/pkg/pattern/template/line.go +++ /dev/null @@ -1,69 +0,0 @@ -package template - -import ( - "git.wtrh.nl/patterns/gopatterns/pkg/pattern" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/path" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" -) - -// Lines contains one Line or more in a slice. -type Lines []Line - -// Line describes a pattern line. -type Line struct { - Through []point.ID `yaml:"through"` - Curve *Curve `yaml:"curve,omitempty"` - Style *Style `yaml:"style,omitempty"` -} - -type Style struct { - Thickness *float64 `yaml:"thickness,omitempty"` -} - -// Curve describes if a Line curves and if it has start and end constraints. -type Curve struct { - Start point.ID `yaml:"start,omitempty"` - End point.ID `yaml:"end,omitempty"` -} - -// Build adds the line to the provided [pattern.Pattern]. -func (l Line) Build(pat *pattern.Pattern) error { - points := pat.GetPoints(l.Through) - for _, p := range points { - p.SetDraw() - } - - style := path.NewDefaultStyle() - - if l.Style != nil && l.Style.Thickness != nil { - style.Thickness = *l.Style.Thickness - } - - switch { - case l.Curve != nil: - pat.AddLine( - path.NewSpline(path.SplineOpts{ - Start: pat.GetPoint(l.Curve.Start), - End: pat.GetPoint(l.Curve.End), - Points: points, - Style: style, - }), - ) - default: - pat.AddLine(path.NewPath(points, style)) - } - - return nil -} - -// Build adds all the lines to the provided [pattern.Pattern]. -func (l Lines) Build(pat *pattern.Pattern) error { - for _, line := range l { - err := line.Build(pat) - if err != nil { - return err - } - } - - return nil -} diff --git a/pkg/pattern/template/renderer.go b/pkg/pattern/template/renderer.go deleted file mode 100644 index 27328db..0000000 --- a/pkg/pattern/template/renderer.go +++ /dev/null @@ -1,124 +0,0 @@ -package template - -import ( - "fmt" - "path/filepath" - "slices" - "strings" - - "git.wtrh.nl/patterns/gopatterns/pkg/pattern" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/text" - "github.com/stoewer/go-strcase" - "github.com/tdewolff/canvas" - "github.com/tdewolff/canvas/renderers" -) - -// RenderPatterns loads a [Request] from yaml file and renders the pattern to an SVG. -func (s Storage) RenderPatterns(request Request, outputDir string, debug bool) ([]string, error) { - template, err := s.LoadTemplate(request.Template) - if err != nil { - return nil, fmt.Errorf("load pattern %q: %w", request.Template, err) - } - - filenames := make([]string, 0, len(template.Panels)) - - dim, err := s.Dimensions(request.Sizes) - if err != nil { - return nil, fmt.Errorf("load dimensions: %w", err) - } - - renderer := Renderer{dimensions: dim, owner: request.Owner, pattern: request.Template} - - for name, panel := range template.Panels { - pat := pattern.NewPattern() - pat.SetDimensions(dim) - - err = template.Points.AddToPattern(pat) - if err != nil { - return nil, fmt.Errorf("add generic points to pattern: %w", err) - } - - err = renderer.BuildPanel(panel, pat) - if err != nil { - return nil, fmt.Errorf("constructing %s panel: %w", name, err) - } - - c := canvas.New(200, 200) - - err = pat.ToCanvas(c, debug) - if err != nil { - return nil, fmt.Errorf("write pattern to canvas: %w", err) - } - - c.Fit(10) - - filename := filepath.Join(outputDir, strings.Join([]string{ - request.Template, name, - strcase.SnakeCase(request.Owner), - }, "_")+".pdf") - - filenames = append(filenames, filename) - - err = renderers.Write(filename, c) - if err != nil { - return nil, fmt.Errorf("write canvas to file: %w", err) - } - } - - return filenames, nil -} - -type Renderer struct { - dimensions pattern.Dimensions - owner string - pattern string -} - -// BuildPanel translates the panel to the provided [pattern.Pattern]. -func (r Renderer) BuildPanel(panel Panel, pat *pattern.Pattern) error { - err := panel.Points.AddToPattern(pat) - if err != nil { - return err - } - - err = panel.Lines.Build(pat) - if err != nil { - return err - } - - err = r.GenerateInformation(panel, pat) - if err != nil { - return err - } - - return nil -} - -func (r Renderer) GenerateInformation(p Panel, pat *pattern.Pattern) error { - err := Points{"_information": p.Information.Point}.AddToPattern(pat) - if err != nil { - return err - } - - dimensions := make([]string, 0, len(r.dimensions)) - for _, dimension := range r.dimensions { - dimensions = append(dimensions, - fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10)) - } - - slices.Sort(dimensions) - dimensions = append([]string{ - "For: " + r.owner, - "Pattern: " + startCase(r.pattern), - "Panel: " + p.Name, - "Hem allowance: " + p.Allowances.Hem, - "Seam allowance: " + p.Allowances.Seam, - "\nMeasurements:", - }, dimensions...) - - point := pat.GetPoint("_information") - point.SetHide() - pat.AddText(text.NewText(point, "", strings.Join(dimensions, "\n"))) - - return nil -} diff --git a/pkg/pattern/template/template.go b/pkg/pattern/template/template.go deleted file mode 100644 index 2e6e671..0000000 --- a/pkg/pattern/template/template.go +++ /dev/null @@ -1,56 +0,0 @@ -// Package template makes it possible to describe templates and patterns in yaml files and -// to render them to SVG. -package template - -import ( - "fmt" - "os" - "unicode" - - "gopkg.in/yaml.v3" -) - -// Sizes defines a map with the size name and the size value. -type Sizes map[string]float64 - -// Template contains the generic information to draw a specific clothing pattern. -type Template struct { - Points `yaml:"points"` - Panels `yaml:"panels"` -} - -// LoadPattern reads and decodes a [Request] from a yaml file. -func LoadPattern(name string) (Request, error) { - fh, err := os.Open(name) - if err != nil { - return Request{}, fmt.Errorf("open pattern file %q: %w", name, err) - } - - pat := Request{} - - err = yaml.NewDecoder(fh).Decode(&pat) - if err != nil { - return Request{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err) - } - - return pat, nil -} - -func startCase(text string) string { - output := make([]rune, len(text)) - - for i, val := range text { - switch { - case i == 0: - output[i] = unicode.ToUpper(val) - case val == '_': - output[i] = ' ' - case output[i-1] == ' ': - output[i] = unicode.ToUpper(val) - default: - output[i] = val - } - } - - return string(output) -} diff --git a/pkg/pattern/text/text.go b/pkg/pattern/text/text.go index 3d92c3e..5628659 100644 --- a/pkg/pattern/text/text.go +++ b/pkg/pattern/text/text.go @@ -1,7 +1,7 @@ package text import ( - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" + "git.wtrh.nl/patterns/gopatterns/pkg/point" "github.com/tdewolff/canvas" ) diff --git a/pkg/pattern/point/absolute_point.go b/pkg/point/absolute_point.go similarity index 91% rename from pkg/pattern/point/absolute_point.go rename to pkg/point/absolute_point.go index 5b9a587..6b4e912 100644 --- a/pkg/pattern/point/absolute_point.go +++ b/pkg/point/absolute_point.go @@ -2,6 +2,7 @@ package point import ( "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/vector" "github.com/tdewolff/canvas" ) @@ -9,7 +10,7 @@ import ( // AbsolutePoint implements Point and defines an absolute position. // All other points should eventually be relative to one or more absolute point(s). type AbsolutePoint struct { - id ID + id util.ID position position.Position name string draw bool @@ -22,7 +23,7 @@ func (a *AbsolutePoint) Matrix() canvas.Matrix { } // ID returns the point ID. -func (a *AbsolutePoint) ID() ID { +func (a *AbsolutePoint) ID() util.ID { return a.id } @@ -42,7 +43,7 @@ func (a *AbsolutePoint) Vector() vector.Vector { } // NewAbsolutePoint returns a new absolute point. -func NewAbsolutePoint(x, y, r float64, id ID) *AbsolutePoint { +func NewAbsolutePoint(x, y, r float64, id util.ID) *AbsolutePoint { return &AbsolutePoint{ position: position.Position{ Vector: vector.Vector{ diff --git a/pkg/pattern/point/between_point.go b/pkg/point/between_point.go similarity index 92% rename from pkg/pattern/point/between_point.go rename to pkg/point/between_point.go index a4551c6..33168aa 100644 --- a/pkg/pattern/point/between_point.go +++ b/pkg/point/between_point.go @@ -1,6 +1,7 @@ package point import ( + "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "git.wtrh.nl/patterns/gopatterns/pkg/position" @@ -10,7 +11,7 @@ import ( // BetweenPoint defines a point on the line between two other points. type BetweenPoint struct { - id ID + id util.ID p Point q Point offset float64 @@ -23,7 +24,7 @@ type BetweenPoint struct { // The given offset defines where the new point is. // With offset = 0 the new point is a p, offset = 0.5 results in a point exactly in the middle. // Offset can be <0 (extending from p side) or >1 (extending from the q side). -func NewBetweenPoint(p, q Point, offset float64, id ID) *BetweenPoint { +func NewBetweenPoint(p, q Point, offset float64, id util.ID) *BetweenPoint { return &BetweenPoint{ id: id, p: p, @@ -57,7 +58,7 @@ func (b *BetweenPoint) Matrix() canvas.Matrix { } // ID returns the point ID. -func (b *BetweenPoint) ID() ID { +func (b *BetweenPoint) ID() util.ID { return b.id } diff --git a/pkg/pattern/point/extend_point.go b/pkg/point/extend_point.go similarity index 89% rename from pkg/pattern/point/extend_point.go rename to pkg/point/extend_point.go index e6b903b..d8c1f6f 100644 --- a/pkg/pattern/point/extend_point.go +++ b/pkg/point/extend_point.go @@ -1,6 +1,7 @@ package point import ( + "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "git.wtrh.nl/patterns/gopatterns/pkg/position" @@ -10,7 +11,7 @@ import ( // ExtendPoint defines a point on the line between two other points. type ExtendPoint struct { - id ID + id util.ID from Point to Point extend float64 @@ -22,7 +23,7 @@ type ExtendPoint struct { // NewExtendPoint returns a new ExtendPoint relative to two other points from and to. // The given offset defines where the new point is. // With offset = 0 the new point is a from, offset = 0.5 results in a point exactly in the middle. -func NewExtendPoint(from, to Point, extend float64, id ID) *ExtendPoint { +func NewExtendPoint(from, to Point, extend float64, id util.ID) *ExtendPoint { return &ExtendPoint{ id: id, from: from, @@ -36,7 +37,7 @@ func NewExtendPoint(from, to Point, extend float64, id ID) *ExtendPoint { func (b *ExtendPoint) Position() position.Position { return position.Position{ Vector: b.to.Vector().Add(b.extendedVector()), - Rotation: b.to.Vector().AngleBetween(b.to.Vector()) - math.Pi/2, + Rotation: b.from.Vector().AngleBetween(b.to.Vector()) - math.Pi/2, } } @@ -56,7 +57,7 @@ func (b *ExtendPoint) Matrix() canvas.Matrix { } // ID returns the point ID. -func (b *ExtendPoint) ID() ID { +func (b *ExtendPoint) ID() util.ID { return b.id } diff --git a/pkg/pattern/point/point.go b/pkg/point/point.go similarity index 97% rename from pkg/pattern/point/point.go rename to pkg/point/point.go index d77eb86..3cf0dee 100644 --- a/pkg/pattern/point/point.go +++ b/pkg/point/point.go @@ -5,17 +5,15 @@ package point import ( "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/vector" "github.com/tdewolff/canvas" ) -// ID defines a point id. -type ID string - // Point defines the interface for different types of points. type Point interface { // ID returns the point ID. - ID() ID + ID() util.ID // Position calculates and returns the absolute [position.Position]. Position() position.Position diff --git a/pkg/pattern/point/relative_point.go b/pkg/point/relative_point.go similarity index 86% rename from pkg/pattern/point/relative_point.go rename to pkg/point/relative_point.go index d570ba7..0dbab40 100644 --- a/pkg/pattern/point/relative_point.go +++ b/pkg/point/relative_point.go @@ -1,6 +1,7 @@ package point import ( + "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "git.wtrh.nl/patterns/gopatterns/pkg/position" @@ -14,7 +15,7 @@ type RelativePoint struct { name string point Point relativeOffset position.Position - id ID + id util.ID hide bool } @@ -29,7 +30,7 @@ func (r *RelativePoint) Done() Point { //nolint:ireturn } // MarkWith sets the ID of the point. -func (r *RelativePoint) MarkWith(n ID) *RelativePoint { +func (r *RelativePoint) MarkWith(n util.ID) *RelativePoint { r.id = n if r.name == "" { @@ -56,12 +57,12 @@ func (r *RelativePoint) Vector() vector.Vector { } // ID returns the point ID. -func (r *RelativePoint) ID() ID { +func (r *RelativePoint) ID() util.ID { return r.id } // NewRelativePointWithVector returns a new RelativePoint. -func NewRelativePointWithVector(point Point, p vector.Vector, id ID) *RelativePoint { +func NewRelativePointWithVector(point Point, p vector.Vector, id util.ID) *RelativePoint { return &RelativePoint{ point: point, relativeOffset: position.Position{Vector: p}, @@ -78,7 +79,7 @@ func NewRelativePoint(point Point) *RelativePoint { } // NewRotationPoint returns a new RelativePoint with a specific rotation. -func NewRotationPoint(point Point, a float64, id ID) *RelativePoint { +func NewRotationPoint(point Point, a float64, id util.ID) *RelativePoint { p := &RelativePoint{ point: point, relativeOffset: position.Position{ @@ -115,22 +116,22 @@ func (r *RelativePoint) WithRotationOffset(value float64) *RelativePoint { } // NewRelativePointBelow returns a RelativePoint distance f below another Point. -func NewRelativePointBelow(point Point, f float64, id ID) *RelativePoint { +func NewRelativePointBelow(point Point, f float64, id util.ID) *RelativePoint { return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: -f}, id) } // NewRelativePointAbove returns a RelativePoint distance f above another Point. -func NewRelativePointAbove(point Point, f float64, id ID) *RelativePoint { +func NewRelativePointAbove(point Point, f float64, id util.ID) *RelativePoint { return NewRelativePointWithVector(point, vector.Vector{X: 0, Y: f}, id) } // NewRelativePointLeft returns a RelativePoint distance f left of another Point. -func NewRelativePointLeft(point Point, f float64, id ID) *RelativePoint { +func NewRelativePointLeft(point Point, f float64, id util.ID) *RelativePoint { return NewRelativePointWithVector(point, vector.Vector{X: -f, Y: 0}, id) } // NewRelativePointRight returns a RelativePoint distance f right of another Point. -func NewRelativePointRight(point Point, f float64, id ID) *RelativePoint { +func NewRelativePointRight(point Point, f float64, id util.ID) *RelativePoint { return NewRelativePointWithVector(point, vector.Vector{X: f, Y: 0}, id) } diff --git a/pkg/position/position.go b/pkg/position/position.go index 78a5fb4..e15dcc4 100644 --- a/pkg/position/position.go +++ b/pkg/position/position.go @@ -22,12 +22,6 @@ func (p Position) Add(q Position) Position { } } -// Translate the position to a new reference frame. -func (p Position) Translate(q Position) Position { - q.Vector = q.Vector.Span(q.Rotation) - return p.Add(q) -} - // Distance returns the distance between two positions. func (p Position) Distance(q Position) float64 { return p.Vector.Distance(q.Vector) diff --git a/pkg/position/position_test.go b/pkg/position/position_test.go index 4e7cf74..b95900a 100644 --- a/pkg/position/position_test.go +++ b/pkg/position/position_test.go @@ -98,3 +98,44 @@ func TestPosition_Add(t *testing.T) { }) } } + +func TestPosition_Translate(t *testing.T) { + tests := map[string]struct { + p, q position.Position + result float64 + }{ + "origin to 1,1": { + p: position.Position{ + Vector: vector.Vector{ + X: 1, Y: 1, + }, + }, + q: position.Position{ + Vector: vector.Vector{ + X: 0, Y: 0, + }, + }, + result: math.Sqrt(2), + }, + "0,2 to 4,5 -> 5": { + p: position.Position{ + Vector: vector.Vector{ + X: 0, Y: 2, + }, + }, + q: position.Position{ + Vector: vector.Vector{ + X: 4, Y: 5, + }, + }, + result: 5, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + translateResult := tt.p.Distance(tt.q) + require.InDelta(t, tt.result, translateResult, 1e-10) + }) + } +} diff --git a/pkg/position/testutil/testutil.go b/pkg/position/testutil/testutil.go new file mode 100644 index 0000000..71e161b --- /dev/null +++ b/pkg/position/testutil/testutil.go @@ -0,0 +1,15 @@ +package testutil + +import ( + "testing" + + "git.wtrh.nl/patterns/gopatterns/pkg/position" + "github.com/stretchr/testify/require" +) + +func EqualPosition(tb testing.TB, expected, actual position.Position, delta float64, msgAndArgs ...interface{}) { + tb.Helper() + require.InDelta(tb, expected.Vector.X, actual.Vector.X, delta, msgAndArgs) + require.InDelta(tb, expected.Vector.Y, actual.Vector.Y, delta, msgAndArgs) + require.InDelta(tb, expected.Rotation, actual.Rotation, delta, msgAndArgs) +} diff --git a/pkg/renderer/renderer.go b/pkg/renderer/renderer.go new file mode 100644 index 0000000..e30b7dc --- /dev/null +++ b/pkg/renderer/renderer.go @@ -0,0 +1,164 @@ +package renderer + +import ( + "fmt" + "path/filepath" + "strings" + "unicode" + + "git.wtrh.nl/patterns/gopatterns/pkg/config" + "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" + "git.wtrh.nl/patterns/gopatterns/pkg/template" + "github.com/stoewer/go-strcase" + "github.com/tdewolff/canvas" + "github.com/tdewolff/canvas/renderers" + "golang.org/x/image/font/gofont/goregular" +) + +type Storage interface { + LoadDimensions(sizes config.Sizes) (dimensions.Dimensions, error) + LoadTemplate(name string) (template.Template, error) +} + +// RenderPatterns loads a [Request] from yaml file and renders the pattern to an SVG. +func RenderPatterns(s Storage, request config.Request, outputDir string, debug bool) ([]string, error) { + loadedTemplate, err := s.LoadTemplate(request.Template) + if err != nil { + return nil, fmt.Errorf("load pattern %q: %w", request.Template, err) + } + + filenames := make([]string, 0, len(loadedTemplate.Panels)) + + dim, err := s.LoadDimensions(request.Sizes) + if err != nil { + return nil, fmt.Errorf("load dimensions: %w", err) + } + + // renderer := Renderer{dimensions: dim, owner: request.Owner, pattern: request.Template} + + for name := range loadedTemplate.Panels { + newPanel, err := loadedTemplate.GetPanel(template.Request{Dims: dim, Panel: name}) + if err != nil { + return nil, err + } + // + //pat := pattern.NewPattern() + //pat.SetDimensions(dim) + // + //err = loadedTemplate.Points.AddToPattern(pat) + //if err != nil { + // return nil, fmt.Errorf("add generic points to pattern: %w", err) + //} + // + //err = renderer.BuildPanel(panel, pat) + //if err != nil { + // return nil, fmt.Errorf("constructing %s panel: %w", name, err) + //} + + c := canvas.New(200, 200) + + err = newPanel.Draw(c, loadFont(), debug) + if err != nil { + return nil, fmt.Errorf("write pattern to canvas: %w", err) + } + + c.Fit(10) + + filename := filepath.Join(outputDir, strings.Join([]string{ + request.Template, name, + strcase.SnakeCase(request.Owner), + }, "_")+".pdf") + + filenames = append(filenames, filename) + + err = renderers.Write(filename, c) + if err != nil { + return nil, fmt.Errorf("write canvas to file: %w", err) + } + } + + return filenames, nil +} + +func loadFont() *canvas.FontFace { + fontDejaVu := canvas.NewFontFamily("latin") + + if err := fontDejaVu.LoadFont(goregular.TTF, 0, canvas.FontRegular); err != nil { + panic(err) + } + + return fontDejaVu.Face(12.0, canvas.Black, canvas.FontRegular) +} + +type Renderer struct { + dimensions dimensions.Dimensions + owner string + pattern string +} + +//// BuildPanel translates the panel to the provided [pattern.Pattern]. +//func (r Renderer) BuildPanel(panel template.Panel, pat *pattern.Pattern) error { +// err := panel.Points.AddToPattern(pat) +// if err != nil { +// return err +// } +// +// err = panel.Lines.Build(pat) +// if err != nil { +// return err +// } +// +// err = r.GenerateInformation(panel, pat) +// if err != nil { +// return err +// } +// +// return nil +//} + +//func (r Renderer) GenerateInformation(p panel.Panel, pat *pattern.Pattern) error { +// err := template.Points{"_information": p.Information.Point} +// if err != nil { +// return err +// } +// +// dims := make([]string, 0, len(r.dimensions)) +// for _, dimension := range r.dimensions { +// dims = append(dims, fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10)) +// } +// +// slices.Sort(dims) +// dims = append([]string{ +// "For: " + r.owner, +// "Pattern: " + startCase(r.pattern), +// "Panel: " + p.Name, +// "Hem allowance: " + p.Allowances.Hem, +// "Seam allowance: " + p.Allowances.Seam, +// "\nMeasurements:", +// }, dims...) +// +// point := pat.GetPoint("_information") +// point.SetHide() +// pat.AddText(text.NewText(point, "", strings.Join(dims, "\n"))) +// +// return nil +//} + +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/storage.go b/pkg/storage/storage.go similarity index 54% rename from pkg/pattern/template/storage.go rename to pkg/storage/storage.go index df8e7d7..23f0837 100644 --- a/pkg/pattern/template/storage.go +++ b/pkg/storage/storage.go @@ -1,11 +1,13 @@ -package template +package storage import ( "fmt" "io/fs" "os" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern" + "git.wtrh.nl/patterns/gopatterns/pkg/config" + "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" + "git.wtrh.nl/patterns/gopatterns/pkg/template" "gopkg.in/yaml.v3" ) @@ -22,13 +24,13 @@ func NewStorage(dir string) (Storage, error) { return Storage{dir: os.DirFS(dir)}, nil } -func (s Storage) Dimensions(sizes Sizes) (pattern.Dimensions, error) { +func (s Storage) LoadDimensions(sizes config.Sizes) (dimensions.Dimensions, error) { f, err := s.dir.Open("dimension_names.yaml") if err != nil { return nil, fmt.Errorf("open \"dimension_names.yaml\": %w", err) } - namedDimensions := pattern.Dimensions{} + namedDimensions := dimensions.Dimensions{} err = yaml.NewDecoder(f).Decode(&namedDimensions) if err != nil { @@ -49,27 +51,19 @@ func (s Storage) Dimensions(sizes Sizes) (pattern.Dimensions, error) { return namedDimensions, nil } -// Request contains the information to draw a pattern for a specific owner, with corresponding -// sizes and the template name. -type Request struct { - Sizes Sizes `yaml:"sizes,omitempty"` - Owner string `yaml:"owner"` - Template string `yaml:"template,omitempty"` -} - // LoadTemplate reads and decodes a [Template] from a yaml file. -func (s Storage) LoadTemplate(name string) (Template, error) { +func (s Storage) LoadTemplate(name string) (template.Template, error) { fh, err := s.dir.Open(name + ".yaml") if err != nil { - return Template{}, fmt.Errorf("open template file %q: %w", name, err) + return template.Template{}, fmt.Errorf("open template file %q: %w", name, err) } - template := Template{} + t := template.Template{} - err = yaml.NewDecoder(fh).Decode(&template) + err = yaml.NewDecoder(fh).Decode(&t) if err != nil { - return Template{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err) + return template.Template{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err) } - return template, nil + return t, nil } diff --git a/pkg/template/fixtures/absolute_points.yaml b/pkg/template/fixtures/absolute_points.yaml new file mode 100644 index 0000000..709114e --- /dev/null +++ b/pkg/template/fixtures/absolute_points.yaml @@ -0,0 +1,13 @@ +--- +points: + 1: + position: + x: test + y: 14.3 +panels: + body: + points: + 2: + position: + x: test*2 + y: -3 diff --git a/pkg/template/fixtures/between_points.yaml b/pkg/template/fixtures/between_points.yaml new file mode 100644 index 0000000..3c144a0 --- /dev/null +++ b/pkg/template/fixtures/between_points.yaml @@ -0,0 +1,20 @@ +--- +points: + 1: + position: + x: 4 + y: 14.3 +panels: + body: + points: + 2: + relativeTo: + 1 + position: + x: 2 + y: -3 + 3: + between: + from: 1 + to: 2 + offset: test diff --git a/pkg/template/fixtures/evaluation_functions.yaml b/pkg/template/fixtures/evaluation_functions.yaml new file mode 100644 index 0000000..29e795c --- /dev/null +++ b/pkg/template/fixtures/evaluation_functions.yaml @@ -0,0 +1,19 @@ +--- +points: + acos: + position: + x: acos(1/2) + asin: + position: + x: asin(pi/2) + atan2: + position: + x: atan2(1,1) + abs1: + position: + x: abs(-1.4) + abs2: + position: + x: abs(3.5) +panels: + test: {} \ No newline at end of file diff --git a/pkg/template/fixtures/extend_points.yaml b/pkg/template/fixtures/extend_points.yaml new file mode 100644 index 0000000..ce1cbfe --- /dev/null +++ b/pkg/template/fixtures/extend_points.yaml @@ -0,0 +1,18 @@ +--- +points: + 1: {} +panels: + body: + points: + 2: + relativeTo: + 1 + position: + x: 4 + y: 3 + 3: + extend: + from: 1 + to: 2 + offset: 5 + diff --git a/pkg/template/fixtures/functions.yaml b/pkg/template/fixtures/functions.yaml new file mode 100644 index 0000000..efd48b7 --- /dev/null +++ b/pkg/template/fixtures/functions.yaml @@ -0,0 +1,52 @@ +--- +points: + 1: {} + 2: + position: + x: 4 + y: 3 + distance: + position: + x: DistanceBetween("1","2") + angle: + position: + x: AngleBetween("1","2") + yDistance: + position: + x: YDistanceBetween("1","2") + xDistance: + position: + x: XDistanceBetween("1","2") + lineLength: + position: + x: LineLength("test.1") + lineLength2: + position: + x: LineLength("test.2") +panels: + test: + points: + 3: + relativeTo: 1 + position: + y: 1 + 4: + relativeTo: 2 + position: + y: 1 + 5: + position: + x: 1 + 6: + position: + y: 1 + x: 1 + 7: + position: + y: 1 + lines: + 1: + through: [1,2,4,3,1] + 2: + through: [1,5,6,7,1] + diff --git a/pkg/template/fixtures/polar_points.yaml b/pkg/template/fixtures/polar_points.yaml new file mode 100644 index 0000000..b579297 --- /dev/null +++ b/pkg/template/fixtures/polar_points.yaml @@ -0,0 +1,17 @@ +--- +points: + 1: {} +panels: + body: + points: + 2: + relativeTo: 1 + polar: + rotation: 0 + length: 1 + 3: + relativeTo: 2 + polar: + rotation: pi/2 + length: 1 + diff --git a/pkg/template/fixtures/references.yaml b/pkg/template/fixtures/references.yaml new file mode 100644 index 0000000..04d82f2 --- /dev/null +++ b/pkg/template/fixtures/references.yaml @@ -0,0 +1,12 @@ +--- +points: + 1: {} + 3: + relativeTo: body.2 +panels: + body: + points: + 2: + relativeTo: 1 + position: + x: 3 diff --git a/pkg/template/fixtures/relative_points.yaml b/pkg/template/fixtures/relative_points.yaml new file mode 100644 index 0000000..52eae9a --- /dev/null +++ b/pkg/template/fixtures/relative_points.yaml @@ -0,0 +1,15 @@ +--- +points: + 1: + position: + x: test + y: 14.3 +panels: + body: + points: + 2: + relativeTo: + 1 + position: + x: test*2 + y: -3 diff --git a/pkg/pattern/template/information.go b/pkg/template/information.go similarity index 100% rename from pkg/pattern/template/information.go rename to pkg/template/information.go diff --git a/pkg/template/line.go b/pkg/template/line.go new file mode 100644 index 0000000..3058cc0 --- /dev/null +++ b/pkg/template/line.go @@ -0,0 +1,157 @@ +package template + +import ( + "errors" + "git.wtrh.nl/patterns/gopatterns/pkg/util" + + "git.wtrh.nl/patterns/gopatterns/pkg/path" + "git.wtrh.nl/patterns/gopatterns/pkg/pattern" + "git.wtrh.nl/patterns/gopatterns/pkg/point" +) + +var ErrLineNotFound = errors.New("required path not found") + +// Lines contain named lines. +type Lines map[util.ID]Line + +// Line describes a pattern line. +type Line struct { + Through []util.ID `yaml:"through"` + Curve *Curve `yaml:"curve,omitempty"` + Style *Style `yaml:"style,omitempty"` +} + +type Style struct { + Thickness *float64 `yaml:"thickness,omitempty"` +} + +// Curve describes if a Line curves and if it has start and end constraints. +type Curve struct { + Start util.ID `yaml:"start,omitempty"` + End util.ID `yaml:"end,omitempty"` +} + +func (t Template) templateLine(panelName string, id util.ID) (Line, error) { + if id.Panel() != "" { + panelName = id.Panel() + } + + panel, err := t.Panel(panelName) + if !errors.Is(err, ErrPanelNotFound) { + l, ok := panel.Lines[id.Deref()] + if ok { + return l, nil + } + } + + line, ok := t.Lines[id.Deref()] + if !ok { + return Line{}, ErrLineNotFound + } + + return line, nil +} + +func (t Template) getOrCreateLine(id util.ID, req request, depth int) (path.Path, error) { + l, ok := req.lines[id] + if ok { + return l, nil + } + + newLine, err := t.createLine(id, req, depth+1) + if err != nil { + return nil, err + } + + req.lines[util.ID(id)] = newLine + + return newLine, nil +} + +func (t Template) createLine(id util.ID, req request, depth int) (path.Path, error) { + line, err := t.templateLine(req.name, id) + if err != nil { + return nil, err + } + + throughPoints, err := t.getOrCreatePoints(line.Through, req, depth+1) + if err != nil { + return nil, err + } + + style := path.NewDefaultStyle() + + if line.Style != nil && line.Style.Thickness != nil { + style.Thickness = *line.Style.Thickness + } + + switch { + case line.Curve != nil: + var start, end point.Point + if line.Curve.Start != "" { + start, err = t.getOrCreatePoint(line.Curve.Start, req, depth+1) + if err != nil { + return nil, err + } + } + + if line.Curve.End != "" { + end, err = t.getOrCreatePoint(line.Curve.End, req, depth+1) + if err != nil { + return nil, err + } + } + + return path.NewSpline(path.SplineOpts{ + Start: start, + End: end, + Points: throughPoints, + Style: style, + ID: util.ID(id), + }), nil + default: + return path.NewPolygon(throughPoints, style, util.ID(id)), nil + } +} + +// Build adds the line to the provided [pattern.Pattern]. +func (l Line) Build(pat *pattern.Pattern) error { + points := pat.GetPoints(l.Through) + for _, p := range points { + p.SetDraw() + } + + style := path.NewDefaultStyle() + + if l.Style != nil && l.Style.Thickness != nil { + style.Thickness = *l.Style.Thickness + } + + switch { + case l.Curve != nil: + pat.AddLine( + path.NewSpline(path.SplineOpts{ + Start: pat.GetPoint(l.Curve.Start), + End: pat.GetPoint(l.Curve.End), + Points: points, + Style: style, + }), + ) + default: + pat.AddLine(path.NewPolygon(points, style, "")) + } + + return nil +} + +// Build adds all the lines to the provided [pattern.Pattern]. +func (l Lines) Build(pat *pattern.Pattern) error { + for _, line := range l { + err := line.Build(pat) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/pattern/template/panel.go b/pkg/template/panel.go similarity index 57% rename from pkg/pattern/template/panel.go rename to pkg/template/panel.go index 7ea0352..b6701b0 100644 --- a/pkg/pattern/template/panel.go +++ b/pkg/template/panel.go @@ -1,9 +1,15 @@ package template +import ( + "errors" +) + +var ErrPanelNotFound = errors.New("panel does not exist") + // Panels contains a map with named panels. type Panels map[string]Panel -// Panel contains all the lines and extra points to draw a panel. +// The Panel contains all the lines and extra points to draw a panel. type Panel struct { Points Points `yaml:"points"` Lines Lines `yaml:"lines"` @@ -16,3 +22,12 @@ type Allowances struct { Hem string `yaml:"hem"` Seam string `yaml:"seam"` } + +func (t Template) Panel(name string) (Panel, error) { + p, ok := t.Panels[name] + if !ok { + return Panel{}, ErrPanelNotFound + } + + return p, nil +} diff --git a/pkg/pattern/template/point.go b/pkg/template/point.go similarity index 51% rename from pkg/pattern/template/point.go rename to pkg/template/point.go index 3621f7b..622661d 100644 --- a/pkg/pattern/template/point.go +++ b/pkg/template/point.go @@ -3,12 +3,13 @@ package template import ( "errors" "fmt" + "git.wtrh.nl/patterns/gopatterns/pkg/path" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "maps" "math" "strconv" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern" - "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point" + "git.wtrh.nl/patterns/gopatterns/pkg/point" "gopkg.in/Knetic/govaluate.v3" ) @@ -25,12 +26,12 @@ var ( ) // Points contains a map with points. -type Points map[point.ID]Point +type Points map[util.ID]Point // Point contains the template information for a point. type Point struct { Position Position `yaml:"position"` - RelativeTo *point.ID `yaml:"relativeTo,omitempty"` + RelativeTo *util.ID `yaml:"relativeTo,omitempty"` Description string `yaml:"description"` Between *BetweenPoint `yaml:"between"` Extend *ExtendPoint `yaml:"extend"` @@ -38,10 +39,31 @@ type Point struct { Polar *PolarPoint `yaml:"polar"` } +func (t Template) templatePoint(panelName string, id util.ID) (Point, error) { + if id.Panel() != "" { + panelName = id.Panel() + } + + panel, err := t.Panel(panelName) + if !errors.Is(err, ErrPanelNotFound) { + p, ok := panel.Points[id.Deref()] + if ok { + return p, nil + } + } + + p, ok := t.Points[id.Deref()] + if !ok { + return Point{}, ErrPointNotFound + } + + return p, nil +} + var ErrInvalidPointID = errors.New("type cannot be converted to a PointID") -func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionFunction { - functions := p.evaluationFunctions() +func (t Template) functions(req request) map[string]govaluate.ExpressionFunction { + functions := t.evaluationFunctions() maps.Copy(functions, map[string]govaluate.ExpressionFunction{ "DistanceBetween": func(args ...interface{}) (interface{}, error) { @@ -50,7 +72,7 @@ func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionF ErrInvalidArguments) } - points, err := p.getOrCreateFromArgs(pat, args...) + points, err := t.getOrCreatePointsFromArgs(req, args...) if err != nil { return nil, err } @@ -62,7 +84,7 @@ func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionF ErrInvalidArguments) } - points, err := p.getOrCreateFromArgs(pat, args...) + points, err := t.getOrCreatePointsFromArgs(req, args...) if err != nil { return nil, err } @@ -75,12 +97,12 @@ func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionF ErrInvalidArguments) } - points, err := p.getOrCreateFromArgs(pat, args...) + points, err := t.getOrCreatePointsFromArgs(req, args...) if err != nil { return nil, err } - return points[0].Vector().Y - points[1].Vector().Y, nil + return points[1].Vector().Y - points[0].Vector().Y, nil }, "XDistanceBetween": func(args ...interface{}) (interface{}, error) { if len(args) != 2 { @@ -88,28 +110,40 @@ func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionF ErrInvalidArguments) } - points, err := p.getOrCreateFromArgs(pat, args...) + points, err := t.getOrCreatePointsFromArgs(req, args...) + if err != nil { + return nil, err + } + + return points[1].Vector().X - points[0].Vector().X, nil + }, + "LineLength": func(args ...interface{}) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf("function LineLength() requires 2 arguments: %w", ErrInvalidArguments) + } + + line, err := t.getOrCreateLinesFromArgs(req, args...) if err != nil { return nil, err } - return points[0].Vector().X - points[1].Vector().X, nil + return line[0].Length() }, }) return functions } -func (p Points) getOrCreateFromArgs(pat *pattern.Pattern, args ...interface{}) ([]point.Point, error) { +func (t Template) getOrCreatePointsFromArgs(req request, args ...interface{}) ([]point.Point, error) { points := make([]point.Point, 0, len(args)) for i, arg := range args { - id, err := toPointID(arg) + id, err := toID(arg) if err != nil { - return nil, fmt.Errorf("parsing args[%d] to pointID: %w", i, err) + return nil, fmt.Errorf("parsing args[%d] to ID: %w", i, err) } - newPoint, err := p.getOrCreate(id, pat, 0) + newPoint, err := t.getOrCreatePoint(id, req, 0) if err != nil { return nil, fmt.Errorf("get or create point %q: %w", id, err) } @@ -120,7 +154,27 @@ func (p Points) getOrCreateFromArgs(pat *pattern.Pattern, args ...interface{}) ( return points, nil } -func toPointID(arg interface{}) (point.ID, error) { +func (t Template) getOrCreateLinesFromArgs(req request, args ...interface{}) ([]path.Path, error) { + points := make([]path.Path, 0, len(args)) + + for i, arg := range args { + id, err := toID(arg) + if err != nil { + return nil, fmt.Errorf("parsing args[%d] to ID: %w", i, err) + } + + newPoint, err := t.getOrCreateLine(id, req, 0) + if err != nil { + return nil, fmt.Errorf("get or create line %q: %w", id, err) + } + + points = append(points, newPoint) + } + + return points, nil +} + +func toID(arg interface{}) (util.ID, error) { v1, ok := arg.(string) if !ok { f, ok := arg.(float64) @@ -131,63 +185,66 @@ func toPointID(arg interface{}) (point.ID, error) { v1 = strconv.FormatFloat(f, 'f', -1, 64) } - id1 := point.ID(v1) - - return id1, nil + return util.ID(v1), 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"` + From util.ID `yaml:"from"` + To util.ID `yaml:"to"` + Offset *Value `yaml:"offset"` } -func (p Points) evaluationFunctions() map[string]govaluate.ExpressionFunction { +func (t Template) evaluationFunctions() map[string]govaluate.ExpressionFunction { return map[string]govaluate.ExpressionFunction{ "acos": func(args ...interface{}) (interface{}, error) { if len(args) != 1 { - return nil, fmt.Errorf("function acos() requires 1 argument: %w", - ErrInvalidArguments) + return nil, fmt.Errorf("function acos() requires 1 argument: %w", ErrInvalidArguments) } x, ok := args[0].(float64) if !ok { - return nil, fmt.Errorf("evaluate acos(): parsing %q as float64: %w", args[0], - ErrInvalidArguments) + return nil, fmt.Errorf("evaluate acos(): parsing %q as float64: %w", args[0], ErrInvalidArguments) } return math.Acos(x), nil }, "asin": func(args ...interface{}) (interface{}, error) { if len(args) != 1 { - return nil, fmt.Errorf("function asin() requires 1 argument: %w", - ErrInvalidArguments) + return nil, fmt.Errorf("function asin() requires 1 argument: %w", ErrInvalidArguments) } x, ok := args[0].(float64) if !ok { - return nil, fmt.Errorf("evaluate asin(): parsing %q as float64: %w", args[0], - ErrInvalidArguments) + return nil, fmt.Errorf("evaluate asin(): parsing %q as float64: %w", args[0], ErrInvalidArguments) } return math.Asin(x), nil }, + "abs": func(args ...interface{}) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf("function abs() requires 1 argument: %w", ErrInvalidArguments) + } + + x, ok := args[0].(float64) + if !ok { + return nil, fmt.Errorf("evaluate abs(): parsing %q as float64: %w", args[0], ErrInvalidArguments) + } + + return math.Abs(x), nil + }, "atan2": func(args ...interface{}) (interface{}, error) { if len(args) != 2 { - return nil, fmt.Errorf("function atan2() requires 2 arguments: %w", - ErrInvalidArguments) + return nil, fmt.Errorf("function atan2() requires 2 arguments: %w", ErrInvalidArguments) } x, ok := args[0].(float64) if !ok { - return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], - ErrInvalidArguments) + return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], ErrInvalidArguments) } y, ok := args[1].(float64) if !ok { - return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], - ErrInvalidArguments) + return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], ErrInvalidArguments) } return math.Atan2(x, y), nil @@ -195,53 +252,39 @@ func (p Points) evaluationFunctions() map[string]govaluate.ExpressionFunction { } } -// 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 +func (t Template) createPoint(id util.ID, req request, depth int) (p point.Point, err error) { + templatePoint, err := t.templatePoint(req.name, id) + if err != nil { + return nil, fmt.Errorf("creating point: %w", err) } var newPoint point.Point switch { case templatePoint.RelativeTo != nil && templatePoint.Polar != nil: - newPoint, err = p.createPolar(id, pat, depth) + newPoint, err = t.createPolar(id, req, depth) if err != nil { - return err + return nil, err } case templatePoint.RelativeTo != nil: - newPoint, err = p.createRelative(id, pat, depth) + newPoint, err = t.createRelative(id, req, depth) if err != nil { - return err + return nil, err } case templatePoint.Between != nil: - newPoint, err = p.createBetween(id, pat, depth) + newPoint, err = t.createBetween(id, req, depth) if err != nil { - return err + return nil, err } case templatePoint.Extend != nil: - newPoint, err = p.createExtend(id, pat, depth) + newPoint, err = t.createExtend(id, req, depth) if err != nil { - return err + return nil, err } default: - x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat)) + x, y, r, err := templatePoint.Position.evaluate(req.dimensions.Parameters(), t.functions(req)) if err != nil { - return err + return nil, err } newPoint = point.NewAbsolutePoint(x, y, r, id) @@ -251,19 +294,13 @@ func (p Points) addSingleToPattern(id point.ID, pat *pattern.Pattern, depth int) newPoint.SetHide() } - pat.AddPoint(newPoint) - - return nil + return newPoint, 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 +func (t Template) createRelative(id util.ID, req request, depth int) (*point.RelativePoint, error) { + templatePoint, err := t.templatePoint(req.name, id) + if err != nil { + return nil, err } relativePointID := *templatePoint.RelativeTo @@ -271,12 +308,12 @@ func (p Points) createRelative( return nil, ErrRelativePointRecursion } - relativePoint, err := p.getOrCreate(relativePointID, pat, depth) + relativePoint, err := t.getOrCreatePoint(relativePointID, req, depth) if err != nil { return nil, err } - x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat)) + x, y, r, err := templatePoint.Position.evaluate(req.dimensions.Parameters(), t.functions(req)) if err != nil { return nil, err } @@ -287,29 +324,29 @@ func (p Points) createRelative( } //nolint:ireturn,dupl -func (p Points) createBetween(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) { - newPoint, ok := p[id] - if !ok { - return nil, ErrPointNotFound +func (t Template) createBetween(id util.ID, req request, depth int) (point.Point, error) { + newPoint, err := t.templatePoint(req.name, id) + if err != nil { + return nil, err } if newPoint.Between.To == id || newPoint.Between.From == id || depth > maxRecursionDepth { return nil, ErrRelativePointRecursion } - fromPoint, err := p.getOrCreate(newPoint.Between.From, pat, depth) + fromPoint, err := t.getOrCreatePoint(newPoint.Between.From, req, depth) if err != nil { return nil, err } - toPoint, err := p.getOrCreate(newPoint.Between.To, pat, depth) + toPoint, err := t.getOrCreatePoint(newPoint.Between.To, req, depth) if err != nil { return nil, err } - params := pat.Parameters() + params := req.dimensions.Parameters() - offset, err := newPoint.Between.Offset.Evaluate(params, p.Functions(pat)) + offset, err := newPoint.Between.Offset.Evaluate(params, t.functions(req)) if err != nil { return nil, err } @@ -317,47 +354,60 @@ func (p Points) createBetween(id point.ID, pat *pattern.Pattern, depth int) (poi 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) +func (t Template) getOrCreatePoints(ids []util.ID, req request, depth int) ([]point.Point, error) { + points := make([]point.Point, 0, len(ids)) + + for _, id := range ids { + createPoint, err := t.getOrCreatePoint(id, req, depth) if err != nil { return nil, err } + + points = append(points, createPoint) } - createdPoint := pat.GetPoint(id) - if createdPoint == nil { - panic("getPoint cannot be nil") + return points, nil +} + +//nolint:ireturn +func (t Template) getOrCreatePoint(id util.ID, req request, depth int) (point.Point, error) { + p, ok := req.points[id] + if ok { + return p, nil } - return createdPoint, nil + newPoint, err := t.createPoint(id, req, depth+1) + if err != nil { + return nil, fmt.Errorf("creating point %q: %w", id, err) + } + + req.points[id] = newPoint + + return newPoint, nil } //nolint:ireturn,dupl -func (p Points) createExtend(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) { - newPoint, ok := p[id] - if !ok { - return nil, ErrPointNotFound +func (t Template) createExtend(id util.ID, req request, depth int) (point.Point, error) { + newPoint, err := t.templatePoint(req.name, id) + if err != nil { + return nil, err } if newPoint.Extend.To == id || newPoint.Extend.From == id || depth > maxRecursionDepth { return nil, ErrRelativePointRecursion } - fromPoint, err := p.getOrCreate(newPoint.Extend.From, pat, depth) + fromPoint, err := t.getOrCreatePoint(newPoint.Extend.From, req, depth) if err != nil { return nil, err } - toPoint, err := p.getOrCreate(newPoint.Extend.To, pat, depth) + toPoint, err := t.getOrCreatePoint(newPoint.Extend.To, req, depth) if err != nil { return nil, err } - params := pat.Parameters() - - offset, err := newPoint.Extend.Offset.Evaluate(params, p.Functions(pat)) + offset, err := newPoint.Extend.Offset.Evaluate(req.dimensions.Parameters(), t.functions(req)) if err != nil { return nil, err } @@ -366,10 +416,10 @@ func (p Points) createExtend(id point.ID, pat *pattern.Pattern, depth int) (poin } //nolint:ireturn -func (p Points) createPolar(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) { - templatePoint, ok := p[id] - if !ok { - return nil, ErrPointNotFound +func (t Template) createPolar(id util.ID, req request, depth int) (point.Point, error) { + templatePoint, err := t.templatePoint(req.name, id) + if err != nil { + return nil, err } relativePointID := *templatePoint.RelativeTo @@ -377,25 +427,25 @@ func (p Points) createPolar(id point.ID, pat *pattern.Pattern, depth int) (point return nil, ErrRelativePointRecursion } - relativePoint, err := p.getOrCreate(relativePointID, pat, depth) + relativePoint, err := t.getOrCreatePoint(relativePointID, req, depth) if err != nil { return nil, err } - x, y, err := templatePoint.Polar.evaluate(pat.Parameters(), p.Functions(pat)) + x, y, r, err := templatePoint.Polar.evaluate(req.dimensions.Parameters(), t.functions(req)) if err != nil { return nil, err } return point.NewRelativePoint(relativePoint). - WithXOffset(x).WithYOffset(y).MarkWith(id), nil + WithXOffset(x).WithYOffset(y).MarkWith(id).WithRotationOffset(r), nil } // ExtendPoint describes how to draw a new point that extends in line with two points. type ExtendPoint struct { - From point.ID `yaml:"from"` - To point.ID `yaml:"to"` - Offset *Value `yaml:"offset"` + From util.ID `yaml:"from"` + To util.ID `yaml:"to"` + Offset *Value `yaml:"offset"` } // PolarPoint describes how to draw a new point with a direction and a distance from the current @@ -408,16 +458,16 @@ type PolarPoint struct { func (p PolarPoint) evaluate( params govaluate.MapParameters, funcs map[string]govaluate.ExpressionFunction, -) (x, y float64, err error) { +) (x, y, r float64, err error) { rotation, err := p.Rotation.Evaluate(params, funcs) if err != nil { - return 0, 0, err + return 0, 0, 0, err } length, err := p.Length.Evaluate(params, funcs) if err != nil { - return 0, 0, err + return 0, 0, 0, err } - return math.Sin(rotation) * -length, math.Cos(rotation) * -length, nil + return math.Cos(rotation) * length, math.Sin(rotation) * length, rotation, nil } diff --git a/pkg/template/point_test.go b/pkg/template/point_test.go new file mode 100644 index 0000000..99d5943 --- /dev/null +++ b/pkg/template/point_test.go @@ -0,0 +1,289 @@ +package template_test + +import ( + _ "embed" + "git.wtrh.nl/patterns/gopatterns/pkg/util" + "math" + "testing" + + "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" + "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/position/testutil" + "git.wtrh.nl/patterns/gopatterns/pkg/template" + "git.wtrh.nl/patterns/gopatterns/pkg/vector" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +//go:embed fixtures/absolute_points.yaml +var absolutePoints []byte + +//go:embed fixtures/relative_points.yaml +var relativePoints []byte + +//go:embed fixtures/between_points.yaml +var betweenPoints []byte + +//go:embed fixtures/extend_points.yaml +var extendPoints []byte + +//go:embed fixtures/polar_points.yaml +var polarPoints []byte + +//go:embed fixtures/functions.yaml +var functions []byte + +//go:embed fixtures/evaluation_functions.yaml +var evaluationFunctions []byte + +//go:embed fixtures/references.yaml +var references []byte + +func TestAbsolutePoint(t *testing.T) { + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(absolutePoints, temp)) + + panel, err := temp.GetPanel(template.Request{ + Dims: dimensions.Dimensions{"test": dimensions.Dimension{ + Name: "Test", + Value: 1.3, + }}, + Panel: "body", + }) + require.NoError(t, err) + + p1, ok := panel.Points["1"] + require.True(t, ok) + require.Equal(t, position.Position{ + Vector: vector.Vector{ + X: 1.3, + Y: 14.3, + }, + Rotation: 0, + }, p1.Position()) + + p2, ok := panel.Points["2"] + require.True(t, ok) + require.Equal(t, position.Position{ + Vector: vector.Vector{ + X: 2.6, + Y: -3, + }, + Rotation: 0, + }, p2.Position()) +} + +func TestBetweenPoints(t *testing.T) { + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(betweenPoints, temp)) + + panel, err := temp.GetPanel(template.Request{ + Dims: dimensions.Dimensions{"test": dimensions.Dimension{ + Name: "Test", + Value: 0.5, + }}, + Panel: "body", + }) + require.NoError(t, err) + + p1, ok := panel.Points["1"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 4, + Y: 14.3, + }, + Rotation: 0, + }, p1.Position(), 1e-10) + + p2, ok := panel.Points["2"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 6, + Y: 11.3, + }, + Rotation: 0, + }, p2.Position(), 1e-10) + + p3, ok := panel.Points["3"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 5, + Y: 12.8, + }, + Rotation: math.Atan2(-3, 2) - math.Pi/2, + }, p3.Position(), 1e-10) +} + +func TestRelativePoints(t *testing.T) { + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(relativePoints, temp)) + + panel, err := temp.GetPanel(template.Request{ + Dims: dimensions.Dimensions{"test": dimensions.Dimension{ + Name: "Test", + Value: 1.3, + }}, + Panel: "body", + }) + require.NoError(t, err) + + p1, ok := panel.Points["1"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 1.3, + Y: 14.3, + }, + Rotation: 0, + }, p1.Position(), 1e-10) + + p2, ok := panel.Points["2"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 3.9, + Y: 11.3, + }, + Rotation: 0, + }, p2.Position(), 1e-10) +} + +func TestExtendPoints(t *testing.T) { + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(extendPoints, temp)) + + panel, err := temp.GetPanel(template.Request{ + Dims: dimensions.Dimensions{"test": dimensions.Dimension{ + Name: "Test", + Value: 1.3, + }}, + Panel: "body", + }) + require.NoError(t, err) + + p1, ok := panel.Points["1"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{}, p1.Position(), 1e-10) + + p2, ok := panel.Points["2"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 4, + Y: 3, + }, + Rotation: 0, + }, p2.Position(), 1e-10) + + p3, ok := panel.Points["3"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 8, + Y: 6, + }, + Rotation: math.Atan2(3, 4) - math.Pi/2, + }, p3.Position(), 1e-10) +} + +func TestPolarPoints(t *testing.T) { + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(polarPoints, temp)) + + panel, err := temp.GetPanel(template.Request{ + Dims: dimensions.Dimensions{"test": dimensions.Dimension{ + Name: "Test", + Value: 1.3, + }}, + Panel: "body", + }) + require.NoError(t, err) + + p1, ok := panel.Points["1"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{}, p1.Position(), 1e-10) + + p2, ok := panel.Points["2"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 1, + Y: 0, + }, + Rotation: 0, + }, p2.Position(), 1e-10) + + p3, ok := panel.Points["3"] + require.True(t, ok) + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 1, + Y: 1, + }, + Rotation: math.Pi / 2, + }, p3.Position(), 1e-10) +} + +func TestFunctions(t *testing.T) { + tests := map[string]struct { + result float64 + }{ + "distance": {result: 5}, + "angle": {result: math.Atan2(3, 4)}, + "yDistance": {result: 3}, + "xDistance": {result: 4}, + "lineLength": {result: 12}, + "lineLength2": {result: 4}, + } + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(functions, temp)) + + panel, err := temp.GetPanel(template.Request{Panel: "test"}) + require.NoError(t, err) + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + p, ok := panel.Points[util.ID(name)] + require.True(t, ok) + require.InDelta(t, test.result, p.Vector().X, 1e-10) + }) + } +} + +func TestEvaluationFunctions(t *testing.T) { + tests := map[string]struct { + result float64 + }{ + "acos": {result: math.Acos(0.5)}, + "asin": {result: math.Asin(math.Pi / 2)}, + "atan2": {result: math.Atan2(1, 1)}, + "abs1": {result: math.Abs(-1.4)}, + "abs2": {result: math.Abs(3.5)}, + } + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(evaluationFunctions, temp)) + + panel, err := temp.GetPanel(template.Request{Panel: "test"}) + require.NoError(t, err) + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + p, ok := panel.Points[util.ID(name)] + require.True(t, ok) + require.InDelta(t, test.result, p.Vector().X, 1e-10) + }) + } +} + +func TestReferences(t *testing.T) { + temp := &template.Template{} + require.NoError(t, yaml.Unmarshal(references, temp)) + + panel, err := temp.GetPanel(template.Request{Panel: "body"}) + require.NoError(t, err) + + _ = panel +} diff --git a/pkg/pattern/template/position.go b/pkg/template/position.go similarity index 100% rename from pkg/pattern/template/position.go rename to pkg/template/position.go diff --git a/pkg/template/template.go b/pkg/template/template.go new file mode 100644 index 0000000..fcc5c28 --- /dev/null +++ b/pkg/template/template.go @@ -0,0 +1,79 @@ +package template + +import ( + "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" + "git.wtrh.nl/patterns/gopatterns/pkg/path" + "git.wtrh.nl/patterns/gopatterns/pkg/pattern/panel" + "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" +) + +type Template struct { + Name string `yaml:"name"` + Points Points `yaml:"points"` + Lines Lines `yaml:"lines"` + Panels Panels `yaml:"panels"` + Version string `yaml:"version"` +} + +type Request struct { + Dims dimensions.Dimensions + Panel string +} + +type request struct { + dimensions dimensions.Dimensions + name string + lines map[util.ID]path.Path + points map[util.ID]point.Point +} + +func (t Template) GetPanel(req Request) (panel.Panel, error) { + p, ok := t.Panels[req.Panel] + if !ok { + return panel.Panel{}, ErrPanelNotFound + } + + r := request{ + dimensions: req.Dims, + name: req.Panel, + lines: make(map[util.ID]path.Path), + points: make(map[util.ID]point.Point), + } + + result := panel.Panel{ + Name: req.Panel, + Lines: make(map[util.ID]path.Path), + Points: make(map[util.ID]point.Point), + Dimensions: req.Dims, + } + + for id := range p.Lines { + line, err := t.getOrCreateLine(id, r, 0) + if err != nil { + return panel.Panel{}, err + } + + result.Lines[id] = line + } + + for id := range p.Points { + newPoint, err := t.getOrCreatePoint(id, r, 0) + if err != nil { + return panel.Panel{}, err + } + + result.Points[id] = newPoint + } + + for id := range t.Points { + newPoint, err := t.getOrCreatePoint(id, r, 0) + if err != nil { + return panel.Panel{}, err + } + + result.Points[id] = newPoint + } + + return result, nil +} diff --git a/pkg/pattern/template/value.go b/pkg/template/value.go similarity index 100% rename from pkg/pattern/template/value.go rename to pkg/template/value.go diff --git a/pkg/util/id.go b/pkg/util/id.go new file mode 100644 index 0000000..3a7fe4f --- /dev/null +++ b/pkg/util/id.go @@ -0,0 +1,28 @@ +package util + +import "strings" + +// ID defines a point id. +type ID string + +func (i ID) Panel() string { + split := strings.Split(string(i), ".") + if len(split) < 2 { + return "" + } + + return split[0] +} + +func (i ID) Name() string { + split := strings.Split(string(i), ".") + if len(split) < 2 { + return split[0] + } + + return split[1] +} + +func (i ID) Deref() ID { + return ID(i.Name()) +} diff --git a/pkg/util/id_test.go b/pkg/util/id_test.go new file mode 100644 index 0000000..dad652b --- /dev/null +++ b/pkg/util/id_test.go @@ -0,0 +1,33 @@ +package util_test + +import ( + "git.wtrh.nl/patterns/gopatterns/pkg/util" + "github.com/stretchr/testify/require" + "testing" +) + +func TestID(t *testing.T) { + tests := map[string]struct { + panel, name string + }{ + "body.2": { + panel: "body", + name: "2", + }, + "2": { + panel: "", + name: "2", + }, + "1.test": { + panel: "1", + name:"test", + }, + } + for testName, tt := range tests { + t.Run(testName, func(t *testing.T) { + id := util.ID(testName) + require.Equal(t, tt.panel, id.Panel()) + require.Equal(t, tt.name, id.Name()) + }) + } +} \ No newline at end of file diff --git a/spec/pattern.v2.yaml b/spec/pattern.v2.yaml new file mode 100644 index 0000000..76fc030 --- /dev/null +++ b/spec/pattern.v2.yaml @@ -0,0 +1,125 @@ +--- +$schema: "https://json-schema.org/draft-04/schema" +id: "https://stsci.edu/schemas/yaml-schema/draft-01" +title: + YAML Schema + +type: object +properties: + version: + type: string + points: + $ref: '#/components/schemas/points' + lines: + $ref: '#/components/schemas/lines' + panels: + type: object + additionalProperties: + type: object + properties: + allowances: + type: object + properties: + hem: + type: string + seam: + type: string + points: + $ref: '#/components/schemas/points' + lines: + $ref: '#/components/schemas/lines' + information: + allOf: + - $ref: '#/components/schemas/point' + - type: object + properties: + anchor: + type: string + enum: [top, center, bottom, left, right] + +components: + schemas: + point: + type: object + properties: + position: + $ref: '#/components/schemas/position' + relativeTo: + $ref: '#/components/schemas/pointID' + polar: + $ref: '#/components/schemas/polar' + description: + type: string + between: + $ref: '#/components/schemas/between' + hide: + type: bool + extend: + $ref: '#/components/schemas/between' + points: + type: object + additionalProperties: + $ref: '#/components/schemas/point' + position: + type: object + properties: + y: + $ref: '#/components/schemas/expression' + x: + $ref: '#/components/schemas/expression' + rotation: + $ref: '#/components/schemas/expression' + polar: + type: object + properties: + length: + $ref: '#/components/schemas/expression' + rotation: + $ref: '#/components/schemas/expression' + between: + type: object + properties: + from: + $ref: '#/components/schemas/pointID' + to: + $ref: '#/components/schemas/pointID' + offset: + $ref: '#/components/schemas/expression' + line: + type: object + properties: + through: + type: array + items: + $ref: '#/components/schemas/pointID' + curve: + type: object + properties: + start: + $ref: '#/components/schemas/pointID' + end: + $ref: '#/components/schemas/pointID' + style: + type: object + properties: + thickness: + type: number + hide: + type: bool + reference: + type: string + lines: + type: object + additionalProperties: + $ref: '#/components/schemas/line' + pointID: + oneOf: + - type: integer + - type: string + expression: + oneOf: + - type: number + - type: string + + + diff --git a/spec/pattern.yaml b/spec/pattern.yaml index 0737295..8d9d296 100644 --- a/spec/pattern.yaml +++ b/spec/pattern.yaml @@ -6,6 +6,8 @@ title: type: object properties: + version: + type: string points: $ref: '#/components/schemas/points' panels: @@ -102,6 +104,8 @@ components: properties: thickness: type: number + reference: + type: string pointID: oneOf: - type: integer diff --git a/templates/dimension_names.yaml b/templates/dimension_names.yaml index e7dee77..54aa40a 100644 --- a/templates/dimension_names.yaml +++ b/templates/dimension_names.yaml @@ -33,6 +33,8 @@ sleeve_length_shirt: name: Sleeve length for shirts cuff_size: name: Cuff size +cuff_depth: + name: Cuff Depth bovenwijdte: name: Bovenwijdte diff --git a/templates/tailored_shirt_block.v2.yaml b/templates/tailored_shirt_block.v2.yaml new file mode 100644 index 0000000..5ac9866 --- /dev/null +++ b/templates/tailored_shirt_block.v2.yaml @@ -0,0 +1,498 @@ +--- +name: Basic Trouser Block +panels: + body: + allowances: + hem: 1cm + seam: 1cm + information: + position: + y: -10 + x: 10 + lines: + 1: + through: [14,8] + style: + thickness: 1 + 2: + through: [8, 7, 0, 1, 2, 3, 37, 19, 35, 6, 22] + 3: + through: [1,11,17,4] + 4: + through: [7,12,10] + 5: + through: [9,15,10,23,11] + 6: + through: [15,16] + curve: + start: 10 + 7: + through: [0,7a,8] + curve: + start: 7 + style: + thickness: 1 + 8: + through: [17,18,19] + 9: + through: [24,21] + style: + thickness: 1 + 10: + through: [21,22] + curve: + start: 20b + end: 20a + style: + thickness: 1 + 11: + through: [14,10,11a,17,25a,26,27a,24] + curve: + start: 14 + style: + thickness: 1 + 12: + through: [28,28a] + 13: + through: [22,29,29a] + 14: + through: [37,34] + curve: + start: 19 + end: 34a + style: + thickness: 1 + 15: + through: [33,36] + curve: + start: 33a + end: 36a + style: + thickness: 1 + 16: + through: [17,31,34] + curve: {} + style: + thickness: 0.6 + 17: + through: [17,30,33] + curve: {} + style: + thickness: 0.6 + 18: + through: [36,29b] + 19: + through: [34,33] + style: + thickness: 1 + 20: + through: [39,43,41] + curve: {} + style: + thickness: 0.6 + 21: + through: [39,42,41] + curve: {} + style: + thickness: 0.6 + 22: + through: [0,3,37] + style: + thickness: 1 + 23: + through: [22,29,29b,36] + style: + thickness: 1 + + points: + 0: + position: {} + 1: + position: + y: -(scye_depth) - 60 + 2: + position: + y: -(back_waist + 25 ) + 3: + position: + y: -(shirt_length) - 40 + 4: + relativeTo: 1 + position: + x: chest/2 + 100 + 5: + relativeTo: 0 + position: + x: DistanceBetween("1","4") + 6: + relativeTo: 3 + position: + x: DistanceBetween("1","4") + 7: + relativeTo: 0 + position: + x: neck_size/5 - 5 + 8: + relativeTo: 7 + position: + y: 45 + 9: + position: + y: -(DistanceBetween("0","1")/5 + 40) + 10: + relativeTo: 9 + position: + x: half_back + 40 + 11: + relativeTo: 1 + position: + x: half_back + 40 + 12: + relativeTo: 0 + position: + x: half_back + 40 + 14: + relativeTo: 12 + position: + x: 15 + y: 20 + 15: + relativeTo: 10 + position: + x: -100 + 16: + relativeTo: 10 + position: + y: -7.5 + 17: + relativeTo: 1 + position: + x: DistanceBetween("1","4")/2 + 5 + 18: + relativeTo: 17 + position: + y: -(DistanceBetween("1","2")+25) + 19: + relativeTo: 17 + position: + y: -DistanceBetween("1","3") + 20: + relativeTo: 5 + position: + y: -45 + 21: + relativeTo: 20 + position: + x: -(neck_size/5-10) + 22: + relativeTo: 20 + position: + y: -(neck_size/5-25) + 23: + relativeTo: 10 + position: + y: -15 + 24: + relativeTo: 21 + polar: + length: -DistanceBetween("8","14") + rotation: asin(abs(YDistanceBetween("21","23"))/abs(DistanceBetween("8","14"))) + 25: + relativeTo: 1 + position: + x: chest/3+40 + 26: + relativeTo: 25 + position: + y: 40 + 27: + between: + from: 26 + to: 24 + offset: 0.5 + 28: + relativeTo: 22 + position: + x: 15 + 28a: + relativeTo: 28 + position: + y: YDistanceBetween("28","3") + hide: true + 29: + relativeTo: 28 + position: + x: 35 + 29a: + relativeTo: 29 + position: + y: YDistanceBetween("29","3") + hide: true + 29b: + relativeTo: 29a + position: + y: DistanceBetween("35","36") + hide: true + 30: + relativeTo: 18 + position: + x: 25 + 31: + relativeTo: 18 + position: + x: -25 + 32: + relativeTo: 19 + position: + y: 80 + 33: + relativeTo: 32 + position: + x: 15 + 33a: + relativeTo: 33 + position: + x: DistanceBetween("33","36") + 34: + relativeTo: 32 + position: + x: -15 + 34a: + relativeTo: 34 + position: + x: -DistanceBetween("19","37") + 35: + between: + from: 6 + to: 19 + offset: 0.5 + 36: + relativeTo: 35 + position: + x: 30 + rotation: -pi/2 + 36a: + relativeTo: 36 + position: + x: -DistanceBetween("33","36") + 37: + between: + from: 3 + to: 19 + offset: 0.5 + 38: + relativeTo: 1 + position: + x: DistanceBetween("1","11")/2 + 20 + 39: + relativeTo: 38 + position: + y: -40 + 40: + relativeTo: 2 + position: + x: DistanceBetween("1","38") + y: -25 + 41: + relativeTo: 40 + position: + y: -160 + 42: + relativeTo: 40 + position: + x: 7.5 + 43: + relativeTo: 40 + position: + x: -7.5 + 7a: + relativeTo: 7 + polar: + length: 20 + rotation: 3*pi/4 + 11a: + relativeTo: 11 + position: + y: 30 + x: 10 + 20a: + relativeTo: 22 + position: + x: -DistanceBetween("21","20")*2 + 20b: + relativeTo: 21 + position: + y: -DistanceBetween("22","20")*2 + 27a: + relativeTo: 27 + position: + x: 10 + 25a: + relativeTo: 25 + position: + y: 7 + x: -30 + sleeve: + points: + 0: {} + 1: + relativeTo: 0 + position: + y: -(502.6 / 4 + 15) + 2: + relativeTo: 0 + position: + y: -(sleeve_length_shirt+60-cuff_depth) + 3: + between: + from: 2 + to: 1 + offset: 0.5 + 4: + relativeTo: 1 + position: + x: -(502.6/2 -5) + 5: + relativeTo: 4 + position: + y: -DistanceBetween("1","2") + 6: + relativeTo: 1 + position: + x: (502.6/2 -5) + 7: + relativeTo: 6 + position: + y: -DistanceBetween("1","2") + 8a: + between: + from: 4 + to: 0 + offset: 0.25 + 8: + relativeTo: 8a + position: + x: 5 + 9a: + between: + from: 4 + to: 0 + offset: 0.5 + 9: + relativeTo: 9a + position: + x: -12.5 + 10a: + between: + from: 4 + to: 0 + offset: 0.75 + 10: + relativeTo: 10a + position: + x: -22.5 + 11a: + between: + from: 0 + to: 6 + offset: 0.25 + 11: + relativeTo: 11a + position: + x: -15 + 12: + between: + from: 0 + to: 6 + offset: 0.5 + 13a: + between: + from: 0 + to: 6 + offset: 0.75 + 13: + relativeTo: 13a + position: + x: 12.5 + 14: + relativeTo: 5 + position: + x: DistanceBetween("5","2")/3+7.5 + 15: + relativeTo: 7 + position: + x: -DistanceBetween("5","14") + 3a: + between: + from: 14 + to: 4 + offset: 0.5 + 3aa: + relativeTo: 3a + position: + x: -7 + 3b: + between: + from: 15 + to: 6 + offset: 0.5 + 3bb: + relativeTo: 3b + position: + x: 7 + A: + relativeTo: 0 + position: + y: -250 + B: + relativeTo: 4 + position: + y: -DistanceBetween("1","A") + C: + relativeTo: 6 + position: + y: -DistanceBetween("1","A") + 16a: + between: + from: 14 + to: 2 + offset: 0.5 + hide: true + 16: + relativeTo: 16a + position: + rotation: pi/2 + 17: + relativeTo: 16 + position: + y: 150 + lines: + scye: + through: [4,8,9,10,0,11,12,13,6] + curve: {} + style: + thickness: 1 + 1: + through: [4,3aa,14] + curve: {} + style: + thickness: 1 + 2: + through: [14,2,15] + style: + thickness: 1 + 3: + through: [6,3bb,15] + curve: {} + style: + thickness: 1 + 4: + through: [0,1,3,2] + 0: + through: [14,4,0,6,15] + abc: + through: [B,A,C] + 5: + through: [16,17] + + From eb89adec89965330fa7eecf67f4ae1cf1de987b1 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Sun, 27 Jul 2025 17:55:45 +0200 Subject: [PATCH 3/6] Update existing templates to new format --- templates/basic_trouser_block.yaml | 62 ++- templates/basis_grondpatroon_heren.yaml | 42 +- templates/classic_trouser_block.yaml | 53 ++- templates/tailored_shirt_block.v2.yaml | 498 ------------------------ templates/tailored_shirt_block.yaml | 259 ++++++++++-- 5 files changed, 327 insertions(+), 587 deletions(-) delete mode 100644 templates/tailored_shirt_block.v2.yaml diff --git a/templates/basic_trouser_block.yaml b/templates/basic_trouser_block.yaml index 018781e..301e5fe 100644 --- a/templates/basic_trouser_block.yaml +++ b/templates/basic_trouser_block.yaml @@ -80,7 +80,7 @@ points: between: from: 16 to: 18 - absolute: 0.5 + offset: 0.5 20: position: x: 20 @@ -182,20 +182,29 @@ panels: offset: 1.8 lines: - - through: [9,1] - - through: [6,8] - - through: [15,13] - - through: [0,2,1,4,3] - - through: [6,10,0l,0b,0r,11] - - through: [9,5d,6] + 1: + through: [9,1] + 2: + through: [6,8] + 3: + through: [15,13] + 4: + through: [0,2,1,4,3] + 5: + through: [6,10,0l,0b,0r,11] + 6: + through: [9,5d,6] curve: start: 5 end: 6e - - through: [15,14,12,13] - - through: [15,9] + 7: + through: [15,14,12,13] + 8: + through: [15,9] curve: start: 15e - - through: [13,8,11] + 9: + through: [13,8,11] curve: start: 13e back: @@ -276,25 +285,36 @@ panels: lines: - - through: [19,21,30l,30b,30r,31l,31b,31r,22] - - through: [24,16d,19] + 1: + through: [19,21,30l,30b,30r,31l,31b,31r,22] + 2: + through: [24,16d,19] curve: start: 23a end: 19e - - through: [29,24] + 3: + through: [29,24] curve: start: 29extend - - through: [27,25,22] + 4: + through: [27,25,22] curve: start: 27extend - - through: [29,28] - - through: [27,26] - - through: [28,3down, 26] + 5: + through: [29,28] + 6: + through: [27,26] + 7: + through: [28,3down,26] curve: {} - - through: [23,1] - - through: [6,25] - - through: [29,27] - - through: [0,2,1,4,3] + 8: + through: [23,1] + 9: + through: [6,25] + 10: + through: [29,27] + 11: + through: [0,2,1,4,3] diff --git a/templates/basis_grondpatroon_heren.yaml b/templates/basis_grondpatroon_heren.yaml index 7078d76..9ea508d 100644 --- a/templates/basis_grondpatroon_heren.yaml +++ b/templates/basis_grondpatroon_heren.yaml @@ -181,28 +181,42 @@ panels: lines: - - through: [N,V] - - through: [R,W] - - through: [M,G,I,C,F,L,K] - - through: [S,H,J,D,F] - - through: [M,Noffset,N] + 1: + through: [N,V] + 2: + through: [R,W] + 3: + through: [M,G,I,C,F,L,K] + 4: + through: [S,H,J,D,F] + 5: + through: [M,Noffset,N] curve: start: Nprime - - through: [S,R] + 6: + through: [S,R] curve: start: Roffset - - through: [V,Arm2,Poffset,K] + 7: + through: [V,Arm2,Poffset,K] curve: end: Arm3 - - through: [W,Arm1,Uoffset,K] + 8: + through: [W,Arm1,Uoffset,K] curve: end: Arm4 # ooit stippellijnen - - through: [G,H,J,I] - - through: [Tprime,U] - - through: [A,O,P] - - through: [S,Eprime,K] - - through: [R,Rprime] - - through: [A,M,Nprime,N] + 9: + through: [G,H,J,I] + 10: + through: [Tprime,U] + 11: + through: [A,O,P] + 12: + through: [S,Eprime,K] + 13: + through: [R,Rprime] + 14: + through: [A,M,Nprime,N] diff --git a/templates/classic_trouser_block.yaml b/templates/classic_trouser_block.yaml index 4d54aa7..cdf2879 100644 --- a/templates/classic_trouser_block.yaml +++ b/templates/classic_trouser_block.yaml @@ -176,20 +176,28 @@ panels: relativeTo: 8 hide: true lines: - - through: [0,4,1,3,2] - - through: [6, 8] - - through: [9, 1extend] - - through: [14,12,13,15,14] - - through: [14,8,11] + 1: + through: [0,4,1,3,2] + 2: + through: [6, 8] + 3: + through: [9, 1extend] + 4: + through: [14,12,13,15,14] + 5: + through: [14,8,11] curve: start: extend12-14 - end: offset_between11-8 - - through: [15,9] + end: offset_between11-8 + 6: + through: [15,9] curve: start: extend13-15 - - through: [9, h5, 6] + 7: + through: [9, h5, 6] curve: {} - - through: [6, 10, 11] + 8: + through: [6, 10, 11] back: name: Back @@ -258,20 +266,29 @@ panels: hide: true lines: - - through: [0extend, 0, 4, 1, 3, 2] - - through: [6, 17, 26] - - through: [22, 16, 16extend] - - through: [28, 30, 29, 27] - - through: [21,25a,25c,25b,24] - - through: [28,2down,27] + 1: + through: [0extend, 0, 4, 1, 3, 2] + 2: + through: [6, 17, 26] + 3: + through: [22, 16, 16extend] + 4: + through: [28, 30, 29, 27] + 5: + through: [21,25a,25c,25b,24] + 6: + through: [28,2down,27] curve: {} - - through: [23,16offset,19,21] + 7: + through: [23,16offset,19,21] curve: start: 23a - - through: [30,23] + 8: + through: [30,23] curve: start: 30extend - - through: [29,26,24] + 9: + through: [29,26,24] curve: start: 29extend diff --git a/templates/tailored_shirt_block.v2.yaml b/templates/tailored_shirt_block.v2.yaml deleted file mode 100644 index 5ac9866..0000000 --- a/templates/tailored_shirt_block.v2.yaml +++ /dev/null @@ -1,498 +0,0 @@ ---- -name: Basic Trouser Block -panels: - body: - allowances: - hem: 1cm - seam: 1cm - information: - position: - y: -10 - x: 10 - lines: - 1: - through: [14,8] - style: - thickness: 1 - 2: - through: [8, 7, 0, 1, 2, 3, 37, 19, 35, 6, 22] - 3: - through: [1,11,17,4] - 4: - through: [7,12,10] - 5: - through: [9,15,10,23,11] - 6: - through: [15,16] - curve: - start: 10 - 7: - through: [0,7a,8] - curve: - start: 7 - style: - thickness: 1 - 8: - through: [17,18,19] - 9: - through: [24,21] - style: - thickness: 1 - 10: - through: [21,22] - curve: - start: 20b - end: 20a - style: - thickness: 1 - 11: - through: [14,10,11a,17,25a,26,27a,24] - curve: - start: 14 - style: - thickness: 1 - 12: - through: [28,28a] - 13: - through: [22,29,29a] - 14: - through: [37,34] - curve: - start: 19 - end: 34a - style: - thickness: 1 - 15: - through: [33,36] - curve: - start: 33a - end: 36a - style: - thickness: 1 - 16: - through: [17,31,34] - curve: {} - style: - thickness: 0.6 - 17: - through: [17,30,33] - curve: {} - style: - thickness: 0.6 - 18: - through: [36,29b] - 19: - through: [34,33] - style: - thickness: 1 - 20: - through: [39,43,41] - curve: {} - style: - thickness: 0.6 - 21: - through: [39,42,41] - curve: {} - style: - thickness: 0.6 - 22: - through: [0,3,37] - style: - thickness: 1 - 23: - through: [22,29,29b,36] - style: - thickness: 1 - - points: - 0: - position: {} - 1: - position: - y: -(scye_depth) - 60 - 2: - position: - y: -(back_waist + 25 ) - 3: - position: - y: -(shirt_length) - 40 - 4: - relativeTo: 1 - position: - x: chest/2 + 100 - 5: - relativeTo: 0 - position: - x: DistanceBetween("1","4") - 6: - relativeTo: 3 - position: - x: DistanceBetween("1","4") - 7: - relativeTo: 0 - position: - x: neck_size/5 - 5 - 8: - relativeTo: 7 - position: - y: 45 - 9: - position: - y: -(DistanceBetween("0","1")/5 + 40) - 10: - relativeTo: 9 - position: - x: half_back + 40 - 11: - relativeTo: 1 - position: - x: half_back + 40 - 12: - relativeTo: 0 - position: - x: half_back + 40 - 14: - relativeTo: 12 - position: - x: 15 - y: 20 - 15: - relativeTo: 10 - position: - x: -100 - 16: - relativeTo: 10 - position: - y: -7.5 - 17: - relativeTo: 1 - position: - x: DistanceBetween("1","4")/2 + 5 - 18: - relativeTo: 17 - position: - y: -(DistanceBetween("1","2")+25) - 19: - relativeTo: 17 - position: - y: -DistanceBetween("1","3") - 20: - relativeTo: 5 - position: - y: -45 - 21: - relativeTo: 20 - position: - x: -(neck_size/5-10) - 22: - relativeTo: 20 - position: - y: -(neck_size/5-25) - 23: - relativeTo: 10 - position: - y: -15 - 24: - relativeTo: 21 - polar: - length: -DistanceBetween("8","14") - rotation: asin(abs(YDistanceBetween("21","23"))/abs(DistanceBetween("8","14"))) - 25: - relativeTo: 1 - position: - x: chest/3+40 - 26: - relativeTo: 25 - position: - y: 40 - 27: - between: - from: 26 - to: 24 - offset: 0.5 - 28: - relativeTo: 22 - position: - x: 15 - 28a: - relativeTo: 28 - position: - y: YDistanceBetween("28","3") - hide: true - 29: - relativeTo: 28 - position: - x: 35 - 29a: - relativeTo: 29 - position: - y: YDistanceBetween("29","3") - hide: true - 29b: - relativeTo: 29a - position: - y: DistanceBetween("35","36") - hide: true - 30: - relativeTo: 18 - position: - x: 25 - 31: - relativeTo: 18 - position: - x: -25 - 32: - relativeTo: 19 - position: - y: 80 - 33: - relativeTo: 32 - position: - x: 15 - 33a: - relativeTo: 33 - position: - x: DistanceBetween("33","36") - 34: - relativeTo: 32 - position: - x: -15 - 34a: - relativeTo: 34 - position: - x: -DistanceBetween("19","37") - 35: - between: - from: 6 - to: 19 - offset: 0.5 - 36: - relativeTo: 35 - position: - x: 30 - rotation: -pi/2 - 36a: - relativeTo: 36 - position: - x: -DistanceBetween("33","36") - 37: - between: - from: 3 - to: 19 - offset: 0.5 - 38: - relativeTo: 1 - position: - x: DistanceBetween("1","11")/2 + 20 - 39: - relativeTo: 38 - position: - y: -40 - 40: - relativeTo: 2 - position: - x: DistanceBetween("1","38") - y: -25 - 41: - relativeTo: 40 - position: - y: -160 - 42: - relativeTo: 40 - position: - x: 7.5 - 43: - relativeTo: 40 - position: - x: -7.5 - 7a: - relativeTo: 7 - polar: - length: 20 - rotation: 3*pi/4 - 11a: - relativeTo: 11 - position: - y: 30 - x: 10 - 20a: - relativeTo: 22 - position: - x: -DistanceBetween("21","20")*2 - 20b: - relativeTo: 21 - position: - y: -DistanceBetween("22","20")*2 - 27a: - relativeTo: 27 - position: - x: 10 - 25a: - relativeTo: 25 - position: - y: 7 - x: -30 - sleeve: - points: - 0: {} - 1: - relativeTo: 0 - position: - y: -(502.6 / 4 + 15) - 2: - relativeTo: 0 - position: - y: -(sleeve_length_shirt+60-cuff_depth) - 3: - between: - from: 2 - to: 1 - offset: 0.5 - 4: - relativeTo: 1 - position: - x: -(502.6/2 -5) - 5: - relativeTo: 4 - position: - y: -DistanceBetween("1","2") - 6: - relativeTo: 1 - position: - x: (502.6/2 -5) - 7: - relativeTo: 6 - position: - y: -DistanceBetween("1","2") - 8a: - between: - from: 4 - to: 0 - offset: 0.25 - 8: - relativeTo: 8a - position: - x: 5 - 9a: - between: - from: 4 - to: 0 - offset: 0.5 - 9: - relativeTo: 9a - position: - x: -12.5 - 10a: - between: - from: 4 - to: 0 - offset: 0.75 - 10: - relativeTo: 10a - position: - x: -22.5 - 11a: - between: - from: 0 - to: 6 - offset: 0.25 - 11: - relativeTo: 11a - position: - x: -15 - 12: - between: - from: 0 - to: 6 - offset: 0.5 - 13a: - between: - from: 0 - to: 6 - offset: 0.75 - 13: - relativeTo: 13a - position: - x: 12.5 - 14: - relativeTo: 5 - position: - x: DistanceBetween("5","2")/3+7.5 - 15: - relativeTo: 7 - position: - x: -DistanceBetween("5","14") - 3a: - between: - from: 14 - to: 4 - offset: 0.5 - 3aa: - relativeTo: 3a - position: - x: -7 - 3b: - between: - from: 15 - to: 6 - offset: 0.5 - 3bb: - relativeTo: 3b - position: - x: 7 - A: - relativeTo: 0 - position: - y: -250 - B: - relativeTo: 4 - position: - y: -DistanceBetween("1","A") - C: - relativeTo: 6 - position: - y: -DistanceBetween("1","A") - 16a: - between: - from: 14 - to: 2 - offset: 0.5 - hide: true - 16: - relativeTo: 16a - position: - rotation: pi/2 - 17: - relativeTo: 16 - position: - y: 150 - lines: - scye: - through: [4,8,9,10,0,11,12,13,6] - curve: {} - style: - thickness: 1 - 1: - through: [4,3aa,14] - curve: {} - style: - thickness: 1 - 2: - through: [14,2,15] - style: - thickness: 1 - 3: - through: [6,3bb,15] - curve: {} - style: - thickness: 1 - 4: - through: [0,1,3,2] - 0: - through: [14,4,0,6,15] - abc: - through: [B,A,C] - 5: - through: [16,17] - - diff --git a/templates/tailored_shirt_block.yaml b/templates/tailored_shirt_block.yaml index 5461ef6..5ac9866 100644 --- a/templates/tailored_shirt_block.yaml +++ b/templates/tailored_shirt_block.yaml @@ -10,74 +10,97 @@ panels: y: -10 x: 10 lines: - - through: [14,8] - style: - thickness: 1 - - through: [8, 7, 0, 1, 2, 3, 37, 19, 35, 6, 22] - - through: [1,11,17,4] - - through: [7,12,10] - - through: [9,15,10,23,11] - - through: [15,16] - curve: - start: 10 - - through: [0,7a,8] - curve: - start: 7 - style: - thickness: 1 - - through: [17,18,19] - - through: [24,21] + 1: + through: [14,8] + style: + thickness: 1 + 2: + through: [8, 7, 0, 1, 2, 3, 37, 19, 35, 6, 22] + 3: + through: [1,11,17,4] + 4: + through: [7,12,10] + 5: + through: [9,15,10,23,11] + 6: + through: [15,16] + curve: + start: 10 + 7: + through: [0,7a,8] + curve: + start: 7 + style: + thickness: 1 + 8: + through: [17,18,19] + 9: + through: [24,21] style: thickness: 1 - - through: [21,22] + 10: + through: [21,22] curve: start: 20b end: 20a style: thickness: 1 - - through: [14,10,11a,17,25a,26,27a,24] + 11: + through: [14,10,11a,17,25a,26,27a,24] curve: start: 14 style: thickness: 1 - - through: [28,28a] - - through: [22,29,29a] - - through: [37,34] + 12: + through: [28,28a] + 13: + through: [22,29,29a] + 14: + through: [37,34] curve: start: 19 end: 34a style: thickness: 1 - - through: [33,36] + 15: + through: [33,36] curve: start: 33a end: 36a style: thickness: 1 - - through: [17,31,34] + 16: + through: [17,31,34] curve: {} style: thickness: 0.6 - - through: [17,30,33] + 17: + through: [17,30,33] curve: {} style: thickness: 0.6 - - through: [36,29b] - - through: [34,33] + 18: + through: [36,29b] + 19: + through: [34,33] style: thickness: 1 - - through: [39,43,41] + 20: + through: [39,43,41] curve: {} style: thickness: 0.6 - - through: [39,42,41] + 21: + through: [39,42,41] curve: {} style: thickness: 0.6 - - through: [0,3,37] + 22: + through: [0,3,37] style: thickness: 1 - - through: [22,29,29b,36] + 23: + through: [22,29,29b,36] style: thickness: 1 @@ -172,8 +195,8 @@ panels: 24: relativeTo: 21 polar: - length: DistanceBetween("8","14") - rotation: acos(YDistanceBetween("21","23")/DistanceBetween("8","14")) + length: -DistanceBetween("8","14") + rotation: asin(abs(YDistanceBetween("21","23"))/abs(DistanceBetween("8","14"))) 25: relativeTo: 1 position: @@ -194,7 +217,7 @@ panels: 28a: relativeTo: 28 position: - y: -YDistanceBetween("28","3") + y: YDistanceBetween("28","3") hide: true 29: relativeTo: 28 @@ -203,7 +226,7 @@ panels: 29a: relativeTo: 29 position: - y: -YDistanceBetween("29","3") + y: YDistanceBetween("29","3") hide: true 29b: relativeTo: 29a @@ -308,4 +331,168 @@ panels: relativeTo: 25 position: y: 7 - x: -30 \ No newline at end of file + x: -30 + sleeve: + points: + 0: {} + 1: + relativeTo: 0 + position: + y: -(502.6 / 4 + 15) + 2: + relativeTo: 0 + position: + y: -(sleeve_length_shirt+60-cuff_depth) + 3: + between: + from: 2 + to: 1 + offset: 0.5 + 4: + relativeTo: 1 + position: + x: -(502.6/2 -5) + 5: + relativeTo: 4 + position: + y: -DistanceBetween("1","2") + 6: + relativeTo: 1 + position: + x: (502.6/2 -5) + 7: + relativeTo: 6 + position: + y: -DistanceBetween("1","2") + 8a: + between: + from: 4 + to: 0 + offset: 0.25 + 8: + relativeTo: 8a + position: + x: 5 + 9a: + between: + from: 4 + to: 0 + offset: 0.5 + 9: + relativeTo: 9a + position: + x: -12.5 + 10a: + between: + from: 4 + to: 0 + offset: 0.75 + 10: + relativeTo: 10a + position: + x: -22.5 + 11a: + between: + from: 0 + to: 6 + offset: 0.25 + 11: + relativeTo: 11a + position: + x: -15 + 12: + between: + from: 0 + to: 6 + offset: 0.5 + 13a: + between: + from: 0 + to: 6 + offset: 0.75 + 13: + relativeTo: 13a + position: + x: 12.5 + 14: + relativeTo: 5 + position: + x: DistanceBetween("5","2")/3+7.5 + 15: + relativeTo: 7 + position: + x: -DistanceBetween("5","14") + 3a: + between: + from: 14 + to: 4 + offset: 0.5 + 3aa: + relativeTo: 3a + position: + x: -7 + 3b: + between: + from: 15 + to: 6 + offset: 0.5 + 3bb: + relativeTo: 3b + position: + x: 7 + A: + relativeTo: 0 + position: + y: -250 + B: + relativeTo: 4 + position: + y: -DistanceBetween("1","A") + C: + relativeTo: 6 + position: + y: -DistanceBetween("1","A") + 16a: + between: + from: 14 + to: 2 + offset: 0.5 + hide: true + 16: + relativeTo: 16a + position: + rotation: pi/2 + 17: + relativeTo: 16 + position: + y: 150 + lines: + scye: + through: [4,8,9,10,0,11,12,13,6] + curve: {} + style: + thickness: 1 + 1: + through: [4,3aa,14] + curve: {} + style: + thickness: 1 + 2: + through: [14,2,15] + style: + thickness: 1 + 3: + through: [6,3bb,15] + curve: {} + style: + thickness: 1 + 4: + through: [0,1,3,2] + 0: + through: [14,4,0,6,15] + abc: + through: [B,A,C] + 5: + through: [16,17] + + From 4679dd8a22dadbceb3333fed2cb04eb84d1b98e6 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Sun, 27 Jul 2025 18:26:37 +0200 Subject: [PATCH 4/6] Add unit tests for points --- pkg/path/path.go | 2 +- pkg/path/splines.go | 2 +- pkg/pattern/pattern.go | 2 +- pkg/point/absolute_point.go | 4 +- pkg/point/absolute_point_test.go | 71 +++++++++++++++++++++++++++++ pkg/point/between_point.go | 5 +- pkg/point/between_point_test.go | 48 +++++++++++++++++++ pkg/point/extend_point.go | 7 +-- pkg/point/point.go | 2 +- pkg/point/relative_point.go | 2 +- pkg/position/position.go | 5 ++ pkg/template/line.go | 2 +- pkg/template/point.go | 4 +- pkg/template/point_test.go | 12 ++--- pkg/util/id_test.go | 7 +-- templates/tailored_shirt_block.yaml | 8 ++-- 16 files changed, 154 insertions(+), 29 deletions(-) create mode 100644 pkg/point/absolute_point_test.go create mode 100644 pkg/point/between_point_test.go diff --git a/pkg/path/path.go b/pkg/path/path.go index e1be8c3..2a96310 100644 --- a/pkg/path/path.go +++ b/pkg/path/path.go @@ -20,7 +20,7 @@ func (p *Polygon) Through() []point.Point { // NewPolygon returns a new [Polygon]. func NewPolygon(points []point.Point, style Style, id util.ID) *Polygon { - return &Polygon{points: points, style: style} + return &Polygon{points: points, style: style, id: id} } // WithStyle updates the style of the Polygon. diff --git a/pkg/path/splines.go b/pkg/path/splines.go index aa80281..6c5445b 100644 --- a/pkg/path/splines.go +++ b/pkg/path/splines.go @@ -2,10 +2,10 @@ package path import ( "fmt" - "git.wtrh.nl/patterns/gopatterns/pkg/util" "log/slog" "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "github.com/tdewolff/canvas" splines "gitlab.com/Achilleshiel/gosplines" ) diff --git a/pkg/pattern/pattern.go b/pkg/pattern/pattern.go index b3ee4d3..d11f211 100644 --- a/pkg/pattern/pattern.go +++ b/pkg/pattern/pattern.go @@ -3,11 +3,11 @@ package pattern import ( "fmt" - "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" "git.wtrh.nl/patterns/gopatterns/pkg/pattern/text" "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "github.com/tdewolff/canvas" "golang.org/x/image/font/gofont/goregular" "gopkg.in/Knetic/govaluate.v3" diff --git a/pkg/point/absolute_point.go b/pkg/point/absolute_point.go index 6b4e912..998621b 100644 --- a/pkg/point/absolute_point.go +++ b/pkg/point/absolute_point.go @@ -19,7 +19,7 @@ type AbsolutePoint struct { // Matrix calculates and returns the [canvas.Matrix] of a point. func (a *AbsolutePoint) Matrix() canvas.Matrix { - return canvas.Identity.Translate(a.position.Vector.Values()).Rotate(a.position.Rotation) + return canvas.Identity.Translate(a.position.Vector.Values()).Rotate(a.position.RotationD()) } // ID returns the point ID. @@ -69,7 +69,7 @@ func (a *AbsolutePoint) SetDraw() { // UnsetDraw indicates that the point should not be drawn. func (a *AbsolutePoint) UnsetDraw() { - a.draw = true + a.draw = false } // Hide returns if the point must remain hidden. diff --git a/pkg/point/absolute_point_test.go b/pkg/point/absolute_point_test.go new file mode 100644 index 0000000..052d631 --- /dev/null +++ b/pkg/point/absolute_point_test.go @@ -0,0 +1,71 @@ +package point_test + +import ( + "math" + "testing" + + "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/util" + "git.wtrh.nl/patterns/gopatterns/pkg/vector" + "github.com/stretchr/testify/require" + "github.com/tdewolff/canvas" +) + +func EqualMatrix(t *testing.T, expected, actual canvas.Matrix, delta float64) { + t.Helper() + + require.InDeltaSlice(t, expected[0][:], actual[0][:], delta) + require.InDeltaSlice(t, expected[1][:], actual[1][:], delta) +} + +func TestAbsolutePoint_Matrix(t *testing.T) { + rotation := math.Pi/2 + 2 + newPoint := point.NewAbsolutePoint(1, 2, rotation, "1") + actual := newPoint.Matrix() + + expected := canvas.Matrix{ + {math.Cos(rotation), -math.Sin(rotation), 1.0}, + {math.Sin(rotation), math.Cos(rotation), 2.0}, + } + + EqualMatrix(t, actual, expected, 1e-10) +} + +func TestAbsolutePoint_ID(t *testing.T) { + newPoint := point.NewAbsolutePoint(1, 2, 3, "this is a test value") + require.Equal(t, util.ID("this is a test value"), newPoint.ID()) +} + +func TestAbsolutePoint_Name(t *testing.T) { + require.Equal(t, "this is a test value", point.NewAbsolutePoint(1, 2, 3, "this is a test value").Name()) +} + +func TestAbsolutePoint_Position(t *testing.T) { + actual := point.NewAbsolutePoint(1, 2, 3, "this is a test value").Position() + expected := position.Position{ + Vector: vector.Vector{X: 1, Y: 2}, + Rotation: 3, + } + + require.Equal(t, expected, actual) +} + +func TestAbsolutePoint_Draw(t *testing.T) { + newPoint := point.NewAbsolutePoint(1, 2, 3, "this is a test value") + require.False(t, newPoint.Draw()) + + newPoint.SetDraw() + require.True(t, newPoint.Draw()) + + newPoint.UnsetDraw() + require.False(t, newPoint.Draw()) +} + +func TestAbsolutePoint_Hide(t *testing.T) { + newPoint := point.NewAbsolutePoint(1, 2, 3, "this is a test value") + require.False(t, newPoint.Hide()) + + newPoint.SetHide() + require.True(t, newPoint.Hide()) +} diff --git a/pkg/point/between_point.go b/pkg/point/between_point.go index 33168aa..f585dab 100644 --- a/pkg/point/between_point.go +++ b/pkg/point/between_point.go @@ -1,10 +1,10 @@ package point import ( - "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/vector" "github.com/tdewolff/canvas" ) @@ -53,8 +53,7 @@ func (b *BetweenPoint) Vector() vector.Vector { // Matrix calculates and returns the [canvas.Matrix] of a point. func (b *BetweenPoint) Matrix() canvas.Matrix { - return b.p.Matrix().Translate(b.inBetween().Values()). - Rotate((b.p.Vector().AngleBetween(b.q.Vector()) - math.Pi/2) * 180 / math.Pi) + return canvas.Identity.Translate(b.Vector().Values()).Rotate(b.Position().RotationD()) } // ID returns the point ID. diff --git a/pkg/point/between_point_test.go b/pkg/point/between_point_test.go new file mode 100644 index 0000000..c12292d --- /dev/null +++ b/pkg/point/between_point_test.go @@ -0,0 +1,48 @@ +package point_test + +import ( + "math" + "testing" + + "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/position/testutil" + "git.wtrh.nl/patterns/gopatterns/pkg/vector" + "github.com/tdewolff/canvas" +) + +func TestBetweenPoint_Position(t *testing.T) { + p1 := point.NewAbsolutePoint(0, 0, 0, "1") + p2 := point.NewAbsolutePoint(1, 1, 0, "2") + p3 := point.NewBetweenPoint(p1, p2, 0.5, "3") + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 0.5, + Y: 0.5, + }, + Rotation: -math.Pi / 4, + }, p3.Position(), 1e-10) + + matrix := p3.Matrix() + EqualMatrix(t, canvas.Matrix{ + {math.Cos(-math.Pi / 4), -math.Sin(-math.Pi / 4), 0.5}, + {math.Sin(-math.Pi / 4), math.Cos(-math.Pi / 4), 0.5}, + }, matrix, 1e-10) + + p4 := point.NewAbsolutePoint(0, 1, 0, "4") + p5 := point.NewBetweenPoint(p3, p4, 0.5, "5") + + testutil.EqualPosition(t, position.Position{ + Vector: vector.Vector{ + X: 0.25, + Y: 0.75, + }, + Rotation: math.Pi / 4, + }, p5.Position(), 1e-10) + + matrix2 := p5.Matrix() + EqualMatrix(t, canvas.Matrix{ + {math.Cos(math.Pi / 4), -math.Sin(math.Pi / 4), 0.25}, + {math.Sin(math.Pi / 4), math.Cos(math.Pi / 4), 0.75}, + }, matrix2, 1e-10) +} diff --git a/pkg/point/extend_point.go b/pkg/point/extend_point.go index d8c1f6f..caf476d 100644 --- a/pkg/point/extend_point.go +++ b/pkg/point/extend_point.go @@ -1,10 +1,10 @@ package point import ( - "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/vector" "github.com/tdewolff/canvas" ) @@ -52,8 +52,9 @@ func (b *ExtendPoint) Vector() vector.Vector { // Matrix calculates and returns the [canvas.Matrix] of a point. func (b *ExtendPoint) Matrix() canvas.Matrix { - return b.to.Matrix().Translate(b.extendedVector().Values()). - Rotate((b.from.Vector().AngleBetween(b.to.Vector()) - math.Pi/2) * 180 / math.Pi) + return canvas.Identity.Translate(b.Position().Vector.Values()).Rotate(b.Position().RotationD()) + //return b.to.Matrix().Translate(b.extendedVector().Values()). + // Rotate((b.from.Vector().AngleBetween(b.to.Vector()) - math.Pi/2) * 180 / math.Pi) } // ID returns the point ID. diff --git a/pkg/point/point.go b/pkg/point/point.go index 3cf0dee..0e03e1f 100644 --- a/pkg/point/point.go +++ b/pkg/point/point.go @@ -53,7 +53,7 @@ func Draw(c *canvas.Canvas, point Point, face *canvas.FontFace, debug bool) { c.RenderPath(path, style, m) text := canvas.NewTextLine(face, point.Name(), canvas.Bottom) - c.RenderText(text, m.Translate(2, -4)) + c.RenderText(text, m.Translate(2, -4).Rotate(-point.Position().RotationD())) if debug { yStyle := canvas.Style{ diff --git a/pkg/point/relative_point.go b/pkg/point/relative_point.go index 0dbab40..9762fa5 100644 --- a/pkg/point/relative_point.go +++ b/pkg/point/relative_point.go @@ -1,10 +1,10 @@ package point import ( - "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "git.wtrh.nl/patterns/gopatterns/pkg/position" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/vector" "github.com/tdewolff/canvas" ) diff --git a/pkg/position/position.go b/pkg/position/position.go index e15dcc4..3050ed8 100644 --- a/pkg/position/position.go +++ b/pkg/position/position.go @@ -22,6 +22,11 @@ func (p Position) Add(q Position) Position { } } +// RotationD returns the rotation angle of the position in degrees. +func (p Position) RotationD() float64 { + return p.Rotation * 180 / math.Pi +} + // Distance returns the distance between two positions. func (p Position) Distance(q Position) float64 { return p.Vector.Distance(q.Vector) diff --git a/pkg/template/line.go b/pkg/template/line.go index 3058cc0..8f5d1c1 100644 --- a/pkg/template/line.go +++ b/pkg/template/line.go @@ -2,11 +2,11 @@ package template import ( "errors" - "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/path" "git.wtrh.nl/patterns/gopatterns/pkg/pattern" "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" ) var ErrLineNotFound = errors.New("required path not found") diff --git a/pkg/template/point.go b/pkg/template/point.go index 622661d..eff169c 100644 --- a/pkg/template/point.go +++ b/pkg/template/point.go @@ -3,13 +3,13 @@ package template import ( "errors" "fmt" - "git.wtrh.nl/patterns/gopatterns/pkg/path" - "git.wtrh.nl/patterns/gopatterns/pkg/util" "maps" "math" "strconv" + "git.wtrh.nl/patterns/gopatterns/pkg/path" "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "gopkg.in/Knetic/govaluate.v3" ) diff --git a/pkg/template/point_test.go b/pkg/template/point_test.go index 99d5943..a20a589 100644 --- a/pkg/template/point_test.go +++ b/pkg/template/point_test.go @@ -2,7 +2,6 @@ package template_test import ( _ "embed" - "git.wtrh.nl/patterns/gopatterns/pkg/util" "math" "testing" @@ -10,6 +9,7 @@ import ( "git.wtrh.nl/patterns/gopatterns/pkg/position" "git.wtrh.nl/patterns/gopatterns/pkg/position/testutil" "git.wtrh.nl/patterns/gopatterns/pkg/template" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "git.wtrh.nl/patterns/gopatterns/pkg/vector" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -231,11 +231,11 @@ func TestFunctions(t *testing.T) { tests := map[string]struct { result float64 }{ - "distance": {result: 5}, - "angle": {result: math.Atan2(3, 4)}, - "yDistance": {result: 3}, - "xDistance": {result: 4}, - "lineLength": {result: 12}, + "distance": {result: 5}, + "angle": {result: math.Atan2(3, 4)}, + "yDistance": {result: 3}, + "xDistance": {result: 4}, + "lineLength": {result: 12}, "lineLength2": {result: 4}, } temp := &template.Template{} diff --git a/pkg/util/id_test.go b/pkg/util/id_test.go index dad652b..0f9dd1f 100644 --- a/pkg/util/id_test.go +++ b/pkg/util/id_test.go @@ -1,9 +1,10 @@ package util_test import ( + "testing" + "git.wtrh.nl/patterns/gopatterns/pkg/util" "github.com/stretchr/testify/require" - "testing" ) func TestID(t *testing.T) { @@ -20,7 +21,7 @@ func TestID(t *testing.T) { }, "1.test": { panel: "1", - name:"test", + name: "test", }, } for testName, tt := range tests { @@ -30,4 +31,4 @@ func TestID(t *testing.T) { require.Equal(t, tt.name, id.Name()) }) } -} \ No newline at end of file +} diff --git a/templates/tailored_shirt_block.yaml b/templates/tailored_shirt_block.yaml index 5ac9866..6276c1e 100644 --- a/templates/tailored_shirt_block.yaml +++ b/templates/tailored_shirt_block.yaml @@ -367,8 +367,8 @@ panels: 8a: between: from: 4 - to: 0 - offset: 0.25 + to: 9a + offset: 0.5 8: relativeTo: 8a position: @@ -384,9 +384,9 @@ panels: x: -12.5 10a: between: - from: 4 + from: 9a to: 0 - offset: 0.75 + offset: 0.5 10: relativeTo: 10a position: From f7a201c486a16b4f3990cd4b5f9b4a0dc070f8f0 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Thu, 18 Sep 2025 15:21:46 +0200 Subject: [PATCH 5/6] Add collar templates --- templates/dimension_names.yaml | 4 + templates/shirt_collar.yaml | 217 ++++++++++++++++++++++++++++++++ templates/standing_collars.yaml | 120 ++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 templates/shirt_collar.yaml create mode 100644 templates/standing_collars.yaml diff --git a/templates/dimension_names.yaml b/templates/dimension_names.yaml index 54aa40a..c290cd8 100644 --- a/templates/dimension_names.yaml +++ b/templates/dimension_names.yaml @@ -5,6 +5,8 @@ trouser_waist: name: Trouser waist body_rise: name: Body rise +button_stand: + name: Button stand inside_leg: name: Inside leg trouser_bottom_width: @@ -35,6 +37,8 @@ cuff_size: name: Cuff size cuff_depth: name: Cuff Depth +collar_depth: + name: Collar Depth bovenwijdte: name: Bovenwijdte diff --git a/templates/shirt_collar.yaml b/templates/shirt_collar.yaml new file mode 100644 index 0000000..372ec80 --- /dev/null +++ b/templates/shirt_collar.yaml @@ -0,0 +1,217 @@ +--- +panels: + Shirt collar: + points: + 1: {} + 2: + relativeTo: 1 + position: + x: neck_size /2 + 3: + relativeTo: 2 + position: + x: button_stand + 12.5 + 4: + relativeTo: 1 + position: + x: DistanceBetween("1","2") / 4*3 + 5: + relativeTo: 1 + position: + y: collar_depth + button_stand + 2 + 6: + between: + from: 1 + to: 5 + offset: 0.5 + 7: + relativeTo: 2 + position: + y: DistanceBetween("1","6") + 8: + relativeTo: 3 + position: + y: DistanceBetween("1","6") + 9: + relativeTo: 8 + position: + x: -10 + 10: + extend: + from: 9 + to: 3 + offset: -7.5 + 11: + extend: + from: 3 + to: 9 + offset: -7.5 + 12: + relativeTo: 1 + position: + y: 5 + 13: + relativeTo: 5 + position: + x: DistanceBetween("1","4")/5 *4 + hide: true + 14: + relativeTo: 2 + position: + y: DistanceBetween("1","5") + 14a: + relativeTo: 14 + polar: + length: 15 + rotation: pi/4 + hide: true + 14b: + relativeTo: 14 + position: + x: 40 + lines: + 1: + through: [12,4,10] + curve: {} + style: + thickness: 1 + 2: + through: [10,11,7,14a] + style: + thickness: 1 + 3: + through: [12,6,5,13] + style: + thickness: 1 + 4: + through: [13,14a] + curve: + start: 14b + style: + thickness: 1 + 5: + through: [13,14,7,2] + 6: + through: [12,1,4,2,3,8,7,6] + 7: + through: [3,10,11,9] + Shirt collar with stand: + points: + 1: {} + 2: + relativeTo: 1 + position: + x: neck_size /2 + 3: + relativeTo: 2 + position: + x: button_stand + 12.5 + 4: + relativeTo: 1 + position: + x: DistanceBetween("1","2") / 4*3 + 5: + relativeTo: 1 + position: + y: collar_depth + button_stand + 2 + 6: + between: + from: 1 + to: 5 + offset: 0.5 + 7: + relativeTo: 2 + position: + y: DistanceBetween("1","6") + 8: + relativeTo: 3 + position: + y: DistanceBetween("1","6") + 9: + relativeTo: 8 + position: + x: -10 + 10: + extend: + from: 9 + to: 3 + offset: -7.5 + 11: + extend: + from: 3 + to: 9 + offset: -7.5 + 12: + relativeTo: 1 + position: + y: 5 + 13: + between: + from: 6 + to: 7 + offset: 0.5 + 14: + relativeTo: 7 + position: + y: -10 + 15: + extend: + from: 10 + to: 11 + offset: -10 + 16: + relativeTo: 2 + position: + y: DistanceBetween("1","5") + 16a: + relativeTo: 16 + polar: + length: 15 + rotation: pi/4 + hide: true + 16b: + relativeTo: 16 + position: + x: 40 + 17: + relativeTo: 5 + position: + x: DistanceBetween("1","4")/5 *4 + hide: true + lines: + 1: + through: [12,4,10] + curve: {} + style: + thickness: 1 + 2: + through: [14,16a] + style: + thickness: 1 + 3: + through: [13,14,15] + style: + thickness: 1 + curve: + start: 8 + 4: + through: [6,13] + style: + thickness: 1 + 5: + through: [12,6,5,17] + style: + thickness: 1 + 6: + through: [12,1,4,2,3,8,7,6] + 7: + through: [10,15] + style: + thickness: 1 + 8: + through: [17,16a] + curve: + start: 16b + style: + thickness: 1 + diff --git a/templates/standing_collars.yaml b/templates/standing_collars.yaml new file mode 100644 index 0000000..7e6b811 --- /dev/null +++ b/templates/standing_collars.yaml @@ -0,0 +1,120 @@ +--- +panels: + 1a Standard straight collar: + points: + 1: {} + 2: + relativeTo: 1 + position: + x: neck_size/2 + 3: + relativeTo: 2 + position: + x: button_stand + 10 + 4: + relativeTo: 1 + position: + y: collar_depth + 20 + 5: + relativeTo: 3 + position: + y: collar_depth + 20 + 6: + relativeTo: 5 + position: + x: -15 + 7: + relativeTo: 6 + position: + x: -DistanceBetween("2","3") + 8: + relativeTo: 1 + position: + x: DistanceBetween("1","2") * 3 / 4 + 9: + relativeTo: 3 + position: + y: 15 + lines: + 1: + through: [9,6,7,4,1,8] + style: + thickness: 1 + 2: + through: [8,3,5,7,2] + 3: + through: [8,9] + curve: + start: 3 + style: + thickness: 1 + 1b Standard straight collar with stand: + points: + 1: {} + 2: + relativeTo: 1 + position: + x: neck_size/2 + 3: + relativeTo: 2 + position: + x: button_stand + 10 + 4: + relativeTo: 1 + position: + y: button_stand + 20 + 5: + relativeTo: 3 + position: + y: collar_depth + 20 + 6: + relativeTo: 5 + position: + x: -15 + 7: + relativeTo: 6 + position: + x: -DistanceBetween("2","3") + 8: + relativeTo: 1 + position: + x: DistanceBetween("1","2") * 3 / 4 + 9: + relativeTo: 3 + position: + y: 15 + 10: + relativeTo: 4 + position: + y: 20 + 11: + relativeTo: 6 + position: + y: 20 + 12: + relativeTo: 10 + position: + y: collar_depth + 20 + 13: + relativeTo: 11 + position: + y: collar_depth + 20 + 14: + relativeTo: 7 + position: + y: 20 + lines: + 1: + through: [9,6,7,4,1,8] + style: + thickness: 1 + 2: + through: [8,3,5,7,2] + 3: + through: [8,9] + curve: + start: 3 + style: + thickness: 1 + + From 343c9578e655de71b53b678821d4d15e251e1617 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Tue, 23 Sep 2025 12:53:53 +0200 Subject: [PATCH 6/6] Fix tailored shirt block --- go.mod | 20 ++++++------ go.sum | 19 +++++++++++ pkg/pattern/panel/panel.go | 6 ++++ pkg/pattern/text/text.go | 17 +++++----- pkg/renderer/renderer.go | 27 ++-------------- pkg/template/information.go | 39 +++++++++++++++++++++++ pkg/template/line.go | 2 +- pkg/template/point.go | 28 ++++++++++++++--- pkg/template/template.go | 34 ++++++++++++++++++-- templates/basic_trouser_block.yaml | 37 +++++++++++++++++----- templates/classic_trouser_block.yaml | 35 +++++++++++++++++++-- templates/tailored_shirt_block.yaml | 47 +++++++++++++++++++++++++--- 12 files changed, 246 insertions(+), 65 deletions(-) diff --git a/go.mod b/go.mod index 947b7e6..3fe3266 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,20 @@ module git.wtrh.nl/patterns/gopatterns -go 1.24.5 +go 1.25.1 require ( github.com/stoewer/go-strcase v1.3.1 - github.com/stretchr/testify v1.10.0 - github.com/tdewolff/canvas v0.0.0-20250508181010-75987a1ae9cc + github.com/stretchr/testify v1.11.1 + github.com/tdewolff/canvas v0.0.0-20250923071733-b2b2ba99a987 gitlab.com/Achilleshiel/gosplines v0.0.0-20240602125710-c93b87aea1ee gitlab.com/slxh/go/env v1.2.0 - golang.org/x/image v0.29.0 + golang.org/x/image v0.31.0 gopkg.in/Knetic/govaluate.v3 v3.0.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - codeberg.org/go-latex/latex v0.1.0 // indirect + codeberg.org/go-latex/latex v0.2.0 // indirect codeberg.org/go-pdf/fpdf v0.11.1 // indirect github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect @@ -33,12 +33,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // indirect - github.com/tdewolff/font v0.0.0-20250602165824-bf05faa75fda // indirect - github.com/tdewolff/minify/v2 v2.23.8 // indirect - github.com/tdewolff/parse/v2 v2.8.1 // indirect + github.com/tdewolff/font v0.0.0-20250902141222-fb72ecc1bc0a // indirect + github.com/tdewolff/minify/v2 v2.24.3 // indirect + github.com/tdewolff/parse/v2 v2.8.4-0.20250902141113-be7b6b11bb1b // indirect github.com/wcharczuk/go-chart/v2 v2.1.2 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/text v0.29.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect gonum.org/v1/plot v0.16.0 // indirect modernc.org/knuth v0.5.5 // indirect diff --git a/go.sum b/go.sum index 319e41a..8a99c30 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,15 @@ codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOL codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= +codeberg.org/go-latex/latex v0.2.0 h1:Ol/a6VHY06N+5gPfewswymoRb5ZcKDXWVaVegcx4hbI= +codeberg.org/go-latex/latex v0.2.0/go.mod h1:VJAwQir7/T8LZxj7xAPivISKiVOwkMpQ8bTuPQ31X0Y= codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs= codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= +git.sr.ht/~sbinet/gg v0.7.0 h1:YmNf7YKd7diDMTPm86hZa1EM3pbkOyD/zzjl0LZUdNM= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g= @@ -70,14 +73,24 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tdewolff/canvas v0.0.0-20250508181010-75987a1ae9cc h1:hZ/uFsNQuNRaJWk/IVvIogyBfmfhRg2CLOhKpfeNGK0= github.com/tdewolff/canvas v0.0.0-20250508181010-75987a1ae9cc/go.mod h1:xXkALI8c2qLMmoMWPRhDHkc1AtNSW/OGPPxp7lBfycU= +github.com/tdewolff/canvas v0.0.0-20250923071733-b2b2ba99a987 h1:tzQqRIECH8fEHpkG16gD7uOadYfgSgAuzxq6GaHk8v0= +github.com/tdewolff/canvas v0.0.0-20250923071733-b2b2ba99a987/go.mod h1:r5O5UHm7WMj6o9mbY1gdBHkg308r0EcfS/10YBbBLHI= github.com/tdewolff/font v0.0.0-20250602165824-bf05faa75fda h1:WB5DpyaMFc/Y+n/neEg8o1lRUQgaj53FVK2H7mTT5zs= github.com/tdewolff/font v0.0.0-20250602165824-bf05faa75fda/go.mod h1:eDnkgh2pt95UFXk0GsUv2JNj5gumg78c02QX0TdcwTA= +github.com/tdewolff/font v0.0.0-20250902141222-fb72ecc1bc0a h1:IuR6wFg9mSxhxcCogXcG5bte813psi1PE4KTjMAkM6k= +github.com/tdewolff/font v0.0.0-20250902141222-fb72ecc1bc0a/go.mod h1:lGIMHKyJnHCmJeb9MqdWnudFoPDVz8COuALmILs95xY= github.com/tdewolff/minify/v2 v2.23.8 h1:tvjHzRer46kwOfpdCBCWsDblCw3QtnLJRd61pTVkyZ8= github.com/tdewolff/minify/v2 v2.23.8/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno= +github.com/tdewolff/minify/v2 v2.24.3 h1:BaKgWSFLKbKDiUskbeRgbe2n5d1Ci1x3cN/eXna8zOA= +github.com/tdewolff/minify/v2 v2.24.3/go.mod h1:1JrCtoZXaDbqioQZfk3Jdmr0GPJKiU7c1Apmb+7tCeE= github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po= github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= +github.com/tdewolff/parse/v2 v2.8.4-0.20250902141113-be7b6b11bb1b h1:ltRewarE+mA/m3nJrYJVfFFUUFP+RXOx8V1g5tVsU64= +github.com/tdewolff/parse/v2 v2.8.4-0.20250902141113-be7b6b11bb1b/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= @@ -98,6 +111,8 @@ golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= +golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= +golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -114,6 +129,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -151,6 +168,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/pkg/pattern/panel/panel.go b/pkg/pattern/panel/panel.go index c186d57..b1489d3 100644 --- a/pkg/pattern/panel/panel.go +++ b/pkg/pattern/panel/panel.go @@ -3,6 +3,7 @@ package panel import ( "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" "git.wtrh.nl/patterns/gopatterns/pkg/path" + "git.wtrh.nl/patterns/gopatterns/pkg/pattern/text" "git.wtrh.nl/patterns/gopatterns/pkg/point" "git.wtrh.nl/patterns/gopatterns/pkg/util" "github.com/tdewolff/canvas" @@ -13,6 +14,7 @@ type Panel struct { Lines map[util.ID]path.Path Points map[util.ID]point.Point Dimensions dimensions.Dimensions + Texts []text.Text } func (p Panel) Draw(c *canvas.Canvas, face *canvas.FontFace, debug bool) error { @@ -31,5 +33,9 @@ func (p Panel) Draw(c *canvas.Canvas, face *canvas.FontFace, debug bool) error { point.Draw(c, drawPoints, face, debug) } + for _, t := range p.Texts { + t.ToCanvas(c, face) + } + return nil } diff --git a/pkg/pattern/text/text.go b/pkg/pattern/text/text.go index 5628659..66d0b38 100644 --- a/pkg/pattern/text/text.go +++ b/pkg/pattern/text/text.go @@ -1,21 +1,22 @@ package text import ( - "git.wtrh.nl/patterns/gopatterns/pkg/point" + "git.wtrh.nl/patterns/gopatterns/pkg/position" "github.com/tdewolff/canvas" ) type Text struct { - point.Point - anchor string - text string + Position position.Position + Anchor string + Text string } -func NewText(point point.Point, anchor string, text string) *Text { - return &Text{Point: point, anchor: anchor, text: text} +func NewText(position position.Position, anchor string, text string) Text { + return Text{Position: position, Anchor: anchor, Text: text} } func (t Text) ToCanvas(c *canvas.Canvas, face *canvas.FontFace) { - text := canvas.NewTextLine(face, t.text, canvas.Left) - c.RenderText(text, t.Matrix()) + text := canvas.NewTextLine(face, t.Text, canvas.Left) + matrix := canvas.Identity.Translate(t.Position.Vector.X, t.Position.Vector.Y).Rotate(t.Position.RotationD()) + c.RenderText(text, matrix) } diff --git a/pkg/renderer/renderer.go b/pkg/renderer/renderer.go index e30b7dc..f45a484 100644 --- a/pkg/renderer/renderer.go +++ b/pkg/renderer/renderer.go @@ -2,10 +2,6 @@ package renderer import ( "fmt" - "path/filepath" - "strings" - "unicode" - "git.wtrh.nl/patterns/gopatterns/pkg/config" "git.wtrh.nl/patterns/gopatterns/pkg/dimensions" "git.wtrh.nl/patterns/gopatterns/pkg/template" @@ -13,6 +9,8 @@ import ( "github.com/tdewolff/canvas" "github.com/tdewolff/canvas/renderers" "golang.org/x/image/font/gofont/goregular" + "path/filepath" + "strings" ) type Storage interface { @@ -37,7 +35,7 @@ func RenderPatterns(s Storage, request config.Request, outputDir string, debug b // renderer := Renderer{dimensions: dim, owner: request.Owner, pattern: request.Template} for name := range loadedTemplate.Panels { - newPanel, err := loadedTemplate.GetPanel(template.Request{Dims: dim, Panel: name}) + newPanel, err := loadedTemplate.GetPanel(template.Request{Dims: dim, Panel: name, Owner: request.Owner}) if err != nil { return nil, err } @@ -143,22 +141,3 @@ type Renderer struct { // // return 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/template/information.go b/pkg/template/information.go index 6457888..d365541 100644 --- a/pkg/template/information.go +++ b/pkg/template/information.go @@ -1,6 +1,45 @@ package template +import ( + "fmt" + "git.wtrh.nl/patterns/gopatterns/pkg/pattern/text" + "slices" + "strings" +) + type Information struct { Point `yaml:",inline"` Anchor string `yaml:"anchor"` } + +func (t Template) createInformation(req request) (text.Text, error) { + templatePanel, ok := t.Panels[req.panel] + if !ok { + return text.Text{}, ErrPanelNotFound + } + + dims := make([]string, 0, len(req.dimensions)) + for _, dimension := range req.dimensions { + dims = append(dims, fmt.Sprintf(" %s: %.1f cm", dimension.Name, dimension.Value/10)) + } + + slices.Sort(dims) + dims = append([]string{ + "For: " + req.owner, + "Pattern: " + startCase(t.Name), + "Panel: " + startCase(req.panel), + "Hem allowance: " + templatePanel.Allowances.Hem, + "Seam allowance: " + templatePanel.Allowances.Seam, + "\nMeasurements:", + }, dims...) + + templatePanel.Points["_information"] = templatePanel.Information.Point + point, err := t.getOrCreatePoint("_information", req, 0) + if err != nil { + return text.Text{}, err + } + + info := text.NewText(point.Position(), templatePanel.Information.Anchor, strings.Join(dims, "\n")) + + return info, nil +} diff --git a/pkg/template/line.go b/pkg/template/line.go index 8f5d1c1..ff7993d 100644 --- a/pkg/template/line.go +++ b/pkg/template/line.go @@ -69,7 +69,7 @@ func (t Template) getOrCreateLine(id util.ID, req request, depth int) (path.Path } func (t Template) createLine(id util.ID, req request, depth int) (path.Path, error) { - line, err := t.templateLine(req.name, id) + line, err := t.templateLine(req.panel, id) if err != nil { return nil, err } diff --git a/pkg/template/point.go b/pkg/template/point.go index eff169c..8100487 100644 --- a/pkg/template/point.go +++ b/pkg/template/point.go @@ -129,6 +129,24 @@ func (t Template) functions(req request) map[string]govaluate.ExpressionFunction return line[0].Length() }, + "DiagonalTo": func(args ...interface{}) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf("function DiagonalTo() requires 3 arguments: %w", ErrInvalidArguments) + } + + points, err := t.getOrCreatePointsFromArgs(req, args[0:2]...) + if err != nil { + return nil, err + } + + f, ok := args[2].(float64) + if !ok { + return nil, fmt.Errorf("function DiagonalTo() requires the third argument to be a float: %w", + ErrInvalidArguments) + } + + return math.Sqrt(math.Pow(f, 2) - math.Pow(points[0].Position().Distance(points[1].Position()), 2)), nil + }, }) return functions @@ -253,7 +271,7 @@ func (t Template) evaluationFunctions() map[string]govaluate.ExpressionFunction } func (t Template) createPoint(id util.ID, req request, depth int) (p point.Point, err error) { - templatePoint, err := t.templatePoint(req.name, id) + templatePoint, err := t.templatePoint(req.panel, id) if err != nil { return nil, fmt.Errorf("creating point: %w", err) } @@ -298,7 +316,7 @@ func (t Template) createPoint(id util.ID, req request, depth int) (p point.Point } func (t Template) createRelative(id util.ID, req request, depth int) (*point.RelativePoint, error) { - templatePoint, err := t.templatePoint(req.name, id) + templatePoint, err := t.templatePoint(req.panel, id) if err != nil { return nil, err } @@ -325,7 +343,7 @@ func (t Template) createRelative(id util.ID, req request, depth int) (*point.Rel //nolint:ireturn,dupl func (t Template) createBetween(id util.ID, req request, depth int) (point.Point, error) { - newPoint, err := t.templatePoint(req.name, id) + newPoint, err := t.templatePoint(req.panel, id) if err != nil { return nil, err } @@ -388,7 +406,7 @@ func (t Template) getOrCreatePoint(id util.ID, req request, depth int) (point.Po //nolint:ireturn,dupl func (t Template) createExtend(id util.ID, req request, depth int) (point.Point, error) { - newPoint, err := t.templatePoint(req.name, id) + newPoint, err := t.templatePoint(req.panel, id) if err != nil { return nil, err } @@ -417,7 +435,7 @@ func (t Template) createExtend(id util.ID, req request, depth int) (point.Point, //nolint:ireturn func (t Template) createPolar(id util.ID, req request, depth int) (point.Point, error) { - templatePoint, err := t.templatePoint(req.name, id) + templatePoint, err := t.templatePoint(req.panel, id) if err != nil { return nil, err } diff --git a/pkg/template/template.go b/pkg/template/template.go index fcc5c28..27fb2d4 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -6,6 +6,7 @@ import ( "git.wtrh.nl/patterns/gopatterns/pkg/pattern/panel" "git.wtrh.nl/patterns/gopatterns/pkg/point" "git.wtrh.nl/patterns/gopatterns/pkg/util" + "unicode" ) type Template struct { @@ -17,13 +18,15 @@ type Template struct { } type Request struct { + Owner string Dims dimensions.Dimensions Panel string } type request struct { dimensions dimensions.Dimensions - name string + panel string + owner string lines map[util.ID]path.Path points map[util.ID]point.Point } @@ -36,9 +39,10 @@ func (t Template) GetPanel(req Request) (panel.Panel, error) { r := request{ dimensions: req.Dims, - name: req.Panel, + panel: req.Panel, lines: make(map[util.ID]path.Path), points: make(map[util.ID]point.Point), + owner: req.Owner, } result := panel.Panel{ @@ -75,5 +79,31 @@ func (t Template) GetPanel(req Request) (panel.Panel, error) { result.Points[id] = newPoint } + text, err := t.createInformation(r) + if err != nil { + return panel.Panel{}, err + } + + result.Texts = append(result.Texts, text) + return result, nil } + +func startCase(text string) string { + output := make([]rune, len(text)) + + for i, val := range text { + switch { + case i == 0: + output[i] = unicode.ToUpper(val) + case val == '_': + output[i] = ' ' + case output[i-1] == ' ': + output[i] = unicode.ToUpper(val) + default: + output[i] = val + } + } + + return string(output) +} diff --git a/templates/basic_trouser_block.yaml b/templates/basic_trouser_block.yaml index 301e5fe..49c7c4a 100644 --- a/templates/basic_trouser_block.yaml +++ b/templates/basic_trouser_block.yaml @@ -144,14 +144,11 @@ panels: y: -10 relativeTo: 1 points: - 5r: - position: - rotation: pi/4 - relativeTo: 5 5d: - position: - y: 35 - relativeTo: 5r + polar: + length: (waist-640)*0.015625+27.5 + rotation: 3*pi/4 + relativeTo: 5 hide: true 6e: between: @@ -192,28 +189,38 @@ panels: through: [0,2,1,4,3] 5: through: [6,10,0l,0b,0r,11] + style: + thickness: 1 6: through: [9,5d,6] curve: start: 5 end: 6e + style: + thickness: 1 7: through: [15,14,12,13] + style: + thickness: 1 8: through: [15,9] curve: start: 15e + style: + thickness: 1 9: through: [13,8,11] curve: start: 13e + style: + thickness: 1 back: name: Back information: position: x: 10 y: -10 - relativeTo: 1 + relativeTo: 2 allowances: hem: none seam: none @@ -287,26 +294,40 @@ panels: lines: 1: through: [19,21,30l,30b,30r,31l,31b,31r,22] + style: + thickness: 1 2: through: [24,16d,19] curve: start: 23a end: 19e + style: + thickness: 1 3: through: [29,24] curve: start: 29extend + style: + thickness: 1 4: through: [27,25,22] curve: start: 27extend + style: + thickness: 1 5: through: [29,28] + style: + thickness: 1 6: through: [27,26] + style: + thickness: 1 7: through: [28,3down,26] curve: {} + style: + thickness: 1 8: through: [23,1] 9: diff --git a/templates/classic_trouser_block.yaml b/templates/classic_trouser_block.yaml index cdf2879..20e0163 100644 --- a/templates/classic_trouser_block.yaml +++ b/templates/classic_trouser_block.yaml @@ -183,21 +183,32 @@ panels: 3: through: [9, 1extend] 4: - through: [14,12,13,15,14] + through: [14,12,13,15] + style: + thickness: 1 5: through: [14,8,11] curve: start: extend12-14 - end: offset_between11-8 + style: + thickness: 1 6: through: [15,9] curve: start: extend13-15 + style: + thickness: 1 7: through: [9, h5, 6] curve: {} + style: + thickness: 1 8: through: [6, 10, 11] + style: + thickness: 1 + 9: + through: [15,14] back: name: Back @@ -273,24 +284,42 @@ panels: 3: through: [22, 16, 16extend] 4: - through: [28, 30, 29, 27] + through: [28, 30] + style: + thickness: 1 + 4a: + through: [30, 29] + 4b: + through: [29, 27] + style: + thickness: 1 5: through: [21,25a,25c,25b,24] + style: + thickness: 1 6: through: [28,2down,27] curve: {} + style: + thickness: 1 7: through: [23,16offset,19,21] curve: start: 23a + style: + thickness: 1 8: through: [30,23] curve: start: 30extend + style: + thickness: 1 9: through: [29,26,24] curve: start: 29extend + style: + thickness: 1 diff --git a/templates/tailored_shirt_block.yaml b/templates/tailored_shirt_block.yaml index 6276c1e..2e45a8e 100644 --- a/templates/tailored_shirt_block.yaml +++ b/templates/tailored_shirt_block.yaml @@ -103,6 +103,14 @@ panels: through: [22,29,29b,36] style: thickness: 1 + 00: + relativeTo: 17 + 01: + relativeTo: 25 + 02: + relativeTo: 01 + position: + y: (LineLength("11")-70)/3 points: 0: @@ -151,10 +159,13 @@ panels: relativeTo: 0 position: x: half_back + 40 - 14: + 13: relativeTo: 12 position: x: 15 + 14: + relativeTo: 13 + position: y: 20 15: relativeTo: 10 @@ -332,7 +343,34 @@ panels: position: y: 7 x: -30 + 00: + relativeTo: 17 + 01: + relativeTo: 25 + 02: + relativeTo: 01 + position: + y: (LineLength("11")-70)/3 + 03: + between: + from: 01 + to: 02 + offset: 0.5 + 06: + relativeTo: 02 + position: + x: ((DistanceBetween("24","26")^2 - (DistanceBetween("02","26")^2))^0.5) + + sleeve: + information: + relativeTo: A + position: + x: 10 + y: -10 + allowances: + hem: none + seam: 1cm points: 0: {} 1: @@ -342,7 +380,7 @@ panels: 2: relativeTo: 0 position: - y: -(sleeve_length_shirt+60-cuff_depth) + y: -(sleeve_length_shirt+60-cuff_depth - DistanceBetween("body.0","body.13")) 3: between: from: 2 @@ -351,7 +389,7 @@ panels: 4: relativeTo: 1 position: - x: -(502.6/2 -5) + x: -DiagonalTo("0","1",502.6/2 -5) 5: relativeTo: 4 position: @@ -359,7 +397,7 @@ panels: 6: relativeTo: 1 position: - x: (502.6/2 -5) + x: DiagonalTo("0","1",502.6/2 -5) 7: relativeTo: 6 position: @@ -466,6 +504,7 @@ panels: relativeTo: 16 position: y: 150 + lines: scye: through: [4,8,9,10,0,11,12,13,6]