Bladeren bron

Build v2 and refactor code base

implement_v2
Wouter Horlings 4 maanden geleden
bovenliggende
commit
be84172729
48 gewijzigde bestanden met toevoegingen van 1991 en 607 verwijderingen
  1. +1
    -1
      .golangci.yml
  2. +6
    -4
      cmd/gopatterns/gopatterns.go
  3. +36
    -0
      pkg/config/config.go
  4. +2
    -7
      pkg/dimensions/dimensions.go
  5. +62
    -0
      pkg/path/path.go
  6. +10
    -7
      pkg/path/splines.go
  7. +0
    -0
      pkg/path/style.go
  8. +35
    -0
      pkg/pattern/panel/panel.go
  9. +0
    -36
      pkg/pattern/path/path.go
  10. +16
    -8
      pkg/pattern/pattern.go
  11. +0
    -119
      pkg/pattern/template/fixtures/classic_trouser_block.yaml
  12. +0
    -9
      pkg/pattern/template/fixtures/trouser.yaml
  13. +0
    -69
      pkg/pattern/template/line.go
  14. +0
    -124
      pkg/pattern/template/renderer.go
  15. +0
    -56
      pkg/pattern/template/template.go
  16. +1
    -1
      pkg/pattern/text/text.go
  17. +4
    -3
      pkg/point/absolute_point.go
  18. +4
    -3
      pkg/point/between_point.go
  19. +5
    -4
      pkg/point/extend_point.go
  20. +2
    -4
      pkg/point/point.go
  21. +10
    -9
      pkg/point/relative_point.go
  22. +0
    -6
      pkg/position/position.go
  23. +41
    -0
      pkg/position/position_test.go
  24. +15
    -0
      pkg/position/testutil/testutil.go
  25. +164
    -0
      pkg/renderer/renderer.go
  26. +12
    -18
      pkg/storage/storage.go
  27. +13
    -0
      pkg/template/fixtures/absolute_points.yaml
  28. +20
    -0
      pkg/template/fixtures/between_points.yaml
  29. +19
    -0
      pkg/template/fixtures/evaluation_functions.yaml
  30. +18
    -0
      pkg/template/fixtures/extend_points.yaml
  31. +52
    -0
      pkg/template/fixtures/functions.yaml
  32. +17
    -0
      pkg/template/fixtures/polar_points.yaml
  33. +12
    -0
      pkg/template/fixtures/references.yaml
  34. +15
    -0
      pkg/template/fixtures/relative_points.yaml
  35. +0
    -0
      pkg/template/information.go
  36. +157
    -0
      pkg/template/line.go
  37. +16
    -1
      pkg/template/panel.go
  38. +168
    -118
      pkg/template/point.go
  39. +289
    -0
      pkg/template/point_test.go
  40. +0
    -0
      pkg/template/position.go
  41. +79
    -0
      pkg/template/template.go
  42. +0
    -0
      pkg/template/value.go
  43. +28
    -0
      pkg/util/id.go
  44. +33
    -0
      pkg/util/id_test.go
  45. +125
    -0
      spec/pattern.v2.yaml
  46. +4
    -0
      spec/pattern.yaml
  47. +2
    -0
      templates/dimension_names.yaml
  48. +498
    -0
      templates/tailored_shirt_block.v2.yaml

+ 1
- 1
.golangci.yml Bestand weergeven

@@ -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


+ 6
- 4
cmd/gopatterns/gopatterns.go Bestand weergeven

@@ -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 <template-dir>] [-out <output-dir>] 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 <template-dir>] [-out <output-dir>] 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


+ 36
- 0
pkg/config/config.go Bestand weergeven

@@ -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
}

pkg/pattern/dimensions.go → pkg/dimensions/dimensions.go Bestand weergeven

@@ -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
}

+ 62
- 0
pkg/path/path.go Bestand weergeven

@@ -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
}

pkg/pattern/path/splines.go → pkg/path/splines.go Bestand weergeven

@@ -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)

pkg/pattern/path/style.go → pkg/path/style.go Bestand weergeven


+ 35
- 0
pkg/pattern/panel/panel.go Bestand weergeven

@@ -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
}

+ 0
- 36
pkg/pattern/path/path.go Bestand weergeven

@@ -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
}

+ 16
- 8
pkg/pattern/pattern.go Bestand weergeven

@@ -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
}

+ 0
- 119
pkg/pattern/template/fixtures/classic_trouser_block.yaml Bestand weergeven

@@ -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]


+ 0
- 9
pkg/pattern/template/fixtures/trouser.yaml Bestand weergeven

@@ -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

+ 0
- 69
pkg/pattern/template/line.go Bestand weergeven

@@ -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
}

+ 0
- 124
pkg/pattern/template/renderer.go Bestand weergeven

@@ -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
}

+ 0
- 56
pkg/pattern/template/template.go Bestand weergeven

@@ -1,56 +0,0 @@
// Package template makes it possible to describe templates and patterns in yaml files and
// to render them to SVG.
package template

