Initial commit

This commit is contained in:
2020-05-08 00:37:29 +02:00
commit e2b8f1d7aa
29 changed files with 1497 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
<?php
namespace Automata;
use Program\Word;
class Definitions
{
private array $G = [
// traverse mode
0 => 'Rapid traverse', // Default
1 => 'Linear interpolation',
2 => 'Circular interpolation (CW)',
3 => 'Circular interpolation (CCW)',
// quest-ce-que c'est?
4 => 'Dwell',
// plane selection mode
17 => 'Plane selection (XY-plane)', // Default
18 => 'Plane selection (XZ-plane)',
19 => 'Plane selection (YZ-plane)',
// extra functionality (one shot)
21 => 'Parameter definition for SP (G21 E1 X1000 sets E1 to 1000)',
22 => 'Call up of SP through X',
23 => 'Addition of parameters in SP (G23 E030201 sets E03 = E02 + E01)',
24 => 'Subtraction of parameters in SP (G23 E030201 sets E03 = E02 - E01)',
25 => 'Parameter association in SP for storing',
26 => '100% feed, no override possible',
29 => 'Skip instruction in SP (G29 E2 X50 means jump to after 50 if E2 > 0)',
// radius compensation mode
40 => 'Radius compensation not effective', // default
41 => 'Radius compensation to left of workpiece',
42 => 'Radius compensation to right of workpiece',
43 => 'Radius compensation, axially parallel, positive',
44 => 'Radius compensation, axially parallel, negative',
// call workcycle
79 => 'Activation of a work cycle',
// work cycle selection
81 => 'Work cycle for drilling',
84 => 'Work cycle for tapping',
85 => 'Work cycle for reaming',
86 => 'Work cycle for boring',
// dimension modes
90 => 'Absolute dimension programming', // Default
91 => 'Incremental dimension programming',
// shift zero datum (one shot)
92 => 'Incremental zero datum shift',
93 => 'Absolute zero datum shift',
// reference to zero
98 => 'Automatic positioning to reference datum point',
];
private array $M = [
0 => 'Programmed stop (Sets M5 and M9)',
2 => 'End of program (Sets M5 and M9)',
3 => 'Spindle start CW (CCW in G18)',
4 => 'Spindle start CCW (CW in G18)',
5 => 'Spindle stop',
6 => 'Tool change (Sets M5 and M9)',
// coolant control
8 => 'Coolant on',
9 => 'Coolant off',
// table clamping (unsupported)
10 => 'Table clamping - close',
11 => 'Table clamping - open',
30 => 'End of program with skip back to start (Sets M5 and M9)',
67 => 'Enter new tool information without machine stop',
];
private array $S = [
0, 40, 50, 63, 80, 100, 125, 160, 200, 250,
315, 400, 500, 630, 800, 1000, 1250, 1600, 2000,
];
public function hasPredefinedValue(Word $word): bool
{
return in_array($word->register, ['G', 'M']);
}
public function translatePredefinedValue(Word $word): ?string
{
return @$this->{$word->register}[$word->value];
}
}

