<commit_before>package main

import (
  "flag"
  "os"
  "fmt"
  "github.com/hschendel/stl"
)

// Command line parameters
type Params struct {
  Command string
  InputFile string
  OutputFile string
  OutputAscii bool
  OutputBinary bool
  ShowMatrix bool
  Fixnorm bool
  Angle float64
  PosX float64
  PosY float64
  PosZ float64
  DirX float64
  DirY float64
  DirZ float64
  Factor float64
  Matrix *stl.Mat4
}

// Context for main()
type Run struct {
  Params Params
  Solid *stl.Solid
  DoReadInput bool
  DoWriteOutput bool
  Command func(run *Run)
  ExitCode int
}

func (run *Run) ParseParams() {
  p := &(run.Params)
  flag.StringVar(&p.InputFile, "i", "", "Input File, default is stdin")
  flag.StringVar(&p.OutputFile, "o", "", "Output File, default is stdout")
  flag.BoolVar(&p.OutputAscii, "ascii", false, "Write output file in human-readable STL ASCII format, not recommended")
  flag.BoolVar(&p.OutputBinary, "binary", false, "Write output file in STL binary format")
  flag.BoolVar(&p.ShowMatrix, "showmat", false, "Print 4x4 transformation matrix to stderr in format [[m11 m12 m13 m14] ... [m41 m42 m43 m44]]")
  flag.BoolVar(&p.Fixnorm, "fixnorm", false, "Recalculate normals, makes only sense together with copy")
  flag.Float64Var(&p.Angle, "a", 90, "Rotation angle in degree")
  flag.Float64Var(&p.PosX, "x", 0, "x for point on rotation axis for rotate, translation on x-axis for translate, x-size in fitbox")
  flag.Float64Var(&p.PosY, "y", 0, "y for point on rotation axis for rotate, translation on y-axis for translate, y-size in fitbox")
  flag.Float64Var(&p.PosZ, "z", 0, "z for point on rotation axis for rotate, translation on z-axis for translate, z-size in fitbox")
  flag.Float64Var(&p.DirX, "dx", 0, "x for direction of rotation axis")
  flag.Float64Var(&p.DirY, "dy", 0, "y for direction of rotation axis")
  flag.Float64Var(&p.DirZ, "dz", 0, "z for direction of rotation axis")
  flag.Float64Var(&p.Factor, "f", 1, "scale factor")
  var matrixStr string
  flag.StringVar(&matrixStr, "m", "[[1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1]]", "Transformation matrix for transform, same format as printed using -showmat.  Default is the identity matrix.")
  flag.Parse()
  p.Matrix = run.parseMat4("-m", matrixStr)
  if flag.NArg() == 1 || flag.NArg() == 2 {
    p.Command = flag.Arg(0)
    if flag.NArg() == 2 {
      if p.InputFile != "" || p.OutputFile != "" {
        fmt.Fprintln(os.Stderr, "Error: providing a single filename after the command forbids the use of -i or -o.")
        p.Command = "help"
        run.ExitCode = 15
      } else {
        p.InputFile = flag.Arg(1)
        p.OutputFile = flag.Arg(1)
      }
    }
  } else if flag.NArg() == 0 {
    fmt.Fprintln(os.Stderr, "Error: no command")
    p.Command = "help"
    run.ExitCode = 10
  } else if flag.NArg() > 2 {
    fmt.Fprintln(os.Stderr, "Error: Too many arguments")
    p.Command = "help"
    run.ExitCode = 13
  }
  if p.OutputAscii && p.OutputBinary {
    fmt.Fprintln(os.Stderr, "Error: -ascii and -binary are exclusive")
    p.Command = "help"
    run.ExitCode = 12
  }
}

