PK .UHԝ* 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 .
from importlib import import_module
def get_context(context_name):
module_name = 'pylux.context.'+context_name
context_module = import_module(module_name)
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)
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 len(user_input) > 0:
if inputs[0] == '::':
globals_dict = context.get_globals()
context = get_context(CONFIG['cli']['default-context'])
context.set_globals(globals_dict)
elif inputs[0][0] == ':':
globals_dict = context.get_globals()
try:
context = get_context(inputs[0].split(':')[1])
except ImportError or AttributeError:
context.log(30, 'Context does not exist')
else:
context.set_globals(globals_dict)
elif inputs[0] in context.commands:
context.process(inputs)
else:
context.log(30, 'Command does not exist')
if __name__ == 'pylux_root':
main()
PK юZH5% pylux/settings.conf[cli]
# The context to load on launch. Also the context accessed using the
# :: command.
default-context = editor
# The tabulate printing format to use for help tables. Must be one of
# [plain, simple, grid, fancy_grid, pipe, psql, orgtbl, rst]
help-table-format = orgtbl
# Whether to show UUIDs when listing fixtures. Must be True or False.
show-uuids = False
[plotter]
# The default options loaded by plotter. These can all be changed on
# a per-usage basis in the plotter context.
# Printed page type, ISO standards only. A[0-4]
paper-size = A4
# Printed orientation [landscape, portrait]
orientation = landscape
# Page margins in mm
margin = 10
# Drawing scale 1:int
scale = 50
# Background SVG image, centred on the plaster/centre intersection
background-image = plot-background.svg
# Standard line weights in mm. See USITT guidelines for information
# on what each weighting is used for.
line-weight-light = 0.4
line-weight-medium = 0.6
line-weight-heavy = 0.8
# Where to display the title block. [corner, sidebar, None]
title-block = corner
# If sidebar title is selected, the width of it as a percentage of
# the page width.
vertical-title-width-pc = 0.1
# If sidebar title is selected, the min and max widths of it.
vertical-title-min-width = 50
vertical-title-max-width = 100
# If corner title is selected, the width and height of it as a
# percentage of the page width.
corner-title-width-pc = 0.25
corner-title-height-pc = 0.25
# If corner title is selected, the min and max widths and heights of it.
corner-title-min-width = 70
corner-title-max-width = 120
corner-title-min-height = 40
corner-title-max-height = 80
# Centre line dasharray, probably best not to change this if you don't
# know what it is.
centre-line-dasharray = 4, 0.5, 1, 1.5
# Whether the centre line should extend over the page margins [True, False]
centre-line-extend = False
# Whether the plaster line should extend over the page margins [True, False]
plaster-line-extend = False
# How much space should be left between the plaster line and margin (in
# real life metres)
plaster-line-padding = 0.5
# Plaster line dasharray, again probably best not to change this.
plaster-line-dasharray = 3, 0.7
# Whether to show focus lines for fixtures (not USITT standard)
show-beams = False
# SVG dasharray to use for the beams
beam-dasharray = 1, 1
[advanced]
# The names to use for different logging levels
log-50 = CRITICAL
log-40 = ERROR
log-30 = WARNING
log-20 = INFO
log-10 = DEBUG
log-1 = TRACE
PK M>HTg~ pylux/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)
if args_list[-1] == '':
args_list.pop(-1)
return args_list
def get_fixture_print(fixture):
"""Return a string that represents this fixture the best.
If the fixture has a name tag, return that, if not and it has a
type tag, return that, otherwise return the uuid.
"""
if 'name' in fixture.data:
return fixture.data['name']
elif 'type' in fixture.data:
return fixture.data['type']
else:
return fixture.uuid
PK T7H_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']
PK AOH>| >| pylux/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 .
usitt_line_weights = {
'scenery' : 'line-weight-light',
'leader' : 'line-weight-light',
'dimension' : 'line-weight-light',
'masking' : 'line-weight-medium',
'drop' : 'line-weight-medium',
'centre' : 'line-weight-medium',
'plaster' : 'line-weight-medium',
'batten' : 'line-weight-heavy',
'fixture' : 'line-weight-heavy',
'architecture' : 'line-weight-heavy',
'border' : 'line-weight-heavy'}
paper_sizes = {
'A0' : (841, 1189),
'A1' : (594, 841),
'A2' : (420, 594),
'A3' : (297, 420),
'A4' : (210, 297)}
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 QTHef ef
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
from pylux.exception import *
class PlotFile:
"""Manage the Pylux plot project file.
Attributes:
path: the path of the project file.
tree: the parsed XML tree.
root: the root element of the XML tree.
"""
def __init__(self, path=None):
"""Initialise the PlotFile instance.
Prepares the instance of PlotFile for a file to be loaded
into it. If the path argument is given, loads the plot file
at that location on the filesystem and parses its XML tree
into an accessible element.
Args:
path: the full system path of the file to load.
Raises:
FileNotFoundError: if the file does not exist on the
filesystem.
FileFormatError: if the XML parser raises a ParseError.
"""
self.path = path
if self.path is None:
self.new()
else:
self.load(self.path)
def load(self, path):
"""Load a project file.
Args:
path: the location of the file to load.
Raeses:
FileNotFoundError: if the file can not be found in the
directory hierarchy.
FileFormatError: if the XML parser raises a ParseError.
"""
try:
self.tree = ET.parse(path)
except ET.ParseError:
raise FileFormatError
else:
self.root = self.tree.getroot()
self.path = path
def write(self):
"""Save the project file to its original location."""
self.tree.write(self.path, encoding='UTF-8', xml_declaration=True)
def write_to(self, path):
"""Save the project file to a new location.
Args:
path: the location to save the file to.
"""
self.path = path
self.write()
def new(self):
"""Create a new plot file in the buffer.
Overwrite the current file buffer with a new empty plot file.
Create a new ElementTree in tree and set the root to be a
plot element.
"""
self.root = ET.Element('plot')
self.tree = ET.ElementTree(self.root)
self.path = None
class DmxRegistry:
"""Manage DMX registries.
Now with multiple functions per channel! Exclusive!!
"""
def __init__(self, plot_file, universe):
self.registry = {}
self.universe = universe
self._xml_registry = None
# Find the corresponding XML registry
for xml_registry in plot_file.root.findall('registry'):
if xml_registry.get('universe') == self.universe:
self._xml_registry = xml_registry
break
# If there isn't one make a new one
if self._xml_registry is None:
self._xml_registry = ET.Element('registry')
self._xml_registry.set('universe', self.universe)
plot_file.root.append(self._xml_registry)
# Otherwise populate the Python registry
else:
for xml_channel in self._xml_registry.findall('channel'):
address = int(xml_channel.get('address'))
self.registry[address] = []
for xml_function in xml_channel.findall('function'):
fixture_uuid = xml_function.get('uuid')
function = xml_function.text
self.registry[address].append((fixture_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.
"""
# Search a channel with address in XML
def get_xml_channel(self, address):
for channel in self._xml_registry.findall('channel'):
found_address = channel.get('address')
if found_address == str(address):
return channel
# Add an empty channel object
def add_xml_channel(self, address):
new_channel = ET.SubElement(self._xml_registry, 'channel')
new_channel.set('address', str(address))
# Iterate over the Python registry
for address in self.registry:
if self.registry[address] != None:
xml_channel = get_xml_channel(self, address)
# If there is no channel with this address, make one
if xml_channel == None:
add_xml_channel(self, address)
xml_channel = get_xml_channel(self, address)
# Clear the channel if it does exist
else:
for function in xml_channel.findall('function'):
xml_channel.remove(function)
# Iterate over functions and add to XML
for function in self.registry[address]:
fixture_uuid = function[0]
fixture_function = function[1]
xml_function = ET.SubElement(xml_channel, 'function')
xml_function.set('uuid', fixture_uuid)
xml_function.text = fixture_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:
if self.registry[address] != None:
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 add_function(self, address, fixture_uuid, function):
if address in self.registry:
self.registry[address].append((fixture_uuid, function))
else:
self.registry[address] = [(fixture_uuid, function)]
self._save()
def remove_function(self, address, uuid):
"""Remove a function from an address.
Remove the function from the channel that has the given
fixture UUID.
"""
for function in self.registry[address]:
if function[0] == uuid:
self.registry[address].remove(function)
self._save()
def get_functions(self, address):
if address in self.registry:
return self.registry[address]
else:
return None
class RegistryList:
def __init__(self, plot_file):
xml_registries = plot_file.root.findall('registry')
self.registries = []
for xml_registry in xml_registries:
registry = DmxRegistry(plot_file, xml_registry.get('universe'))
self.registries.append(registry)
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._root = plot_file.root
self.fixtures = []
for xml_fixture in self._root.findall('fixture'):
fixture = Fixture(plot_file, uuid=xml_fixture.get('uuid'))
self.fixtures.append(fixture)
def remove(self, fixture):
"""Remove a fixture from the plot."""
self._root.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
def assign_usitt_numbers(self):
count = 1
hung = []
for fixture in self.fixtures:
if 'posY' in fixture.data:
hung.append(fixture)
for fixture in sorted(hung, key=lambda fixture: fixture.data['posY']):
fixture.set_data('usitt_key', str(count))
count = count+1
for fixture in self.fixtures:
if fixture not in hung:
fixture.set_data('usitt_key', str(None))
def get_fixtures_for_dimmer(self, dimmer):
"""Get a list of fixtures controlled by this fixture.
If this is a dimmer fixture, get a list of fixture objects
that the dimmer controls.
"""
if 'is_dimmer' not in dimmer.data:
return []
if dimmer.data['is_dimmer'] != 'True':
return []
controlled = []
for fixture in self.fixtures:
if 'dimmer_uuid' in fixture.data:
if fixture.data['dimmer_uuid'] == dimmer.uuid:
controlled.append(fixture)
return controlled
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, template=None, src_fixture=None):
"""Create a new fixture in Python.
If uuid is given, load data from the plot file from the fixture
with the corresponding UUID. If template is given, create a new
fixture based on the template file. If src_fixture is given,
copy the contents of an existing fixture into this one.
Args:
plot_file: the PlotFile object containing the plot.
uuid: the UUID of the fixture to load from XML.
src_fixture: the Fixture object to copy into this one.
"""
self.data = {}
if uuid != None:
self.uuid = uuid
xml_fixtures = plot_file.root.findall('fixture')
for xml_fixture in plot_file.root.findall('fixture'):
if uuid == xml_fixture.get('uuid'):
self._xml_fixture = xml_fixture
for data_item in self._xml_fixture:
self.data[data_item.tag] = data_item.text
self._save()
elif template != None:
self._new_from_template(template)
self._xml_fixture = ET.Element('fixture')
self._xml_fixture.set('uuid', self.uuid)
plot_file.root.append(self._xml_fixture)
self._save()
elif src_fixture != None:
self._new_from_fixture(src_fixture)
self._xml_fixture = ET.Element('fixture')
self._xml_fixture.set('uuid', self.uuid)
plot_file.root.append(self._xml_fixture)
self._save()
def _new_from_template(self, template_file):
"""Load information from a template into this fixture.
Args:
template: the name of the template the new fixture should copy.
"""
self.uuid = str(uuid.uuid4())
src_tree = ET.parse(template_file)
src_root = src_tree.getroot()
for xml_data in src_root:
self.data[xml_data.tag] = xml_data.text
def _new_from_fixture(self, src_fixture):
"""Copy the contents of another fixture into this one.
Make a verbatim copy of an existing fixture, except create
a new UUID for this fixture.
Args:
src_fixture: the source Fixture to be copied.
"""
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.SubElement(self._xml_fixture, tag)
new_data_item.text = value
# 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 self.data[data_name] == '':
self._xml_fixture.remove(data_item)
def set_data(self, name, value):
"""Set the value of a piece of data."""
self.data[name] = value
self._save()
def get_data(self, name):
"""Get the value of a piece of data."""
if name in self.data:
return self.data[name]
else:
return None
def address(self, registry, start_address):
address = start_address
for function in self.data['dmx_functions'].split(','):
registry.add_function(address, self.uuid, function)
address = address+1
def unaddress(self, registries):
for registry in registries.registries:
for address in registry.registry:
registry.remove_function(address, self.uuid)
def get_rotation(self):
if ('posX' not in self.data or 'posY' not in self.data
or 'focusX' not in self.data or 'focusY' not in self.data):
return None
else:
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 get_colour(self):
if 'gel' not in self.data:
return None
elif self.data['gel'] in reference.gel_colours:
return reference.gel_colours[self.data['gel']]
else:
return None
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._root = plot_file.root
self.meta = {}
for metaitem in self._root.findall('metadata'):
self.meta[metaitem.get('name')] = metaitem.text
def set_data(self, name, value):
self.meta[name] = value
self._save()
def _save(self):
"""Save the metadata dictionary to XML."""
# Add a new meta item
def add_xml_meta(self, name, value):
new_metadata = ET.Element('metadata')
new_metadata.set('name', name)
new_metadata.text = value
self._root.append(new_metadata)
# Edit an existing meta item
def edit_xml_meta(self, xml_meta, new_value):
xml_meta.text = new_value
# Search for meta in XML
def get_xml_meta(self, name):
try:
for metaitem in self._root.findall('metadata'):
if metaitem.get('name') == 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, xml_meta, self.meta[metaitem])
# Iterate over XML meta object to remove empty values
for metaitem in self._root.findall('metadata'):
name = metaitem.get('name')
if self.meta[name] == None:
self._root.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
self._xml_cue = ET.Element('cue')
self._xml_cue.set('uuid', self.uuid)
plot_file.root.append(self._xml_cue)
else:
self.uuid = UUID
for xml_cue in plot_file.root.findall('cue'):
if xml_cue.get('uuid') == self.uuid:
self._xml_cue = xml_cue
self.key = int(xml_cue.get('key'))
for cue_data in xml_cue:
self.data[cue_data.tag] = cue_data.text
def set_data(self, name, value):
"""Set the value of name to value and save to XML."""
self.data[name] = value
self._save()
def get_data(self, name):
"""Get the value of name."""
if name in self.data:
return self.data[name]
else:
return None
def _save(self):
"""Save the cue to XML."""
# Find data tags already in XML
data_in_xml = []
for data_item_xml in self._xml_cue:
data_in_xml.append(data_item_xml.tag)
# Set the sorting key
self._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(self._xml_cue, data_item)
new_data_item.text = self.data[data_item]
# Otherwise edit existing data
else:
for data_item_xml in self._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 self._xml_cue:
if self.data[data_item_xml.tag] is None:
self._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._root = plot_file.root
self.cues = []
for xml_cue in self._root.findall('cue'):
cue_uuid = xml_cue.get('uuid')
cue = Cue(plot_file, UUID=cue_uuid)
self.cues.append(cue)
def remove(self, cue):
"""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.
"""
self._root.remove(cue._xml_cue)
def move_after(self, 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()
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()
def move_before(self, 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()
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()
def assign_identifiers(self):
count = {'LX': 1, 'SX': 1, 'VX': 1}
for cue in sorted(self.cues, key=lambda cue: cue.key):
cue_type = cue.data['type']
cue.set_data('identifier', cue_type+str(count[cue_type]))
count[cue_type] = count[cue_type]+1
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)
PK EO7HO. . 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()
PK G; ; 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()
PK `SHE~K K 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 = configparser.ConfigParser()
config.read([get_data('settings.conf', location='root'),
get_data('settings.conf', location='home')])
# Handle verbosity
verbosity_dict = {
None: 30,
1: 20,
2: 10,
3: 1}
print('Logging level is '+config['advanced']['log-'+str(verbosity_dict[launch_args.verbose])])
# Load plot file
plot_file = plot.PlotFile()
if launch_args.file != None:
plot_file.load(launch_args.file)
print('Using plot file '+plot_file.path)
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]}
if launch_args.gui == True:
print('Running in GUI mode\n')
runpy.run_module('pylux.gui', 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 yLH]
]j j pylux/__init__.py"""Pylux is a suite for the management of lighting documentation"""
import os
import pkg_resources
__version__ = pkg_resources.get_distribution('pylux').version
_ROOT = os.path.abspath(os.path.dirname(__file__))
_HOME = os.path.expanduser('~/.pylux')
def get_data(path, location='auto'):
if location == 'auto':
if os.path.isfile(os.path.join(_HOME, path)):
return os.path.join(_HOME, path)
else:
return os.path.join(_ROOT, path)
elif location == 'root':
return os.path.join(_ROOT, path)
elif location == 'home':
return os.path.join(_HOME, path)
PK T7Hi# # 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()
PK lIHi8 8 pylux/exception.py# exception.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 .
"""Exceptions used by Pylux."""
class FileFormatError(Exception):
pass
PK UW)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()
PK JBHj