plcopen/structures.py
author Andrey Skvortsov <andrej.skvortzov@gmail.com>
Fri, 13 Jan 2017 19:51:36 +0300
changeset 1631 940e20a8865b
parent 1571 486f94a8032c
child 1730 64d8f52bc8c8
permissions -rw-r--r--
fix issue with printing scheme (FBD, LD or SFC) with comment element on GNU/Linux

If you draw FBD scheme and place comment on it and then try to print
it, then no wires will be printed and comment box is empty (text is
missing). This happens only for wx.PrinterDC and not on wx.MemoryDC
that is used to draw print preview window in Beremiz IDE.
Looks like a bug in wxPython or wxWidgets.

There were found several workaround for this issue.
1) If some dc.DrawLines call is placed before dc.DrawPolygon, then the
problem is gone.

...
dc.DrawLines(polygon)
dc.DrawPolygon(polygon)
...

2) Reseting DC brush solves the problem as well (see this changeset).
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
# See COPYING file for copyrights details.
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import string, re
from plcopen import LoadProject
from collections import OrderedDict
from definitions import *

TypeHierarchy = dict(TypeHierarchy_list)

"""
returns true if the given data type is the same that "reference" meta-type or one of its types.
"""
def IsOfType(type, reference):
    if reference is None:
        return True
    elif type == reference:
        return True
    else:
        parent_type = TypeHierarchy[type]
        if parent_type is not None:
            return IsOfType(parent_type, reference)
    return False

"""
returns list of all types that correspont to the ANY* meta type
"""
def GetSubTypes(type):
    return [typename for typename, parenttype in TypeHierarchy.items() if not typename.startswith("ANY") and IsOfType(typename, type)]

DataTypeRange = dict(DataTypeRange_list)

"""
Ordered list of common Function Blocks defined in the IEC 61131-3
Each block have this attributes:
    - "name" : The block name
    - "type" : The block type. It can be "function", "functionBlock" or "program"
    - "extensible" : Boolean that define if the block is extensible
    - "inputs" : List of the block inputs
    - "outputs" : List of the block outputs
    - "comment" : Comment that will be displayed in the block popup
    - "generate" : Method that generator will call for generating ST block code
Inputs and outputs are a tuple of characteristics that are in order:
    - The name
    - The data type
    - The default modifier which can be "none", "negated", "rising" or "falling"
"""

StdBlckLibs = {libname : LoadProject(tc6fname)[0]
             for libname, tc6fname in StdTC6Libs}
StdBlckLst = [{"name" : libname, "list":
               [GetBlockInfos(pous) for pous in lib.getpous()]}
             for libname, lib in StdBlckLibs.iteritems()]

#-------------------------------------------------------------------------------
#                             Test identifier
#-------------------------------------------------------------------------------

IDENTIFIER_MODEL = re.compile(
    "(?:%(letter)s|_(?:%(letter)s|%(digit)s))(?:_?(?:%(letter)s|%(digit)s))*$" %
    {"letter": "[a-zA-Z]", "digit": "[0-9]"})

# Test if identifier is valid
def TestIdentifier(identifier):
     return IDENTIFIER_MODEL.match(identifier) is not None

#-------------------------------------------------------------------------------
#                        Standard functions list generation
#-------------------------------------------------------------------------------


"""
take a .csv file and translate it it a "csv_table"
"""            
def csv_file_to_table(file):
    return [ map(string.strip,line.split(';')) for line in file.xreadlines()]

"""
seek into the csv table to a section ( section_name match 1st field )
return the matching row without first field
"""
def find_section(section_name, table):
    fields = [None]
    while(fields[0] != section_name):
        fields = table.pop(0)
    return fields[1:]

"""
extract the standard functions standard parameter names and types...
return a { ParameterName: Type, ...}
"""
def get_standard_funtions_input_variables(table):
    variables = find_section("Standard_functions_variables_types", table)
    standard_funtions_input_variables = {}
    fields = [True,True]
    while(fields[1]):
        fields = table.pop(0)
        variable_from_csv = dict([(champ, val) for champ, val in zip(variables, fields[1:]) if champ!=''])
        standard_funtions_input_variables[variable_from_csv['name']] = variable_from_csv['type']
    return standard_funtions_input_variables
    
"""
translate .csv file input declaration into PLCOpenEditor interessting values
in : "(ANY_NUM, ANY_NUM)" and { ParameterName: Type, ...}
return [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")] 
"""
def csv_input_translate(str_decl, variables, base):
    decl = str_decl.replace('(','').replace(')','').replace(' ','').split(',')
    params = []
    
    len_of_not_predifined_variable = len([True for param_type in decl if param_type not in variables])
    
    for param_type in decl:
        if param_type in variables.keys():
            param_name = param_type
            param_type = variables[param_type]
        elif len_of_not_predifined_variable > 1:
            param_name = "IN%d"%base
            base += 1
        else:
            param_name = "IN"
        params.append((param_name, param_type, "none"))
    return params