func (run *Run) parseMat4(param, s string) *stl.Mat4 {
  if s == "" {
    return nil
  }
  var r stl.Mat4
  n, err := fmt.Sscanf(s, "[[%f %f %f %f] [%f %f %f %f] [%f %f %f %f] [%f %f %f %f]]",
                      &r[0][0], &r[0][1], &r[0][2], &r[0][3],
                      &r[1][0], &r[1][1], &r[1][2], &r[1][3],
                      &r[2][0], &r[2][1], &r[2][2], &r[2][3],
                      &r[3][0], &r[3][1], &r[3][2], &r[3][3],
                      )
  if err != nil {
    fmt.Fprintf(os.Stderr, "Error: Cannot read %s - %s\n", param, err.Error())
    return nil
  } else if n == 0 {
    return nil
  } else {
    return &r
  }
}

func (run *Run) ReadInput() bool {
  var err error
  if run.Params.InputFile == "" {
    run.Solid, err = stl.ReadAll(os.Stdin)
  } else {
    run.Solid, err = stl.ReadFile(run.Params.InputFile)
  }
  if err != nil {
    fmt.Fprintln(os.Stderr, err.Error())
    run.ExitCode = 40
    return false
  } else {
    return true
  }
}

func (run *Run) WriteOutput() bool {
  if run.Params.OutputAscii {
    run.Solid.IsAscii = true
  } else if run.Params.OutputBinary {
    run.Solid.IsAscii = false
  }
  
  var err error
  if run.Params.OutputFile == "" {
    err = run.Solid.WriteAll(os.Stdout)
  } else {
    err = run.Solid.WriteFile(run.Params.OutputFile)
  }
  if err != nil {
    fmt.Fprintln(os.Stderr, err.Error())
    run.ExitCode = 50
    return false
  } else {
    return true
  }
}

