| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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) | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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] | |||
| @@ -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 | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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,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" | |||
| ) | |||
| @@ -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{ | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| @@ -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) | |||
| } | |||
| @@ -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) | |||
| @@ -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) | |||
| }) | |||
| } | |||
| } | |||
| @@ -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) | |||
| } | |||
| @@ -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) | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| --- | |||
| points: | |||
| 1: | |||
| position: | |||
| x: test | |||
| y: 14.3 | |||
| panels: | |||
| body: | |||
| points: | |||
| 2: | |||
| position: | |||
| x: test*2 | |||
| y: -3 | |||
| @@ -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 | |||
| @@ -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: {} | |||
| @@ -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 | |||
| @@ -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] | |||
| @@ -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 | |||
| @@ -0,0 +1,12 @@ | |||
| --- | |||
| points: | |||
| 1: {} | |||
| 3: | |||
| relativeTo: body.2 | |||
| panels: | |||
| body: | |||
| points: | |||
| 2: | |||
| relativeTo: 1 | |||
| position: | |||
| x: 3 | |||
| @@ -0,0 +1,15 @@ | |||
| --- | |||
| points: | |||
| 1: | |||
| position: | |||
| x: test | |||
| y: 14.3 | |||
| panels: | |||
| body: | |||
| points: | |||
| 2: | |||
| relativeTo: | |||
| 1 | |||
| position: | |||
| x: test*2 | |||
| y: -3 | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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()) | |||
| } | |||
| @@ -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()) | |||
| }) | |||
| } | |||
| } | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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] | |||