"""
Returns this kind of declaration for all standard functions

            [{"name" : "Numerical", 'list': [   {   
                'baseinputnumber': 1,
                'comment': 'Addition',
                'extensible': True,
                'inputs': [   ('IN1', 'ANY_NUM', 'none'),
                              ('IN2', 'ANY_NUM', 'none')],
                'name': 'ADD',
                'outputs': [('OUT', 'ANY_NUM', 'none')],
                'type': 'function'}, ...... ] },.....]
"""
def get_standard_funtions(table):
    
    variables = get_standard_funtions_input_variables(table)
    
    fonctions = find_section("Standard_functions_type",table)

    Standard_Functions_Decl = []
    Current_section = None
    
    translate = {
            "extensible" : lambda x: {"yes":True, "no":False}[x],
            "inputs" : lambda x:csv_input_translate(x,variables,baseinputnumber),
            "outputs":lambda x:[("OUT",x,"none")]}
    
    for fields in table:
        if fields[1]:
            # If function section name given
            if fields[0]:
                words = fields[0].split('"')
                if len(words) > 1:
                    section_name = words[1]
                else:
                    section_name = fields[0]
                Current_section = {"name" : section_name, "list" : []}
                Standard_Functions_Decl.append(Current_section)
                Function_decl_list = []
            if Current_section:
                Function_decl = dict([(champ, val) for champ, val in zip(fonctions, fields[1:]) if champ])
                baseinputnumber = int(Function_decl.get("baseinputnumber",1))
                Function_decl["baseinputnumber"] = baseinputnumber
                for param, value in Function_decl.iteritems():
                    if param in translate:
                        Function_decl[param] = translate[param](value)
                Function_decl["type"] = "function"
                
                if Function_decl["name"].startswith('*') or Function_decl["name"].endswith('*') :
                    input_ovrloading_types = GetSubTypes(Function_decl["inputs"][0][1])
                    output_types = GetSubTypes(Function_decl["outputs"][0][1])
                else:
                    input_ovrloading_types = [None]
                    output_types = [None]
                
                funcdeclname_orig = Function_decl["name"]
                funcdeclname = Function_decl["name"].strip('*_')
                fdc = Function_decl["inputs"][:]
                for intype in input_ovrloading_types:
                    if intype != None:
                        Function_decl["inputs"] = []
                        for decl_tpl in fdc:
                            if IsOfType(intype, decl_tpl[1]):
                                Function_decl["inputs"] += [(decl_tpl[0], intype, decl_tpl[2])]
                            else:
                                Function_decl["inputs"] += [(decl_tpl)]
                            
                            if funcdeclname_orig.startswith('*'):
                                funcdeclin = intype + '_' + funcdeclname 
                            else:
                                funcdeclin = funcdeclname
                    else:
                        funcdeclin = funcdeclname
                        
                    for outype in output_types:
                        if outype != None:
                            decl_tpl = Function_decl["outputs"][0]
                            Function_decl["outputs"] = [ (decl_tpl[0] , outype,  decl_tpl[2])]
                            if funcdeclname_orig.endswith('*'):
                                funcdeclout =  funcdeclin + '_' + outype
                            else:
                                funcdeclout =  funcdeclin
                        else:
                            funcdeclout =  funcdeclin
                        Function_decl["name"] = funcdeclout

                        # apply filter given in "filter" column
                        filter_name = Function_decl["filter"]
                        store = True
                        for (InTypes, OutTypes) in ANY_TO_ANY_FILTERS.get(filter_name,[]):
                            outs = reduce(lambda a,b: a or b, 
                                       map(lambda testtype : IsOfType(
                                           Function_decl["outputs"][0][1],
                                           testtype), OutTypes))
                            inps = reduce(lambda a,b: a or b,
                                       map(lambda testtype : IsOfType(
                                           Function_decl["inputs"][0][1],
                                           testtype), InTypes))
                            if inps and outs and Function_decl["outputs"][0][1] != Function_decl["inputs"][0][1]:
                                store = True
                                break
                            else:
                                store = False
                        if store :
                            # create the copy of decl dict to be appended to section
                            Function_decl_copy = Function_decl.copy()
                            Current_section["list"].append(Function_decl_copy)
            else:
                raise "First function must be in a category"
    
    return Standard_Functions_Decl

StdBlckLst.extend(get_standard_funtions(csv_file_to_table(open(StdFuncsCSV))))

# Dictionary to speedup block type fetching by name
StdBlckDct = OrderedDict()

for section in StdBlckLst:
    for desc in section["list"]:
        words = desc["comment"].split('"')
        if len(words) > 1:
            desc["comment"] = words[1]
        desc["usage"] = ("\n (%s) => (%s)" % 
            (", ".join(["%s:%s" % (input[1], input[0]) 
                        for input in desc["inputs"]]),
             ", ".join(["%s:%s" % (output[1], output[0]) 
                        for output in desc["outputs"]])))
        BlkLst = StdBlckDct.setdefault(desc["name"],[])
        BlkLst.append((section["name"], desc))

