PK+u7Hrr pylux/cli.py# cli.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # # Pylux 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. # # Pylux 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, see . import logging from importlib import import_module def get_context(context_name): module_name = 'pylux.context.'+context_name try: context_module = import_module(module_name) except ImportError: print('Error: Context does not exist') else: context_class = context_module.get_context() return context_class def main(): context = get_context(CONFIG['cli']['default-context']) globals_dict = { 'PLOT_FILE': PLOT_FILE, 'CONFIG': CONFIG, 'LOG_LEVEL': LOG_LEVEL} context.set_globals(globals_dict) logging.basicConfig(level=LOG_LEVEL) print('Welcome to Pylux! Type \'h\' to view a list of commands.') while True: user_input = input('(pylux:'+context.name+') ') inputs = user_input.split(' ') if inputs[0] == '::': globals_dict = context.get_globals() context = get_context('editor') context.set_globals(globals_dict) elif inputs[0][0] == ':': globals_dict = context.get_globals() context = get_context(inputs[0].split(':')[1]) context.set_globals(globals_dict) elif inputs[0] in context.commands: context.process(inputs) else: print('Error: Command doesn\'t exist.') print('Type \'h\' for a list of available commands.') if __name__ == 'pylux_root': main() PKS7H/IIpylux/settings.conf[cli] # full: (pylux), minimal: > prompt = full default-context = editor PK`7H=hpylux/clihelper.py# clihelper.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . class Interface: """Manage the CLI interactivity. Manage the interactive CLI lists, whereby a unique key which is presented to the user on the CLI returns an object, without the user having to specify the object itself. Attributes: option_list: a dictionary of the options presented to the user on the CLI. """ def __init__(self): """Create a dictionary for the options. Create a dictionary ready to populate with options, and add an entry for the special 'this' with the value None. """ self.option_list = {'this': None} def append(self, ref, object): """Add an object to the option list. Args: ref: the unique CLI identifier of the option being added. object: the object that should be returned if the user selects this option. """ self.option_list[ref] = object def get(self, refs): """Return the object of a user selection. Args: ref: the unique CLI identifier that the user selected. Returns: A list of objects that correspond to the references that were given. """ objects= [] if refs == 'all': for ref in self.option_list: if ref != 'this': objects.append(self.option_list[ref]) else: if refs == 'this': refs = self.option_list['this'] references = resolve_references(refs) for ref in references: objects.append(self.option_list[ref]) return objects def clear(self): """Clear the option list.""" self.option_list.clear() self.option_list['this'] = None def update_this(self, reference): """Update the 'this' special reference. Set the 'this' special reference to a specified reference. If the given reference is also 'this', do nothing as 'this' will already point to the desired reference. Args: reference: the reference that 'this' should point to. """ if reference != 'this': self.option_list['this'] = reference def resolve_references(user_input): """Parse the reference input. From a user input string of references, generate a list of integers that can then be passed to the Interface class to return objects. Parse comma separated values such as a,b,c and colon separated ranges such as a:b, or a combination of the two such as a,b:c,d:e,f. Args: user_input: the input string that the user entered. Returns: A list containing a list of integers. """ reference_list = [] all_input = user_input.split(',') for input_item in all_input: if ':' in input_item: limits = input_item.split(':') i = int(limits[0]) while i <= int(limits[1]): reference_list.append(i) i = i+1 else: reference_list.append(int(input_item)) reference_list.sort() return reference_list def resolve_input(inputs_list, number_args): """Parse user input that contains a multi-word argument. From a list of user arguments which have already been split, return a new list containing a set number of arguments, where the last argument is a multi-word argument is a multi-word argument. Args: inputs_list: a list containing strings which have been split from the user input using split(' '). number_args: the number of arguments the input should contain, excluding the action itself. For example, the add metadata action takes two arguments: the tag and value. Returns: A list containing a list of the arguments, where the last argument is a concatenation of any arguments that were left after processing the rest of the inputs list. For example, the metadata example above would return ['ma', 'tag', 'value which can be many words long']. """ i = 1 args_list = [] multiword_input = "" while i < number_args: args_list.append(inputs_list[i]) i = i+1 while number_args <= i <= len(inputs_list)-1: if multiword_input == "": multiword_input = multiword_input+inputs_list[i] else: multiword_input = multiword_input+' '+inputs_list[i] i = i+1 args_list.append(multiword_input) return args_list PKT7H_eb||pylux/context.py# context.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . from importlib import import_module class Context: """A context defines a set of commands that the user can access.""" def __init__(self, name): self.name = name def process_input(inputs): def init_globals(globals_dict): self.plot_file = globals_dict['PLOT_FILE'] self.config = globals_dict['CONFIG'] self.log_level = globals_dict['LOG_LEVEL'] PK1Hz{zzpylux/reference.py# reference.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . gel_colours = { # Rosco E-Colour+ 'Rose Pink' : '#FF40B9', 'R002' : '#FF40B9', 'Lavender Tint' : '#F5E6FF', 'R003' : '#F5E6FF', 'Medium Bastard Amber' : '#FAB8AC', 'R004' : '#FAB8AC', 'Pale Yellow' : '#FFFFDE', 'R007' : '#FFFFDE', 'Dark Salmon' : '#FF5E48', 'R008' : '#FF5E48', 'Pale Amber Gold' : '#FFD28A', 'R009' : '#FFD28A', 'Medium Yellow' : '#FFF30D', 'R010' : '#FFF30D', 'Straw Tint' : '#FFDBA1', 'R013' : '#FFDBA1', 'Deep Straw' : '#FDC819', 'R015' : '#FDC819', 'Surprise Peach' : '#CC5F3D', 'R017' : '#CC5F3D', 'Fire' : '#ED2000', 'R019' : '#ED2000', 'Medium Amber' : '#FF8A24', 'R020' : '#FF8A24', 'Gold Amber' : '#FF4800', 'R021' : '#FF4800', 'Dark Amber' : '#FF1900', 'R022' : '#FF1900', 'Scarlet' : '#F00E25', 'R024' : '#F00E25', 'Sunset Red' : '#FF4B2B', 'R025' : '#FF4B2B', 'Bright Red' : '#C70011', 'R026' : '#C70011', 'Medium Red' : '#A10000', 'R027' : '#A10000', 'Plasa Red' : '#BC0010', 'R029' : '#BC0010', 'Light Pink' : '#FFB8CE', 'R035' : '#FFB8CE', 'Medium Pink' : '#FF6E9E', 'R036' : '#FF6E9E', 'Pink Carnation' : '#FAC0D8', 'R039' : '#FAC0D8', 'Dark Magenta' : '#C5004F', 'R046' : '#C5004F', 'Rose Purple' : '#C43BFF', 'R048' : '#C43BFF', 'Medium Purple' : '#BE00D4', 'R049' : '#BE00D4', 'Light Lavender' : '#D7BAFF', 'R052' : '#D7BAFF', 'Paler Lavender' : '#E5DEFF', 'R053' : '#E5DEFF', 'Lavender' : '#9235FD', 'R058' : '#9235FD', 'Mist Blue' : '#D6E8FF', 'R061' : '#D6E8FF', 'Pale Blue' : '#B0D5F7', 'R063' : '#B0D5F7', 'Sky Blue' : '#597DFF', 'R068' : '#597DFF', 'Tokyo Blue' : '#3600B1', 'R071' : '#3600B1', 'Evening Blue' : '#4C79FF', 'R075' : '#4C79FF', 'Just Blue' : '#3700EB', 'R079' : '#3700EB', 'Deeper Blue' : '#1A00BF', 'R085' : '#1A00BF', 'Lime Green' : '#BEFF85', 'R088' : '#BEFF85', 'Moss Green' : '#00CD55', 'R089' : '#00CD55', 'Dark Yellow Green' : '#00870B', 'R090' : '#00870B', 'Spring Yellow' : '#F2FF30', 'R100' : '#F2FF30', 'Yellow' : '#FFEB0F', 'R101' : '#FFEB0F', 'Light Amber' : '#FFE74A', 'R102' : '#FFE74A', 'Straw' : '#FFE7C4', 'R103' : '#FFE7C4', 'Deep Amber' : '#FCD628', 'R104' : '#FCD628', 'Orange' : '#FF760D', 'R105' : '#FF760D', 'Primary Red' : '#DE0000', 'R106' : '#DE0000', 'Light Rose' : '#FF809F', 'R107' : '#FF809F', 'English Rose' : '#FAAD96', 'R108' : '#FAAD96', 'Light Salmon' : '#FF919C', 'R109' : '#FF919C', 'Middle Rose' : '#FFA3CA', 'R110' : '#FFA3CA', 'Dark Pink' : '#FF63A4', 'R111' : '#FF63A4', 'Magenta' : '#FF004D', 'R113' : '#FF004D', 'Peacock Blue' : '#00C9BF', 'R115' : '#00C9BF', 'Medium Blue Green' : '#009E96', 'R116' : '#009E96', 'Steel Blue' : '#A3E2FF', 'R117' : '#A3E2FF', 'Light Blue' : '#00B7FF', 'R118' : '#00B7FF', 'Dark Blue' : '#3300D9', 'R119' : '#3300D9', 'Deep Blue' : '#2800C9', 'R120' : '#2800C9', 'Leaf Green' : '#93FF54', 'R121' : '#93FF54', 'Fern Green' : '#74F55D', 'R122' : '#74F55D', 'Dark Green' : '#00AB44', 'R124' : '#00AB44', 'Mauve' : '#D400DB', 'R126' : '#D400DB', 'Smokey Pink' : '#BB334C', 'R127' : '#BB334C', 'Bright Pink' : '#FF177F', 'R128' : '#FF177F', 'Heavy Frost' : '#FFFFFF', 'R129' : '#FFFFFF', 'Clear' : '#FFFFFF', 'R130' : '#FFFFFF', 'Marine Blue' : '#02E3CC', 'R131' : '#02E3CC', 'Medium Blue' : '#5286FF', 'R132' : '#5286FF', 'Golden Amber' : '#F5632F', 'R134' : '#F5632F', 'Deep Golden Amber' : '#FF4A00', 'R135' : '#FF4A00', 'Pale Lavender' : '#E2C7FF', 'R136' : '#E2C7FF', 'Special Lavender' : '#B695FC', 'R137' : '#B695FC', 'Pale Green' : '#B4FFA8', 'R138' : '#B4FFA8', 'Primary Green' : '#009107', 'R139' : '#009107', 'Summer Blue' : '#38CAFF', 'R140' : '#38CAFF', 'Bright Blue' : '#00ACF0', 'R141' : '#00ACF0', 'Pale Violet' : '#AA96FF', 'R142' : '#AA96FF', 'Pale Navy Blue' : '#007194', 'R143' : '#007194', 'No Color Blue' : '#4FC4FF', 'R144' : '#4FC4FF', 'Apricot' : '#FF7438', 'R147' : '#FF7438', 'Bright Rose' : '#FF1472', 'R148' : '#FF1472', 'Gold Tint' : '#FFC0B5', 'R151' : '#FFC0B5', 'Pale Gold' : '#FFCAA8', 'R152' : '#FFCAA8', 'Pale Salmon' : '#FFB2BA', 'R153' : '#FFB2BA', 'Pale Rose' : '#FFB2BA', 'R154' : '#FFB2BA', 'Chocolate' : '#C57951', 'R156' : '#C57951', 'Pink' : '#FF4551', 'R157' : '#FF4551', 'Deep Orange' : '#FF5E00', 'R158' : '#FF5E00', 'No Color Straw' : '#FFFAE0', 'R159' : '#FFFAE0', 'Slate Blue' : '#4AABFF', 'R161' : '#4AABFF', 'Bastard Amber' : '#FFCFA8', 'R162' : '#FFCFA8', 'Flame Red' : '#F02225', 'R164' : '#F02225', 'Daylight Blue' : '#1CACFF', 'R165' : '#1CACFF', 'Pale Red' : '#FF3352', 'R166' : '#FF3352', 'Lilac Tint' : '#EBDAF5', 'R169' : '#EBDAF5', 'Deep Lavender' : '#DAADFF', 'R170' : '#DAADFF', 'Lagoon Blue' : '#00AACC', 'R172' : '#00AACC', 'Dark Steel Blue' : '#52B4FF', 'R174' : '#52B4FF', 'Loving Amber' : '#FAA498', 'R176' : '#FAA498', 'Chrome Orange' : '#FF9900', 'R179' : '#FF9900', 'Dark Lavender' : '#8B2BFF', 'R180' : '#8B2BFF', 'Congo Blue' : '#29007A', 'R181' : '#29007A', 'Light Red' : '#CC0000', 'R182' : '#CC0000', 'Moonlight Blue' : '#00BAF2', 'R183' : '#00BAF2', 'Cosmetic Peach' : '#FFFFFF', 'R184' : '#FFFFFF', 'Cosmetic Burgundy' : '#FFFFFF', 'R185' : '#FFFFFF', 'Cosmetic Silver Rose' : '#FFFFFF', 'R186' : '#FFFFFF', 'Cosmetic Rouge' : '#FFFFFF', 'R187' : '#FFFFFF', 'Cosmetic Highlight' : '#FFFFFF', 'R188' : '#FFFFFF', 'Cosmetic Silver Moss' : '#FFFFFF', 'R189' : '#FFFFFF', 'Cosmetic Emerald' : '#FFFFFF', 'R190' : '#FFFFFF', 'Cosmetic Aqua Blue' : '#FFFFFF', 'R191' : '#FFFFFF', 'Flesh Pink' : '#FF639F', 'R192' : '#FF639F', 'Rosy Amber' : '#FF454B', 'R193' : '#FF454B', 'Surprise Pink' : '#AC82FF', 'R194' : '#AC82FF', 'Zenith Blue' : '#0003CC', 'R195' : '#0003CC', 'True Blue' : '#00A1FF', 'R196' : '#00A1FF', 'Alice Blue' : '#1958CF', 'R197' : '#1958CF', 'Palace Blue' : '#43009C', 'R198' : '#43009C', 'Regal Blue' : '#3700EE', 'R199' : '#3700EE', 'Double CT Blue' : '#0F5BFF', 'R200' : '#0F5BFF', 'Full CT Blue' : '#73A9FF', 'R201' : '#73A9FF', '1/2 CT Blue' : '#B8D5FF', 'R202' : '#B8D5FF', '1/4 CT Blue' : '#E0EDFF', 'R203' : '#E0EDFF', 'Full CT Orange' : '#FF9B30', 'R204' : '#FF9B30', '1/2 CT Orange' : '#FFD28F', 'R205' : '#FFD28F', '1/4 CT Orange' : '#FFE6B8', 'R206' : '#FFE6B8', 'CT Orange + .3 Neutral Density' : '#A86300', 'R207' : '#A86300', 'CT Orange + .6 Neutral Density' : '#974400', 'R208' : '#974400', '.3 Neutral Density' : '#BFBDBD', 'R209' : '#BFBDBD', '.6 Neutral Density' : '#969595', 'R210' : '#969595', '.9 Neutral Density' : '#636262', 'R211' : '#636262', 'LCT Yellow' : '#FBFFD9', 'R212' : '#FBFFD9', 'White Flame Green' : '#E0FCB3', 'R213' : '#E0FCB3', 'Full Tough Spun' : '#FFFFFF', 'R214' : '#FFFFFF', 'Half Tough Spun' : '#FFFFFF', 'R215' : '#FFFFFF', 'White Diffusion' : '#FFFFFF', 'R216' : '#FFFFFF', 'Blue Diffusion' : '#FFFFFF', 'R217' : '#FFFFFF', 'Eighth CT Blue ' : '#EBF3FF', 'R218' : '#EBF3FF', 'Fluorescent Green ' : '#2EE8CF', 'R219' : '#2EE8CF', 'White Frost' : '#FFFFFF', 'R220' : '#FFFFFF', 'Blue Frost' : '#FFFFFF', 'R221' : '#FFFFFF', '1/8 CT Orange' : '#FFEAD1', 'R223' : '#FFEAD1', 'Daylight Blue Frost' : '#FFFFFF', 'R224' : '#FFFFFF', 'Neutral Density Frost' : '#FFFFFF', 'R225' : '#FFFFFF', 'U.V. Filter' : '#FFFFFF', 'R226' : '#FFFFFF', 'Brushed Silk' : '#FFFFFF', 'R228' : '#FFFFFF', 'Quarter Tough Spun' : '#FFFFFF', 'R229' : '#FFFFFF', 'Super Correction WF Green' : '#AD6824', 'R232' : '#AD6824', 'HMI To Tungsten' : '#FF8438', 'R236' : '#FF8438', 'C.I.D. to Tungsten' : '#F08F56', 'R237' : '#F08F56', 'C.S.I. to Tungsten' : '#E5B1A0', 'R238' : '#E5B1A0', 'Polarizer' : '#FFFFFF', 'R239' : '#FFFFFF', 'Fluorescent 5700K' : '#1AD8D8', 'R241' : '#1AD8D8', 'Fluorescent 4300K' : '#5AE2C7', 'R242' : '#5AE2C7', 'Fluorescent 3600K' : '#87E5B6', 'R243' : '#87E5B6', 'Plus Green' : '#E0FC90', 'R244' : '#E0FC90', 'Half Plus Green' : '#EAFCB8', 'R245' : '#EAFCB8', 'Quarter Plus Green' : '#F0FCD2', 'R246' : '#F0FCD2', 'Minus Green' : '#FFB8D0', 'R247' : '#FFB8D0', 'Half Minus Green' : '#FACDE0', 'R248' : '#FACDE0', 'Quarter Minus Green' : '#FADEE8', 'R249' : '#FADEE8', 'Half White Diffusion' : '#FFFFFF', 'R250' : '#FFFFFF', 'Quarter White Diffusion' : '#FFFFFF', 'R251' : '#FFFFFF', 'Eighth White Diffusion' : '#FFFFFF', 'R252' : '#FFFFFF', 'Hanover Frost' : '#FFFFFF', 'R253' : '#FFFFFF', 'HT New Hanover Frost' : '#FFFFFF', 'R254' : '#FFFFFF', 'Haarlem Frost' : '#FFFFFF', 'R255' : '#FFFFFF', 'Half Hanover Frost' : '#FFFFFF', 'R256' : '#FFFFFF', 'Quarter Hanover Frost' : '#FFFFFF', 'R257' : '#FFFFFF', 'Eighth Hanover Frost' : '#FFFFFF', 'R258' : '#FFFFFF', 'Heat Shield' : '#FFFFFF', 'R269' : '#FFFFFF', 'Scrim' : '#FFFFFF', 'R270' : '#FFFFFF', 'Mirror Silver' : '#FFFFFF', 'R271' : '#FFFFFF', 'Soft Gold Reflector' : '#FFFFFF', 'R272' : '#FFFFFF', 'Soft Silver Reflector' : '#FFFFFF', 'R273' : '#FFFFFF', 'Mirror Gold' : '#FFFFFF', 'R274' : '#FFFFFF', 'Black Scrim' : '#FFFFFF', 'R275' : '#FFFFFF', 'Eighth Plus Green' : '#F6FFE0', 'R278' : '#F6FFE0', 'Eighth Minus Green' : '#FCE8F3', 'R279' : '#FCE8F3', 'Three Quarter CT Blue' : '#9CC5FF', 'R281' : '#9CC5FF', '1.5 CT Blue' : '#759EE5', 'R283' : '#759EE5', '3/4 CT Orange' : '#F7AF5C', 'R285' : '#F7AF5C', '1.5 CT Orange' : '#F8963E', 'R286' : '#F8963E', 'Double CT Orange' : '#F77F1E', 'R287' : '#F77F1E', '.15 Neutral Density' : '#DCD9D9', 'R298' : '#DCD9D9', '1.2 Neutral Density' : '#474747', 'R299' : '#474747', 'Soft Green' : '#02E59A', 'R322' : '#02E59A', 'Jade' : '#02E2A3', 'R323' : '#02E2A3', 'Mallard Green' : '#005C46', 'R325' : '#005C46', 'Forest Green' : '#006539', 'R327' : '#006539', 'Follies Pink' : '#FF33A0', 'R328' : '#FF33A0', 'Special Rose Pink' : '#FF0D6A', 'R332' : '#FF0D6A', 'Plum' : '#CD9BD1', 'R341' : '#CD9BD1', 'Special Medium Lavender' : '#7345FF', 'R343' : '#7345FF', 'Violet' : '#A98AFF', 'R344' : '#A98AFF', 'Fuschia Pink' : '#C953DB', 'R345' : '#C953DB', 'Glacier Blue' : '#00A6FF', 'R352' : '#00A6FF', 'Lighter Blue' : '#54D5FF', 'R353' : '#54D5FF', 'Special Steel Blue ' : '#00BFD8', 'R354' : '#00BFD8', 'Special Medium Blue' : '#0236DF', 'R363' : '#0236DF', 'Cornflower' : '#5783CF', 'R366' : '#5783CF', 'Rolux' : '#FFFFFF', 'R400' : '#FFFFFF', 'Light Rolux' : '#FFFFFF', 'R401' : '#FFFFFF', 'Soft Frost' : '#FFFFFF', 'R402' : '#FFFFFF', 'Half Soft Frost' : '#FFFFFF', 'R404' : '#FFFFFF', 'Opal Frost' : '#FFFFFF', 'R410' : '#FFFFFF', 'Highlight' : '#FFFFFF', 'R414' : '#FFFFFF', 'Three Quarter White' : '#FFFFFF', 'R416' : '#FFFFFF', 'Light Opal Frost' : '#FFFFFF', 'R420' : '#FFFFFF', 'Quiet Frost' : '#FFFFFF', 'R429' : '#FFFFFF', 'Grid Cloth' : '#FFFFFF', 'R430' : '#FFFFFF', 'Light Grid Cloth' : '#FFFFFF', 'R432' : '#FFFFFF', 'Quarter Grid Cloth' : '#FFFFFF', 'R434' : '#FFFFFF', 'Full CT Straw' : '#F7BF4F', 'R441' : '#F7BF4F', 'Half CT Straw' : '#FFCE9C', 'R442' : '#FFCE9C', 'Quarter CT Straw' : '#FFE3BA', 'R443' : '#FFE3BA', 'Eighth CT Straw' : '#FFF5DC', 'R444' : '#FFF5DC', 'Three Eighths White' : '#FFFFFF', 'R450' : '#FFFFFF', 'One Sixteenth White' : '#FFFFFF', 'R452' : '#FFFFFF', 'Quiet Grid Cloth' : '#FFFFFF', 'R460' : '#FFFFFF', 'Quiet Light Grid Cloth' : '#FFFFFF', 'R462' : '#FFFFFF', 'Quiet Quarter Grid Cloth' : '#FFFFFF', 'R464' : '#FFFFFF', 'Full Atlantic Frost' : '#FFFFFF', 'R480' : '#FFFFFF', 'Half Atlantic Frost' : '#FFFFFF', 'R481' : '#FFFFFF', 'Quarter Atlantic Frost' : '#FFFFFF', 'R482' : '#FFFFFF', 'Double New Colour Blue' : '#6977FF', 'R500' : '#6977FF', 'New Colour Blue (Robertson Blue)' : '#BFC7FB', 'R501' : '#BFC7FB', 'Half New Colour Blue' : '#D9E3FF', 'R502' : '#D9E3FF', 'Quarter New Colour Blue' : '#F0F5FF', 'R503' : '#F0F5FF', 'Waterfront Green' : '#B3DCE3', 'R504' : '#B3DCE3', 'Sally Green' : '#BFFF59', 'R505' : '#BFFF59', 'Marlene' : '#F7C9A3', 'R506' : '#F7C9A3', 'Madge' : '#E93511', 'R507' : '#E93511', 'Midnight Maya' : '#1602AA', 'R508' : '#1602AA', 'Argent Blue' : '#2261D6', 'R525' : '#2261D6', 'Gold Medal' : '#F5AE3F', 'R550' : '#F5AE3F', 'Full CT Eight Five' : '#FFC470', 'R604' : '#FFC470', 'Half Mustard Yellow' : '#DFAB00', 'R642' : '#DFAB00', 'Quarter Mustard Yellow' : '#FDC200', 'R643' : '#FDC200', 'Industry Sodium' : '#D9CE73', 'R650' : '#D9CE73', 'HI Sodium' : '#FFB95C', 'R651' : '#FFB95C', 'Urban Sodium' : '#FF752B', 'R652' : '#FF752B', 'LO Sodium' : '#5E2A02', 'R653' : '#5E2A02', 'Perfect Lavender' : '#7500EB', 'R700' : '#7500EB', 'Provence' : '#9A3BFF', 'R701' : '#9A3BFF', 'Special Pale Lavender' : '#DACCFF', 'R702' : '#DACCFF', 'Cold Lavender' : '#C587FF', 'R703' : '#C587FF', 'Lily' : '#E2BAFF', 'R704' : '#E2BAFF', 'Lily Frost' : '#D59EFF', 'R705' : '#D59EFF', 'King Fals Lavender' : '#6600FF', 'R706' : '#6600FF', 'Ultimate Violet' : '#7500F2', 'R707' : '#7500F2', 'Cool Lavender' : '#BFC8FF', 'R708' : '#BFC8FF', 'Electric Lilac' : '#7394FF', 'R709' : '#7394FF', 'Spir Special Blue' : '#554CFF', 'R710' : '#554CFF', 'Cold Blue' : '#224ED4', 'R711' : '#224ED4', 'Bedford Blue' : '#3853FF', 'R712' : '#3853FF', 'Winter Blue' : '#1F009A', 'R713' : '#1F009A', 'Elysian Blue' : '#0F17FF', 'R714' : '#0F17FF', 'Cabanna Blue' : '#072EDE', 'R715' : '#072EDE', 'Mikkel Blue' : '#2600BF', 'R716' : '#2600BF', 'Shanklin Frost' : '#FFFFFF', 'R717' : '#FFFFFF', '1/2 Shanklin Frost' : '#FFFFFF', 'R718' : '#FFFFFF', 'Colour Wash Blue' : '#2265F5', 'R719' : '#2265F5', 'Daylight Frost' : '#FFFFFF', 'R720' : '#FFFFFF', 'Berry Blue' : '#0036E8', 'R721' : '#0036E8', 'Bray Blue' : '#0024C2', 'R722' : '#0024C2', 'Virgin Blue' : '#0031F7', 'R723' : '#0031F7', 'Ocean Blue' : '#2BC7FF', 'R724' : '#2BC7FF', 'Old Steel Blue' : '#8CDFFF', 'R725' : '#8CDFFF', 'QFD Blue' : '#007385', 'R727' : '#007385', 'Steel Green' : '#95DEDA', 'R728' : '#95DEDA', 'Scuba Blue' : '#007070', 'R729' : '#007070', 'Liberty Green' : '#A3F7DB', 'R730' : '#A3F7DB', 'Dirty Ice' : '#B4F0D2', 'R731' : '#B4F0D2', 'Damp Squib' : '#A8E5C7', 'R733' : '#A8E5C7', 'Velvet Green' : '#005C1D', 'R735' : '#005C1D', 'Twickenham Green' : '#0D5700', 'R736' : '#0D5700', 'Jas Green' : '#5FE300', 'R738' : '#5FE300', 'Aurora Borealis Green' : '#354D15', 'R740' : '#354D15', 'Mustard Yellow' : '#C5A100', 'R741' : '#C5A100', 'Bram Brown' : '#8E5324', 'R742' : '#8E5324', 'Dirty White' : '#F7C757', 'R744' : '#F7C757', 'Brown ' : '#753900', 'R746' : '#753900', 'Easy White' : '#CC8C7C', 'R747' : '#CC8C7C', 'Seedy Pink' : '#C23061', 'R748' : '#C23061', 'Hanover Rose' : '#FFBCBA', 'R749' : '#FFBCBA', 'Durham Frost' : '#FFFFFF', 'R750' : '#FFFFFF', 'Wheat' : '#FFEFBA', 'R763' : '#FFEFBA', 'Sun Colour Straw' : '#FFEC94', 'R764' : '#FFEC94', 'Sunlight Yellow' : '#FFEC6E', 'R765' : '#FFEC6E', 'Oklahoma Yellow' : '#FFDE24', 'R767' : '#FFDE24', 'Egg Yolk Yellow' : '#FCC200', 'R768' : '#FCC200', 'Burnt Yellow' : '#FF8A0D', 'R770' : '#FF8A0D', 'Cardbox Amber' : '#FFB28F', 'R773' : '#FFB28F', 'Soft Amber' : '#FFC49C', 'R774' : '#FFC49C', 'Soft Amber 2' : '#FFBA8C', 'R775' : '#FFBA8C', 'Nectarine' : '#FF8345', 'R776' : '#FF8345', 'Rust' : '#D94F18', 'R777' : '#D94F18', 'Millennium Gold' : '#FF4405', 'R778' : '#FF4405', 'Bastard Pink' : '#F56A2F', 'R779' : '#F56A2F', 'As Golden Amber' : '#FF3B05', 'R780' : '#FF3B05', 'Terry Red' : '#FF0F0D', 'R781' : '#FF0F0D', 'Marius Red' : '#91001B', 'R787' : '#91001B', 'Blood Red' : '#99000D', 'R789' : '#99000D', 'Moroccan Pink' : '#FF919C', 'R790' : '#FF919C', 'Moroccan Frost' : '#FFFFFF', 'R791' : '#FFFFFF', 'Vanity Fair' : '#FF12AC', 'R793' : '#FF12AC', 'Pretty N Pink' : '#FF82DE', 'R794' : '#FF82DE', 'Magical Magenta' : '#FF00C8', 'R795' : '#FF00C8', 'Deep Purple' : '#AD00CC', 'R797' : '#AD00CC', 'Chrysalis Pink' : '#7B0FFF', 'R798' : '#7B0FFF', 'Special K.H. Lavender' : '#120096', 'R799' : '#120096', 'Damson Violet' : '#8800C7', 'R5084' : '#8800C7', 'French Lilac' : '#6D00F2', 'R5085' : '#6D00F2', 'Max Blue' : '#B8D4FF', 'R5202' : '#B8D4FF', 'Ice Blue' : '#E8F4FF', 'R5211' : '#E8F4FF', 'Venetian Blue' : '#96C9FF', 'R5264' : '#96C9FF', 'Fuji Blue' : '#002DE3', 'R5287' : '#002DE3', 'Aztec Gold' : '#F2CF88', 'R5336' : '#F2CF88', 'Wisteria' : '#DFCFFF', 'R5404' : '#DFCFFF', 'Olympia Green' : '#009C72', 'R5454' : '#009C72', 'Tarragon' : '#7DFFB1', 'R5455' : '#7DFFB1', 'Grotto Green' : '#02BF9C', 'R5461' : '#02BF9C', 'Prussian Green' : '#00A6B5', 'R5463' : '#00A6B5', # Rosco Supergel 'Dempster Open White' : '#FFFFFF', 'S00' : '#FFFFFF', 'Light Bastard Amber' : '#FBB39A', 'S01' : '#FBB39A', 'Bastard Amber' : '#FFD1AC', 'S02' : '#FFD1AC', 'Dark Bastard Amber' : '#FBBA9A', 'S03' : '#FBBA9A', 'Warm Peach' : '#FF8A4A', 'S303' : '#FF8A4A', 'Medium Bastard Amber' : '#F9B09A', 'S04' : '#F9B09A', 'Pale Apricot' : '#FABCA9', 'S304' : '#FABCA9', 'Rose Tint' : '#FFD7D3', 'S05' : '#FFD7D3', 'Rose Gold' : '#F5BAB8', 'S305' : '#F5BAB8', 'No Color Straw' : '#FCFADB', 'S06' : '#FCFADB', 'Pale Yellow ' : '#FDFAD1', 'S07' : '#FDFAD1', 'Pale Amber Gold' : '#FFCB86', 'S09' : '#FFCB86', 'Medium Yellow' : '#FFF200', 'S10' : '#FFF200', 'Light Straw' : '#FFD21A', 'S11' : '#FFD21A', 'Canary' : '#FFEA00', 'S312' : '#FFEA00', 'Straw Tint ' : '#FFD88F', 'S13' : '#FFD88F', 'Light Relief Yellow' : '#FFE462', 'S313' : '#FFE462', 'Medium Straw' : '#FCD419', 'S14' : '#FCD419', 'Deep Straw ' : '#FECB00', 'S15' : '#FECB00', 'Apricot' : '#FF7418', 'S317' : '#FF7418', 'Mayan Sun' : '#FF6F29', 'S318' : '#FF6F29', 'Fire' : '#FF390B', 'S19' : '#FF390B', 'Medium Amber' : '#FF871C', 'S20' : '#FF871C', 'Golden Amber' : '#FF6613', 'S21' : '#FF6613', 'Deep Amber' : '#FF430A', 'S22' : '#FF430A', 'Orange' : '#FF5A00', 'S23' : '#FF5A00', 'Scarlet' : '#F50014', 'S24' : '#F50014', 'Gypsy Red' : '#F50F39', 'S324' : '#F50F39', 'Orange Red' : '#E51F00', 'S25' : '#E51F00', 'Light Red' : '#D70229', 'S26' : '#D70229', 'Medium Red' : '#B00202', 'S27' : '#B00202', 'Light Salmon Pink' : '#FF7A59', 'S30' : '#FF7A59', 'Salmon Pink' : '#FF847F', 'S31' : '#FF847F', 'Shell Pink' : '#FF9D8D', 'S331' : '#FF9D8D', 'Medium Salmon Pink' : '#FF413C', 'S32' : '#FF413C', 'Cherry Rose' : '#FF2957', 'S332' : '#FF2957', 'No Color Pink' : '#FFC2D0', 'S33' : '#FFC2D0', 'Light Pink' : '#FFA7BB', 'S35' : '#FFA7BB', 'Medium Pink' : '#FF6D96', 'S36' : '#FF6D96', 'Billington Pink' : '#FF73B7', 'S336' : '#FF73B7', 'True Pink' : '#FFAFC2', 'S337' : '#FFAFC2', 'Light Rose' : '#FFBBE2', 'S38' : '#FFBBE2', 'Broadway Pink' : '#FF1283', 'S339' : '#FF1283', 'Skelton Exotic Sangria' : '#E800BC', 'S39' : '#E800BC', 'Light Salmon' : '#FF4F1F', 'S40' : '#FF4F1F', 'Rose Pink' : '#FF1562', 'S342' : '#FF1562', 'Deep Pink' : '#FF3E93', 'S43' : '#FF3E93', 'Neon Pink' : '#FF397F', 'S343' : '#FF397F', 'Follies Pink' : '#FF05D3', 'S344' : '#FF05D3', 'Rose' : '#EB016D', 'S45' : '#EB016D', 'Magenta' : '#BD045D', 'S46' : '#BD045D', 'Tropical Magenta' : '#FF2DD5', 'S346' : '#FF2DD5', 'Light Rose Purple' : '#CC4EB9', 'S47' : '#CC4EB9', 'Belladonna Rose' : '#B101DD', 'S347' : '#B101DD', 'Rose Purple' : '#C800CF', 'S48' : '#C800CF', 'Purple Jazz' : '#DA2DFF', 'S348' : '#DA2DFF', 'Medium Purple' : '#C900E6', 'S49' : '#C900E6', 'Fisher Fuchsia' : '#F000FF', 'S349' : '#F000FF', 'Mauve' : '#BB002C', 'S50' : '#BB002C', 'Lavender Mist' : '#EFDCFF', 'S351' : '#EFDCFF', 'Light Lavender' : '#DDBFFF', 'S52' : '#DDBFFF', 'Pale Lavender' : '#E4DCFF', 'S53' : '#E4DCFF', 'Lilly Lavender' : '#C4ADFF', 'S353' : '#C4ADFF', 'Special Lavender' : '#E6C7FF', 'S54' : '#E6C7FF', 'Lilac' : '#C0AAFD', 'S55' : '#C0AAFD', 'Pale Violet' : '#A590FF', 'S355' : '#A590FF', 'Gypsy Lavender' : '#8C2FFF', 'S56' : '#8C2FFF', 'Middle Lavender' : '#C38DFF', 'S356' : '#C38DFF', 'Lavender' : '#B482FF', 'S57' : '#B482FF', 'Royal Lavender' : '#8A2BFF', 'S357' : '#8A2BFF', 'Deep Lavender' : '#933FFD', 'S58' : '#933FFD', 'Rose Indigo' : '#8E0AEA', 'S358' : '#8E0AEA', 'Indigo' : '#7200FF', 'S59' : '#7200FF', 'Medium Violet' : '#683FFF', 'S359' : '#683FFF', 'Mist Blue' : '#D3EAFF', 'S61' : '#D3EAFF', 'Hemsley Blue' : '#669EFC', 'S361' : '#669EFC', 'Booster Blue' : '#A1CEFF', 'S62' : '#A1CEFF', 'Pale Blue' : '#A4D3FF', 'S63' : '#A4D3FF', 'Aquamarine' : '#ABE9FF', 'S363' : '#ABE9FF', 'Light Steel Blue' : '#50AEFD', 'S64' : '#50AEFD', 'Daylight Blue' : '#00A9FF', 'S65' : '#00A9FF', 'Cool Blue' : '#94EAFF', 'S66' : '#94EAFF', 'Jordan Blue' : '#29C0F9', 'S366' : '#29C0F9', 'Light Sky Blue' : '#14A9FF', 'S67' : '#14A9FF', 'Slate Blue' : '#44A5FF', 'S367' : '#44A5FF', 'Parry Sky Blue' : '#447DFF', 'S68' : '#447DFF', 'Winkler Blue' : '#448AFF', 'S368' : '#448AFF', 'Brilliant Blue' : '#00A3F7', 'S69' : '#00A3F7', 'Tahitian Blue' : '#00C6FF', 'S369' : '#00C6FF', 'Nile Blue' : '#6CE5FF', 'S70' : '#6CE5FF', 'Italian Blue' : '#01CDDF', 'S370' : '#01CDDF', 'Sea Blue' : '#0096C7', 'S71' : '#0096C7', 'Theatre Booster 1' : '#A3A8FF', 'S371' : '#A3A8FF', 'Azure Blue' : '#55CCFF', 'S72' : '#55CCFF', 'Theatre Booster 2' : '#D9DCFF', 'S372' : '#D9DCFF', 'Peacock Blue' : '#00A4B8', 'S73' : '#00A4B8', 'Theatre Booster 3' : '#E0E9FD', 'S373' : '#E0E9FD', 'Night Blue' : '#4200FF', 'S74' : '#4200FF', 'Sea Green' : '#01A4A6', 'S374' : '#01A4A6', 'Twilight Blue' : '#007AAC', 'S75' : '#007AAC', 'Light Green Blue' : '#005773', 'S76' : '#005773', 'Iris Purple' : '#7124FF', 'S377' : '#7124FF', 'Trudy Blue' : '#6F6FFF', 'S78' : '#6F6FFF', 'Bright Blue' : '#1626FF', 'S79' : '#1626FF', 'Primary Blue' : '#0048FF', 'S80' : '#0048FF', 'Urban Blue' : '#486FFF', 'S81' : '#486FFF', 'Surprise Blue' : '#4F34F8', 'S82' : '#4F34F8', 'Congo Blue' : '#250070', 'S382' : '#250070', 'Medium Blue' : '#0228EC', 'S83' : '#0228EC', 'Sapphire Blue' : '#0022D1', 'S383' : '#0022D1', 'Zephyr Blue' : '#5767FF', 'S84' : '#5767FF', 'Midnight Blue' : '#0500D0', 'S384' : '#0500D0', 'Deep Blue' : '#0049CE', 'S85' : '#0049CE', 'Royal Blue' : '#4F02CF', 'S385' : '#4F02CF', 'Pea Green' : '#89FA19', 'S86' : '#89FA19', 'Leaf Green' : '#7BD300', 'S386' : '#7BD300', 'Gaslight Green' : '#D0F54E', 'S388' : '#D0F54E', 'Moss Green' : '#51F655', 'S89' : '#51F655', 'Chroma Green' : '#29F433', 'S389' : '#29F433', 'Dark Yellow Green' : '#007F06', 'S90' : '#007F06', 'Primary Green' : '#005E2C', 'S91' : '#005E2C', 'Pacific Green' : '#009493', 'S392' : '#009493', 'Blue Green' : '#01A3A0', 'S93' : '#01A3A0', 'Emerald Green' : '#007150', 'S393' : '#007150', 'Kelly Green' : '#00985D', 'S94' : '#00985D', 'Medium Blue Green' : '#009C91', 'S95' : '#009C91', 'Teal Green' : '#00726A', 'S395' : '#00726A', 'Lime' : '#F3FF6B', 'S96' : '#F3FF6B', 'Neutral Grey' : '#B0B4B9', 'S398' : '#B0B4B9', 'Frost' : '#FFFFFF', 'S100' : '#FFFFFF', 'Light Frost' : '#FFFFFF', 'S101' : '#FFFFFF', 'Tough Silk' : '#FFFFFF', 'S104' : '#FFFFFF', 'Matte Silk' : '#FFFFFF', 'S113' : '#FFFFFF', 'Hamburg Frost' : '#FFFFFF', 'S114' : '#FFFFFF', 'Light Hamburg Frost' : '#FFFFFF', 'S119' : '#FFFFFF', 'Red Diffusion' : '#FFFFFF', 'S120' : '#FFFFFF', 'Blue Diffusion' : '#FFFFFF', 'S121' : '#FFFFFF', 'Green Diffusion' : '#FFFFFF', 'S122' : '#FFFFFF', 'Red Cyc Silk' : '#FFFFFF', 'S124' : '#FFFFFF', 'Blue Cyc Silk' : '#FFFFFF', 'S125' : '#FFFFFF', 'Green Cyc Silk' : '#FFFFFF', 'S126' : '#FFFFFF', 'Amber Cyc Silk' : '#FFFFFF', 'S127' : '#FFFFFF', 'Quarter Hamburg Frost' : '#FFFFFF', 'S132' : '#FFFFFF', 'Subtle Hamburg Frost' : '#FFFFFF', 'S140' : '#FFFFFF', 'Light Tough Silk' : '#FFFFFF', 'S160' : '#FFFFFF', # HTML standard colours 'AliceBlue': '#F0F8FF', 'AntiqueWhite': '#FAEBD7', 'Aqua': '#00FFFF', 'Aquamarine': '#7FFFD4', 'Azure': '#F0FFFF', 'Beige': '#F5F5DC', 'Bisque': '#FFE4C4', 'Black': '#000000', 'BlanchedAlmond': '#FFEBCD', 'Blue': '#0000FF', 'BlueViolet': '#8A2BE2', 'Brown': '#A52A2A', 'BurlyWood': '#DEB887', 'CadetBlue': '#5F9EA0', 'Chartreuse': '#7FFF00', 'Chocolate': '#D2691E', 'Coral': '#FF7F50', 'CornflowerBlue': '#6495ED', 'Cornsilk': '#FFF8DC', 'Crimson': '#DC143C', 'Cyan': '#00FFFF', 'DarkBlue': '#00008B', 'DarkCyan': '#008B8B', 'DarkGoldenrod': '#B8860B', 'DarkGray': '#A9A9A9', 'DarkGreen': '#006400', 'DarkKhaki': '#BDB76B', 'DarkMagenta': '#8B008B', 'DarkOliveGreen': '#556B2F', 'DarkOrange': '#FF8C00', 'DarkOrchid': '#9932CC', 'DarkRed': '#8B0000', 'DarkSalmon': '#E9967A', 'DarkSeaGreen': '#8FBC8F', 'DarkSlateBlue': '#483D8B', 'DarkSlateGray': '#2F4F4F', 'DarkTurquoise': '#00CED1', 'DarkViolet': '#9400D3', 'DeepPink': '#FF1493', 'DeepSkyBlue': '#00BFFF', 'DimGray': '#696969', 'DodgerBlue': '#1E90FF', 'FireBrick': '#B22222', 'FloralWhite': '#FFFAF0', 'ForestGreen': '#228B22', 'Fuchsia': '#FF00FF', 'Gainsboro': '#DCDCDC', 'GhostWhite': '#F8F8FF', 'Gold': '#FFD700', 'Goldenrod': '#DAA520', 'Gray': '#808080', 'Green': '#008000', 'GreenYellow': '#ADFF2F', 'Honeydew': '#F0FFF0', 'HotPink': '#FF69B4', 'IndianRed': '#CD5C5C', 'Indigo': '#4B0082', 'Ivory': '#FFFFF0', 'Khaki': '#F0E68C', 'Lavender': '#E6E6FA', 'LavenderBlush': '#FFF0F5', 'LawnGreen': '#7CFC00', 'LemonChiffon': '#FFFACD', 'LightBlue': '#ADD8E6', 'LightCoral': '#F08080', 'LightCyan': '#E0FFFF', 'LightGoldenrodYellow': '#FAFAD2', 'LightGreen': '#90EE90', 'LightGrey': '#D3D3D3', 'LightPink': '#FFB6C1', 'LightSalmon': '#FFA07A', 'LightSeaGreen': '#20B2AA', 'LightSkyBlue': '#87CEFA', 'LightSlateGray': '#778899', 'LightSteelBlue': '#B0C4DE', 'LightYellow': '#FFFFE0', 'Lime': '#00FF00', 'LimeGreen': '#32CD32', 'Linen': '#FAF0E6', 'Magenta': '#FF00FF', 'Maroon': '#800000', 'MediumAquamarine': '#66CDAA', 'MediumBlue': '#0000CD', 'MediumOrchid': '#BA55D3', 'MediumPurple': '#9370DB', 'MediumSeaGreen': '#3CB371', 'MediumSlateBlue': '#7B68EE', 'MediumSpringGreen': '#00FA9A', 'MediumTurquoise': '#48D1CC', 'MediumVioletRed': '#C71585', 'MidnightBlue': '#191970', 'MintCream': '#F5FFFA', 'MistyRose': '#FFE4E1', 'Moccasin': '#FFE4B5', 'NavajoWhite': '#FFDEAD', 'Navy': '#000080', 'OldLace': '#FDF5E6', 'Olive': '#808000', 'OliveDrab': '#6B8E23', 'Orange': '#FFA500', 'OrangeRed': '#FF4500', 'Orchid': '#DA70D6', 'PaleGoldenrod': '#EEE8AA', 'PaleGreen': '#98FB98', 'PaleTurquoise': '#AFEEEE', 'PaleVioletRed': '#DB7093', 'PapayaWhip': '#FFEFD5', 'PeachPuff': '#FFDAB9', 'Peru': '#CD853F', 'Pink': '#FFC0CB', 'Plum': '#DDA0DD', 'PowderBlue': '#B0E0E6', 'Purple': '#800080', 'Red': '#FF0000', 'RosyBrown': '#BC8F8F', 'RoyalBlue': '#4169E1', 'SaddleBrown': '#8B4513', 'Salmon': '#FA8072', 'SandyBrown': '#F4A460', 'SeaGreen': '#2E8B57', 'Seashell': '#FFF5EE', 'Sienna': '#A0522D', 'Silver': '#C0C0C0', 'SkyBlue': '#87CEEB', 'SlateBlue': '#6A5ACD', 'SlateGray': '#708090', 'Snow': '#FFFAFA', 'SpringGreen': '#00FF7F', 'SteelBlue': '#4682B4', 'Tan': '#D2B48C', 'Teal': '#008080', 'Thistle': '#D8BFD8', 'Tomato': '#FF6347', 'Turquoise': '#40E0D0', 'Violet': '#EE82EE', 'Wheat': '#F5DEB3', 'White': '#FFFFFF', 'WhiteSmoke': '#F5F5F5', 'Yellow': '#FFFF00', 'YellowGreen': '#9ACD32' } PK-u7HB^gaga pylux/plot.py# plot.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . """Interact with Pylux plot files without an XML parser. Manipulate Pylux plot files without having to use an XML parser, instead use the series of objects defined by plot which allow for quicker editing of plots, and management of the XML tree. Also provides some utility functions to make writing extensions for Pylux quicker and easier. """ import xml.etree.ElementTree as ET import uuid import math import pylux.reference as reference class PlotFile: """Manage the Pylux plot project file. Attributes: file: the path of the project file. tree: the parsed XML tree. root: the root element of the XML tree. """ def load(self, path): """Load a project file. Args: path: the location of the file to load. """ self.file = path try: self.tree = ET.parse(self.file) except FileNotFoundError: print('The file you are trying to load doesn\'t exist!') self.root = self.tree.getroot() def save(self): """Save the project file to its original location.""" self.tree.write(self.file, encoding='UTF-8', xml_declaration=True) def saveas(self, path): """Save the project file to a new location. Args: path: the location to save the file to. """ self.tree.write(path, encoding='UTF-8', xml_declaration=True) def generate(self, path): """Generate a blank project file. Generate a file containing the olplot root element, the metadata element and the fixtures element. """ with open(path, 'w') as new_file: new_file.write('\n' '\n') class DmxRegistry: """Manages DMX registries. Attributes: registry: the registry as a Python dictionary. universe: the universe id of the registry. xml_registry: the XML tree of the registry. Is False if the registry doesn't exist in XML. """ def __init__(self, plot_file, universe): """Create a new Python registry. Creates a new Python registry with the id universe. Then searches the project file for a registry with the same id. If one is found, loads that data into the Python registry, if the registry doesn't exist in XML, creates one and adds it to the tree. Args: universe: the universe id of the registry to be created. """ self.plot_file = plot_file self.registry = {} self.universe = universe self.xml_registry = False # Search for this universe in the XML file xml_registries = self.plot_file.root.findall('registry') for xml_registry in xml_registries: testing_universe = xml_registry.get('universe') if testing_universe == self.universe: self.xml_registry = xml_registry break # Return XML registry if it exists # Create a new XML registry if one doesn't exist if self.xml_registry == False: self.xml_registry = ET.Element('registry') self.xml_registry.set('universe', self.universe) self.plot_file.root.append(self.xml_registry) # Populate the Python registry if an XML registry was found else: for channel in xml_registry: address = int(channel.get('address')) uuid = channel.find('fixture_uuid').text function = channel.find('function').text self.registry[address] = (uuid, function) def save(self): """Saves the Python registry to the XML tree. Saves the contents of the Python DMX registry to the registry in XML and deletes any XML channels that no longer exist in the Python registry. """ # Add a new XML entry def add_xml_entry(self, address, uuid, function): self.registry[address] = (uuid, function) new_channel = ET.Element('channel') new_channel.set('address', str(address)) new_uuid = ET.SubElement(new_channel, 'fixture_uuid') new_uuid.text = uuid new_function = ET.SubElement(new_channel, 'function') new_function.text = function self.xml_registry.append(new_channel) # Edit an existing XML entry def edit_xml_entry(self, xml_channel, new_uuid, new_function): xml_channel.find('fixture_uuid').text = new_uuid xml_channel.find('function').text = function # Search a channel with address in XML def get_xml_channel(self, address): for channel in self.xml_registry: found_address = channel.get('address') if found_address == str(address): return channel break # Iterate over the Python registry for address in self.registry: if self.registry[address] != None: uuid = self.registry[address][0] function = self.registry[address][1] xml_channel = get_xml_channel(self, address) if xml_channel == None: add_xml_entry(self, address, uuid, function) else: edit_xml_entry(self, xml_channel, uuid, function) else: self.xml_registry.remove(get_xml_channel(self, address)) def get_occupied(self): """Returns a list of occupied DMX channels. Returns: A list containing the addresses of the occupied channels in the Python registry. """ occupied = [] for address in self.registry: occupied.append(address) occupied.sort() return occupied def get_start_address(self, n): """Returns a recommended start address for a new fixture. Finds the next run of n free DMX channels in the Python registry or returns 1 if no channels are occupied. Args: n: the number of DMX channels required by the new fixture. Returns: An integer giving the ideal DMX start address. """ occupied = self.get_occupied() if occupied == []: print('All channels are free so choosing start address 1') return 1 for i in occupied: free_from = i+1 if occupied[-1] == i: next_test = 513 else: next_test = occupied[occupied.index(i)+1] free_until = next_test-1 if free_until-free_from >= 0: print('Found free channels in the range '+ str(free_from)+':'+str(free_until)) if free_until-free_from+1 >= n: print('Automatically chose start address '+ str(free_from)) return free_from def address(self, fixture, start_address): """Address a fixture.""" required_channels = int(fixture.data['dmx_channels']) if start_address == 'auto': address = self.get_start_address(required_channels) else: address = int(start_address) try: fixture.data['universe'] except KeyError: pass else: old_start_addr = int(fixture.data['dmx_start_address']) i = old_start_addr while i < old_start_addr+required_channels: self.registry[i] = None i = i+1 fixture.data['dmx_start_address'] = str(address) fixture.data['universe'] = self.universe for function in fixture.dmx_functions: self.registry[address] = (fixture.uuid, function) address = int(address)+1 fixture.save() self.save() def unaddress(self, fixture): """Remove a fixture from the registry""" start_address = int(fixture.data['dmx_start_address']) i = start_address while i < start_address+int(fixture.data['dmx_channels']): self.registry[i] = None i = i+1 self.save() class FixtureList: """Manage all the fixtures in a plot.""" def __init__(self, plot_file): """Creates fixture objects for all the fixtures in the plot.""" self.xml_fixtures = plot_file.root.findall('fixture') self.fixtures = [] for xml_fixture in self.xml_fixtures: fixture = Fixture(plot_file) fixture.load(xml_fixture) self.fixtures.append(fixture) def remove(self, fixture): """Remove a fixture from the plot.""" self.xml_fixture_list.remove(fixture.xml_fixture) def get_data_values(self, data_type): """Returns a list containing the values of data...etc""" data_values = [] for fixture in self.fixtures: try: data_values.append(fixture.data[data_type]) except KeyError: pass data_values = list(set(data_values)) data_values.sort() return data_values class Fixture: """Manage individual fixtures. Attributes: uuid: the UUID of the fixture. data: a dictionary containing all other data for the fixture. dmx: a list of the functions of the DMX channels used by this fixture. dmx_num: the number of DMX channels required by this fixture. """ def __init__(self, plot_file, uuid=None): """Create a new fixture in Python and load data based on UUID.""" self.plot_file = plot_file self.data = {} self.dmx_functions = [] if uuid != None: xml_fixtures_list = plot_file.root.findall('fixture') for xml_fixture in xml_fixtures_list: if uuid == xml_fixture.get('uuid'): self.load(xml_fixture) def new(self, template_file): """Make this fixture as a brand new fixture. Given template name, assign a UUID and load the constants from the OLF file into the data dictionary. Args: template: the name of the template the new fixture should copy. """ self.uuid = str(uuid.uuid4()) # Random UUID assigned src_tree = ET.parse(template_file) self.src_root = src_tree.getroot() dmx_xml = self.src_root.find('dmx_functions') for channel in dmx_xml: self.dmx_functions.append(channel.tag) dmx_num = len(self.dmx_functions) # Add constants from OLF file for xml_data in self.src_root: if xml_data.tag != 'dmx_functions': self.data[xml_data.tag] = xml_data.text self.data['dmx_channels'] = str(dmx_num) def add(self, plot_file): """Create an XML object for the fixture and add to the tree. Generate a fixture XML object and populate it with the contents of the data dictionary, then add the newly created fixture to the XML tree. """ new_fixture = ET.Element('fixture') new_fixture.set('uuid', self.uuid) # Iterate over data for data_item in self.data: new_detail = ET.SubElement(new_fixture, data_item) new_detail.text = self.data[data_item] xml_dmx_functions = ET.SubElement(new_fixture, 'dmx_functions') for dmx_function in self.dmx_functions: new_dmx_function = ET.SubElement(xml_dmx_functions, dmx_function) plot_file.root.append(new_fixture) self.xml_fixture = new_fixture def load(self, xml_fixture): """Make this fixture as an existing fixture in the XML tree. Load the contents of an existing fixture in the XML document into this Python fixture object. Args: fixture: the XML fixture object to load. """ self.xml_fixture = xml_fixture self.uuid = xml_fixture.get('uuid') for data_item in xml_fixture: if data_item.tag != 'dmx_functions': self.data[data_item.tag] = data_item.text xml_dmx_functions = xml_fixture.find('dmx_functions') for dmx_function in xml_dmx_functions: self.dmx_functions.append(dmx_function.tag) self.dmx_num = len(self.dmx_functions) def clone(self, src_fixture): """Clone a fixture. an exact copy of a fixture in XML, but assign a new UUID. Args: src_fixture: source Python fixture object to copy. """ self.uuid = str(uuid.uuid4()) for data_item in src_fixture.data: self.data[data_item] = src_fixture.data[data_item] def save(self): """Save the Python fixture object to XML.""" # Add a new data item def add_xml_data(self, tag, value): new_data_item = ET.Element(tag) new_data_item.text = value self.xml_fixture.append(new_data_item) # Edit an existing data item def edit_xml_data(self, tag, new_value): self.xml_fixture.find(tag).text = new_value # Search for data in XML def get_xml_data(self, tag): try: return self.xml_fixture.find(tag) except AttributeError: return None # Iterate over the data dictionary for data_item in self.data: xml_data = get_xml_data(self, data_item) if xml_data == None: add_xml_data(self, data_item, self.data[data_item]) else: edit_xml_data(self, data_item, self.data[data_item]) # Iterate over XML fixture to remove empty data for data_item in self.xml_fixture: data_name = data_item.tag if data_name != 'dmx_functions' and self.data[data_name] == "" : self.xml_fixture.remove(data_item) def generate_rotation(self): posX = float(self.data['posX']) posY = float(self.data['posY']) focusX = float(self.data['focusX']) focusY = float(self.data['focusY']) return math.degrees(math.atan2((focusY-posY), (focusX-posX))) def generate_colour(self): if self.data['gel'] in reference.gel_colours: return reference.gel_colours[self.data['gel']] else: return False class Metadata: """Manages the metadata section of the XML file. Attributes: xml_meta: the XML object containing a the metadata. meta: a dictionary containing the metadata. """ def __init__(self, plot_file): """Find the metadata in XML and add to the attribute. Args: plot_file: the FileManager object of the project file. """ self.xml_meta = plot_file.root.findall('metadata') self.meta = {} for metaitem in self.xml_meta: self.meta[metaitem.tag] = metaitem.text def save(self, plot_file): """Save the metadata dictionary to XML.""" # Add a new meta item def add_xml_meta(self, name, value): new_metadata = ET.Element(name) new_metadata.text = value plot_file.append(new_metadata) # Edit an existing meta item def edit_xml_meta(self, name, new_value): self.xml_meta.find(name).text = new_value # Search for meta in XML def get_xml_meta(self, name): try: for metaitem in self.xml_meta: if metaitem.find('name').tag == metaitem: return metaitem else: return None except AttributeError: return None # Iterate over the meta values dictionary for metaitem in self.meta: xml_meta = get_xml_meta(self, metaitem) if xml_meta == None: add_xml_meta(self, metaitem, self.meta[metaitem]) else: edit_xml_meta(self, metaitem, self.meta[metaitem]) # Iterate over XML meta object to remove empty values for metaitem in self.xml_meta: name = metaitem.find('name').tag if self.meta[name] == None: plot_file.remove(metaitem) class Cue: """Manages cues something something bored of docstrings.""" def __init__(self, plot_file, UUID=None): """Create an empty cue.""" self.data = {} if UUID is None: self.uuid = str(uuid.uuid4()) self.key = len(CueList(plot_file).cues)+1 xml_cue = ET.Element('cue') xml_cue.set('uuid', self.uuid) plot_file.root.append(xml_cue) else: self.uuid = UUID for xml_cue in plot_file.root.findall('cue'): if xml_cue.get('uuid') == self.uuid: self.key = int(xml_cue.get('key')) for cue_data in xml_cue: self.data[cue_data.tag] = cue_data.text def save(self, plot_file): """Save the cue to XML.""" for xml_cue_test in plot_file.root.findall('cue'): if xml_cue_test.get('uuid') == self.uuid: xml_cue = xml_cue_test # Find data tags already in XML data_in_xml = [] for data_item_xml in xml_cue: data_in_xml.append(data_item_xml.tag) # Set the sorting key xml_cue.set('key', str(self.key)) # Iterate through data in dict for data_item in self.data: # If data not in XML, make a new sub element if data_item not in data_in_xml: new_data_item = ET.SubElement(xml_cue, data_item) new_data_item.text = self.data[data_item] # Otherwise edit existing data else: for data_item_xml in xml_cue: if data_item_xml.tag == data_item: data_item_xml.text = self.data[data_item] # Iterate through data in XML and remove empty for data_item_xml in xml_cue: if self.data[data_item_xml.tag] is None: xml_cue.remove(data_item_xml) class CueList: """Manage all the cues in the document. Create a list contaning cue objects for every cue in the document. Also manage the keys of cues by moving cues relative to one another and remove cues entirely. Attributes: cues: a list of all the cue objects in the document. """ def __init__(self, plot_file): """Generate a list of all the cues present. Search through the plot file for any cue tags, create a Cue object for them, then add to a list. Args: plot_file: the PlotFile object containing the document. """ self.cues = [] for xml_cue in plot_file.root.findall('cue'): cue_uuid = xml_cue.get('uuid') cue = Cue(plot_file, UUID=cue_uuid) self.cues.append(cue) def remove(self, plot_file, UUID): """Remove a cue from the plot entirely. Args: plot_file: the PlotFile object containing the document. UUID: the UUID of the cue to be deleted. """ for xml_cue in plot_file.root.findall('cue'): if xml_cue.get('uuid') == UUID: plot_file.root.remove(xml_cue) def move_after(self, plot_file, origin, dest): """Move a cue after another in the list. Manipulate the key attributes of any necessary cues to rearrange the list such that origin is placed immediately after dest. Args: plot_file: the PlotFile object containing the document. origin: the key of the cue to be moved. dest: the key of the cue after which this cue should be immediately located. """ if dest > origin: for cue in self.cues: if cue.key == origin: cue.key = dest elif origin < cue.key <= dest: cue.key = cue.key-1 cue.save(plot_file) if dest < origin: for cue in self.cues: if dest < cue.key < origin: cue.key = cue.key+1 elif cue.key == origin: cue.key = dest+1 cue.save(plot_file) def move_before(self, plot_file, origin, dest): """Move a cue before another in the list. Manipulate the key attributes of any necessary cues to rearrange the list such that origin is placed immediately before dest. Args: plot_file: the PlotFile object containing the document. origin: the key of the cue to be moved. dest: the key of the cue before which this cue should be immediately located. """ if dest > origin: for cue in self.cues: if cue.key == origin: cue.key = dest elif dest >= cue.key > origin: cue.key = cue.key-1 cue.save(plot_file) if dest < origin: for cue in self.cues: if origin > cue.key >= dest: cue.key = cue.key+1 elif cue.key == origin: cue.key = dest cue.save(plot_file) class Scene: """Scenes store DMX output states.""" def __init__(self, plot_file, UUID=None): """Create an empty scene.""" self.dmx_data = {} if UUID is None: self.uuid = str(uuid.uuid4()) xml_cue = ET.Element('scene') xml_cue.set('uuid', self.uuid) plot_file.root.append(xml_cue) else: self.uuid = UUID for xml_scene in plot_file.root.findall('scene'): if xml_scene.get('uuid') == self.uuid: for dmx_info_xml in xml_scene.findall('dmx'): address = dmx_info_xml.get('address') output = int(dmx_info_xml.text) self.dmx_data[address] = output def save(self, plot_file): """Save the scene to XML.""" for xml_scene_test in plot_file.root.findall('scene'): if xml_scene_test.get('uuid') == self.uuid: xml_scene = xml_scene_test # Find DMX info already in XML dmx_in_xml = [] for dmx_info_xml in xml_scene.findall('dmx'): dmx_in_xml.append(dmx_info_xml.get('address')) # Iterate through data in DMX dict for dmx_info in self.dmx_data: # If DMX not in XML, make a new sub element if dmx_info not in dmx_in_xml: new_dmx_info = ET.SubElement(xml_scene, 'dmx') new_dmx_info.text = self.dmx_data[dmx_info] new_dmx_info.set('address', dmx_info) # Otherwise edit existing data else: for dmx_info_xml in xml_scene: if dmx_info_xml.tag == dmx_info: dmx_info_xml.text = self.dmx_data[dmx_info] # Iterate through data in XML and remove empty for dmx_info_xml in xml_scene: if self.dmx_data[dmx_info_xml.tag] is None: xml_scene.remove(dmx_info_xml) class FixtureSymbol: """Manages the SVG symbols for fixtures.""" def __init__(self, path): tree = ET.parse(path) root = tree.getroot() self.ns = {'ns0': 'http://www.w3.org/2000/svg'} self.image_group = root.find('ns0:g', self.ns) def prepare(self, posX, posY, rotation, colour): posX = str(float(posX)*1000) posY = str(float(posY)*1000) rotation = str(rotation) self.image_group.set('transform', 'translate('+ posX+' '+posY+') rotate('+rotation+')') for path in self.image_group: if path.get('class') == 'outer': path.set('fill', colour) PKEO7HO..pylux/editor.py# editor.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . """Edit the content of Pylux plot files. editor is a CLI implementation of the Pylux plot editor, allowing for the reading and editing of Pylux plot files. """ import os import sys import pylux.plot as plot import pylux.clihelper as clihelper import runpy import logging from pylux import get_data from importlib import import_module def file_open(inputs): try: PLOT_FILE.load(inputs[1]) except IndexError: print('Error: You need to specify a file path to load') except AttributeError: pass def file_write(inputs): try: PLOT_FILE.save() except AttributeError: print('Error: No file is loaded') def file_writeas(inputs): try: PLOT_FILE.saveas(inputs[1]) except IndexError: print('Error: You need to specify a destination path!') def file_get(inputs): print('Using plot file '+PLOT_FILE.file) def file_new(inputs): PLOT_FILE.generate(os.path.expanduser(inputs[1])) PLOT_FILE.load(os.path.expanduser(inputs[1])) file_get(inputs) def metadata_list(inputs): metadata = plot.Metadata(PLOT_FILE) for i in metadata.meta: print(i+': '+metadata.meta[i]) def metadata_set(inputs): metadata = plot.Metadata(PLOT_FILE) metadata.meta[inputs[1]] = clihelper.resolve_input(inputs, 2)[-1] metadata.save() def metadata_remove(inputs): metadata = plot.Metadata(PLOT_FILE) metadata.meta[inputs[1]] = None metadata.save() def metadata_get(inputs): metadata = plot.Metadata(PLOT_FILE) print(inputs[1]+': '+metadata.meta[inputs[1]]) def fixture_new(inputs): fixture = plot.Fixture(PLOT_FILE) try: fixture.new(inputs[1], FIXTURES_DIR) except FileNotFoundError: print('Error: Couldn\'t find a fixture file with this name') else: fixture.add() fixture.save() def fixture_clone(inputs): src_fixture = INTERFACE.get(inputs[1]) if len(src_fixture) > 1: print('Error: You can only clone one fixture!') else: new_fixture = plot.Fixture(PLOT_FILE, FIXTURES_DIR) new_fixture.clone(src_fixture[0]) new_fixture.add() new_fixture.save() def fixture_list(inputs): fixtures = plot.FixtureList(PLOT_FILE) i = 1 INTERFACE.clear() for fixture in fixtures.fixtures: if 'name' in fixture.data: name = fixture.data['name'] else: name = fixture.data['type'] print('\033[4m'+str(i)+'\033[0m '+name+', id: '+fixture.uuid) INTERFACE.append(i, fixture) i = i+1 def fixture_filter(inputs): try: key = inputs[1] value = clihelper.resolve_input(inputs, 2)[-1] fixtures = plot.FixtureList(PLOT_FILE) INTERFACE.clear() i = 1 for fixture in fixtures.fixtures: if key in fixture.data: if fixture.data[key] == value: if 'name' in fixture.data: name = fixture.data['name'] else: name = fixture.data['type'] print('\033[4m'+str(i)+'\033[0m '+name+ ', id: '+fixture.uuid+', '+key+': '+value) INTERFACE.append(i, fixture) i = i+1 else: pass except IndexError: print('Error: You need to specify a key and value!') def fixture_remove(inputs): fixture_list = plot.FixtureList(PLOT_FILE) fixtures = INTERFACE.get(inputs[1]) for fixture in fixtures: fixture_list.remove(fixture) def fixture_get(inputs): fixtures = INTERFACE.get(inputs[1]) for fixture in fixtures: if inputs[2] in fixture.data: print(fixture.data[inputs[2]]) else: print(None) INTERFACE.update_this(inputs[1]) def fixture_getall(inputs): fixtures = INTERFACE.get(inputs[1]) for fixture in fixtures: for data_item in fixture.data: print(data_item+': '+str(fixture.data[data_item])) INTERFACE.update_this(inputs[1]) def fixture_set(inputs): fixtures = INTERFACE.get(inputs[1]) tag = inputs[2] value = clihelper.resolve_input(inputs, 3)[-1] for fixture in fixtures: # See if it can be automatically generated if value == 'auto': if tag == 'rotation': fixture.data['rotation'] = str(fixture.generate_rotation()) elif tag == 'colour': fixture.data['colour'] = fixture.generate_colour() else: print('Error: No automatic generation is available for ' 'this tag') # See if it is a special pseudo tag elif tag == 'position': fixture.data['posX'] = value.split(',')[0] fixture.data['posY'] = value.split(',')[1] elif tag == 'focus': fixture.data['focusX'] = value.split(',')[0] fixture.data['focusY'] = value.split(',')[1] elif tag == 'dimmer': fixture.data['dimmer_uuid'] = INTERFACE.get(inputs[3])[0].uuid fixture.data['dimmer_channel'] = inputs[4] # Otherwise just set it else: fixture.data[tag] = value fixture.save() INTERFACE.update_this(inputs[1]) def fixture_address(inputs): fixtures = INTERFACE.get(inputs[1]) if len(fixtures) > 1 and inputs[3] != 'auto': print('Error: You must specify auto if you address more than ' 'one fixture.') else: registry = plot.DmxRegistry(PLOT_FILE, inputs[2]) for fixture in fixtures: registry.address(fixture, inputs[3]) INTERFACE.update_this(inputs[1]) def fixture_purge(inputs): fixtures = INTERFACE.get(inputs[1]) for fixture in fixtures: registry = plot.DmxRegistry(PLOT_FILE, fixture.data['universe']) registry.unaddress(fixture) fixture_list = plot.FixtureList(PLOT_FILE) fixture_list.remove(fixture) def registry_list(inputs): try: registry = plot.DmxRegistry(PLOT_FILE, inputs[1]) for channel in registry.registry: uuid = registry.registry[channel][0] func = registry.registry[channel][1] print(str(format(channel, '03d'))+' uuid: '+uuid+', func: '+func) except IndexError: print('You need to specify a DMX registry!') def cue_list(inputs): cues = plot.CueList(PLOT_FILE) INTERFACE.clear() for cue in cues.cues: cue_type = cue.data['type'] cue_location = cue.data['location'] print('\033[4m'+str(cue.key)+'\033[0m ('+cue_type+') at '+ cue_location) INTERFACE.append(cue.key, cue) def cue_new(inputs): cue = plot.Cue(PLOT_FILE) cue.data['type'] = inputs[1] cue.data['location'] = clihelper.resolve_input(inputs, 2)[-1] cue.save(PLOT_FILE) def cue_remove(inputs): cues = plot.CueList(PLOT_FILE) removal_candidates = INTERFACE.get(inputs[1]) for rc in removal_candidates: cues.remove(PLOT_FILE, rc.uuid) def cue_set(inputs): cues_to_change = INTERFACE.get(inputs[1]) for cue in cues_to_change: cue.data[inputs[2]] = clihelper.resolve_input(inputs, 3)[-1] cue.save(PLOT_FILE) def cue_get(inputs): cues_to_get = INTERFACE.get(inputs[1]) for cue in cues_to_get: if inputs[2] in cue.data: print(cue.data[inputs[2]]) else: print(None) def cue_getall(inputs): cues_to_get = INTERFACE.get(inputs[1]) for cue in cues_to_get: for data_item in cue.data: print(data_item+': '+cue.data[data_item]) def cue_moveafter(inputs): cues = plot.CueList(PLOT_FILE) cues.move_after(PLOT_FILE, int(inputs[1]), int(inputs[2])) def cue_movebefore(inputs): cues = plot.CueList(PLOT_FILE) cues.move_before(PLOT_FILE, int(inputs[1]), int(inputs[2])) def utility_help(inputs): text = "" with open('help.txt') as man: for line in man: text = text+line print(text) #def extension_run(inputs): # init_globals = { # 'PLOT_FILE': PLOT_FILE, # 'CONFIG': CONFIG, # 'LOG_LEVEL': LOG_LEVEL} # extensions_dir = '/usr/share/pylux/extension/' # module_name = inputs[0].split(':')[1] # try: # runpy.run_path(extensions_dir+module_name+'.py', # init_globals=init_globals, run_name='pyext') # except FileNotFoundError: # print('No extension with this name!') def utility_clear(inputs): os.system('cls' if os.name == 'nt' else 'clear') def utility_quit(inputs): print('Autosaving changes...') file_write(inputs) sys.exit() def utility_kill(inputs): print('Ignoring changes and exiting...') sys.exit() def main(): """The main user loop.""" global INTERFACE INTERFACE = clihelper.Interface() global FIXTURES_DIR FIXTURES_DIR = get_data('fixture') functions_dict = { 'fo': file_open, 'fw': file_write, 'fW': file_writeas, 'fg': file_get, 'fn': file_new, 'mG': metadata_list, 'ms': metadata_set, 'mr': metadata_remove, 'mg': metadata_get, 'xn': fixture_new, 'xc': fixture_clone, 'xl': fixture_list, 'xf': fixture_filter, 'xr': fixture_remove, 'xg': fixture_get, 'xG': fixture_getall, 'xs': fixture_set, 'xa': fixture_address, 'xp': fixture_purge, 'rl': registry_list, 'ql': cue_list, 'qn': cue_new, 'qr': cue_remove, 'qs': cue_set, 'qg': cue_get, 'qG': cue_getall, 'qm': cue_moveafter, 'qM': cue_movebefore, # 'Xr': extension_run, 'h': utility_help, 'c': utility_clear, 'q': utility_quit, 'Q': utility_kill} print('Welcome to Pylux! Type \'h\' to view a list of commands.') # Begin the main loop logging.basicConfig(level=LOG_LEVEL) while True: user_input = input(CONFIG['cli']['prompt']+' ') inputs = user_input.split(' ') if inputs[0][0] == ':': init_globals = { 'PLOT_FILE': PLOT_FILE, 'CONFIG': CONFIG, 'LOG_LEVEL': LOG_LEVEL} context_name = inputs[0].split(':')[1] module_name = 'pylux.context.'+inputs[0].split(':')[1] try: context = import_module(module_name) except ImportError: print('Error: Context does not exist') else: context.set_globals(init_globals) while True: user_input = input('(pylux:'+context_name+') ') inputs = user_input.split(' ') if inputs[0] == '::': break else: context.process_input(inputs) elif inputs[0] in functions_dict: functions_dict[inputs[0]](inputs) else: print('Error: Command doesn\'t exist.') print('Type \'h\' for a list of available commands.') if __name__ == 'pylux_root': main() PKG; ; pylux/gplotter.py#!/usr/bin/python3 # gplotter.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . import plot from tkinter import * from tkinter.ttk import * from tkinter import tix from tkinter import constants from tkinter import filedialog class GuiApp(Frame): def __init__(self, plot_file, config, master=None): Frame.__init__(self, master) self.pack() self.master = master self.plot_file = plot_file self.config = config self.create_menubar() try: self.create_fixtures_list() except AttributeError: print('no plot file') def create_menubar(self): self.menubar = Menu(self) self.master.config(menu=self.menubar) self.file_menu = Menu(self.menubar) self.menubar.add_cascade(label='File', menu=self.file_menu) self.file_menu.add_command(label='Open...', command=self.command_load_file) self.debug_menu = Menu(self.menubar) self.menubar.add_cascade(label='Debug', menu=self.debug_menu) self.debug_menu.add_command(label='GenFixList', command=self.create_fixtures_list) def create_fixtures_list(self): self.gfixtures_tree = Treeview(self, selectmode='browse', columns=['tag', 'value']) fixtures = plot.FixtureList(self.plot_file) for fixture in fixtures.fixtures: uuid = fixture.uuid olid = fixture.olid try: name = fixture.data['name'] except IndexError: name = uuid self.gfixtures_tree.insert('', 'end', uuid, text=name, values=[olid]) for data_item in fixture.data: name = data_item value = fixture.data[data_item] self.gfixtures_tree.insert(uuid, 'end', values=[name, value]) self.gfixtures_tree.pack() def command_load_file(self): gfile_dialog = filedialog.askopenfile() self.plot_file.load(gfile_dialog.name) def main(plot_file, config): root = tix.Tk() app = GuiApp(plot_file, config, master=root) app.mainloop() if __name__ == '__main__': main() PKyi7HcWm m pylux/__main__.py# __main__.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . """The __main__ module takes options and does stuff.""" import argparse import os import configparser import sys import runpy import logging import pylux.plot as plot from pylux import __version__, get_data def main(): """Initialise the argument and config parsers.""" print('This is Pylux, version '+__version__) # Initiate the argument parser and get launch arguments parser = argparse.ArgumentParser(prog='pylux', description='Create and modify OpenLighting Plot files') parser.add_argument('-v', '--version', action='version', version='%(prog`)s '+__version__) parser.add_argument('-f', '--file', dest='file', help='load this project file on launch') parser.add_argument('-g', '--gui', action='store_true', help='launch Pylux with a GUI') parser.add_argument('-V', '--verbose', dest='verbose', action='count', help='set the verbosity of output') launch_args = parser.parse_args() # Load configuration config_file = get_data('settings.conf') config = configparser.ConfigParser() config.read(config_file) print('Using configuration file '+config_file) # Handle verbosity verbosity_dict = { None: (logging.WARNING, 'WARNING'), 1: (logging.INFO, 'INFO'), 2: (logging.DEBUG, 'DEBUG')} print('Logging level is '+verbosity_dict[launch_args.verbose][1]) # Load plot file plot_file = plot.PlotFile() if launch_args.file != None: plot_file.load(launch_args.file) print('Using plot file '+plot_file.file) else: print('No plot file loaded') # Prepare globals for launch init_globals = { 'PLOT_FILE': plot_file, 'CONFIG': config, 'LOG_LEVEL': verbosity_dict[launch_args.verbose][0]} if launch_args.gui == True: print('Running in GUI mode\n') runpy.run_module('pylux.geditor', init_globals=init_globals, run_name='pylux_root') else: print('Running in CLI mode\n') runpy.run_module('pylux.cli', init_globals=init_globals, run_name='pylux_root') if __name__ == '__main__': main() PK"6HYpylux/__init__.py"""Pylux is a suite for the management of lighting documentation""" import os __version__ = '0.1-alpha2' _ROOT = os.path.abspath(os.path.dirname(__file__)) def get_data(path): return os.path.join(_ROOT, path) PKT7Hi##pylux/geditor.py# gplotter.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . import pylux.plot as plot import xml.etree.ElementTree as ET import gi.repository gi.require_version('Gtk', '3.0') from gi.repository import Gtk class TextInputDialog(Gtk.Dialog): def __init__(self, parent, title): Gtk.Dialog.__init__(self, title, parent, 0, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK)) self.set_default_size(150, 100) self.entry_box = Gtk.Entry() self.container_box = self.get_content_area() self.container_box.add(self.entry_box) self.show_all() class FixtureInfoWindow(Gtk.Window): def __init__(self, fixture): if 'name' in fixture.data: fixture_name = fixture.data['name'] else: fixture_name = fixture.data['type'] Gtk.Window.__init__(self, title='Editing '+fixture_name) self.set_default_size(300, 350) tree_list_model = Gtk.ListStore(str, str) for info_item in fixture.data: tree_list_model.append([info_item, fixture.data[info_item]]) self.fixture_info_tree = Gtk.TreeView(tree_list_model) tree_renderer = Gtk.CellRendererText() tag_column = Gtk.TreeViewColumn('Tag', tree_renderer, text=0) tag_column.set_sort_column_id(0) value_column = Gtk.TreeViewColumn('Value', tree_renderer, text=1) self.fixture_info_tree.append_column(tag_column) self.fixture_info_tree.append_column(value_column) self.add(self.fixture_info_tree) class FixturesWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title='Fixtures') self.set_default_size(0, 500) self.box_container = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL) self.add(self.box_container) # Create the fixtures list self.gui_list_fixtures() # Create fixture action buttons self.button_fixture_new = Gtk.Button(label='New fixture') self.button_fixture_new.connect('clicked', self.action_fixture_new) # Pack fixture action buttons into Box self.box_fixture_buttons = Gtk.Box(spacing=4) self.box_container.pack_start(self.box_fixture_buttons, True, True, 0) self.box_fixture_buttons.pack_start(self.button_fixture_new, True, True, 0) def gui_list_fixtures(self): """Create an empty ListBox for fixtures.""" self.listbox_fixtures = Gtk.ListBox() self.listbox_fixtures.set_selection_mode(Gtk.SelectionMode.NONE) self.box_container.pack_start(self.listbox_fixtures, True, True, 0) fixtures = plot.FixtureList(PLOT_FILE) for fixture in fixtures.fixtures: self.gui_add_fixture(fixture) def gui_add_fixture(self, fixture): """Add a fixture to the ListBox as a ListBoxRow.""" listbox_row_fixture = Gtk.ListBoxRow() grid_fixture_listbox = Gtk.Grid() listbox_row_fixture.add(grid_fixture_listbox) # LHS: name, uuid box_listbox_row_LHS = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) if 'name' in fixture.data: fixture_name = fixture.data['name'] else: fixture_name = fixture.data['type'] label_fixture_name = Gtk.Label(fixture_name, halign=1) label_fixture_uuid = Gtk.Label(halign=1) label_fixture_uuid.set_markup(''+fixture.uuid+'') box_listbox_row_LHS.pack_start(label_fixture_name, True, True, 0) box_listbox_row_LHS.pack_start(label_fixture_uuid, True, True, 0) grid_fixture_listbox.attach(box_listbox_row_LHS, 0, 0, 2, 1) # RHS: action buttons box_listbox_row_RHS = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) button_fixture_getall = Gtk.Button.new_from_icon_name('dialog-information', 1) button_fixture_getall.connect('clicked', self.action_fixture_getall) button_fixture_clone = Gtk.Button.new_from_icon_name('edit-copy', 1) button_fixture_clone.connect('clicked', self.action_fixture_clone) button_fixture_remove = Gtk.Button.new_from_icon_name('edit-delete', 1) button_fixture_remove.connect('clicked', self.action_fixture_remove) box_listbox_row_RHS.pack_start(button_fixture_getall, True, True, 0) box_listbox_row_RHS.pack_start(button_fixture_clone, True, True, 0) box_listbox_row_RHS.pack_start(button_fixture_remove, True, True, 0) grid_fixture_listbox.attach(box_listbox_row_RHS, 2, 0, 1, 1) self.listbox_fixtures.add(listbox_row_fixture) def action_fixture_new(self, widget): type_dialog = TextInputDialog(self, 'Fixture Type') response = type_dialog.run() if response == Gtk.ResponseType.OK: fixture_type = type_dialog.entry_box.get_text() fixture = plot.Fixture(PLOT_FILE) try: fixture.new(fixture_type, '/usr/share/pylux/fixture/') except FileNotFoundError: print('Error: Couldn\'t find a fixture file with this name') else: fixture.add() fixture.save() self.gui_add_fixture(fixture) self.show_all() type_dialog.destroy() def action_fixture_remove(self, widget): print('Removing fixture...') def action_fixture_clone(self, widget): print('Cloning fixture...') def action_fixture_getall(self, widget): fixture_info_box = widget.props.parent.props.parent.get_child_at(0,0) uuid_markup = fixture_info_box.get_children()[1].get_label() fixture_uuid = ET.fromstring(uuid_markup).text fixture = plot.Fixture(PLOT_FILE, uuid=fixture_uuid) info_window = FixtureInfoWindow(fixture) info_window.connect('delete-event', info_window.destroy) info_window.show_all() def main(): win = FixturesWindow() win.connect('delete-event', Gtk.main_quit) win.show_all() Gtk.main() if __name__ == 'pylux_root': main() PKUW)H[c#c#pylux/plotter.py#!/usr/bin/python3 # plotter.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . import os import sys from __init__ import __version__ import plot import clihelper import importlib.util as IL def main(plot_file, config): """The main user loop.""" interface = clihelper.Interface() prompt = config['Settings']['prompt']+' ' fixtures_dir = '/usr/share/pylux/fixture/' print('Welcome to Pylux! Type \'h\' to view a list of commands.') # Begin the main loop while True: user_input = input(config['Settings']['prompt']+' ') inputs = [] for i in user_input.split(' '): inputs.append(i) # File actions if inputs[0] == 'fo': try: plot_file.load(inputs[1]) except UnboundLocalError: print('Error: You need to specify a file path to load') elif inputs[0] == 'fw': plot_file.save() elif inputs[0] == 'fW': try: plot_file.saveas(inputs[1]) except IndexError: print('Error: You need to specify a destination path!') elif inputs[0] == 'fg': print('Using plot file '+plot_file.file) elif inputs[0] == 'fn': plot_file.generate(os.path.expanduser(inputs[1])) plot_file.load(os.path.expanduser(inputs[1])) print('Using plot file '+plot_file.file) # Metadata actions elif inputs[0] == 'ml': metadata = plot.Metadata(plot_file) for i in metadata.meta: print(i+': '+metadata.meta[i]) elif inputs[0] == 'ms': metadata = plot.Metadata(plot_file) metadata.meta[inputs[1]] = clihelper.resolve_input(inputs, 2)[-1] metadata.save() elif inputs[0] == 'mr': metadata = plot.Metadata(plot_file) metadata.meta[inputs[1]] = None metadata.save() elif inputs[0] == 'mg': metadata = plot.Metadata(plot_file) print(inputs[1]+': '+metadata.meta[inputs[1]]) # Fixture actions elif inputs[0] == 'xn': fixture = plot.Fixture(plot_file) try: fixture.new(inputs[1], fixtures_dir) except FileNotFoundError: print('Error: Couldn\'t find a fixture file with this name') else: fixture.add() fixture.save() interface.option_list['this'] = fixture elif inputs[0] == 'xc': src_fixture = interface.get(inputs[1]) new_fixture = plot.Fixture(plot_file, fixtures_dir) new_fixture.clone(src_fixture) new_fixture.add() new_fixture.save() elif inputs[0] == 'xl': fixtures = plot.FixtureList(plot_file) i = 1 interface.clear() for fixture in fixtures.fixtures: fixture_type = fixture.data['type'] print('\033[4m'+str(i)+'\033[0m '+fixture_type+', id: '+ fixture.uuid) interface.append(i, fixture) i = i+1 elif inputs[0] == 'xf': try: key = inputs[1] value = clihelper.resolve_input(inputs, 2)[-1] fixtures = plot.FixtureList(plot_file) interface.clear() i = 1 for fixture in fixtures.fixtures: try: test_value = fixture.data[key] except KeyError: pass else: if test_value == value: fix_type = fixture.data['type'] print('\033[4m'+str(i)+'\033[0m '+fix_type+ ', id: '+fixture.uuid+', '+key+': '+value) interface.append(i, fixture) i = i+1 except IndexError: print('Error: You need to specify a key and value!') elif inputs[0] == 'xr': try: fixtures = plot.FixtureList(plot_file) fixtures.remove(interface.get(inputs[1])) except IndexError: print('Error: You need to run either xl or xf then specify the' ' interface id of the fixture you wish to remove') elif inputs[0] == 'xg': fixture = interface.get(inputs[1]) try: print(fixture.data[inputs[2]]) except KeyError: print('Error: This fixture has no data with that name') interface.option_list['this'] = fixture elif inputs[0] == 'xG': fixture = interface.get(inputs[1]) for data_item in fixture.data: print(data_item+': '+str(fixture.data[data_item])) interface.option_list['this'] = fixture elif inputs[0] == 'xs': fixture = interface.get(inputs[1]) tag = inputs[2] value = clihelper.resolve_input(inputs, 3)[-1] if value == 'auto': if tag == 'rotation': fixture.data['rotation'] = str(fixture.generate_rotation()) elif tag == 'colour': fixture.data['colour'] = fixture.generate_colour() else: print('Error: No automatic generation is available for ' 'this tag') else: fixture.data[tag] = value fixture.save() interface.option_list['this'] = fixture elif inputs[0] == 'xA': fixture = interface.get(inputs[1]) registry = plot.DmxRegistry(plot_file, inputs[2]) registry.address(fixture, inputs[3]) interface.option_list['this'] = fixture elif inputs[0] == 'xp': fixture = interface.get(inputs[1]) registry = plot.DmxRegistry(plot_file, fixture.data['universe']) registry.unaddress(fixture) fixtures = plot.FixtureList(plot_file) fixtures.remove(fixture) # DMX registry actions elif inputs[0] == 'rl': try: registry = plot.DmxRegistry(plot_file, inputs[1]) interface.clear() for channel in registry.registry: uuid = registry.registry[channel][0] func = registry.registry[channel][1] print('\033[4m'+str(format(channel, '03d'))+ '\033[0m uuid: '+uuid+', func: '+func) interface.append(channel, plot.Fixture(plot_file, uuid)) except IndexError: print('You need to specify a DMX registry!') # Extension actions elif inputs[0][0] == ':': extensions_dir = '/usr/share/pylux/extension/' module_name = inputs[0].split(':')[1] try: ext_spec = IL.spec_from_file_location(module_name, extensions_dir+module_name+'.py') ext_module = IL.module_from_spec(ext_spec) ext_spec.loader.exec_module(ext_module) except ImportError: print('No extension with this name!') else: ext_module.run_pylux_extension(plot_file) #try: #except Exception: # print('This module is not a valid Pylux extension!') # Utility actions elif inputs[0] == 'h': text = "" with open('help.txt') as man: for line in man: text = text+line print(text) elif inputs[0] == 'c': os.system('cls' if os.name == 'nt' else 'clear') elif inputs[0] == 'q': print('Autosaving changes...') plot_file.save() sys.exit() elif inputs[0] == 'Q': print('Ignoring changes and exiting...') sys.exit() else: print('Error: Command doesn\'t exist.') print('Type \'h\' for a list of available commands.') # Check that the program isn't imported, then run main if __name__ == '__main__': main() PKa,HGɴpylux/fixture/p650.xml Hutton P650 650 p650 PKa,H.npylux/fixture/patt23.xml Strand Patt.23 500 patt23 3.75 8 PK_$H+ɵ,,pylux/fixture/ledjrgbpar.xml LEDJ RGB PAR ledjrgbpar PKa,Hpylux/fixture/par64mfl.xml PAR64 MFL par64mfl PKa,Huupylux/fixture/betapack2.xml Zero88 Betapack 2 PHANTOM True 12 PKa,H{{pylux/fixture/generic.xml Generic Incandescent generic PKuv7H pylux/context/texlux.py# reporter.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # # Pylux 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. # # Pylux 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, see . """Generate text reports from Jinja templates. TeXlux uses a Jinja2 template to generate documentation from the plot file. It was made primarily for LaTeX documentation but could just as easily be used for other formats. """ from jinja2 import Environment, FileSystemLoader import os import pylux.plot as plot import pylux.clihelper as clihelper from pylux import get_data from pylux.context.context import Context class Report: def __init__(self): self.environment = Environment(loader=FileSystemLoader(get_data('template'))) def generate(self, template): template = self.environment.get_template(template) cue_list = sorted(plot.CueList(GLOBALS['PLOT_FILE']).cues, key=lambda cue: cue.key) fixture_list = plot.FixtureList(GLOBALS['PLOT_FILE']).fixtures self.content = template.render(cues=cue_list, fixtures=fixture_list) class ReporterContext(Context): def __init__(self): self.name = 'reporter' self.init_commands() self.register('rn', self.report_new, 1) self.register('rg', self.report_get, 1) self.register('rw', self.report_write, 1) def report_new(self, parsed_input): self.report = Report() self.report.generate(parsed_input[1]+'.jinja') def report_get(self, parsed_input): print(self.report) def report_write(self, parsed_input): with open(os.path.expanduser(parsed_input[0])) as outfile: outfile.write(self.report) def get_context(): return ReporterContext() PKUc7Hr88pylux/context/context.py# context.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # Pylux 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. # # Pylux 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, see . from pylux.clihelper import resolve_input, Interface from importlib import import_module import sys class Context: """A context defines a set of commands that the user can access.""" def init_commands(self): """Initialise the commands dictionary.""" self.commands = {} self.register('c', self.utility_clear, 0) self.register('Q', self.utility_kill, 0) def process(self, inputs): function_definition = self.commands[inputs[0]] parsed_input = resolve_input(inputs, function_definition[1]) function_definition[0](parsed_input) def set_globals(self, globals_dict): self.plot_file = globals_dict['PLOT_FILE'] self.config = globals_dict['CONFIG'] self.log_level = globals_dict['LOG_LEVEL'] self.interface = Interface() def get_globals(self): globals_dict = { 'PLOT_FILE': self.plot_file, 'CONFIG': self.config, 'LOG_LEVEL': self.log_level} return globals_dict def register(self, mnemonic, function, nargs): self.commands[mnemonic] = (function, nargs) def utility_clear(self, parsed_input): os.system('cls' if os.name == 'nt' else 'clear') sys.exit() def utility_kill(self, parsed_input): print('Ignoring changes and exiting...') sys.exit() PKvw7HtR R pylux/context/reporter.py# reporter.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # # Pylux 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. # # Pylux 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, see . """Generate text reports from Jinja templates. Reporter uses a Jinja2 template to generate documentation from the plot file. It was made primarily for LaTeX documentation but could just as easily be used for other formats. """ from jinja2 import Environment, FileSystemLoader import os import pylux.plot as plot import pylux.clihelper as clihelper from pylux import get_data from pylux.context.context import Context class Report: def __init__(self, plot_file): self.environment = Environment(loader=FileSystemLoader(get_data('template/'))) self.plot_file = plot_file def generate(self, template): template = self.environment.get_template(template) cue_list = sorted(plot.CueList(self.plot_file).cues, key=lambda cue: cue.key) fixture_list = plot.FixtureList(self.plot_file).fixtures self.content = template.render(cues=cue_list, fixtures=fixture_list) class ReporterContext(Context): def __init__(self): self.name = 'reporter' self.init_commands() self.register('rn', self.report_new, 1) self.register('rg', self.report_get, 1) self.register('rw', self.report_write, 1) def report_new(self, parsed_input): self.report = Report(self.plot_file) self.report.generate(parsed_input[0]+'.jinja') def report_get(self, parsed_input): print(self.report.content) def report_write(self, parsed_input): with open(os.path.expanduser(parsed_input[0]), 'w') as outfile: outfile.write(self.report.content) def get_context(): return ReporterContext() PK{i7Hd.d.pylux/context/editor.py# editor.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # # Pylux 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. # # Pylux 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, see . """Edit the content of Pylux plot files. editor is a CLI implementation of the Pylux plot editor, allowing for the reading and editing of Pylux plot files. """ import os import pylux.plot as plot import pylux.clihelper as clihelper import logging from pylux.context.context import Context from pylux import get_data class EditorContext(Context): def __init__(self): """Registers commands and globals for this context.""" self.name = 'editor' self.init_commands() # Register commands self.register('fo', self.file_open, 1) self.register('fw', self.file_write, 0) self.register('fW', self.file_writeas, 1) self.register('fg', self.file_get, 0) self.register('fn', self.file_new, 1) self.register('ml', self.metadata_list, 0) self.register('ms', self.metadata_set, 2) self.register('mr', self.metadata_remove, 1) self.register('mg', self.metadata_get, 1) self.register('xn', self.fixture_new, 1) self.register('xc', self.fixture_clone, 1) self.register('xl', self.fixture_list, 0) self.register('xf', self.fixture_filter, 2) self.register('xr', self.fixture_remove, 1) self.register('xg', self.fixture_get, 2) self.register('xG', self.fixture_getall, 1) self.register('xs', self.fixture_set, 3) self.register('xa', self.fixture_address, 3) self.register('xp', self.fixture_purge, 1) self.register('rl', self.registry_list, 1) self.register('ql', self.cue_list, 0) self.register('qn', self.cue_new, 2) self.register('qr', self.cue_remove, 1) self.register('qs', self.cue_set, 3) self.register('qg', self.cue_get, 2) self.register('qG', self.cue_getall, 1) self.register('qm', self.cue_moveafter, 2) self.register('qM', self.cue_movebefore, 2) def file_open(self, parsed_input): try: self.plot_file.load(parsed_input[0]) except IndexError: print('Error: You need to specify a file path to load') except AttributeError: pass def file_write(self, parsed_input): try: self.plot_file.save() except AttributeError: print('Error: No file is loaded') def file_writeas(self, parsed_input): try: self.plot_file.saveas(parsed_input[0]) except IndexError: print('Error: You need to specify a destination path!') def file_get(self, parsed_input): print('Using plot file '+self.plot_file.file) def file_new(self, parsed_input): self.plot_file.generate(os.path.expanduser(parsed_input[0])) self.plot_file.load(os.path.expanduser(parsed_input[0])) file_get(self, parsed_input) def metadata_list(self, parsed_input): metadata = plot.Metadata(self.plot_file) for i in metadata.meta: print(i+': '+metadata.meta[i]) def metadata_set(self, parsed_input): metadata = plot.Metadata(self.plot_file) metadata.meta[parsed_input[0]] = parsed_input[1] metadata.save() def metadata_remove(self, parsed_input): metadata = plot.Metadata(self.plot_file) metadata.meta[parsed_input[0]] = None metadata.save() def metadata_get(self, parsed_input): metadata = plot.Metadata(self.plot_file) print(parsed_input[0]+': '+metadata.meta[parsed_input[0]]) def fixture_new(self, parsed_input): fixture = plot.Fixture(self.plot_file) template_file = get_data('fixture/'+parsed_input[0]+'.xml') try: fixture.new(template_file) except FileNotFoundError: print('Error: Couldn\'t find a fixture file with this name') else: fixture.add(self.plot_file) fixture.save() def fixture_clone(self, parsed_input): src_fixture = self.interface.get(parsed_input[0]) if len(src_fixture) > 1: print('Error: You can only clone one fixture!') else: new_fixture = plot.Fixture(self.plot_file, FIXTURES_DIR) new_fixture.clone(src_fixture[0]) new_fixture.add() new_fixture.save() def fixture_list(self, parsed_input): fixtures = plot.FixtureList(self.plot_file) i = 1 self.interface.clear() for fixture in fixtures.fixtures: if 'name' in fixture.data: name = fixture.data['name'] else: name = fixture.data['type'] print('\033[4m'+str(i)+'\033[0m '+name+', id: '+fixture.uuid) self.interface.append(i, fixture) i = i+1 def fixture_filter(self, parsed_input): try: key = parsed_input[0] value = parsed_input[1] fixtures = plot.FixtureList(self.plot_file) self.interface.clear() i = 1 for fixture in fixtures.fixtures: if key in fixture.data: if fixture.data[key] == value: if 'name' in fixture.data: name = fixture.data['name'] else: name = fixture.data['type'] print('\033[4m'+str(i)+'\033[0m '+name+ ', id: '+fixture.uuid+', '+key+': '+value) self.interface.append(i, fixture) i = i+1 else: pass except IndexError: print('Error: You need to specify a key and value!') def fixture_remove(self, parsed_input): fixture_list = plot.FixtureList(self.plot_file) fixtures = self.interface.get(parsed_input[0]) for fixture in fixtures: fixture_list.remove(fixture) def fixture_get(self, parsed_input): fixtures = self.interface.get(parsed_input[0]) for fixture in fixtures: if parsed_input[1] in fixture.data: print(fixture.data[parsed_input[1]]) else: print(None) self.interface.update_this(parsed_input[0]) def fixture_getall(self, parsed_input): fixtures = self.interface.get(parsed_input[0]) for fixture in fixtures: for data_item in fixture.data: print(data_item+': '+str(fixture.data[data_item])) self.interface.update_this(parsed_input[0]) def fixture_set(self, parsed_input): fixtures = self.interface.get(parsed_input[0]) tag = parsed_input[1] value = parsed_input[2] for fixture in fixtures: # See if it can be automatically generated if value == 'auto': if tag == 'rotation': fixture.data['rotation'] = str(fixture.generate_rotation()) elif tag == 'colour': fixture.data['colour'] = fixture.generate_colour() else: print('Error: No automatic generation is available for ' 'this tag') # See if it is a special pseudo tag elif tag == 'position': fixture.data['posX'] = value.split(',')[0] fixture.data['posY'] = value.split(',')[1] elif tag == 'focus': fixture.data['focusX'] = value.split(',')[0] fixture.data['focusY'] = value.split(',')[1] elif tag == 'dimmer': fixture.data['dimmer_uuid'] = self.interface.get(parsed_input[2])[0].uuid fixture.data['dimmer_channel'] = parsed_input[3] # Otherwise just set it else: fixture.data[tag] = value fixture.save() self.interface.update_this(parsed_input[0]) def fixture_address(self, parsed_input): fixtures = self.interface.get(parsed_input[0]) if len(fixtures) > 1 and parsed_input[2] != 'auto': print('Error: You must specify auto if you address more than ' 'one fixture.') else: registry = plot.DmxRegistry(self.plot_file, parsed_input[1]) for fixture in fixtures: registry.address(fixture, parsed_input[2]) self.interface.update_this(parsed_input[0]) def fixture_purge(self, parsed_input): fixtures = self.interface.get(parsed_input[0]) for fixture in fixtures: registry = plot.DmxRegistry(self.plot_file, fixture.data['universe']) registry.unaddress(fixture) fixture_list = plot.FixtureList(self.plot_file) fixture_list.remove(fixture) def registry_list(self, parsed_input): try: registry = plot.DmxRegistry(self.plot_file, parsed_input[0]) for channel in registry.registry: uuid = registry.registry[channel][0] func = registry.registry[channel][1] print(str(format(channel, '03d'))+' uuid: '+uuid+', func: '+func) except IndexError: print('You need to specify a DMX registry!') def cue_list(self, parsed_input): cues = plot.CueList(self.plot_file) self.interface.clear() for cue in cues.cues: cue_type = cue.data['type'] cue_location = cue.data['location'] print('\033[4m'+str(cue.key)+'\033[0m ('+cue_type+') at '+ cue_location) self.interface.append(cue.key, cue) def cue_new(self, parsed_input): cue = plot.Cue(self.plot_file) cue.data['type'] = parsed_input[0] cue.data['location'] = parsed_input[1] cue.save(self.plot_file) def cue_remove(self, parsed_input): cues = plot.CueList(self.plot_file) removal_candidates = self.interface.get(parsed_input[0]) for rc in removal_candidates: cues.remove(self.plot_file, rc.uuid) def cue_set(self, parsed_input): cues_to_change = self.interface.get(parsed_input[0]) for cue in cues_to_change: cue.data[parsed_input[1]] = parsed_input[2] cue.save(self.plot_file) def cue_get(self, parsed_input): cues_to_get = self.interface.get(parsed_input[0]) for cue in cues_to_get: if parsed_input[1] in cue.data: print(cue.data[parsed_input[1]]) else: print(None) def cue_getall(self, parsed_input): cues_to_get = self.interface.get(parsed_input[0]) for cue in cues_to_get: for data_item in cue.data: print(data_item+': '+cue.data[data_item]) def cue_moveafter(self, parsed_input): cues = plot.CueList(self.plot_file) cues.move_after(self.plot_file, int(parsed_input[0]), int(parsed_input[1])) def cue_movebefore(self, parsed_input): cues = plot.CueList(self.plot_file) cues.move_before(self.plot_file, int(parsed_input[0]), int(parsed_input[1])) def get_context(): return EditorContext() PKEO7H`,,pylux/context/__init__.py"""Additional contexts for Pylux editor.""" PKww7H\9]33pylux/context/plotter.py# plotter.py is part of Pylux # # Pylux is a program for the management of lighting documentation # Copyright 2015 Jack Page # # Pylux 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. # # Pylux 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, see . """Generate SVG lighting plots. Context that provides commands to create lighting plot images in SVG format. """ from pylux.context.context import Context import os.path import logging from tqdm import tqdm import xml.etree.ElementTree as ET import pylux.plot as plot import pylux.clihelper as clihelper import pylux.reference as reference from pylux import get_data class ImagePlot: def __init__(self, plot_file, options): self.fixtures = plot.FixtureList(plot_file) self.image_plot = ET.Element('svg') self.options = options def verify_fixture(self, fixture): errors = [] if 'posX' not in fixture.data or 'posY' not in fixture.data: errors.append('No position') if 'focusX' not in fixture.data or 'focusY' not in fixture.data: errors.append('No focus') if 'gel' not in fixture.data: errors.append('No gel') if 'symbol' not in fixture.data: errors.append('No symbol') elif fixture.data['symbol'] == 'PHANTOM': errors.append('Phantom fixture') if len(errors) == 0: return True else: return errors def add_fixtures(self): for fixture in tqdm(self.fixtures.fixtures): if self.verify_fixture(fixture) == True: fixture.data['rotation'] = fixture.generate_rotation() if not fixture.generate_colour(): fixture.data['colour'] = '#000000' else: fixture.data['colour'] = fixture.generate_colour() symbol_name = fixture.data['symbol'] posX = fixture.data['posX'] posY = fixture.data['posY'] rotation = fixture.data['rotation'] colour = fixture.data['colour'] symbol = plot.FixtureSymbol(get_data('symbol/'+symbol_name+'.svg')) symbol.prepare(posX, posY, rotation, colour) self.image_plot.append(symbol.image_group) else: pass def add_beams(self): beam_group = ET.SubElement(self.image_plot, 'g') beam_group.set('class', 'beam-group') for fixture in self.fixtures.fixtures: if self.verify_fixture(fixture) == True: posX = str(float(fixture.data['posX'])*1000) posY = str(float(fixture.data['posY'])*1000) focusX = str(float(fixture.data['focusX'])*1000) focusY = str(float(fixture.data['focusY'])*1000) beam = ET.SubElement(beam_group, 'path') beam.set('d', 'M '+posX+' '+posY+' L '+focusX+' '+focusY) if self.options['beam_colour'] == 'auto': beam.set('stroke', fixture.data['colour']) else: beam.set('stroke', reference.gel_colours[self.options['beam_colour']]) beam.set('stroke-dasharray', '10,10') beam.set('stroke-width', self.options['beam_width']) class PlotOptions(): def __init__(self): self.options = { 'beam_colour': 'Black', 'beam_width': '6', 'show_beams': 'true'} def set(self, option, value): self.options[option] = value def get(self, option): if option in self.options: return self.options[option] else: return None class PlotterContext(Context): def __init__(self): self.name = 'plotter' self.init_commands() self.register('pn', self.plot_new, 0) self.register('pw', self.plot_write, 1) self.register('os', self.option_set, 2) self.register('og', self.option_get, 1) self.register('ol', self.option_list, 0) self.init_plot() def init_plot(self): self.options = PlotOptions() def plot_new(self, parsed_input): self.image_plot = ImagePlot(self.plot_file, self.options.options) if self.options.options['show_beams'] == 'true': self.image_plot.add_beams() self.image_plot.add_fixtures() def plot_write(self, parsed_input): output_tree = ET.ElementTree(self.image_plot.image_plot) output_tree.write(os.path.expanduser(parsed_input[0])) def option_set(self, parsed_input): options.set(parsed_input[0], parsed_input[1]) def option_get(self, parsed_input): print(options.get(parsed_input[0])) def option_list(self): for option in options.options: print(option+': '+options.options[option]) def get_context(): return PlotterContext() PK&6H)pylux/template/cuelist.tex\documentclass[12pt]{article} \usepackage[a4paper,landscape,hmargin=0.79in,vmargin=0.79in]{geometry} \usepackage[parfill]{parskip} \usepackage{longtable} \pagestyle{empty} \linespread{1.5} \setlength{\doublerulesep}{\arrayrulewidth} \renewcommand{\tabcolsep}{0.8ex} \setlength\LTleft{0pt} \setlength\LTright{0pt} \begin{document} \newcounter{LX} \newcounter{SX} \newcounter{VX} \setcounter{LX}{1} \setcounter{SX}{1} \setcounter{VX}{1} \begin{longtable}{@{\extracolsep{\fill}\hspace{\tabcolsep}} l l l l } \hline {\bf Cue \#} & \multicolumn{1}{c}{\bf Cue} & \multicolumn{1}{c}{\bf Description} & \multicolumn{1}{c}{\bf Notes} \\* \hline\hline {% for cue in cues %} {{ cue.data['type'] }}\arabic{{ '{' }}{{ cue.data['type'] }}{{ '}' }} & {{ cue.data['location'] }} & {{ cue.data['description'] }} & {{ cue.data['notes'] }} \\ \addtocounter{{ '{' }}{{ cue.data['type'] }}{{ '}' }}{1} {% endfor %} \end{longtable} \end{document} PK&6H)pylux/template/cuelist.jinja\documentclass[12pt]{article} \usepackage[a4paper,landscape,hmargin=0.79in,vmargin=0.79in]{geometry} \usepackage[parfill]{parskip} \usepackage{longtable} \pagestyle{empty} \linespread{1.5} \setlength{\doublerulesep}{\arrayrulewidth} \renewcommand{\tabcolsep}{0.8ex} \setlength\LTleft{0pt} \setlength\LTright{0pt} \begin{document} \newcounter{LX} \newcounter{SX} \newcounter{VX} \setcounter{LX}{1} \setcounter{SX}{1} \setcounter{VX}{1} \begin{longtable}{@{\extracolsep{\fill}\hspace{\tabcolsep}} l l l l } \hline {\bf Cue \#} & \multicolumn{1}{c}{\bf Cue} & \multicolumn{1}{c}{\bf Description} & \multicolumn{1}{c}{\bf Notes} \\* \hline\hline {% for cue in cues %} {{ cue.data['type'] }}\arabic{{ '{' }}{{ cue.data['type'] }}{{ '}' }} & {{ cue.data['location'] }} & {{ cue.data['description'] }} & {{ cue.data['notes'] }} \\ \addtocounter{{ '{' }}{{ cue.data['type'] }}{{ '}' }}{1} {% endfor %} \end{longtable} \end{document} PKUW)HEDpylux/symbol/par64mfl.svg PKUW)H 1pylux/symbol/p650.svg PK$)H }9pylux/symbol/patt23.svg PKЅ)Ha=pylux/symbol/generic.svg PK7H6%pylux-0.1.0.dist-info/DESCRIPTION.rstPylux ===== [![PyPI](https://img.shields.io/pypi/v/pylux.svg)](https://pypi.python.org/pypi/pylux/) [![PyPI](https://img.shields.io/pypi/format/pylux.svg)](https://pypi.python.org/pypi/pylux#license) Pylux is a program for the management of lighting documentation written in Python. It uses its XML files called plots to store information about a lighting project. The Pylux program comes with multiple 'contexts'. Each context has a specific command and feature set. Currently included are ``editor`` which allows for the editing of the aforementioned XML plots, ``reporter`` which creates reports from Jinja2 templates and ``plotter`` which creates SVG diagrams of the plot. Installation and Dependencies ----------------------------- Pylux is written in Python 3; you will need the Python 3 interpreter to run it. You can either download a source distribution, then install by running ``` sudo python setup.py install ``` or you can install directly from the PyPI using pip: ``` pip install pylux ``` To install either way you will need Python, setuptools and pip, which are available from http://python.org or you can install them using your package manager: ``` sudo apt install python3 python3-pip python3-setuptools ``` ``` sudo pacman -S python python-pip python-setuptools ``` Contributing ------------ If you are interested in contributing towards this project, there are many ways in which you can help: + [Python] writing code; + [XML] making fixture templates; + [SVG] making fixture symbols; + [Jinja] making ``reporter`` templates; + [English] submitting bug reports and feature requests. License ------- Pylux is licensed under the GNU GPL v3.0. A full copy of the license is available in the file ``COPYING``. PK7HP//&pylux-0.1.0.dist-info/entry_points.txt[console_scripts] pylux = pylux.__main__:main PK7H9 EE#pylux-0.1.0.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Intended Audience :: Information Technology", "Topic :: Office/Business", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.commands": {"wrap_console": {"pylux": "pylux.__main__:main"}}, "python.details": {"contacts": [{"email": "jdpboc98@gmail.com", "name": "Jack Page", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://os.pwrg.uk/software/pylux"}}, "python.exports": {"console_scripts": {"pylux": "pylux.__main__:main"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["lighting", "theatre", "stage", "tech"], "license": "GPLv3+", "metadata_version": "2.0", "name": "pylux", "run_requires": [{"requires": ["jinja2", "tqdm"]}], "summary": "A program for managing lighting documentation.", "version": "0.1.0"}PK7H