/
code.py
160 lines (125 loc) · 5.66 KB
/
code.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from typing import List, Optional
from .base_command import BaseCommand
from .code_channel import CodeChannel
from .code_flags import CodeFlags
from .code_parameter import CodeParameter
from .code_type import CodeType
from .condition_type import KeywordType
from ..object_model.messages import Message
class Code(BaseCommand):
"""A parsed representation of a generic G/M/T-code"""
@classmethod
def from_json(cls, data):
"""Deserialize an instance of this class from JSON deserialized dictionary"""
data["result"] = None if data["result"] is None else list(map(Message.from_json, data["result"]))
data["parameters"] = list(map(CodeParameter.from_json, data["parameters"]))
if "channel" in data:
data["channel"] = CodeChannel(data["channel"])
return cls(**data)
def __init__(self, **kwargs):
# The connection ID this code was received from. If this is 0, the code originates from an internal DCS task
# Usually there is no need to populate this property.
# It is internally overwritten by the control server on receipt
self.sourceConnection = 0
# Result of this code. This property is only set when the code has finished its execution.
# It remains None if the code has been cancelled
self.result: Optional[List[Message]] = None
# Type of the code
self.type = CodeType.CodeNone
# Code channel to send this code to
self.channel = CodeChannel.DEFAULT_CHANNEL
# Line number of this code
self.lineNumber = None
# Number of whitespaces prefixing the command content
self.indent = 0
# Type of conditional G-code (if any)
self.keyword = KeywordType.KeywordNone
# Argument of the conditional G-code (if any)
self.keywordArgument = None
# Major code number (e.g. 28 in G28)
self.majorNumber = None
# Minor code number (e.g. 3 in G54.3)
self.minorNumber = None
# Flags of this code
self.flags = CodeFlags.CodeFlagsNone
# Comment of the G/M/T-code. May be null if no comment is present
# The parser combines different comment segments and concatenates them as a single value.
# So for example a code like 'G28 (Do homing) ; via G28'
# causes the Comment field to be filled with 'Do homing via G28'
self.comment = None
# File position of this code in bytes (optional)
self.filePosition = None
# Length of the original code in bytes (optional)
self.length = None
# List of parsed code parameters
self.parameters: List[CodeParameter] = []
super().__init__(**kwargs)
@property
def is_from_file_channel(self) -> bool:
"""Check if this code is from a file channel"""
return self.channel is CodeChannel.File or self.channel is CodeChannel.File2
def parameter(self, letter: str, default=None):
"""Retrieve the parameter whose letter equals c or generate a default parameter"""
letter = letter.upper()
param = [param for param in self.parameters if param.letter.upper() == letter]
if len(param) > 0:
return param[0]
if default is not None:
return CodeParameter.simple_param(letter, default)
return None
def get_unprecedented_string(self, quote: bool = False):
"""
Reconstruct an unprecedented string from the parameter list or
retrieve the parameter which does not have a letter assigned.
"""
str_list = []
for param in self.parameters:
if quote and param.is_string:
str_list.append(f'{param.letter}"{param.string_value}"')
else:
str_list.append(f"{param.letter}{param.string_value}")
return " ".join(str_list)
def __str__(self):
"""Convert the parsed code back to a text-based G/M/T-code"""
if self.keyword != KeywordType.KeywordNone:
if self.keywordArgument is not None:
return f"{self.keyword_to_str()} {self.keywordArgument}"
else:
return self.keyword_to_str()
if self.type == CodeType.Comment:
return f";{self.comment}"
str_list = [self.short_str()]
for param in self.parameters:
str_list.append(f" {param}")
if self.comment:
if len(str_list) > 0:
str_list.append(" ")
str_list.append(f";{self.comment}")
if self.result and len(self.result) > 0:
str_list.append(f" => {self.result}")
return "".join(str_list)
def short_str(self):
"""Convert only the command portion to a text-based G/M/T-code (e.g. G28)"""
if self.type == CodeType.Comment:
return "(comment)"
prefix = "G53 " if self.flags & CodeFlags.EnforceAbsolutePosition != 0 else ""
if self.majorNumber is not None:
if self.minorNumber is not None:
return f"{prefix}{self.type}{self.majorNumber}.{self.minorNumber}"
return f"{prefix}{self.type}{self.majorNumber}"
return f"{prefix}{self.type}"
def keyword_to_str(self):
"""Convert the keyword to a string"""
return {
KeywordType.If: "if",
KeywordType.ElseIf: "elif",
KeywordType.Else: "else",
KeywordType.While: "while",
KeywordType.Break: "break",
KeywordType.Continue: "continue",
KeywordType.Abort: "abort",
KeywordType.Var: "var",
KeywordType.Set: "set",
KeywordType.Echo: "echo",
KeywordType.Global: "global",
}.get(self.keyword)