# HG changeset patch # User Edouard Tisserant # Date 1725706257 -7200 # Node ID 76dede1e34037f250cd83899a65a81a56fe3e56f # Parent e16c8443e8770119beda56819b9447c1b9e61522# Parent 22ba103801ee8c04988c8a72da05bd30b1afbb5e merge MQTT grafted from python2 branch - untested diff -r e16c8443e877 -r 76dede1e3403 .gitignore --- a/.gitignore Thu Aug 22 12:16:45 2024 +0200 +++ b/.gitignore Sat Sep 07 12:50:57 2024 +0200 @@ -14,4 +14,6 @@ doc/locale/** C_runtime/**/*.d -C_runtime/**/*.o \ No newline at end of file +C_runtime/**/*.o +beremiz_runtime +**/tmp/** diff -r e16c8443e877 -r 76dede1e3403 C_runtime/PLCObject.cpp --- a/C_runtime/PLCObject.cpp Thu Aug 22 12:16:45 2024 +0200 +++ b/C_runtime/PLCObject.cpp Sat Sep 07 12:50:57 2024 +0200 @@ -344,6 +344,9 @@ uint32_t PLCObject::LoadPLC(void) { + + // TODO use PLCLibMutex + // Load the last transferred PLC md5 hex digest std::string md5sum; try { diff -r e16c8443e877 -r 76dede1e3403 C_runtime/PLCObject.hpp --- a/C_runtime/PLCObject.hpp Thu Aug 22 12:16:45 2024 +0200 +++ b/C_runtime/PLCObject.hpp Sat Sep 07 12:50:57 2024 +0200 @@ -71,7 +71,10 @@ uint32_t SetTraceVariablesList(const list_trace_order_1_t * orders, int32_t * debugtoken); uint32_t StartPLC(void); uint32_t StopPLC(bool * success); + + // Public interface used by runtime uint32_t AutoLoad(); + uint32_t LogMessage(uint8_t level, std::string message); private: // A map of all the blobs @@ -112,10 +115,9 @@ uint32_t BlobAsFile(const binary_t * BlobID, std::filesystem::path filename); uint32_t LoadPLC(void); uint32_t UnLoadPLC(void); - uint32_t LogMessage(uint8_t level, std::string message); uint32_t PurgePLC(void); void PurgeTraceBuffer(void); void TraceThreadProc(void); }; -#endif \ No newline at end of file +#endif diff -r e16c8443e877 -r 76dede1e3403 CodeFileTreeNode.py --- a/CodeFileTreeNode.py Thu Aug 22 12:16:45 2024 +0200 +++ b/CodeFileTreeNode.py Sat Sep 07 12:50:57 2024 +0200 @@ -107,7 +107,7 @@ filepath = self.CodeFileName() if os.path.isfile(filepath): - xmlfile = open(filepath, 'r') + xmlfile = open(filepath, 'r', encoding='utf-8', errors='backslashreplace') codefile_xml = xmlfile.read() xmlfile.close() diff -r e16c8443e877 -r 76dede1e3403 IDEFrame.py --- a/IDEFrame.py Thu Aug 22 12:16:45 2024 +0200 +++ b/IDEFrame.py Sat Sep 07 12:50:57 2024 +0200 @@ -1116,7 +1116,7 @@ printout2 = GraphicPrintout(window, page_size, margins, True) preview = wx.PrintPreview(printout, printout2, data) - if preview.Ok(): + if preview.IsOk(): preview_frame = wx.PreviewFrame(preview, self, _("Print preview"), style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) preview_frame.Initialize() @@ -2599,7 +2599,7 @@ self.PageSize = page_size if self.PageSize[0] == 0 or self.PageSize[1] == 0: self.PageSize = (1050, 1485) - self.Preview = preview + self.IsPreview = lambda *_x : preview self.Margins = margins self.FontSize = 5 self.TextMargin = 3 @@ -2620,9 +2620,9 @@ def OnBeginDocument(self, startPage, endPage): dc = self.GetDC() - if not self.Preview and isinstance(dc, wx.PostScriptDC): + if not self.IsPreview() and isinstance(dc, wx.PostScriptDC): dc.SetResolution(720) - super(GraphicPrintout, self).OnBeginDocument(startPage, endPage) + return super(GraphicPrintout, self).OnBeginDocument(startPage, endPage) def OnPrintPage(self, page): dc = self.GetDC() @@ -2630,21 +2630,21 @@ dc.Clear() dc.SetUserScale(1.0, 1.0) dc.SetDeviceOrigin(0, 0) - dc.printing = not self.Preview + dc.printing = not self.IsPreview() # Get the size of the DC in pixels ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() pw, ph = self.GetPageSizePixels() - dw, dh = dc.GetSizeTuple() + dw, dh = dc.GetSize().Get() Xscale = (dw * ppiPrinterX) / (pw * 25.4) Yscale = (dh * ppiPrinterY) / (ph * 25.4) - fontsize = self.FontSize * Yscale - - margin_left = self.Margins[0].x * Xscale - margin_top = self.Margins[0].y * Yscale - area_width = dw - self.Margins[1].x * Xscale - margin_left - area_height = dh - self.Margins[1].y * Yscale - margin_top + fontsize = round(self.FontSize * Yscale) + + margin_left = round(self.Margins[0].x * Xscale) + margin_top = round(self.Margins[0].y * Yscale) + area_width = dw - round(self.Margins[1].x * Xscale) - margin_left + area_height = dh - round(self.Margins[1].y * Yscale) - margin_top dc.SetPen(MiterPen(wx.BLACK)) dc.SetBrush(wx.TRANSPARENT_BRUSH) @@ -2667,7 +2667,7 @@ # Set the scale and origin dc.SetDeviceOrigin(-posX + margin_left, -posY + margin_top) - dc.SetClippingRegion(posX, posY, self.PageSize[0] * scale, self.PageSize[1] * scale) + dc.SetClippingRegion(posX, posY, round(self.PageSize[0] * scale), round(self.PageSize[1] * scale)) dc.SetUserScale(scale, scale) self.Viewer.DoDrawing(dc, True) diff -r e16c8443e877 -r 76dede1e3403 LocalRuntimeMixin.py --- a/LocalRuntimeMixin.py Thu Aug 22 12:16:45 2024 +0200 +++ b/LocalRuntimeMixin.py Sat Sep 07 12:50:57 2024 +0200 @@ -51,7 +51,10 @@ # shutdown local runtime self.local_runtime.kill(gently=False) # clear temp dir - shutil.rmtree(self.local_runtime_tmpdir) + try: + shutil.rmtree(self.local_runtime_tmpdir) + except: + pass self.local_runtime = None diff -r e16c8443e877 -r 76dede1e3403 PLCControler.py --- a/PLCControler.py Thu Aug 22 12:16:45 2024 +0200 +++ b/PLCControler.py Sat Sep 07 12:50:57 2024 +0200 @@ -1373,7 +1373,8 @@ return False def IsEndType(self, typename): - if typename is not None: + # Check if the type is a base type + if type(typename) == str: return not typename.startswith("ANY") return True diff -r e16c8443e877 -r 76dede1e3403 ProjectController.py --- a/ProjectController.py Thu Aug 22 12:16:45 2024 +0200 +++ b/ProjectController.py Sat Sep 07 12:50:57 2024 +0200 @@ -508,6 +508,9 @@ self._setBuildPath(BuildPath) # get confnodes bloclist (is that usefull at project creation?) self.RefreshConfNodesBlockLists() + # default scaling + for iec_lang in ["FBD", "LD", "SFC"]: + PLCControler.SetProjectProperties(self, properties={"scaling": {iec_lang: (8, 8)}}) # this will create files base XML files self.SaveProject() return None diff -r e16c8443e877 -r 76dede1e3403 bacnet/BacnetSlaveEditor.py --- a/bacnet/BacnetSlaveEditor.py Thu Aug 22 12:16:45 2024 +0200 +++ b/bacnet/BacnetSlaveEditor.py Sat Sep 07 12:50:57 2024 +0200 @@ -828,7 +828,7 @@ # use only to enable drag'n'drop # self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) self.VariablesGrid.Bind( - wx.grid.EVT_GRID_CELL_CHANGING, self.OnVariablesGridCellChange) + wx.grid.EVT_GRID_CELL_CHANGED, self.OnVariablesGridCellChange) # self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick) # self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown) self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW) diff -r e16c8443e877 -r 76dede1e3403 controls/LocationCellEditor.py --- a/controls/LocationCellEditor.py Thu Aug 22 12:16:45 2024 +0200 +++ b/controls/LocationCellEditor.py Sat Sep 07 12:50:57 2024 +0200 @@ -188,9 +188,7 @@ var_type = self.CellControl.GetVarType() if var_type is not None: self.Table.SetValueByName(row, 'Type', var_type) - else: - wx.CallAfter(self.Table.Parent.ShowErrorMessage, - _("Selected location is identical to previous one")) + self.CellControl.Disable() return changed diff -r e16c8443e877 -r 76dede1e3403 controls/SearchResultPanel.py --- a/controls/SearchResultPanel.py Thu Aug 22 12:16:45 2024 +0200 +++ b/controls/SearchResultPanel.py Sat Sep 07 12:50:57 2024 +0200 @@ -172,7 +172,7 @@ "type": ITEM_PROJECT, "data": None, "text": None, - "matches": None, + "matches": 0, } search_results_tree_children = search_results_tree_infos.setdefault("children", []) for tagname in self.ElementsOrder: @@ -291,7 +291,7 @@ start_idx = start[1] end_idx = reduce(lambda x, y: x + y, [len(x) + 1 for x in text_lines[:end[0] - start[0]]], end[1] + 1) style = wx.TextAttr(wx.BLACK, wx.Colour(206, 204, 247)) - elif infos["type"] is not None and infos["matches"] > 1: + elif infos["type"] is not None and infos["matches"] > 0: text = _("(%d matches)") % infos["matches"] start_idx, end_idx = 0, len(text) style = wx.TextAttr(wx.Colour(0, 127, 174)) diff -r e16c8443e877 -r 76dede1e3403 dialogs/ActionBlockDialog.py --- a/dialogs/ActionBlockDialog.py Thu Aug 22 12:16:45 2024 +0200 +++ b/dialogs/ActionBlockDialog.py Sat Sep 07 12:50:57 2024 +0200 @@ -148,7 +148,7 @@ self.ActionsGrid = CustomGrid(self, size=wx.Size(-1, 250), style=wx.VSCROLL) self.ActionsGrid.DisableDragGridSize() self.ActionsGrid.EnableScrolling(False, True) - self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING, + self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnActionsGridCellChange) main_sizer.Add(self.ActionsGrid, border=20, flag=wx.GROW | wx.LEFT | wx.RIGHT) diff -r e16c8443e877 -r 76dede1e3403 dialogs/FBDVariableDialog.py --- a/dialogs/FBDVariableDialog.py Thu Aug 22 12:16:45 2024 +0200 +++ b/dialogs/FBDVariableDialog.py Sat Sep 07 12:50:57 2024 +0200 @@ -155,10 +155,8 @@ # Get variable expression and select corresponding value in name list # box if it exists selected = self.Expression.GetValue() - if selected != "" and self.VariableName.FindString(selected) != wx.NOT_FOUND: - self.VariableName.SetStringSelection(selected) - else: - self.VariableName.SetSelection(wx.NOT_FOUND) + self.VariableName.SetSelection( + wx.NOT_FOUND if selected == "" else self.VariableName.FindString(selected, True)) # Disable name list box if no name present inside self.VariableName.Enable(self.VariableName.GetCount() > 0) @@ -185,10 +183,7 @@ # Set expression text control value self.Expression.ChangeValue(value) # Select corresponding text in name list box if it exists - if self.VariableName.FindString(value) != wx.NOT_FOUND: - self.VariableName.SetStringSelection(value) - else: - self.VariableName.SetSelection(wx.NOT_FOUND) + self.VariableName.SetSelection(self.VariableName.FindString(value, True)) # Parameter is variable execution order elif name == "executionOrder": @@ -265,7 +260,7 @@ """ # Select the corresponding value in name list box if it exists self.VariableName.SetSelection( - self.VariableName.FindString(self.Expression.GetValue())) + self.VariableName.FindString(self.Expression.GetValue(), True)) self.Refresh() event.Skip() diff -r e16c8443e877 -r 76dede1e3403 editors/TextViewer.py --- a/editors/TextViewer.py Thu Aug 22 12:16:45 2024 +0200 +++ b/editors/TextViewer.py Sat Sep 07 12:50:57 2024 +0200 @@ -877,13 +877,15 @@ lineText = self.Editor.GetTextRange(start_pos, end_pos).replace("\t", " ") # Code completion - if key == wx.WXK_SPACE and event.ControlDown(): + if key == wx.WXK_SPACE and event.RawControlDown(): words = lineText.split(" ") words = [word for i, word in enumerate(words) if word != '' or i == len(words) - 1] kw = [] + self.RefreshVariableTree() + if self.TextSyntax == "IL": if len(words) == 1: kw = self.Keywords @@ -898,13 +900,17 @@ kw = self.Keywords + list(self.Variables.keys()) + list(self.Functions.keys()) if len(kw) > 0: if len(words[-1]) > 0: - kw = [keyword for keyword in kw if keyword.startswith(words[-1])] + kw = [keyword for keyword in kw if keyword.startswith(words[-1].upper())] + if len(kw) > 0: kw.sort() self.Editor.AutoCompSetIgnoreCase(True) self.Editor.AutoCompShow(len(words[-1]), " ".join(kw)) key_handled = True elif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER: - if self.TextSyntax in ["ST", "ALL"]: + if self.Editor.AutoCompActive(): + self.Editor.AutoCompComplete() + key_handled = True + elif self.TextSyntax in ["ST", "ALL"]: indent = self.Editor.GetLineIndentation(line) if LineStartswith(lineText.strip(), self.BlockStartKeywords): indent = (indent // 2 + 1) * 2 diff -r e16c8443e877 -r 76dede1e3403 editors/Viewer.py --- a/editors/Viewer.py Thu Aug 22 12:16:45 2024 +0200 +++ b/editors/Viewer.py Sat Sep 07 12:50:57 2024 +0200 @@ -1542,15 +1542,18 @@ # Popup menu functions # ------------------------------------------------------------------------------- - def GetForceVariableMenuFunction(self, iec_path, element): - iec_type = self.GetDataType(iec_path) - - def ForceVariableFunction(event): - if iec_type is not None: - dialog = ForceVariableDialog(self.ParentWindow, iec_type, str(element.GetValue())) - if dialog.ShowModal() == wx.ID_OK: - self.ParentWindow.AddDebugVariable(iec_path) - self.ForceDataValue(iec_path, dialog.GetValue()) + def GetForceVariableMenuFunction(self, iec_path, iec_type, value, immediate = False): + + def ForceVariableFunction(event, value=value): + if not immediate: + # use value as default value in dialog + dialog = ForceVariableDialog(self.ParentWindow, iec_type, str(value)) + if dialog.ShowModal() != wx.ID_OK: + return + value = dialog.GetValue() + self.ParentWindow.AddDebugVariable(iec_path) + self.ForceDataValue(iec_path, value) + return ForceVariableFunction def GetReleaseVariableMenuFunction(self, iec_path): @@ -1570,13 +1573,39 @@ def PopupForceMenu(self): iec_path = self.GetElementIECPath(self.SelectedElement) + + if iec_path is None: + # GetElementIECPath() does not work for variables and coils + # In such case get the IEC path using the instance path + for ElementType in [FBD_Variable, LD_Coil]: + if isinstance(self.SelectedElement, ElementType): + instance_path = self.GetInstancePath(True) + iec_path = "%s.%s" % (instance_path, self.SelectedElement.GetName()) + menu = wx.Menu(title='') + break + if iec_path is not None: menu = wx.Menu(title='') - item = self.AppendItem(menu, - _("Force value"), - self.GetForceVariableMenuFunction( - iec_path.upper(), - self.SelectedElement)) + iec_type = self.GetDataType(iec_path) + if iec_type == "BOOL": + self.AppendItem(menu, + _("Force Toggle"), + self.GetForceVariableMenuFunction( + iec_path.upper(), iec_type, not(self.SelectedElement.GetValue()), True)) + self.AppendItem(menu, + _("Force True"), + self.GetForceVariableMenuFunction( + iec_path.upper(), iec_type, True, True)) + self.AppendItem(menu, + _("Force False"), + self.GetForceVariableMenuFunction( + iec_path.upper(), iec_type, False, True)) + else: + self.AppendItem(menu, + _("Force value"), + self.GetForceVariableMenuFunction( + iec_path.upper(), iec_type, + self.SelectedElement.GetValue())) ritem = self.AppendItem(menu, _("Release value"), @@ -1585,6 +1614,7 @@ ritem.Enable(True) else: ritem.Enable(False) + if self.Editor.HasCapture(): self.Editor.ReleaseMouse() self.Editor.PopupMenu(menu) diff -r e16c8443e877 -r 76dede1e3403 etherlab/ConfigEditor.py --- a/etherlab/ConfigEditor.py Thu Aug 22 12:16:45 2024 +0200 +++ b/etherlab/ConfigEditor.py Sat Sep 07 12:50:57 2024 +0200 @@ -672,7 +672,7 @@ self.ProcessVariablesGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL) self.ProcessVariablesGrid.SetMinSize(wx.Size(0, 150)) self.ProcessVariablesGrid.SetDropTarget(ProcessVariableDropTarget(self)) - self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING, + self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnProcessVariablesGridCellChange) self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnProcessVariablesGridCellLeftClick) @@ -697,7 +697,7 @@ self.StartupCommandsGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL) self.StartupCommandsGrid.SetDropTarget(StartupCommandDropTarget(self)) self.StartupCommandsGrid.SetMinSize(wx.Size(0, 150)) - self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING, + self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnStartupCommandsGridCellChange) self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnStartupCommandsGridEditorShow) diff -r e16c8443e877 -r 76dede1e3403 graphics/GraphicCommons.py --- a/graphics/GraphicCommons.py Thu Aug 22 12:16:45 2024 +0200 +++ b/graphics/GraphicCommons.py Sat Sep 07 12:50:57 2024 +0200 @@ -349,10 +349,10 @@ posx, posy = self.GetPosition() min_width, min_height = self.GetMinSize() if width < min_width: - self.Pos.x = max(0, self.Pos.x - (width - min_width) * x_factor) + self.Pos.x = max(0, round(self.Pos.x - (width - min_width) * x_factor)) width = min_width if height < min_height: - self.Pos.y = max(0, self.Pos.y - (height - min_height) * y_factor) + self.Pos.y = max(0, round(self.Pos.y - (height - min_height) * y_factor)) height = min_height if scaling is not None: self.Pos.x = round_scaling(self.Pos.x, scaling[0]) @@ -1026,16 +1026,16 @@ """ # Create a new connector - def __init__(self, parent, name, type, position, direction, negated=False, edge="none", onlyone=False): + def __init__(self, parent, name, Type, position, direction, negated=False, edge="none", onlyone=False): DebugDataConsumer.__init__(self) ToolTipProducer.__init__(self, parent.Parent) self.ParentBlock = parent self.Name = name - self.Type = type + self.Type = Type self.Pos = position self.Direction = direction self.Wires = [] - if self.ParentBlock.IsOfType("BOOL", type): + if self.ParentBlock.IsOfType("BOOL", Type): self.Negated = negated self.Edge = edge else: @@ -1141,8 +1141,8 @@ return self.ParentBlock.IsOfType(type, reference) or self.ParentBlock.IsOfType(reference, type) # Changes the connector name - def SetType(self, type): - self.Type = type + def SetType(self, Type): + self.Type = Type for wire, _handle in self.Wires: wire.SetValid(wire.IsConnectedCompatible()) diff -r e16c8443e877 -r 76dede1e3403 graphics/LD_Objects.py --- a/graphics/LD_Objects.py Thu Aug 22 12:16:45 2024 +0200 +++ b/graphics/LD_Objects.py Sat Sep 07 12:50:57 2024 +0200 @@ -593,11 +593,11 @@ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR)) dc.SetLogicalFunction(wx.AND) # Draw two rectangles for representing the contact - left_left = (self.Pos.x - 1) * scalex - 2 - right_left = (self.Pos.x + self.Size[0] - 2) * scalex - 2 - top = (self.Pos.y - 1) * scaley - 2 - width = 4 * scalex + 5 - height = (self.Size[1] + 3) * scaley + 5 + left_left = round((self.Pos.x - 1) * scalex) - 2 + right_left = round((self.Pos.x + self.Size[0] - 2) * scalex) - 2 + top = round((self.Pos.y - 1) * scaley) - 2 + width = round(4 * scalex + 5) + height = round((self.Size[1] + 3) * scaley) + 5 dc.DrawRectangle(left_left, top, width, height) dc.DrawRectangle(right_left, top, width, height) @@ -974,31 +974,24 @@ elif self.Type == COIL_FALLING: typetext = "N" - if getattr(dc, "printing", False) and not isinstance(dc, wx.PostScriptDC): - # Draw an clipped ellipse for representing the coil - clipping_box = dc.GetClippingBox() - dc.SetClippingRegion(self.Pos.x - 1, self.Pos.y, self.Size[0] + 2, self.Size[1] + 1) - dc.DrawEllipse(self.Pos.x, self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1, self.Size[0], int(self.Size[1] * sqrt(2)) - 1) - dc.DestroyClippingRegion() - if clipping_box != (0, 0, 0, 0): - dc.SetClippingRegion(*clipping_box) - name_size = dc.GetTextExtent(self.Name) - if typetext != "": - type_size = dc.GetTextExtent(typetext) - else: - # Draw a two ellipse arcs for representing the coil - dc.DrawEllipticArc(self.Pos.x, self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1, self.Size[0], int(self.Size[1] * sqrt(2)) - 1, 135, 225) - dc.DrawEllipticArc(self.Pos.x, self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1, self.Size[0], int(self.Size[1] * sqrt(2)) - 1, -45, 45) - # Draw a point to avoid hole in left arc - if not getattr(dc, "printing", False): - if self.Value is not None and self.Value: - dc.SetPen(MiterPen(wx.GREEN)) - else: - dc.SetPen(MiterPen(wx.BLACK)) - dc.DrawPoint(self.Pos.x + 1, self.Pos.y + self.Size[1] // 2 + 1) - name_size = self.NameSize - if typetext != "": - type_size = self.TypeSize + printing = getattr(dc, "printing", False) + # Draw a two ellipse arcs for representing the coil + pos = (self.Pos.x, + self.Pos.y - round(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1, + self.Size[0], round(self.Size[1] * sqrt(2)) - 1) + + if printing: + # workaround for printing bug with DrawEllipticArc + # add an offset to the y position proportional to the height of the ellipse + # sqrt(2) ratio obtained heuristically + pos = (pos[0], pos[1] + round(sqrt(2)*pos[3]), pos[2], pos[3]) + + dc.DrawEllipticArc(*pos, 135, 225) + dc.DrawEllipticArc(*pos, -45, 45) + + name_size = self.NameSize + if typetext != "": + type_size = self.TypeSize # Draw coil name name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) // 2, diff -r e16c8443e877 -r 76dede1e3403 plcopen/structures.py --- a/plcopen/structures.py Thu Aug 22 12:16:45 2024 +0200 +++ b/plcopen/structures.py Sat Sep 07 12:50:57 2024 +0200 @@ -340,7 +340,7 @@ ST_BLOCK_START_KEYWORDS = ["IF", "ELSIF", "ELSE", "CASE", "FOR", "WHILE", "REPEAT"] ST_BLOCK_END_KEYWORDS = ["END_IF", "END_CASE", "END_FOR", "END_WHILE", "END_REPEAT"] ST_KEYWORDS = [ - "TRUE", "FALSE", "THEN", "OF", "TO", "BY", "DO", "DO", "UNTIL", "EXIT", + "TRUE", "FALSE", "THEN", "OF", "TO", "BY", "DO", "DO", "UNTIL", "EXIT", "CONTINUE", "RETURN", "NOT", "MOD", "AND", "XOR", "OR" ] + ST_BLOCK_START_KEYWORDS + ST_BLOCK_END_KEYWORDS diff -r e16c8443e877 -r 76dede1e3403 requirements.txt --- a/requirements.txt Thu Aug 22 12:16:45 2024 +0200 +++ b/requirements.txt Sat Sep 07 12:50:57 2024 +0200 @@ -24,7 +24,7 @@ lxml==4.9.2 matplotlib==3.7.1 msgpack==1.0.5 -Nevow @ git+https://git@github.com/beremiz/nevow-py3.git@6deba7284e159af46a412699f046e060d7026cf9 +Nevow @ git+https://git@github.com/beremiz/nevow-py3.git@81c2eaeaaa20022540a98a3106f72e0199fbcc1b numpy==1.24.3 packaging==23.1 Pillow==9.5.0 diff -r e16c8443e877 -r 76dede1e3403 util/ProcessLogger.py --- a/util/ProcessLogger.py Thu Aug 22 12:16:45 2024 +0200 +++ b/util/ProcessLogger.py Sat Sep 07 12:50:57 2024 +0200 @@ -136,7 +136,7 @@ if _debug and self.logger: self.logger.write("(DEBUG) launching:\n" + self.Command_str + "\n") - self.Proc = subprocess.Popen(self.Command, encoding="utf-8", **popenargs) + self.Proc = subprocess.Popen(self.Command, encoding="utf-8", errors="backslashreplace", **popenargs) self.outt = outputThread( self.Proc,