The-Legend-of-Delta/Repetier Firmware/Repetier/gcode.cpp

948 lines
27 KiB
C++
Raw Normal View History

2019-08-07 14:27:54 +02:00
/*
This file is part of Repetier-Firmware.
Repetier-Firmware is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Repetier-Firmware is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Repetier-Firmware. If not, see <http://www.gnu.org/licenses/>.
This firmware is a nearly complete rewrite of the sprinter firmware
by kliment (https://github.com/kliment/Sprinter)
which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware.
Functions in this file are used to communicate using ascii or repetier protocol.
*/
#include "Repetier.h"
#ifndef FEATURE_CHECKSUM_FORCED
#define FEATURE_CHECKSUM_FORCED false
#endif
GCode GCode::commandsBuffered[GCODE_BUFFER_SIZE]; ///< Buffer for received commands.
uint8_t GCode::bufferReadIndex = 0; ///< Read position in gcode_buffer.
uint8_t GCode::bufferWriteIndex = 0; ///< Write position in gcode_buffer.
uint8_t GCode::commandReceiving[MAX_CMD_SIZE]; ///< Current received command.
uint8_t GCode::commandsReceivingWritePosition = 0; ///< Writing position in gcode_transbuffer.
uint8_t GCode::sendAsBinary; ///< Flags the command as binary input.
uint8_t GCode::wasLastCommandReceivedAsBinary = 0; ///< Was the last successful command in binary mode?
uint8_t GCode::commentDetected = false; ///< Flags true if we are reading the comment part of a command.
uint8_t GCode::binaryCommandSize; ///< Expected size of the incoming binary command.
bool GCode::waitUntilAllCommandsAreParsed = false; ///< Don't read until all commands are parsed. Needed if gcode_buffer is misused as storage for strings.
uint32_t GCode::lastLineNumber = 0; ///< Last line number received.
uint32_t GCode::actLineNumber; ///< Line number of current command.
int8_t GCode::waitingForResend = -1; ///< Waiting for line to be resend. -1 = no wait.
volatile uint8_t GCode::bufferLength = 0; ///< Number of commands stored in gcode_buffer
millis_t GCode::timeOfLastDataPacket = 0; ///< Time, when we got the last data packet. Used to detect missing uint8_ts.
uint8_t GCode::formatErrors = 0;
/** \page Repetier-protocol
\section Introduction
The repetier-protocol was developed, to overcome some shortcommings in the standard
RepRap communication method, while remaining backward compatible. To use the improved
features of this protocal, you need a host which speaks it. On Windows the recommended
host software is Repetier-Host. It is developed in parallel to this firmware and supports
all implemented features.
\subsection Improvements
- With higher speeds, the serial connection is more likely to produce communication failures.
The standard method is to transfer a checksum at the end of the line. This checksum is the
XORd value of all characters send. The value is limited to a range between 0 and 127. It can
not detect two identical missing characters or a wrong order. Therefore the new protocol
uses Fletchers checksum, which overcomes these shortcommings.
- The new protocol send data in binary format. This reduces the data size to less then 50% and
it speeds up decoding the command. No slow conversion from string to floats are needed.
*/
/** \brief Computes size of binary data from bitfield.
In the repetier-protocol in binary mode, the first 2 uint8_ts define the
data. From this bitfield, this function computes the size of the command
including the 2 uint8_ts of the bitfield and the 2 uint8_ts for the checksum.
Gcode Letter to Bit and Datatype:
- N : Bit 0 : 16-Bit Integer
- M : Bit 1 : 8-Bit unsigned uint8_t
- G : Bit 2 : 8-Bit unsigned uint8_t
- X : Bit 3 : 32-Bit Float
- Y : Bit 4 : 32-Bit Float
- Z : Bit 5 : 32-Bit Float
- E : Bit 6 : 32-Bit Float
- : Bit 7 : always set to distinguish binary from ASCII line.
- F : Bit 8 : 32-Bit Float
- T : Bit 9 : 8 Bit Integer
- S : Bit 10 : 32 Bit Value
- P : Bit 11 : 32 Bit Integer
- V2 : Bit 12 : Version 2 command for additional commands/sizes
- Ext : Bit 13 : There are 2 more uint8_ts following with Bits, only for future versions
- Int :Bit 14 : Marks it as internal command,
- Text : Bit 15 : 16 Byte ASCII String terminated with 0
Second word if V2:
- I : Bit 0 : 32-Bit float
- J : Bit 1 : 32-Bit float
- R : Bit 2 : 32-Bit float
- D : Bit 3 : 32-Bit float
- C : Bit 4 : 32-Bit float
- H : Bit 5 : 32-Bit float
- A : Bit 6 : 32-Bit float
- B : Bit 7 : 32-Bit float
- K : Bit 8 : 32-Bit float
- L : Bit 9 : 32-Bit float
- O : Bit 0 : 32-Bit float
*/
uint8_t GCode::computeBinarySize(char *ptr) // unsigned int bitfield) {
{
uint8_t s = 4; // include checksum and bitfield
uint16_t bitfield = *(uint16_t*)ptr;
if(bitfield & 1) s += 2;
if(bitfield & 8) s += 4;
if(bitfield & 16) s += 4;
if(bitfield & 32) s += 4;
if(bitfield & 64) s += 4;
if(bitfield & 256) s += 4;
if(bitfield & 512) s += 1;
if(bitfield & 1024) s += 4;
if(bitfield & 2048) s += 4;
if(bitfield & 4096) // Version 2 or later
{
s += 2; // for bitfield 2
uint16_t bitfield2 = *(uint16_t*)(ptr + 2);
if(bitfield & 2) s += 2;
if(bitfield & 4) s += 2;
if(bitfield2 & 1) s += 4;
if(bitfield2 & 2) s += 4;
if(bitfield2 & 4) s += 4;
if(bitfield2 & 8) s += 4;
if(bitfield2 & 16) s += 4;
if(bitfield2 & 32) s += 4;
if(bitfield2 & 64) s += 4;
if(bitfield2 & 128) s += 4;
if(bitfield2 & 256) s += 4;
if(bitfield2 & 512) s += 4;
if(bitfield2 & 1024) s += 4;
if(bitfield2 & 2048) s += 4;
if(bitfield2 & 4096) s += 4;
if(bitfield2 & 8192) s += 4;
if(bitfield2 & 16384) s += 4;
if(bitfield2 & 32768) s += 4;
if(bitfield & 32768) s += RMath::min(80,(uint8_t)ptr[4] + 1);
}
else
{
if(bitfield & 2) s += 1;
if(bitfield & 4) s += 1;
if(bitfield & 32768) s += 16;
}
return s;
}
void GCode::requestResend()
{
HAL::serialFlush();
commandsReceivingWritePosition = 0;
if(sendAsBinary)
waitingForResend = 30;
else
waitingForResend = 14;
Com::println();
Com::printFLN(Com::tResend,lastLineNumber + 1);
Com::printFLN(Com::tOk);
}
/**
Check if result is plausible. If it is, an ok is send and the command is stored in queue.
If not, a resend and ok is send.
*/
void GCode::checkAndPushCommand()
{
if(hasM())
{
if(M == 110) // Reset line number
{
lastLineNumber = actLineNumber;
Com::printFLN(Com::tOk);
waitingForResend = -1;
return;
}
if(M == 112) // Emergency kill - freeze printer
{
Commands::emergencyStop();
}
#ifdef DEBUG_COM_ERRORS
if(M == 666) // force an communication error
{
lastLineNumber++;
return;
}
#endif // DEBUG_COM_ERRORS
}
if(hasN())
{
if((((lastLineNumber + 1) & 0xffff) != (actLineNumber & 0xffff)))
{
if(static_cast<uint16_t>(lastLineNumber - actLineNumber) < 40)
{
// we have seen that line already. So we assume it is a repeated resend and we ignore it
commandsReceivingWritePosition = 0;
Com::printFLN(Com::tSkip,actLineNumber);
Com::printFLN(Com::tOk);
}
else if(waitingForResend < 0) // after a resend, we have to skip the garbage in buffers, no message for this
{
if(Printer::debugErrors())
{
Com::printF(Com::tExpectedLine, lastLineNumber + 1);
Com::printFLN(Com::tGot, actLineNumber);
}
requestResend(); // Line missing, force resend
}
else
{
--waitingForResend;
commandsReceivingWritePosition = 0;
Com::printFLN(Com::tSkip, actLineNumber);
Com::printFLN(Com::tOk);
}
return;
}
lastLineNumber = actLineNumber;
}
pushCommand();
#if ACK_WITH_LINENUMBER
Com::printFLN(Com::tOkSpace, actLineNumber);
#else
Com::printFLN(Com::tOk);
#endif
wasLastCommandReceivedAsBinary = sendAsBinary;
waitingForResend = -1; // everything is ok.
}
void GCode::pushCommand()
{
#if !ECHO_ON_EXECUTE
commandsBuffered[bufferWriteIndex].echoCommand();
#endif
if(++bufferWriteIndex >= GCODE_BUFFER_SIZE) bufferWriteIndex = 0;
bufferLength++;
}
/**
Get the next buffered command. Returns 0 if no more commands are buffered. For each
returned command, the gcode_command_finished() function must be called.
*/
GCode *GCode::peekCurrentCommand()
{
if(bufferLength == 0) return NULL; // No more data
return &commandsBuffered[bufferReadIndex];
}
/** \brief Removes the last returned command from cache. */
void GCode::popCurrentCommand()
{
if(!bufferLength) return; // Should not happen, but safety first
#if ECHO_ON_EXECUTE
echoCommand();
#endif
if(++bufferReadIndex == GCODE_BUFFER_SIZE) bufferReadIndex = 0;
bufferLength--;
}
void GCode::echoCommand()
{
if(Printer::debugEcho())
{
Com::printF(Com::tEcho);
printCommand();
}
}
void GCode::debugCommandBuffer()
{
Com::printF(PSTR("CommandBuffer"));
for(int i = 0; i < commandsReceivingWritePosition; i++)
Com::printF(Com::tColon,(int)commandReceiving[i]);
Com::println();
Com::printFLN(PSTR("Binary:"), (int)sendAsBinary);
if(!sendAsBinary)
{
Com::print((char*)commandReceiving);
Com::println();
}
}
/** \brief Execute commands in progmem stored string. Multiple commands are seperated by \n */
void GCode::executeFString(FSTRINGPARAM(cmd))
{
char buf[80];
uint8_t buflen;
char c;
GCode code;
do
{
// Wait for a free place in command buffer
// Scan next command from string
uint8_t comment = 0;
buflen = 0;
do
{
c = HAL::readFlashByte(cmd++);
if(c == 0 || c == '\n') break;
if(c == ';') comment = 1;
if(comment) continue;
buf[buflen++] = c;
}
while(buflen < 79);
if(buflen == 0) // empty line ignore
continue;
buf[buflen] = 0;
// Send command into command buffer
if(code.parseAscii((char *)buf,false) && (code.params & 518)) // Success
{
#ifdef DEBUG_PRINT
debugWaitLoop = 7;
#endif
Commands::executeGCode(&code);
Printer::defaultLoopActions();
}
}
while(c);
}
/** \brief Read from serial console or sdcard.
This function is the main function to read the commands from serial console or from sdcard.
It must be called frequently to empty the incoming buffer.
*/
void GCode::readFromSerial()
{
if(bufferLength >= GCODE_BUFFER_SIZE) return; // all buffers full
if(waitUntilAllCommandsAreParsed && bufferLength) return;
waitUntilAllCommandsAreParsed = false;
millis_t time = HAL::timeInMilliseconds();
if(!HAL::serialByteAvailable())
{
if((waitingForResend >= 0 || commandsReceivingWritePosition > 0) && time - timeOfLastDataPacket > 200)
{
requestResend(); // Something is wrong, a started line was not continued in the last second
timeOfLastDataPacket = time;
}
#ifdef WAITING_IDENTIFIER
else if(bufferLength == 0 && time - timeOfLastDataPacket > 1000) // Don't do it if buffer is not empty. It may be a slow executing command.
{
Com::printFLN(Com::tWait); // Unblock communication in case the last ok was not received correct.
timeOfLastDataPacket = time;
}
#endif
}
while(HAL::serialByteAvailable() && commandsReceivingWritePosition < MAX_CMD_SIZE) // consume data until no data or buffer full
{
timeOfLastDataPacket = time; //HAL::timeInMilliseconds();
commandReceiving[commandsReceivingWritePosition++] = HAL::serialReadByte();
// first lets detect, if we got an old type ascii command
if(commandsReceivingWritePosition == 1)
{
if(waitingForResend >= 0 && wasLastCommandReceivedAsBinary)
{
if(!commandReceiving[0])
waitingForResend--; // Skip 30 zeros to get in sync
else
waitingForResend = 30;
commandsReceivingWritePosition = 0;
continue;
}
if(!commandReceiving[0]) // Ignore zeros
{
commandsReceivingWritePosition = 0;
continue;
}
sendAsBinary = (commandReceiving[0] & 128) != 0;
}
if(sendAsBinary)
{
if(commandsReceivingWritePosition < 2 ) continue;
if(commandsReceivingWritePosition == 5 || commandsReceivingWritePosition == 4)
binaryCommandSize = computeBinarySize((char*)commandReceiving);
if(commandsReceivingWritePosition == binaryCommandSize)
{
GCode *act = &commandsBuffered[bufferWriteIndex];
if(act->parseBinary(commandReceiving, true)) // Success
act->checkAndPushCommand();
else
requestResend();
commandsReceivingWritePosition = 0;
return;
}
}
else // Ascii command
{
char ch = commandReceiving[commandsReceivingWritePosition - 1];
if(ch == 0 || ch == '\n' || ch == '\r' || (!commentDetected && ch == ':')) // complete line read
{
//Com::printF(PSTR("Parse ascii"));Com::print((char*)commandReceiving);Com::println();
commandReceiving[commandsReceivingWritePosition - 1] = 0;
commentDetected = false;
if(commandsReceivingWritePosition == 1) // empty line ignore
{
commandsReceivingWritePosition = 0;
continue;
}
GCode *act = &commandsBuffered[bufferWriteIndex];
if(act->parseAscii((char *)commandReceiving, true)) // Success
act->checkAndPushCommand();
else
requestResend();
commandsReceivingWritePosition = 0;
return;
}
else
{
if(ch == ';') commentDetected = true; // ignore new data until lineend
if(commentDetected) commandsReceivingWritePosition--;
}
}
if(commandsReceivingWritePosition == MAX_CMD_SIZE)
{
requestResend();
return;
}
}
#if SDSUPPORT
if(sd.sdmode == 0 || commandsReceivingWritePosition != 0) // not reading or incoming serial command
return;
while( sd.filesize > sd.sdpos && commandsReceivingWritePosition < MAX_CMD_SIZE) // consume data until no data or buffer full
{
timeOfLastDataPacket = HAL::timeInMilliseconds();
int n = sd.file.read();
if(n == -1)
{
Com::printFLN(Com::tSDReadError);
UI_ERROR("SD Read Error");
// Second try in case of recoverable errors
sd.file.seekSet(sd.sdpos);
n = sd.file.read();
if(n == -1)
{
Com::printErrorFLN(PSTR("SD error did not recover!"));
sd.sdmode = 0;
break;
}
UI_ERROR("SD error fixed");
}
sd.sdpos++; // = file.curPosition();
commandReceiving[commandsReceivingWritePosition++] = (uint8_t)n;
// first lets detect, if we got an old type ascii command
if(commandsReceivingWritePosition == 1)
{
sendAsBinary = (commandReceiving[0] & 128) != 0;
}
if(sendAsBinary)
{
if(commandsReceivingWritePosition < 2 ) continue;
if(commandsReceivingWritePosition == 4 || commandsReceivingWritePosition == 5)
binaryCommandSize = computeBinarySize((char*)commandReceiving);
if(commandsReceivingWritePosition == binaryCommandSize)
{
GCode *act = &commandsBuffered[bufferWriteIndex];
if(act->parseBinary(commandReceiving, false)) // Success, silently ignore illegal commands
pushCommand();
commandsReceivingWritePosition = 0;
if(sd.sdmode == 2)
sd.sdmode = 0;
return;
}
}
else
{
char ch = commandReceiving[commandsReceivingWritePosition-1];
bool returnChar = ch == '\n' || ch == '\r';
if(returnChar || sd.filesize == sd.sdpos || (!commentDetected && ch == ':') || commandsReceivingWritePosition >= (MAX_CMD_SIZE - 1) ) // complete line read
{
if(returnChar || ch == ':')
commandReceiving[commandsReceivingWritePosition - 1] = 0;
else
commandReceiving[commandsReceivingWritePosition] = 0;
commentDetected = false;
if(commandsReceivingWritePosition == 1) // empty line ignore
{
commandsReceivingWritePosition = 0;
continue;
}
GCode *act = &commandsBuffered[bufferWriteIndex];
if(act->parseAscii((char *)commandReceiving, false)) // Success
pushCommand();
commandsReceivingWritePosition = 0;
if(sd.sdmode == 2)
sd.sdmode = 0;
return;
}
else
{
if(ch == ';') commentDetected = true; // ignore new data until lineend
if(commentDetected) commandsReceivingWritePosition--;
}
}
}
sd.sdmode = 0;
Com::printFLN(Com::tDonePrinting);
commandsReceivingWritePosition = 0;
commentDetected = false;
Printer::setMenuMode(MENU_MODE_SD_PRINTING, false);
#endif
}
/**
Converts a binary uint8_tfield containing one GCode line into a GCode structure.
Returns true if checksum was correct.
*/
bool GCode::parseBinary(uint8_t *buffer,bool fromSerial)
{
internalCommand = !fromSerial;
unsigned int sum1 = 0,sum2 = 0; // for fletcher-16 checksum
// first do fletcher-16 checksum tests see
// http://en.wikipedia.org/wiki/Fletcher's_checksum
uint8_t *p = buffer;
uint8_t len = binaryCommandSize - 2;
while (len)
{
uint8_t tlen = len > 21 ? 21 : len;
len -= tlen;
do
{
sum1 += *p++;
if(sum1 >= 255) sum1 -= 255;
sum2 += sum1;
if(sum2 >= 255) sum2 -= 255;
}
while (--tlen);
}
sum1 -= *p++;
sum2 -= *p;
if(sum1 | sum2)
{
if(Printer::debugErrors())
{
Com::printErrorFLN(Com::tWrongChecksum);
}
return false;
}
p = buffer;
params = *(unsigned int *)p;
p += 2;
uint8_t textlen = 16;
if(isV2())
{
params2 = *(unsigned int *)p;
p+=2;
if(hasString())
textlen = *p++;
}
else params2 = 0;
if(params & 1)
{
actLineNumber = N = *(uint16_t *)p;
p+=2;
}
if(isV2()) // Read G,M as 16 bit value
{
if(params & 2)
{
M = *(uint16_t *)p;
p += 2;
}
if(params & 4)
{
G = *(uint16_t *)p;
p += 2;
}
}
else
{
if(params & 2)
{
M = *p++;
}
if(params & 4)
{
G = *p++;
}
}
//if(code->params & 8) {memcpy(&code->X,p,4);p+=4;}
if(params & 8)
{
X = *(float *)p;
p += 4;
}
if(params & 16)
{
Y = *(float *)p;
p += 4;
}
if(params & 32)
{
Z = *(float *)p;
p += 4;
}
if(params & 64)
{
E = *(float *)p;
p += 4;
}
if(params & 256)
{
F = *(float *)p;
p += 4;
}
if(params & 512)
{
T = *p++;
}
if(params & 1024)
{
S = *(int32_t*)p;
p += 4;
}
if(params & 2048)
{
P = *(int32_t*)p;
p += 4;
}
if(hasI())
{
I = *(float *)p;
p += 4;
}
if(hasJ())
{
J = *(float *)p;
p += 4;
}
if(hasR())
{
R = *(float *)p;
p += 4;
}
if(hasD())
{
D = *(float *)p;
p += 4;
}
if(hasC())
{
C = *(float *)p;
p += 4;
}
if(hasH())
{
H = *(float *)p;
p += 4;
}
if(hasA())
{
A = *(float *)p;
p += 4;
}
if(hasB())
{
B = *(float *)p;
p += 4;
}
if(hasK())
{
K = *(float *)p;
p += 4;
}
if(hasL())
{
L = *(float *)p;
p += 4;
}
if(hasO())
{
O = *(float *)p;
p += 4;
}
if(hasString()) // set text pointer to string
{
text = (char*)p;
text[textlen] = 0; // Terminate string overwriting checksum
waitUntilAllCommandsAreParsed=true; // Don't destroy string until executed
}
formatErrors = 0;
return true;
}
/**
Converts a ascii GCode line into a GCode structure.
*/
bool GCode::parseAscii(char *line,bool fromSerial)
{
char *pos = line;
params = 0;
params2 = 0;
internalCommand = !fromSerial;
char c;
while ( (c = *(pos++)) )
{
switch(c)
{
case 'N':
case 'n':
{
actLineNumber = parseLongValue(pos);
params |=1;
N = actLineNumber;
break;
}
case 'G':
case 'g':
{
G = parseLongValue(pos) & 0xffff;
params |= 4;
if(G > 255) params |= 4096;
break;
}
case 'M':
case 'm':
{
M = parseLongValue(pos) & 0xffff;
params |=2;
if(M > 255) params |= 4096;
// handle non standard text arguments that some M codes have
if (M == 23 || M == 28 || M == 29 || M == 30 || M == 32 || M == 117)
{
// after M command we got a filename or text
char digit;
while( (digit = *pos) )
{
if (digit < '0' || digit > '9') break;
pos++;
}
while( (digit = *pos) )
{
if (digit != ' ') break;
pos++;
// skip leading whitespaces (may be no white space)
}
text = pos;
while (*pos)
{
if((M != 117 && *pos==' ') || *pos=='*') break;
pos++; // find a space as file name end
}
*pos = 0; // truncate filename by erasing space with nul, also skips checksum
waitUntilAllCommandsAreParsed = true; // don't risk string be deleted
params |= 32768;
}
break;
}
case 'X':
case 'x':
{
X = parseFloatValue(pos);
params |= 8;
break;
}
case 'Y':
case 'y':
{
Y = parseFloatValue(pos);
params |= 16;
break;
}
case 'Z':
case 'z':
{
Z = parseFloatValue(pos);
params |= 32;
break;
}
case 'E':
case 'e':
{
E = parseFloatValue(pos);
params |= 64;
break;
}
case 'F':
case 'f':
{
F = parseFloatValue(pos);
params |= 256;
break;
}
case 'T':
case 't':
{
T = parseLongValue(pos) & 0xff;
params |= 512;
break;
}
case 'S':
case 's':
{
S = parseLongValue(pos);
params |= 1024;
break;
}
case 'P':
case 'p':
{
P = parseLongValue(pos);
params |= 2048;
break;
}
case 'I':
case 'i':
{
I = parseFloatValue(pos);
params2 |= 1;
params |= 4096; // Needs V2 for saving
break;
}
case 'J':
case 'j':
{
J = parseFloatValue(pos);
params2 |= 2;
params |= 4096; // Needs V2 for saving
break;
}
case 'R':
case 'r':
{
R = parseFloatValue(pos);
params2 |= 4;
params |= 4096; // Needs V2 for saving
break;
}
case 'D':
case 'd':
{
D = parseFloatValue(pos);
params2 |= 8;
params |= 4096; // Needs V2 for saving
break;
}
case '*' : //checksum
{
uint8_t checksum_given = parseLongValue(pos);
uint8_t checksum = 0;
while(line != (pos-1)) checksum ^= *line++;
#if FEATURE_CHECKSUM_FORCED
Printer::flag0 |= PRINTER_FLAG0_FORCE_CHECKSUM;
#endif
if(checksum != checksum_given)
{
if(Printer::debugErrors())
{
Com::printErrorFLN(Com::tWrongChecksum);
}
return false; // mismatch
}
break;
}
default:
break;
}// end switch
}// end while
if(hasFormatError() || (params & 518)==0) // Must contain G, M or T command and parameter need to have variables!
{
formatErrors++;
if(Printer::debugErrors())
Com::printErrorFLN(Com::tFormatError);
if(formatErrors<3) return false;
}
else formatErrors = 0;
return true;
}
/** \brief Print command on serial console */
void GCode::printCommand()
{
if(hasN()) {
Com::print('N');
Com::print((long)N);
Com::print(' ');
}
if(hasM())
{
Com::print('M');
Com::print((int)M);
Com::print(' ');
}
if(hasG())
{
Com::print('G');
Com::print((int)G);
Com::print(' ');
}
if(hasT())
{
Com::print('T');
Com::print((int)T);
Com::print(' ');
}
if(hasX())
{
Com::printF(Com::tX,X);
}
if(hasY())
{
Com::printF(Com::tY,Y);
}
if(hasZ())
{
Com::printF(Com::tZ,Z);
}
if(hasE())
{
Com::printF(Com::tE,E,4);
}
if(hasF())
{
Com::printF(Com::tF,F);
}
if(hasS())
{
Com::printF(Com::tS,S);
}
if(hasP())
{
Com::printF(Com::tP,P);
}
if(hasI())
{
Com::printF(Com::tI,I);
}
if(hasJ())
{
Com::printF(Com::tJ,J);
}
if(hasR())
{
Com::printF(Com::tR,R);
}
if(hasString())
{
Com::print(text);
}
Com::println();
}