import (
"fmt"
"os"
"unicode"

"gopkg.in/yaml.v3"
)

// Sizes defines a map with the size name and the size value.
type Sizes map[string]float64

// Template contains the generic information to draw a specific clothing pattern.
type Template struct {
Points `yaml:"points"`
Panels `yaml:"panels"`
}

// LoadPattern reads and decodes a [Request] from a yaml file.
func LoadPattern(name string) (Request, error) {
fh, err := os.Open(name)
if err != nil {
return Request{}, fmt.Errorf("open pattern file %q: %w", name, err)
}

pat := Request{}

err = yaml.NewDecoder(fh).Decode(&pat)
if err != nil {
return Request{}, fmt.Errorf("decode content of file %q as yaml: %w", name, err)
}

return pat, nil
}

func startCase(text string) string {
output := make([]rune, len(text))

for i, val := range text {
switch {
case i == 0:
output[i] = unicode.ToUpper(val)
case val == '_':
output[i] = ' '
case output[i-1] == ' ':
output[i] = unicode.ToUpper(val)
default:
output[i] = val
}
}

return string(output)
}

+ 1
- 1
pkg/pattern/text/text.go Bestand weergeven

@@ -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"
)



pkg/pattern/point/absolute_point.go → pkg/point/absolute_point.go Bestand weergeven

@@ -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{

pkg/pattern/point/between_point.go → pkg/point/between_point.go Bestand weergeven

@@ -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
}


pkg/pattern/point/extend_point.go → pkg/point/extend_point.go Bestand weergeven

@@ -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
}


pkg/pattern/point/point.go → pkg/point/point.go Bestand weergeven

@@ -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

pkg/pattern/point/relative_point.go → pkg/point/relative_point.go Bestand weergeven

@@ -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)
}


+ 0
- 6
pkg/position/position.go Bestand weergeven

@@ -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)


+ 41
- 0
pkg/position/position_test.go Bestand weergeven

@@ -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)
})
}
}

+ 15
- 0
pkg/position/testutil/testutil.go Bestand weergeven

@@ -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)
}

+ 164
- 0
pkg/renderer/renderer.go Bestand weergeven

@@ -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)
}

pkg/pattern/template/storage.go → pkg/storage/storage.go Bestand weergeven

@@ -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
}

+ 13
- 0
pkg/template/fixtures/absolute_points.yaml Bestand weergeven

@@ -0,0 +1,13 @@
---
points:
1:
position:
x: test
y: 14.3
panels:
body:
points:
2:
position:
x: test*2
y: -3

+ 20
- 0
pkg/template/fixtures/between_points.yaml Bestand weergeven

@@ -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

+ 19
- 0
pkg/template/fixtures/evaluation_functions.yaml Bestand weergeven

@@ -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: {}

+ 18
- 0
pkg/template/fixtures/extend_points.yaml Bestand weergeven

@@ -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


+ 52
- 0
pkg/template/fixtures/functions.yaml Bestand weergeven

@@ -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]


+ 17
- 0
pkg/template/fixtures/polar_points.yaml Bestand weergeven

@@ -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


+ 12
- 0
pkg/template/fixtures/references.yaml Bestand weergeven

@@ -0,0 +1,12 @@
---
points:
1: {}
3:
relativeTo: body.2
panels:
body:
points:
2:
relativeTo: 1
position:
x: 3

+ 15
- 0
pkg/template/fixtures/relative_points.yaml Bestand weergeven

@@ -0,0 +1,15 @@
---
points:
1:
position:
x: test
y: 14.3
panels:
body:
points:
2:
relativeTo:
1
position:
x: test*2
y: -3

pkg/pattern/template/information.go → pkg/template/information.go Bestand weergeven


+ 157
- 0
pkg/template/line.go Bestand weergeven

@@ -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
}

pkg/pattern/template/panel.go → pkg/template/panel.go Bestand weergeven

@@ -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
}

pkg/pattern/template/point.go → pkg/template/point.go Bestand weergeven

@@ -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
}

+ 289
- 0
pkg/template/point_test.go Bestand weergeven

@@ -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
}

pkg/pattern/template/position.go → pkg/template/position.go Bestand weergeven


+ 79
- 0
pkg/template/template.go Bestand weergeven

@@ -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
}

pkg/pattern/template/value.go → pkg/template/value.go Bestand weergeven


+ 28
- 0
pkg/util/id.go Bestand weergeven

@@ -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())
}

+ 33
- 0
pkg/util/id_test.go Bestand weergeven

@@ -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())
})
}
}

+ 125
- 0
spec/pattern.v2.yaml Bestand weergeven

@@ -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




+ 4
- 0
spec/pattern.yaml Bestand weergeven

@@ -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


+ 2
- 0
templates/dimension_names.yaml Bestand weergeven

@@ -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


+ 498
- 0
templates/tailored_shirt_block.v2.yaml Bestand weergeven

@@ -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]



Laden…
Annuleren
Opslaan