func (run *Run) Help() {
  fmt.Fprintln(os.Stderr,
`stltool can be used to measure, validate, and transform .stl files used
for 3D printing.

Usage:

  stltool [Options] <command> [filename]
  
  The command always has to be provided after the options.

List of commands:

  copy        Simply copy from input to output, can be used to convert from ASCII
              to binary or vice versa with -binary or -ascii
  fitbox      Linear scale down into box described by -x, -y, and -z<commit_msg>add version to usage output<commit_after>package main

import (
  "flag"
  "os"
  "fmt"
  "github.com/hschendel/stl"
)

const version = "1.0.1"

// Command line parameters
type Params struct {
  Command string
  InputFile string
  OutputFile string
  OutputAscii bool
  OutputBinary bool
  ShowMatrix bool
  Fixnorm bool
  Angle float64
  PosX float64
  PosY float64
  PosZ float64
  DirX float64
  DirY float64
  DirZ float64
  Factor float64
  Matrix *stl.Mat4
}

// Context for main()
type Run struct {
  Params Params
  Solid *stl.Solid
  DoReadInput bool
  DoWriteOutput bool
  Command func(run *Run)
  ExitCode int
}

func (run *Run) ParseParams() {
  p := &(run.Params)
  flag.StringVar(&p.InputFile, "i", "", "Input File, default is stdin")
  flag.StringVar(&p.OutputFile, "o", "", "Output File, default is stdout")
  flag.BoolVar(&p.OutputAscii, "ascii", false, "Write output file in human-readable STL ASCII format, not recommended")
  flag.BoolVar(&p.OutputBinary, "binary", false, "Write output file in STL binary format")
  flag.BoolVar(&p.ShowMatrix, "showmat", false, "Print 4x4 transformation matrix to stderr in format [[m11 m12 m13 m14] ... [m41 m42 m43 m44]]")
  flag.BoolVar(&p.Fixnorm, "fixnorm", false, "Recalculate normals, makes only sense together with copy")
  flag.Float64Var(&p.Angle, "a", 90, "Rotation angle in degree")
  flag.Float64Var(&p.PosX, "x", 0, "x for point on rotation axis for rotate, translation on x-axis for translate, x-size in fitbox")
  flag.Float64Var(&p.PosY, "y", 0, "y for point on rotation axis for rotate, translation on y-axis for translate, y-size in fitbox")
  flag.Float64Var(&p.PosZ, "z", 0, "z for point on rotation axis for rotate, translation on z-axis for translate, z-size in fitbox")
  flag.Float64Var(&p.DirX, "dx", 0, "x for direction of rotation axis")
  flag.Float64Var(&p.DirY, "dy", 0, "y for direction of rotation axis")
  flag.Float64Var(&p.DirZ, "dz", 0, "z for direction of rotation axis")
  flag.Float64Var(&p.Factor, "f", 1, "scale factor")
  var matrixStr string
  flag.StringVar(&matrixStr, "m", "[[1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1]]", "Transformation matrix for transform, same format as printed using -showmat.  Default is the identity matrix.")
  flag.Parse()
  p.Matrix = run.parseMat4("-m", matrixStr)
  
  if flag.NArg() == 1 || flag.NArg() == 2 {
    p.Command = flag.Arg(0)
    if flag.NArg() == 2 {
      if p.InputFile != "" || p.OutputFile != "" {
        fmt.Fprintln(os.Stderr, "Error: providing a single filename after the command forbids the use of -i or -o.")
        p.Command = "help"
        run.ExitCode = 15
      } else {
        p.InputFile = flag.Arg(1)
        p.OutputFile = flag.Arg(1)
      }
    }
  } else if flag.NArg() == 0 {
    fmt.Fprintln(os.Stderr, "Error: no command")
    p.Command = "help"
    run.ExitCode = 10
  } else if flag.NArg() > 2 {
    fmt.Fprintln(os.Stderr, "Error: Too many arguments")
    p.Command = "help"
    run.ExitCode = 13
  }
  if p.OutputAscii && p.OutputBinary {
    fmt.Fprintln(os.Stderr, "Error: -ascii and -binary are exclusive")
    p.Command = "help"
    run.ExitCode = 12
  }
}

func (run *Run) parseMat4(param, s string) *stl.Mat4 {
  if s == "" {
    return nil
  }
  var r stl.Mat4
  n, err := fmt.Sscanf(s, "[[%f %f %f %f] [%f %f %f %f] [%f %f %f %f] [%f %f %f %f]]",
                      &r[0][0], &r[0][1], &r[0][2], &r[0][3],
                      &r[1][0], &r[1][1], &r[1][2], &r[1][3],
                      &r[2][0], &r[2][1], &r[2][2], &r[2][3],
                      &r[3][0], &r[3][1], &r[3][2], &r[3][3],
                      )
  if err != nil {
    fmt.Fprintf(os.Stderr, "Error: Cannot read %s - %s\n", param, err.Error())
    return nil
  } else if n == 0 {
    return nil
  } else {
    return &r
  }
}

func (run *Run) ReadInput() bool {
  var err error
  if run.Params.InputFile == "" {
    run.Solid, err = stl.ReadAll(os.Stdin)
  } else {
    run.Solid, err = stl.ReadFile(run.Params.InputFile)
  }
  if err != nil {
    fmt.Fprintln(os.Stderr, err.Error())
    run.ExitCode = 40
    return false
  } else {
    return true
  }
}

func (run *Run) WriteOutput() bool {
  if run.Params.OutputAscii {
    run.Solid.IsAscii = true
  } else if run.Params.OutputBinary {
    run.Solid.IsAscii = false
  }
  
  var err error
  if run.Params.OutputFile == "" {
    err = run.Solid.WriteAll(os.Stdout)
  } else {
    err = run.Solid.WriteFile(run.Params.OutputFile)
  }
  if err != nil {
    fmt.Fprintln(os.Stderr, err.Error())
    run.ExitCode = 50
    return false
  } else {
    return true
  }
}

func (run *Run) Help() {
  fmt.Fprintln(os.Stderr, "stltool v" + version + `
stltool can be used to measure, validate, and transform .stl files used
for 3D printing.

Usage:

  stltool [Options] <command> [filename]
  
  The command always has to be provided after the options.

List of commands:

  copy        Simply copy from input to output, can be used to convert from ASCII
              to binary or vice versa with -binary or -ascii
  fitbox      Linear scale down into box described by -x, -y, and -z
{'fraction_non_alphanumeric': 0.14591381872213968, 'fraction_numerical': 0.02684497275879148, 'mean_word_length': 2.825691549829481, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '701102', 'n_tokens_mistral': 3840, 'n_tokens_neox': 3633, 'n_words': 1129}