#-------------------------------------------------------------------------------
#                            Languages Keywords
#-------------------------------------------------------------------------------

# Keywords for Pou Declaration
POU_BLOCK_START_KEYWORDS = ["FUNCTION", "FUNCTION_BLOCK", "PROGRAM"]
POU_BLOCK_END_KEYWORDS = ["END_FUNCTION", "END_FUNCTION_BLOCK", "END_PROGRAM"]
POU_KEYWORDS = ["EN", "ENO", "F_EDGE", "R_EDGE"] + POU_BLOCK_START_KEYWORDS + POU_BLOCK_END_KEYWORDS
for category in StdBlckLst:
    for block in category["list"]:
        if block["name"] not in POU_KEYWORDS:
            POU_KEYWORDS.append(block["name"])


# Keywords for Type Declaration
TYPE_BLOCK_START_KEYWORDS = ["TYPE", "STRUCT"]
TYPE_BLOCK_END_KEYWORDS = ["END_TYPE", "END_STRUCT"]
TYPE_KEYWORDS = ["ARRAY", "OF", "T", "D", "TIME_OF_DAY", "DATE_AND_TIME"] + TYPE_BLOCK_START_KEYWORDS + TYPE_BLOCK_END_KEYWORDS
TYPE_KEYWORDS.extend([keyword for keyword in TypeHierarchy.keys() if keyword not in TYPE_KEYWORDS])


# Keywords for Variable Declaration
VAR_BLOCK_START_KEYWORDS = ["VAR", "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR_EXTERNAL"]
VAR_BLOCK_END_KEYWORDS = ["END_VAR"]
VAR_KEYWORDS = ["AT", "CONSTANT", "RETAIN", "NON_RETAIN"] + VAR_BLOCK_START_KEYWORDS + VAR_BLOCK_END_KEYWORDS


# Keywords for Configuration Declaration
CONFIG_BLOCK_START_KEYWORDS = ["CONFIGURATION", "RESOURCE", "VAR_ACCESS", "VAR_CONFIG", "VAR_GLOBAL"]
CONFIG_BLOCK_END_KEYWORDS = ["END_CONFIGURATION", "END_RESOURCE", "END_VAR"]
CONFIG_KEYWORDS = ["ON", "PROGRAM", "WITH", "READ_ONLY", "READ_WRITE", "TASK"] + CONFIG_BLOCK_START_KEYWORDS + CONFIG_BLOCK_END_KEYWORDS

# Keywords for Structured Function Chart
SFC_BLOCK_START_KEYWORDS = ["ACTION", "INITIAL_STEP", "STEP", "TRANSITION"]
SFC_BLOCK_END_KEYWORDS = ["END_ACTION", "END_STEP", "END_TRANSITION"]
SFC_KEYWORDS = ["FROM", "TO"] + SFC_BLOCK_START_KEYWORDS + SFC_BLOCK_END_KEYWORDS


# Keywords for Instruction List
IL_KEYWORDS = ["TRUE", "FALSE", "LD", "LDN", "ST", "STN", "S", "R", "AND", "ANDN", "OR", "ORN",
 "XOR", "XORN", "NOT", "ADD", "SUB", "MUL", "DIV", "MOD", "GT", "GE", "EQ", "NE",
 "LE", "LT", "JMP", "JMPC", "JMPCN", "CAL", "CALC", "CALCN", "RET", "RETC", "RETCN"]


# Keywords for Structured Text
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", 
 "RETURN", "NOT", "MOD", "AND", "XOR", "OR"] + ST_BLOCK_START_KEYWORDS + ST_BLOCK_END_KEYWORDS

# All the keywords of IEC
IEC_BLOCK_START_KEYWORDS = []
IEC_BLOCK_END_KEYWORDS = []
IEC_KEYWORDS = ["E", "TRUE", "FALSE"]
for all_keywords, keywords_list in [(IEC_BLOCK_START_KEYWORDS, [POU_BLOCK_START_KEYWORDS, TYPE_BLOCK_START_KEYWORDS,
                                                                VAR_BLOCK_START_KEYWORDS, CONFIG_BLOCK_START_KEYWORDS,
                                                                SFC_BLOCK_START_KEYWORDS, ST_BLOCK_START_KEYWORDS]),
                                    (IEC_BLOCK_END_KEYWORDS, [POU_BLOCK_END_KEYWORDS, TYPE_BLOCK_END_KEYWORDS,
                                                              VAR_BLOCK_END_KEYWORDS, CONFIG_BLOCK_END_KEYWORDS,
                                                              SFC_BLOCK_END_KEYWORDS, ST_BLOCK_END_KEYWORDS]),
                                    (IEC_KEYWORDS, [POU_KEYWORDS, TYPE_KEYWORDS, VAR_KEYWORDS, CONFIG_KEYWORDS,
                                                    SFC_KEYWORDS, IL_KEYWORDS, ST_KEYWORDS])]:
    for keywords in keywords_list:
        all_keywords.extend([keyword for keyword in keywords if keyword not in all_keywords])