74
src/Automata/Machine.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
namespace Automata;
use Automata\Storage\ProgramMemory;
use Automata\Storage\ToolMemory;
use Program\Program;
use Program\Word;
use RuntimeException;
use SplStack;
class Machine
{
public State $state;
public ToolMemory $toolMemory;
public ProgramMemory $subProgramMemory;
public ProgramMemory $partProgramMemory;
/** @var SplStack|Program[] */
private SplStack $programStack;
private ?Program $activeProgram = null;
private Definitions $definitions;
public function __construct()
{
$this->state = new State();
$this->definitions = new Definitions();
$this->toolMemory = new ToolMemory();
$this->subProgramMemory = new ProgramMemory();
$this->partProgramMemory = new ProgramMemory();
$this->programStack = new SplStack();
}
public function loadProgram(int $N): void
{
$this->programStack = new SplStack();
$program = $this->partProgramMemory->read($N);
$this->programStack->push($program);
}
public function start(): void
{
$this->activeProgram = $this->programStack->pop();
if (is_null($this->activeProgram)) {
throw new RuntimeException('No program loaded');
}
foreach ($this->activeProgram as $block) {
// Do we apply a block or a word?
// Answer: depends! Some commands need
// more information from other words.
// TODO: Interpret commands such as preparation of a work cycle
$words = array_map(fn(Word $w) => $w->toString(), $block->words);
$words = implode("\t", $words);
$words = "N{$block->N}\t$words";
foreach ($block->words as $word) {
$this->state->apply($word);
system('clear');
print("\n $words\n\n");
dump($this->state);
readline();
}
}
}
public function reset(): void
{
$this->activeProgram = null;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Automata\Modes\G;
class DimensionMode extends G
{
public function supports(int $G): bool
{
return in_array($G, [90, 91]);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Automata\Modes\G;
abstract class G
{
public int $G;
public function __construct(int $G)
{
$this->G = $G;
}
public final function apply(int $G): void
{
if ($this->supports($G)) {
$this->G = $G;
}
}
public abstract function supports(int $G): bool;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Automata\Modes\G;
class PlaneSelection extends G
{
public function supports(int $G): bool
{
return in_array($G, [17, 18, 19]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Automata\Modes\G;
class RadiusCompensation extends G
{
public function supports(int $G): bool
{
return in_array($G, [40, 41, 42, 43, 44]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Automata\Modes\G;
class TraverseMode extends G
{
public function supports(int $G): bool
{
return in_array($G, [0, 1, 2, 3]);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Automata\Modes\G;
class WorkCycle extends G
{
public int $X = 0;
public int $Y = 0;
public int $Z = 0;
public int $B = 0;
public function supports(int $G): bool
{
return in_array($G, [81, 84, 85, 86]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Automata\Modes\M;
class CoolantMode extends M
{
public function supports(int $M): bool
{
return in_array($M, [0, 2, 6, 8, 9, 30]);
}
public function apply(int $M): void
{
if (!$this->supports($M)) {
return;
}
if (in_array($M, [0, 2, 6, 30])) {
$this->M = 9;
return;
}
// 8 or 9
$this->M = $M;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Automata\Modes\M;
abstract class M
{
public int $M;
public function __construct(int $M)
{
$this->M = $M;
}
public function apply(int $M): void
{
if ($this->supports($M)) {
$this->M = $M;
}
}
public abstract function supports(int $M): bool;
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Automata\Modes\M;
class SpindleMode extends M
{
public function supports(int $M): bool
{
return in_array($M, [0, 2, 3, 4, 5, 6, 30]);
}
public function apply(int $M): void
{
if (!$this->supports($M)) {
return;
}
if (in_array($M, [0, 2, 6, 30])) {
$this->M = 5;
return;
}
// 3, 4 or 5
$this->M = $M;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Automata\Modes\M;
class TableClampingMode extends M
{
public function supports(int $M): bool
{
return in_array($M, [10, 11]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Automata;
class Registers
{
public int $B = 0; // Traverse information
public int $X = 0; // Dimension along X-axis
public int $Y = 0; // Dimension along Y-axis
public int $Z = 0; // Dimension along Z-axis
public int $N = 0; // Current line or program number
public int $I = 0; // Circle centre, X-axis
public int $J = 0; // Circle centre, Y-axis
public int $K = 0; // Circle centre, Z-axis
public int $F = 0; // Feed in 0.1 mm/min
public int $S = 0; // Spindle speed in RPM
public int $T = 0; // Tool number
public int $H = 0; // Additional functions (unused)
public int $M = 0; // Auxiliary functions
public int $E = 0; // Number of repetitions / multiple meanins
}

70
src/Automata/State.php Normal file
View File

@@ -0,0 +1,70 @@
<?php
namespace Automata;
use Automata\Modes\G\DimensionMode;
use Automata\Modes\G\PlaneSelection;
use Automata\Modes\G\RadiusCompensation;
use Automata\Modes\G\TraverseMode;
use Automata\Modes\G\WorkCycle;
use Automata\Modes\M\CoolantMode;
use Automata\Modes\M\SpindleMode;
use Automata\Modes\M\TableClampingMode;
use Program\Word;
class State
{
public Registers $registers;
// G
public TraverseMode $traverseMode;
public PlaneSelection $planeSelection;
public RadiusCompensation $radiusCompensation;
public DimensionMode $dimensionMode;
public WorkCycle $workCycleSelection;
// M
public CoolantMode $coolantMode;
public SpindleMode $spindleMode;
public TableClampingMode $tableClamping;
public function __construct()
{
$this->registers = new Registers();
$this->traverseMode = new TraverseMode(0);
$this->planeSelection = new PlaneSelection(17);
$this->radiusCompensation = new RadiusCompensation(40);
$this->dimensionMode = new DimensionMode(90);
$this->workCycleSelection = new WorkCycle(0);
$this->coolantMode = new CoolantMode(9);
$this->spindleMode = new SpindleMode(5);
$this->tableClamping = new TableClampingMode(10);
}
public function apply(Word $word): void
{
if ($word->register === 'G') {
$this->traverseMode->apply($word->value);
$this->planeSelection->apply($word->value);
$this->radiusCompensation->apply($word->value);
$this->dimensionMode->apply($word->value);
$this->workCycleSelection->apply($word->value);
// TODO Handle other G commands
return;
}
if ($word->register === 'M') {
$this->coolantMode->apply($word->value);
$this->spindleMode->apply($word->value);
$this->tableClamping->apply($word->value);
// TODO Handle other M commands
return;
}
if (property_exists($this->registers, $word->register)) {
$this->registers->{$word->register} = $word->value;
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Automata\Storage;
use InvalidArgumentException;
use Program\Program;
class ProgramMemory
{
/** @var Program[] */
private array $memory = [];
public function save(Program $program): void
{
if ($program->N > 9998 || $program->N < 9001) {
throw new InvalidArgumentException('Program number must be in range 9001 - 9998');
}
$this->memory[$program->N] = $program;
}
public function read(int $N): ?Program
{
return @$this->memory[$N];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Automata\Storage;
use Automata\Tool;
class ToolMemory
{
/** @var Tool[] */
private array $memory = [];
public function save(Tool $tool): void
{
$this->memory[$tool->T] = $tool;
}
public function read(int $T): ?Tool
{
return @$this->memory[$T];
}
}

18
src/Automata/Tool.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace Automata;
class Tool
{
public int $T; // Tool number
public int $X; // Radius in 0.001 mm
public int $Z; // Length offset in 0.002 mm
public function __construct(int $T, int $X, int $Z)
{
$this->T = $T;
$this->X = $X;
$this->Z = $Z;
}
}

23
src/Program/Block.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace Program;
class Block
{
public string $N;
/** @var Word[] */
public array $words;
public function __construct(string $N)
{
$this->N = $N;
$this->words = [];
}
public function addWord(Word $word): void
{
$this->words[] = $word;
}
}

38
src/Program/Program.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
namespace Program;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
class Program implements IteratorAggregate
{
public int $N;
/** @var Block[] */
private array $blocks;
public function __construct(int $N)
{
$this->N = $N;
$this->blocks = [];
}
public function addBlock(Block $block): self
{
$this->blocks[] = $block;
return $this;
}
/**
* @return ArrayIterator|Traversable|Block[]
*/
public function getIterator()
{
return new ArrayIterator($this->blocks);
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Program;
use RuntimeException;
class ProgramParser
{
public function parseFile(string $filename): Program
{
$lines = explode(PHP_EOL, file_get_contents($filename));
$lines = array_filter($lines);
return $this->parse($lines);
}
public function parse(array $lines): Program
{
// In the first parsing stage, all words are parsed from the file.
/** @var Word[] $words */
$words = [];
foreach ($lines as $line) {
$characters = str_split(trim($line) . ' ');
$state = 'NONE';
$wordBuffer = [];
foreach ($characters as $character) {
if ('READING' === $state) {
if ('-' === $character || is_numeric($character)) {
$wordBuffer['value'] .= $character;
continue;
}
// Word is finished
$words[] = new Word($wordBuffer['register'], $wordBuffer['value']);
$wordBuffer = [];
$state = 'NONE';
continue;
}
if ('NONE' === $state && ctype_alpha($character)) {
$state = 'READING';
$wordBuffer = ['register' => $character, 'value' => ''];
continue;
}
if (stripos($line, 'EOF') !== false) {
break 2; // End of program reached
}
if (';' === $character) {
break; // Rest of line is a comment
}
}
}
// In the second stage, the words are ordered by N numbers
// and shaped into a program
$firstWord = array_shift($words);
if (!preg_match('/^N9\d\d\d$/i', $firstWord)) {
throw new RuntimeException('Expected program number, got ' . $firstWord);
}
$program = new Program($firstWord->value);
$lineBuffer = null;
foreach ($words as $word) {
if ($word->register === 'N') {
if (!is_null($lineBuffer)) {
$program->addBlock($lineBuffer);
}
$lineBuffer = new Block($word->value);
continue;
}
$lineBuffer->addWord($word);
}
return $program;
}
}

28
src/Program/Word.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace Program;
class Word
{
public string $register;
public int $value;
public function __construct(string $register, int $value)
{
$this->register = $register;
$this->value = $value;
}
public function toString(): string
{
return "{$this->register}{$this->value}";
}
public function __toString()
{
return $this->toString();
}
}

22
src/ProgramLoader.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
use Automata\Machine;
use Program\ProgramParser;
class ProgramLoader
{
public function loadPrograms(Machine $machine): void
{
$parser = new ProgramParser();
foreach (glob(__DIR__ . '/../programs/part/*') as $partProgramFile) {
$partProgram = $parser->parseFile($partProgramFile);
$machine->partProgramMemory->save($partProgram);
}
foreach (glob(__DIR__ . '/../programs/sub/*') as $subProgramFile) {
$subProgram = $parser->parseFile($subProgramFile);
$machine->subProgramMemory->save($subProgram);
}
}
}