feat(descriptions): Add human-readable descriptions of commonly used codes.
This commit is contained in:
parent
875749e09b
commit
dd763dc09a
21 changed files with 867 additions and 22 deletions
|
@ -46,7 +46,7 @@ defmodule Gcode.Model.Block do
|
|||
|
||||
"""
|
||||
@spec comment(t, Comment.t()) :: Result.t(t, block_error)
|
||||
def comment(%Block{comment: none()} = block, %Comment{comment: some(_)} = comment),
|
||||
def comment(%Block{comment: none()} = block, %Comment{} = comment),
|
||||
do: {:ok, %Block{block | comment: some(comment)}}
|
||||
|
||||
def comment(%Block{comment: some(_)}, _comment),
|
||||
|
@ -66,7 +66,7 @@ defmodule Gcode.Model.Block do
|
|||
...> {:ok, block} = Block.push(block, word)
|
||||
...> {:ok, word} = Word.init("N", 100)
|
||||
...> Block.push(block, word)
|
||||
{:ok, %Block{words: [%Word{word: some("N"), address: some(100)}, %Word{word: some("G"), address: some(0)}]}}
|
||||
{:ok, %Block{words: [%Word{word: "N", address: 100}, %Word{word: "G", address: 0}]}}
|
||||
"""
|
||||
@spec push(t, block_contents) :: Result.t(t, block_error)
|
||||
def push(%Block{words: words} = block, word)
|
||||
|
@ -89,7 +89,7 @@ defmodule Gcode.Model.Block do
|
|||
...> {:ok, word} = Word.init("N", 100)
|
||||
...> {:ok, block} = Block.push(block, word)
|
||||
...> Block.words(block)
|
||||
{:ok, [%Word{word: some("G"), address: some(0)}, %Word{word: some("N"), address: some(100)}]}
|
||||
{:ok, [%Word{word: "G", address: 0}, %Word{word: "N", address: 100}]}
|
||||
"""
|
||||
@spec words(t) :: Result.t([Word.t()], block_error)
|
||||
def words(%Block{words: words}) when is_list(words), do: {:ok, Enum.reverse(words)}
|
||||
|
|
43
lib/gcode/model/block/describe.ex
Normal file
43
lib/gcode/model/block/describe.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defimpl Gcode.Model.Describe, for: Gcode.Model.Block do
|
||||
alias Gcode.Model.{Block, Describe, Serialise}
|
||||
use Gcode.Option
|
||||
use Gcode.Result
|
||||
|
||||
@spec describe(Block.t(), options :: []) :: Option.t(String.t())
|
||||
def describe(%Block{words: words, comment: some(comment)}, options) do
|
||||
words = describe_words(words, options)
|
||||
some("#{words} (#{comment.comment})")
|
||||
end
|
||||
|
||||
def describe(%Block{words: words}, options) do
|
||||
words = describe_words(words, options)
|
||||
some(words)
|
||||
end
|
||||
|
||||
defp describe_words(words, options) do
|
||||
words
|
||||
|> Enum.reverse()
|
||||
|> Enum.map(&describe_or_serialise(&1, options))
|
||||
|> Enum.reject(&Option.none?/1)
|
||||
|> Enum.map(&Option.unwrap!/1)
|
||||
|> Enum.join(", ")
|
||||
end
|
||||
|
||||
defp describe_or_serialise(word, options) do
|
||||
case Describe.describe(word, options) do
|
||||
some(description) ->
|
||||
some(description)
|
||||
|
||||
none() ->
|
||||
case Serialise.serialise(word) do
|
||||
ok(serialised) ->
|
||||
serialised
|
||||
|> Enum.join(", ")
|
||||
|> some()
|
||||
|
||||
error(_) ->
|
||||
none()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,7 +9,7 @@ defmodule Gcode.Model.Comment do
|
|||
"""
|
||||
|
||||
@type t :: %Comment{
|
||||
comment: Option.t(String.t())
|
||||
comment: String.t()
|
||||
}
|
||||
|
||||
@type error :: {:comment_error, String.t()}
|
||||
|
@ -21,12 +21,12 @@ defmodule Gcode.Model.Comment do
|
|||
|
||||
iex> "Doc, in the carpark, with plutonium"
|
||||
...> |> Comment.init()
|
||||
{:ok, %Comment{comment: some("Doc, in the carpark, with plutonium")}}
|
||||
{:ok, %Comment{comment: "Doc, in the carpark, with plutonium"}}
|
||||
"""
|
||||
@spec init(String.t()) :: Result.t(t, error)
|
||||
def init(comment) when is_binary(comment) do
|
||||
if String.printable?(comment) do
|
||||
ok(%Comment{comment: some(comment)})
|
||||
ok(%Comment{comment: comment})
|
||||
else
|
||||
error(
|
||||
{:comment_error,
|
||||
|
|
7
lib/gcode/model/comment/describe.ex
Normal file
7
lib/gcode/model/comment/describe.ex
Normal file
|
@ -0,0 +1,7 @@
|
|||
defimpl Gcode.Model.Describe, for: Gcode.Model.Comment do
|
||||
alias Gcode.Model.Comment
|
||||
use Gcode.Option
|
||||
|
||||
@spec describe(Comment.t(), options :: []) :: Option.t(String.t())
|
||||
def describe(_comment, _opts \\ []), do: none()
|
||||
end
|
|
@ -4,7 +4,7 @@ defimpl Gcode.Model.Serialise, for: Gcode.Model.Comment do
|
|||
use Gcode.Result
|
||||
|
||||
@spec serialise(Comment.t()) :: Result.t([String.t()], {:serialise_error, any})
|
||||
def serialise(%Comment{comment: some(comment)}) do
|
||||
def serialise(%Comment{comment: comment}) when is_binary(comment) do
|
||||
comment
|
||||
|> String.split(~r/(\r\n|\r|\n)/)
|
||||
|> Enum.reject(&(byte_size(&1) == 0))
|
||||
|
@ -12,5 +12,5 @@ defimpl Gcode.Model.Serialise, for: Gcode.Model.Comment do
|
|||
|> ok()
|
||||
end
|
||||
|
||||
def serialise(%Comment{comment: none()}), do: {:error, {:serialise_error, :empty_comment}}
|
||||
def serialise(_comment), do: {:error, {:serialise_error, "Invalid comment"}}
|
||||
end
|
||||
|
|
11
lib/gcode/model/describe.ex
Normal file
11
lib/gcode/model/describe.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
defprotocol Gcode.Model.Describe do
|
||||
alias Gcode.Model.Describe
|
||||
use Gcode.Option
|
||||
|
||||
@moduledoc """
|
||||
A protocol which is used to describe the model for human consumption.
|
||||
"""
|
||||
|
||||
@spec describe(Describe.t(), options :: []) :: Option.t(String.t())
|
||||
def describe(describable, opts \\ [])
|
||||
end
|
17
lib/gcode/model/program/describe.ex
Normal file
17
lib/gcode/model/program/describe.ex
Normal file
|
@ -0,0 +1,17 @@
|
|||
defimpl Gcode.Model.Describe, for: Gcode.Model.Program do
|
||||
alias Gcode.Model.{Describe, Program}
|
||||
use Gcode.Option
|
||||
|
||||
@spec describe(Program.t(), options :: []) :: Option.t(String.t())
|
||||
def describe(%Program{elements: elements}, options) do
|
||||
lines =
|
||||
elements
|
||||
|> Enum.reverse()
|
||||
|> Enum.map(&Describe.describe(&1, options))
|
||||
|> Enum.reject(&(&1 == none()))
|
||||
|> Enum.map(fn some(line) -> "#{line}\n" end)
|
||||
|> Enum.join("")
|
||||
|
||||
some(lines)
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
defprotocol Gcode.Model.Serialise do
|
||||
alias Gcode.Model.Serialise
|
||||
alias Gcode.Result
|
||||
|
||||
@moduledoc """
|
||||
|
@ -6,5 +7,5 @@ defprotocol Gcode.Model.Serialise do
|
|||
"""
|
||||
|
||||
@spec serialise(Serialise.t()) :: Result.t([String.t()])
|
||||
def serialise(value)
|
||||
def serialise(serialisable)
|
||||
end
|
||||
|
|
7
lib/gcode/model/skip/describe.ex
Normal file
7
lib/gcode/model/skip/describe.ex
Normal file
|
@ -0,0 +1,7 @@
|
|||
defimpl Gcode.Model.Describe, for: Gcode.Model.Skip do
|
||||
alias Gcode.Model.Skip
|
||||
use Gcode.Option
|
||||
|
||||
@spec describe(Skip.t(), options :: []) :: Option.t(String.t())
|
||||
def describe(_skip, _opts \\ []), do: none()
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Gcode.Model.Tape do
|
||||
defstruct leader: :none
|
||||
defstruct leader: :error
|
||||
alias Gcode.{Model.Tape}
|
||||
use Gcode.Option
|
||||
use Gcode.Result
|
||||
|
@ -18,7 +18,7 @@ defmodule Gcode.Model.Tape do
|
|||
## Example
|
||||
|
||||
iex> Tape.init()
|
||||
{:ok, %Tape{leader: :none}}
|
||||
{:ok, %Tape{leader: :error}}
|
||||
"""
|
||||
@spec init :: Result.t(t)
|
||||
def init, do: ok(%Tape{leader: Option.none()})
|
||||
|
|
7
lib/gcode/model/tape/describe.ex
Normal file
7
lib/gcode/model/tape/describe.ex
Normal file
|
@ -0,0 +1,7 @@
|
|||
defimpl Gcode.Model.Describe, for: Gcode.Model.Tape do
|
||||
alias Gcode.Model.Tape
|
||||
use Gcode.Option
|
||||
|
||||
@spec describe(Tape.t(), options :: []) :: Option.t(String.t())
|
||||
def describe(_tape, _opts \\ []), do: none()
|
||||
end
|
|
@ -10,8 +10,8 @@ defmodule Gcode.Model.Word do
|
|||
"""
|
||||
|
||||
@type t :: %Word{
|
||||
word: Option.some(String.t()),
|
||||
address: Option.some(number)
|
||||
word: String.t(),
|
||||
address: number
|
||||
}
|
||||
|
||||
@doc """
|
||||
|
@ -20,12 +20,12 @@ defmodule Gcode.Model.Word do
|
|||
## Example
|
||||
|
||||
iex> Word.init("G", 0)
|
||||
{:ok, %Word{word: {:ok, "G"}, address: {:ok, 0}}}
|
||||
{:ok, %Word{word: "G", address: 0}}
|
||||
"""
|
||||
@spec init(String.t(), number) :: Result.t(t)
|
||||
def init(word, address) when is_binary(word) and is_number(address) do
|
||||
if Regex.match?(~r/^[A-Z]$/, word) do
|
||||
ok(%Word{word: some(word), address: some(address)})
|
||||
ok(%Word{word: word, address: address})
|
||||
else
|
||||
error({:word_error, "Expected word to be a single character, received #{inspect(word)}"})
|
||||
end
|
||||
|
|
331
lib/gcode/model/word/describe.ex
Normal file
331
lib/gcode/model/word/describe.ex
Normal file
|
@ -0,0 +1,331 @@
|
|||
defimpl Gcode.Model.Describe, for: Gcode.Model.Word do
|
||||
use Gcode.Option
|
||||
use Gcode.Result
|
||||
alias Gcode.Model.Word
|
||||
|
||||
@moduledoc """
|
||||
Describes common/conventional words.
|
||||
"""
|
||||
|
||||
@type options :: [option]
|
||||
@type option :: operation | positioning | compensation | units
|
||||
@type operation :: {:operation, :milling | :turning | :printing | :plotting}
|
||||
@type positioning :: {:positioning, :absolute | :relative}
|
||||
@type compensation :: {:compensation, :left | :right}
|
||||
@type units :: {:units, :mm | :inches}
|
||||
|
||||
@doc "Refer `describe/2`"
|
||||
@spec describe(Word.t()) :: Option.t(String.t())
|
||||
def describe(word), do: do_describe(word, %{})
|
||||
|
||||
@doc """
|
||||
Describe a word for human consumption.
|
||||
|
||||
*Note:* Many words have different meanings depending on the operation, machine
|
||||
state or program state. Use can use the `options` argument to provide a hint,
|
||||
otherwise a more-generic response will be shown.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, word} = Word.init("N", 100)
|
||||
...> Word.Describe.describe(word)
|
||||
{:ok, "Line/block number 100"}
|
||||
|
||||
iex> {:ok, word} = Word.init("G", 0)
|
||||
...> Word.Describe.describe(word)
|
||||
{:ok, "Rapid move"}
|
||||
|
||||
iex> {:ok, word} = Word.init("A", 15)
|
||||
{:ok, "Rotate A axis counterclockwise by/to 15º"}
|
||||
|
||||
iex> {:ok, word} = Word.init("A", -15, positioning: :absolute)
|
||||
{:ok, "Rotate A axis clockwise to 15º"}
|
||||
|
||||
iex> {:ok, word} = Word.init("G", 8)
|
||||
:error
|
||||
"""
|
||||
@spec describe(Word.t(), options) :: Option.t(String.t())
|
||||
def describe(%Word{} = word, options) when is_list(options),
|
||||
do: do_describe(word, Enum.into(options, %{}))
|
||||
|
||||
defp do_describe(%Word{word: axis, address: angle}, %{positioning: :absolute})
|
||||
when axis in ~w[A B C] and angle >= 0,
|
||||
do: some("Rotate #{axis} axis counterclockwise to #{angle}º")
|
||||
|
||||
defp do_describe(%Word{word: axis, address: angle}, %{positioning: :relative})
|
||||
when axis in ~w[A B C] and angle >= 0,
|
||||
do: some("Rotate #{axis} axis counterclockwise by #{angle}º")
|
||||
|
||||
defp do_describe(%Word{word: axis, address: angle}, _)
|
||||
when axis in ~w[A B C] and angle >= 0,
|
||||
do: some("Rotate #{axis} axis counterclockwise by/to #{angle}º")
|
||||
|
||||
defp do_describe(%Word{word: axis, address: angle}, %{positioning: :absolute})
|
||||
when axis in ~w[A B C] and angle < 0,
|
||||
do: some("Rotate #{axis} axis clockwise to #{abs(angle)}º")
|
||||
|
||||
defp do_describe(%Word{word: axis, address: angle}, %{positioning: :relative})
|
||||
when axis in ~w[A B C] and angle < 0,
|
||||
do: some("Rotate #{axis} axis clockwise by #{abs(angle)}º")
|
||||
|
||||
defp do_describe(%Word{word: axis, address: angle}, _)
|
||||
when axis in ~w[A B C] and angle < 0,
|
||||
do: some("Rotate #{axis} axis clockwise by/to #{abs(angle)}º")
|
||||
|
||||
defp do_describe(%Word{word: "D", address: depth}, %{operation: :turning} = options),
|
||||
do: some("Depth of cut #{distance_with_unit(depth, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "D", address: aperture}, %{operation: :plotting}),
|
||||
do: some("Aperture #{aperture}")
|
||||
|
||||
defp do_describe(%Word{word: "D", address: offset}, %{compensation: :left} = options),
|
||||
do: some("Left radial offset #{distance_with_unit(offset, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "D", address: offset}, %{compensation: :right} = options),
|
||||
do: some("Right radial offset #{distance_with_unit(offset, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "D", address: offset}, options),
|
||||
do: some("Radial offset #{distance_with_unit(offset, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "E", address: feedrate}, %{operation: :printing} = options),
|
||||
do: some("Extruder feedrate #{feedrate(feedrate, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "E", address: feedrate}, %{operation: :turning} = options),
|
||||
do: some("Precision feedrate #{feedrate(feedrate, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "F", address: feedrate}, options),
|
||||
do: some("Feedrate #{feedrate(feedrate, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 0}, _), do: some("Rapid move")
|
||||
defp do_describe(%Word{word: "G", address: 1}, _), do: some("Linear move")
|
||||
defp do_describe(%Word{word: "G", address: 2}, _), do: some("Clockwise circular move")
|
||||
defp do_describe(%Word{word: "G", address: 3}, _), do: some("Counterclockwise circular move")
|
||||
defp do_describe(%Word{word: "G", address: 4}, _), do: some("Dwell")
|
||||
defp do_describe(%Word{word: "G", address: 5}, _), do: some("High-precision contour control")
|
||||
defp do_describe(%Word{word: "G", address: 5.1}, _), do: some("AI advanced preview control")
|
||||
defp do_describe(%Word{word: "G", address: 6.1}, _), do: some("NURBS machining")
|
||||
defp do_describe(%Word{word: "G", address: 7}, _), do: some("Imaginary axis designation")
|
||||
defp do_describe(%Word{word: "G", address: 9}, _), do: some("Exact stop check - non-modal")
|
||||
defp do_describe(%Word{word: "G", address: 10}, _), do: some("Programmable data input")
|
||||
defp do_describe(%Word{word: "G", address: 11}, _), do: some("Data write cancel")
|
||||
defp do_describe(%Word{word: "G", address: 17}, _), do: some("XY plane selection")
|
||||
defp do_describe(%Word{word: "G", address: 18}, _), do: some("ZX plane selection")
|
||||
defp do_describe(%Word{word: "G", address: 19}, _), do: some("YZ plane selection")
|
||||
defp do_describe(%Word{word: "G", address: 20}, _), do: some("Unit is inches")
|
||||
defp do_describe(%Word{word: "G", address: 21}, _), do: some("Unit is mm")
|
||||
defp do_describe(%Word{word: "G", address: 28}, _), do: some("Return to home position")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 30}, _),
|
||||
do: some("Return to secondary home position")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 31}, _), do: some("Feed until skip function")
|
||||
defp do_describe(%Word{word: "G", address: 32}, _), do: some("Single-point threading")
|
||||
defp do_describe(%Word{word: "G", address: 33}, _), do: some("Variable pitch threading")
|
||||
defp do_describe(%Word{word: "G", address: 40}, _), do: some("Tool radius compensation off")
|
||||
defp do_describe(%Word{word: "G", address: 41}, _), do: some("Tool radius compensation left")
|
||||
defp do_describe(%Word{word: "G", address: 42}, _), do: some("Tool radius compensation right")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 43}, _),
|
||||
do: some("Tool height offset compensation negative")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 44}, _),
|
||||
do: some("Tool height offset compensation positive")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 45}, _), do: some("Axis offset single increase")
|
||||
defp do_describe(%Word{word: "G", address: 46}, _), do: some("Axis offset single decrease")
|
||||
defp do_describe(%Word{word: "G", address: 47}, _), do: some("Axis offset double increase")
|
||||
defp do_describe(%Word{word: "G", address: 48}, _), do: some("Axis offset double decrease")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 49}, _),
|
||||
do: some("Tool length offset compensation cancel")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 50}, %{operation: :turning}),
|
||||
do: some("Position register")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 50}, _), do: some("Scaling function cancel")
|
||||
defp do_describe(%Word{word: "G", address: 52}, _), do: some("Local coordinate system")
|
||||
defp do_describe(%Word{word: "G", address: 53}, _), do: some("Machine coordinate system")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: address}, _)
|
||||
when address in [54, 55, 56, 57, 58, 59, 54.1],
|
||||
do: some("Work coordinate system")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 61}, _), do: some("Exact stop check - modal")
|
||||
defp do_describe(%Word{word: "G", address: 62}, _), do: some("Automatic corner override")
|
||||
defp do_describe(%Word{word: "G", address: 64}, _), do: some("Default cutting mode")
|
||||
defp do_describe(%Word{word: "G", address: 68}, _), do: some("Rotate coordinate system")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 69}, _),
|
||||
do: some("Turn off coordinate system rotation")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 70}, %{operation: :turning}),
|
||||
do: some("Fixed cycle, multiple repetitive cycle - for finishing")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 71}, %{operation: :turning}),
|
||||
do: some("Fixed cycle, multiple repetitive cycle - for roughing with Z axis emphasis")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 72}, %{operation: :turning}),
|
||||
do: some("Fixed cycle, multiple repetitive cycle - for roughing with X axis emphasis")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 73}, %{operation: :turning}),
|
||||
do: some("Fixed cycle, multiple repetitive cycle - for roughing with pattern repetition")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 73}, _), do: some("Peck drilling cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 74}, %{operation: :turning}),
|
||||
do: some("Peck drilling cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 74}, _), do: some("Tapping cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 75}, %{operation: :turning}),
|
||||
do: some("Peck grooving cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 76}, %{operation: :turning}),
|
||||
do: some("Threading cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 76}, _), do: some("Fine boring cycle")
|
||||
defp do_describe(%Word{word: "G", address: 80}, _), do: some("Cancel cycle")
|
||||
defp do_describe(%Word{word: "G", address: 81}, _), do: some("Simple drilling cycle")
|
||||
defp do_describe(%Word{word: "G", address: 82}, _), do: some("Drilling cycle with dwell")
|
||||
defp do_describe(%Word{word: "G", address: 83}, _), do: some("Peck drilling cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 84}, _),
|
||||
do: some("Tapping cycle, righthand thread, M03 spindle direction")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 84.2}, _),
|
||||
do: some("Tapping cycle, righthand thread, M03 spindle direction, rigid toolholder")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 84.3}, _),
|
||||
do: some("Tapping cycle, lefthand thread, M04 spindle direction, rigid toolholder")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 85}, _), do: some("Boring cycle, feed in/feed out")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 86}, _),
|
||||
do: some("Boring cycle, feed in/spindle stop/rapid out")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 87}, _), do: some("Boring cycle, backboring")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 88}, _),
|
||||
do: some("Boring cycle, feed in/spindle stop/manual operation")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 89}, _),
|
||||
do: some("Boring cycle, feed in/dwell/feed out")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 90}, _), do: some("Absolute positioning")
|
||||
defp do_describe(%Word{word: "G", address: 91}, _), do: some("Relative positioning")
|
||||
defp do_describe(%Word{word: "G", address: 92}, _), do: some("Position register")
|
||||
defp do_describe(%Word{word: "G", address: 94}, _), do: some("Feedrate per minute")
|
||||
defp do_describe(%Word{word: "G", address: 95}, _), do: some("Feedrate per revolution")
|
||||
defp do_describe(%Word{word: "G", address: 96}, _), do: some("Constant surface speed")
|
||||
defp do_describe(%Word{word: "G", address: 97}, _), do: some("Constant spindle speed")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 98}, %{operation: :turning}),
|
||||
do: some("Feedrate per minute")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 98}, _),
|
||||
do: some("Return to initial Z level in canned cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 99}, %{operation: :turning}),
|
||||
do: some("Feedrate per revolution")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 99}, _),
|
||||
do: some("Return to R level in canned cycle")
|
||||
|
||||
defp do_describe(%Word{word: "G", address: 100}, _), do: some("Tool length measurement")
|
||||
|
||||
defp do_describe(%Word{word: "H", address: length}, options),
|
||||
do: some("Tool length offset #{distance_with_unit(length, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "I", address: offset}, options),
|
||||
do: some("X arc center offset #{distance_with_unit(offset, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "J", address: offset}, options),
|
||||
do: some("Y arc center offset #{distance_with_unit(offset, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "K", address: offset}, options),
|
||||
do: some("Z arc center offset #{distance_with_unit(offset, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "L", address: count}, _), do: some("Loop count #{count}")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 0}, _), do: some("Compulsory stop")
|
||||
defp do_describe(%Word{word: "M", address: 1}, _), do: some("Optional stop")
|
||||
defp do_describe(%Word{word: "M", address: 2}, _), do: some("End of program")
|
||||
defp do_describe(%Word{word: "M", address: 3}, _), do: some("Spindle on clockwise")
|
||||
defp do_describe(%Word{word: "M", address: 4}, _), do: some("Spindle on counterclockwise")
|
||||
defp do_describe(%Word{word: "M", address: 5}, _), do: some("Spindle stop")
|
||||
defp do_describe(%Word{word: "M", address: 6}, _), do: some("Automatic tool change")
|
||||
defp do_describe(%Word{word: "M", address: 7}, _), do: some("Coolant mist")
|
||||
defp do_describe(%Word{word: "M", address: 8}, _), do: some("Coolant flood")
|
||||
defp do_describe(%Word{word: "M", address: 9}, _), do: some("Coolant off")
|
||||
defp do_describe(%Word{word: "M", address: 10}, _), do: some("Pallet clamp on")
|
||||
defp do_describe(%Word{word: "M", address: 11}, _), do: some("Pallet clamp off")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 13}, _),
|
||||
do: some("Spindle on clockwise and coolant flood")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 19}, _), do: some("Spindle orientation")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 21}, %{operation: :turning}),
|
||||
do: some("Tailstock forward")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 21}, _), do: some("Mirror X axis")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 22}, %{operation: :turning}),
|
||||
do: some("Tailstock backward")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 22}, _), do: some("Mirror Y axis")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 23}, %{operation: :turning}),
|
||||
do: some("Thread gradual pullout on")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 23}, _), do: some("Mirror off")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 24}, %{operation: :turning}),
|
||||
do: some("Thread gradual pullout off")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 30}, _), do: some("End of program")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: gear}, %{operation: :turning})
|
||||
when is_integer(gear) and gear > 40 and gear < 45,
|
||||
do: some("Gear select #{gear - 40}")
|
||||
|
||||
defp do_describe(%Word{word: "M", address: 48}, _), do: some("Feedrate override allowed")
|
||||
defp do_describe(%Word{word: "M", address: 49}, _), do: some("Feedrate override not allowed")
|
||||
defp do_describe(%Word{word: "M", address: 52}, _), do: some("Unload tool")
|
||||
defp do_describe(%Word{word: "M", address: 60}, _), do: some("Automatic pallet change")
|
||||
defp do_describe(%Word{word: "M", address: 98}, _), do: some("Subprogram call")
|
||||
defp do_describe(%Word{word: "M", address: 99}, _), do: some("Subprogram end")
|
||||
defp do_describe(%Word{word: "M", address: 100}, _), do: some("Clean nozzle")
|
||||
defp do_describe(%Word{word: "N", address: line}, _), do: some("Line #{line}")
|
||||
defp do_describe(%Word{word: "O", address: name}, _), do: some("Program #{name}")
|
||||
defp do_describe(%Word{word: "P", address: param}, _), do: some("Parameter #{param}")
|
||||
|
||||
defp do_describe(%Word{word: "Q", address: distance}, options),
|
||||
do: some("Peck increment #{distance_with_unit(distance, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "R", address: distance}, options),
|
||||
do: some("Radius #{distance_with_unit(distance, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "S", address: speed}, _), do: some("Speed #{speed}")
|
||||
defp do_describe(%Word{word: "T", address: tool}, _), do: some("Tool #{tool}")
|
||||
|
||||
defp do_describe(%Word{word: "X", address: position}, options),
|
||||
do: some("X #{distance_with_unit(position, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "Y", address: position}, options),
|
||||
do: some("Y #{distance_with_unit(position, options)}")
|
||||
|
||||
defp do_describe(%Word{word: "Z", address: position}, options),
|
||||
do: some("Z #{distance_with_unit(position, options)}")
|
||||
|
||||
defp do_describe(%Word{}, _options), do: none()
|
||||
|
||||
defp distance_with_unit(distance, %{units: :mm}), do: "#{distance}mm"
|
||||
defp distance_with_unit(distance, %{units: :inches}), do: "#{distance}\""
|
||||
defp distance_with_unit(distance, _), do: distance
|
||||
|
||||
defp feedrate(distance, %{operation: :turning} = options),
|
||||
do: "#{distance_with_unit(distance, options)}/rev"
|
||||
|
||||
defp feedrate(distance, options), do: "#{distance_with_unit(distance, options)}/min"
|
||||
end
|
20
lib/gcode/model/word/inspect.ex
Normal file
20
lib/gcode/model/word/inspect.ex
Normal file
|
@ -0,0 +1,20 @@
|
|||
defimpl Inspect, for: Gcode.Model.Word do
|
||||
alias Gcode.Model.{Describe, Word}
|
||||
use Gcode.Option
|
||||
import Inspect.Algebra
|
||||
@moduledoc false
|
||||
|
||||
def inspect(%Word{word: letter, address: address} = word, opts) do
|
||||
case Describe.describe(word) do
|
||||
some(description) ->
|
||||
concat([
|
||||
"#Gcode.Word<",
|
||||
to_doc([word: letter, address: address], opts),
|
||||
" (#{description})>"
|
||||
])
|
||||
|
||||
none() ->
|
||||
concat(["#Gcode.Word<", to_doc([word: letter, address: address], opts), ">"])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,11 +4,11 @@ defimpl Gcode.Model.Serialise, for: Gcode.Model.Word do
|
|||
use Gcode.Result
|
||||
|
||||
@spec serialise(Word.t()) :: Result.t([String.t()], {:serialise_error, any})
|
||||
def serialise(%Word{word: some(word), address: some(address)}) when is_integer(address) do
|
||||
def serialise(%Word{word: word, address: address}) when is_integer(address) do
|
||||
ok(["#{word}#{address}"])
|
||||
end
|
||||
|
||||
def serialise(%Word{word: some(word), address: some(address)}) when is_float(address) do
|
||||
def serialise(%Word{word: word, address: address}) when is_float(address) do
|
||||
ok(["#{word}#{address}"])
|
||||
end
|
||||
|
||||
|
|
|
@ -11,22 +11,23 @@ defmodule Gcode.Option do
|
|||
end
|
||||
end
|
||||
|
||||
@type t :: t(any)
|
||||
@type t(value) :: some(value) | opt_none
|
||||
@type some(t) :: {:ok, t}
|
||||
@type opt_none :: :none
|
||||
@type opt_none :: :error
|
||||
|
||||
@spec none?(t(any)) :: boolean
|
||||
def none?(:none), do: true
|
||||
def none?(:error), do: true
|
||||
def none?({:ok, _}), do: false
|
||||
|
||||
@spec some?(t(any)) :: boolean
|
||||
def some?(:none), do: false
|
||||
def some?(:error), do: false
|
||||
def some?({:ok, _}), do: true
|
||||
|
||||
@spec none :: Macro.t()
|
||||
defmacro none do
|
||||
quote do
|
||||
:none
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,4 +37,8 @@ defmodule Gcode.Option do
|
|||
{:ok, unquote(pattern)}
|
||||
end
|
||||
end
|
||||
|
||||
@spec unwrap!(t) :: any | no_return
|
||||
def unwrap!({:ok, result}), do: result
|
||||
def unwrap!(:error), do: raise("Attempt to unwrap a none")
|
||||
end
|
||||
|
|
7
mix.exs
7
mix.exs
|
@ -1,5 +1,6 @@
|
|||
defmodule Gcode.MixProject do
|
||||
use Mix.Project
|
||||
@moduledoc false
|
||||
|
||||
@version "0.2.1"
|
||||
@description """
|
||||
|
@ -15,7 +16,8 @@ defmodule Gcode.MixProject do
|
|||
package: package(),
|
||||
description: @description,
|
||||
deps: deps(),
|
||||
consolidate_protocols: Mix.env() != :test
|
||||
consolidate_protocols: Mix.env() != :test,
|
||||
elixirc_paths: elixirc_paths(Mix.env())
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -45,4 +47,7 @@ defmodule Gcode.MixProject do
|
|||
{:git_ops, "~> 2.3", only: ~w[dev test]a, runtime: false}
|
||||
]
|
||||
end
|
||||
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
end
|
||||
|
|
11
test/model/block/describe_test.exs
Normal file
11
test/model/block/describe_test.exs
Normal file
|
@ -0,0 +1,11 @@
|
|||
defmodule Gcode.Model.Block.DescribeTest do
|
||||
use DescribeCase, async: true
|
||||
@moduledoc false
|
||||
|
||||
describes_block("A13 B-15.2",
|
||||
with: [positioning: :absolute],
|
||||
as: "Rotate A axis counterclockwise to 13º, Rotate B axis clockwise to 15.2º"
|
||||
)
|
||||
|
||||
describes_block("G90 M213", as: "Absolute positioning, M213")
|
||||
end
|
46
test/model/program/describe_test.exs
Normal file
46
test/model/program/describe_test.exs
Normal file
|
@ -0,0 +1,46 @@
|
|||
defmodule Gcode.Model.Program.DescribeTest do
|
||||
use DescribeCase, async: true
|
||||
@moduledoc false
|
||||
|
||||
@program """
|
||||
O4968
|
||||
N01 M216
|
||||
N02 G20 G90 G54 D200 G40
|
||||
N03 G50 S2000
|
||||
N04 T0300
|
||||
N05 G96 S854 M03
|
||||
N06 G41 G00 X1.1 Z1.1 T0303 M08
|
||||
N07 G01 Z1.0 F.05
|
||||
N08 X-0.016
|
||||
N09 G00 Z1.1
|
||||
N10 X1.0
|
||||
N11 G01 Z0.0 F.05
|
||||
N12 G00 X1.1 M05 M09
|
||||
N13 G91 G28 X0
|
||||
N14 G91 G28 Z0
|
||||
N15 G90
|
||||
N16 M30
|
||||
"""
|
||||
|
||||
@expected """
|
||||
Program 4968
|
||||
Line 1, M216
|
||||
Line 2, Unit is inches, Absolute positioning, Work coordinate system, Radial offset 200, Tool radius compensation off
|
||||
Line 3, Scaling function cancel, Speed 2000
|
||||
Line 4, Tool 300
|
||||
Line 5, Constant surface speed, Speed 854, Spindle on clockwise
|
||||
Line 6, Tool radius compensation left, Rapid move, X 1.1, Z 1.1, Tool 303, Coolant flood
|
||||
Line 7, Linear move, Z 1.0
|
||||
Line 8, X -0.016
|
||||
Line 9, Rapid move, Z 1.1
|
||||
Line 10, X 1.0
|
||||
Line 11, Linear move, Z 0.0
|
||||
Line 12, Rapid move, X 1.1, Spindle stop, Coolant off
|
||||
Line 13, Relative positioning, Return to home position, X 0
|
||||
Line 14, Relative positioning, Return to home position, Z 0
|
||||
Line 15, Absolute positioning
|
||||
Line 16, End of program
|
||||
"""
|
||||
|
||||
describes_program(@program, as: @expected)
|
||||
end
|
192
test/model/word/describe_test.exs
Normal file
192
test/model/word/describe_test.exs
Normal file
|
@ -0,0 +1,192 @@
|
|||
defmodule Gcode.Model.Word.DescribeTest do
|
||||
use DescribeCase, async: true
|
||||
@moduledoc false
|
||||
|
||||
describes_word("A13", as: "Rotate A axis counterclockwise by/to 13º")
|
||||
describes_word("B-15.2", with: [positioning: :absolute], as: "Rotate B axis clockwise to 15.2º")
|
||||
|
||||
describes_word("C99",
|
||||
with: [positioning: :relative],
|
||||
as: "Rotate C axis counterclockwise by 99º"
|
||||
)
|
||||
|
||||
describes_word("D22", as: "Radial offset 22")
|
||||
describes_word("D22", with: [operation: :turning], as: "Depth of cut 22")
|
||||
describes_word("D22", with: [operation: :plotting], as: "Aperture 22")
|
||||
describes_word("D22", with: [compensation: :left, units: :mm], as: "Left radial offset 22mm")
|
||||
|
||||
describes_word("D22",
|
||||
with: [compensation: :right, units: :inches],
|
||||
as: "Right radial offset 22\""
|
||||
)
|
||||
|
||||
describes_word("E123",
|
||||
with: [operation: :turning, units: :mm],
|
||||
as: "Precision feedrate 123mm/rev"
|
||||
)
|
||||
|
||||
describes_word("E123",
|
||||
with: [operation: :printing, units: :inches],
|
||||
as: "Extruder feedrate 123\"/min"
|
||||
)
|
||||
|
||||
describes_word("E123", with: [operation: :turning], as: "Precision feedrate 123/rev")
|
||||
describes_word("F100", with: [operation: :turning, units: :mm], as: "Feedrate 100mm/rev")
|
||||
describes_word("F100", with: [units: :inches], as: "Feedrate 100\"/min")
|
||||
describes_word("F100", as: "Feedrate 100/min")
|
||||
|
||||
describes_word("G0", as: "Rapid move")
|
||||
describes_word("G1", as: "Linear move")
|
||||
describes_word("G2", as: "Clockwise circular move")
|
||||
describes_word("G3", as: "Counterclockwise circular move")
|
||||
describes_word("G4", as: "Dwell")
|
||||
describes_word("G5", as: "High-precision contour control")
|
||||
describes_word("G5.1", as: "AI advanced preview control")
|
||||
describes_word("G6.1", as: "NURBS machining")
|
||||
describes_word("G7", as: "Imaginary axis designation")
|
||||
describes_word("G9", as: "Exact stop check - non-modal")
|
||||
describes_word("G10", as: "Programmable data input")
|
||||
describes_word("G11", as: "Data write cancel")
|
||||
describes_word("G17", as: "XY plane selection")
|
||||
describes_word("G18", as: "ZX plane selection")
|
||||
describes_word("G19", as: "YZ plane selection")
|
||||
describes_word("G20", as: "Unit is inches")
|
||||
describes_word("G21", as: "Unit is mm")
|
||||
describes_word("G28", as: "Return to home position")
|
||||
describes_word("G30", as: "Return to secondary home position")
|
||||
describes_word("G31", as: "Feed until skip function")
|
||||
describes_word("G32", as: "Single-point threading")
|
||||
describes_word("G33", as: "Variable pitch threading")
|
||||
describes_word("G40", as: "Tool radius compensation off")
|
||||
describes_word("G41", as: "Tool radius compensation left")
|
||||
describes_word("G42", as: "Tool radius compensation right")
|
||||
describes_word("G43", as: "Tool height offset compensation negative")
|
||||
describes_word("G44", as: "Tool height offset compensation positive")
|
||||
describes_word("G45", as: "Axis offset single increase")
|
||||
describes_word("G46", as: "Axis offset single decrease")
|
||||
describes_word("G47", as: "Axis offset double increase")
|
||||
describes_word("G48", as: "Axis offset double decrease")
|
||||
describes_word("G49", as: "Tool length offset compensation cancel")
|
||||
describes_word("G50", as: "Scaling function cancel")
|
||||
describes_word("G50", with: [operation: :turning], as: "Position register")
|
||||
describes_word("G52", as: "Local coordinate system")
|
||||
describes_word("G53", as: "Machine coordinate system")
|
||||
describes_word("G54", as: "Work coordinate system")
|
||||
describes_word("G55", as: "Work coordinate system")
|
||||
describes_word("G56", as: "Work coordinate system")
|
||||
describes_word("G57", as: "Work coordinate system")
|
||||
describes_word("G58", as: "Work coordinate system")
|
||||
describes_word("G59", as: "Work coordinate system")
|
||||
describes_word("G54.1", as: "Work coordinate system")
|
||||
describes_word("G61", as: "Exact stop check - modal")
|
||||
describes_word("G62", as: "Automatic corner override")
|
||||
describes_word("G64", as: "Default cutting mode")
|
||||
describes_word("G68", as: "Rotate coordinate system")
|
||||
describes_word("G69", as: "Turn off coordinate system rotation")
|
||||
|
||||
describes_word("G70",
|
||||
with: [operation: :turning],
|
||||
as: "Fixed cycle, multiple repetitive cycle - for finishing"
|
||||
)
|
||||
|
||||
describes_word("G71",
|
||||
with: [operation: :turning],
|
||||
as: "Fixed cycle, multiple repetitive cycle - for roughing with Z axis emphasis"
|
||||
)
|
||||
|
||||
describes_word("G72",
|
||||
with: [operation: :turning],
|
||||
as: "Fixed cycle, multiple repetitive cycle - for roughing with X axis emphasis"
|
||||
)
|
||||
|
||||
describes_word("G73",
|
||||
with: [operation: :turning],
|
||||
as: "Fixed cycle, multiple repetitive cycle - for roughing with pattern repetition"
|
||||
)
|
||||
|
||||
describes_word("G73", as: "Peck drilling cycle")
|
||||
describes_word("G74", with: [operation: :turning], as: "Peck drilling cycle")
|
||||
describes_word("G74", as: "Tapping cycle")
|
||||
describes_word("G75", with: [operation: :turning], as: "Peck grooving cycle")
|
||||
describes_word("G76", as: "Fine boring cycle")
|
||||
describes_word("G76", with: [operation: :turning], as: "Threading cycle")
|
||||
describes_word("G80", as: "Cancel cycle")
|
||||
describes_word("G81", as: "Simple drilling cycle")
|
||||
describes_word("G82", as: "Drilling cycle with dwell")
|
||||
describes_word("G83", as: "Peck drilling cycle")
|
||||
describes_word("G84", as: "Tapping cycle, righthand thread, M03 spindle direction")
|
||||
|
||||
describes_word("G84.2",
|
||||
as: "Tapping cycle, righthand thread, M03 spindle direction, rigid toolholder"
|
||||
)
|
||||
|
||||
describes_word("G84.3",
|
||||
as: "Tapping cycle, lefthand thread, M04 spindle direction, rigid toolholder"
|
||||
)
|
||||
|
||||
describes_word("G85", as: "Boring cycle, feed in/feed out")
|
||||
describes_word("G86", as: "Boring cycle, feed in/spindle stop/rapid out")
|
||||
describes_word("G87", as: "Boring cycle, backboring")
|
||||
describes_word("G88", as: "Boring cycle, feed in/spindle stop/manual operation")
|
||||
describes_word("G89", as: "Boring cycle, feed in/dwell/feed out")
|
||||
describes_word("G90", as: "Absolute positioning")
|
||||
describes_word("G91", as: "Relative positioning")
|
||||
describes_word("G92", as: "Position register")
|
||||
describes_word("G94", as: "Feedrate per minute")
|
||||
describes_word("G95", as: "Feedrate per revolution")
|
||||
describes_word("G96", as: "Constant surface speed")
|
||||
describes_word("G97", as: "Constant spindle speed")
|
||||
describes_word("G98", as: "Return to initial Z level in canned cycle")
|
||||
describes_word("G98", with: [operation: :turning], as: "Feedrate per minute")
|
||||
describes_word("G99", as: "Return to R level in canned cycle")
|
||||
describes_word("G99", with: [operation: :turning], as: "Feedrate per revolution")
|
||||
describes_word("G100", as: "Tool length measurement")
|
||||
describes_word("H76", with: [units: :mm], as: "Tool length offset 76mm")
|
||||
describes_word("I1.21", with: [units: :mm], as: "X arc center offset 1.21mm")
|
||||
describes_word("J1.21", with: [units: :inches], as: "Y arc center offset 1.21\"")
|
||||
describes_word("K1.21", as: "Z arc center offset 1.21")
|
||||
describes_word("L87", as: "Loop count 87")
|
||||
describes_word("M0", as: "Compulsory stop")
|
||||
describes_word("M1", as: "Optional stop")
|
||||
describes_word("M2", as: "End of program")
|
||||
describes_word("M3", as: "Spindle on clockwise")
|
||||
describes_word("M4", as: "Spindle on counterclockwise")
|
||||
describes_word("M5", as: "Spindle stop")
|
||||
describes_word("M6", as: "Automatic tool change")
|
||||
describes_word("M7", as: "Coolant mist")
|
||||
describes_word("M8", as: "Coolant flood")
|
||||
describes_word("M9", as: "Coolant off")
|
||||
describes_word("M10", as: "Pallet clamp on")
|
||||
describes_word("M11", as: "Pallet clamp off")
|
||||
describes_word("M13", as: "Spindle on clockwise and coolant flood")
|
||||
describes_word("M19", as: "Spindle orientation")
|
||||
describes_word("M21", as: "Mirror X axis")
|
||||
describes_word("M21", with: [operation: :turning], as: "Tailstock forward")
|
||||
describes_word("M22", as: "Mirror Y axis")
|
||||
describes_word("M22", with: [operation: :turning], as: "Tailstock backward")
|
||||
describes_word("M23", as: "Mirror off")
|
||||
describes_word("M23", with: [operation: :turning], as: "Thread gradual pullout on")
|
||||
describes_word("M24", with: [operation: :turning], as: "Thread gradual pullout off")
|
||||
describes_word("M30", as: "End of program")
|
||||
describes_word("M41", with: [operation: :turning], as: "Gear select 1")
|
||||
describes_word("M42", with: [operation: :turning], as: "Gear select 2")
|
||||
describes_word("M43", with: [operation: :turning], as: "Gear select 3")
|
||||
describes_word("M44", with: [operation: :turning], as: "Gear select 4")
|
||||
describes_word("M48", as: "Feedrate override allowed")
|
||||
describes_word("M49", as: "Feedrate override not allowed")
|
||||
describes_word("M52", as: "Unload tool")
|
||||
describes_word("M60", as: "Automatic pallet change")
|
||||
describes_word("M98", as: "Subprogram call")
|
||||
describes_word("M99", as: "Subprogram end")
|
||||
describes_word("M100", as: "Clean nozzle")
|
||||
describes_word("N100", as: "Line 100")
|
||||
describes_word("O200", as: "Program 200")
|
||||
describes_word("P12", as: "Parameter 12")
|
||||
describes_word("Q34", as: "Peck increment 34")
|
||||
describes_word("R37", with: [units: :mm], as: "Radius 37mm")
|
||||
describes_word("S12", as: "Speed 12")
|
||||
describes_word("T4", as: "Tool 4")
|
||||
describes_word("X2.34", as: "X 2.34")
|
||||
describes_word("Y3.45", as: "Y 3.45")
|
||||
describes_word("Z4.56", as: "Z 4.56")
|
||||
end
|
142
test/support/describe_case.ex
Normal file
142
test/support/describe_case.ex
Normal file
|
@ -0,0 +1,142 @@
|
|||
defmodule DescribeCase do
|
||||
use ExUnit.CaseTemplate
|
||||
alias Gcode.Model.{Block, Describe, Program, Word}
|
||||
use Gcode.Option
|
||||
use Gcode.Result
|
||||
@moduledoc false
|
||||
|
||||
using do
|
||||
quote do
|
||||
use Gcode.Option
|
||||
use Gcode.Result
|
||||
import DescribeCase
|
||||
end
|
||||
end
|
||||
|
||||
@regex ~r/^(?<word>[A-Z])(?<address>[+-]?(?<float>[0-9]+\.)?[0-9]+)$/
|
||||
|
||||
def make_word(input) do
|
||||
case Regex.named_captures(@regex, input) do
|
||||
%{"word" => word, "address" => address, "float" => ""} ->
|
||||
Word.init(word, String.to_integer(address))
|
||||
|
||||
%{"word" => word, "address" => address, "float" => _} ->
|
||||
Word.init(word, String.to_float(address))
|
||||
|
||||
_ ->
|
||||
none()
|
||||
end
|
||||
end
|
||||
|
||||
def make_block(input) do
|
||||
{:ok, block} = Block.init()
|
||||
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split(~r/\s+/)
|
||||
|> Enum.map(&make_word/1)
|
||||
|> Enum.reject(&Option.none?/1)
|
||||
|> Enum.map(&Result.unwrap!/1)
|
||||
|> Result.Enum.reduce_while_ok(block, &Block.push(&2, &1))
|
||||
end
|
||||
|
||||
@spec make_program(binary) :: {:error, any} | {:ok, any}
|
||||
def make_program(input) do
|
||||
{:ok, program} = Program.init()
|
||||
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(&make_block/1)
|
||||
|> Enum.reject(&Option.none?/1)
|
||||
|> Enum.map(&Result.unwrap!/1)
|
||||
|> Result.Enum.reduce_while_ok(program, &Program.push(&2, &1))
|
||||
end
|
||||
|
||||
defmacro describes_word(input, opts) when is_list(opts) do
|
||||
output = Keyword.fetch!(opts, :as)
|
||||
options = Keyword.get(opts, :with, [])
|
||||
|
||||
description =
|
||||
if Enum.any?(options) do
|
||||
plural = if Enum.count(options) == 1, do: "option", else: "options"
|
||||
|
||||
description =
|
||||
options
|
||||
|> Enum.map(fn {k, v} -> "#{k}: #{v}" end)
|
||||
|> Enum.join(", ")
|
||||
|
||||
"#{input} with #{plural} #{description}"
|
||||
else
|
||||
input
|
||||
end
|
||||
|
||||
quote do
|
||||
describe unquote(description) do
|
||||
test "is described as #{inspect(unquote(output))}" do
|
||||
assert ok(word) = make_word(unquote(input))
|
||||
assert ok(description) = Describe.describe(word, unquote(options))
|
||||
assert description == unquote(output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro describes_block(input, opts) when is_list(opts) do
|
||||
output = Keyword.fetch!(opts, :as)
|
||||
options = Keyword.get(opts, :with, [])
|
||||
|
||||
description =
|
||||
if Enum.any?(options) do
|
||||
plural = if Enum.count(options) == 1, do: "option", else: "options"
|
||||
|
||||
description =
|
||||
options
|
||||
|> Enum.map(fn {k, v} -> "#{k}: #{v}" end)
|
||||
|> Enum.join(", ")
|
||||
|
||||
"#{input} with #{plural} #{description}"
|
||||
else
|
||||
input
|
||||
end
|
||||
|
||||
quote do
|
||||
describe unquote(description) do
|
||||
test "is described as #{inspect(unquote(output))}" do
|
||||
assert ok(block) = make_block(unquote(input))
|
||||
assert ok(description) = Describe.describe(block, unquote(options))
|
||||
assert description == unquote(output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro describes_program(input, opts) do
|
||||
output = Keyword.fetch!(opts, :as)
|
||||
options = Keyword.get(opts, :with, [])
|
||||
|
||||
description =
|
||||
if Enum.any?(options) do
|
||||
plural = if Enum.count(options) == 1, do: "option", else: "options"
|
||||
|
||||
description =
|
||||
options
|
||||
|> Enum.map(fn {k, v} -> "#{k}: #{v}" end)
|
||||
|> Enum.join(", ")
|
||||
|
||||
"program with #{plural} #{description}"
|
||||
else
|
||||
"program"
|
||||
end
|
||||
|
||||
quote do
|
||||
describe unquote(description) do
|
||||
test "is described correctly" do
|
||||
assert ok(program) = make_program(unquote(input))
|
||||
assert ok(description) = Describe.describe(program, unquote(options))
|
||||
assert description == unquote(output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue