├── ies ├── __init__.py └── ies.py ├── preview ├── __init__.py └── preview.py ├── icon ├── exit.gif ├── exit.png ├── moveup.gif ├── moveup.png ├── collapsed.gif ├── collapsed.png ├── expanded.gif ├── expanded.png ├── movedown.gif ├── movedown.png ├── visible.gif ├── visible.png ├── exit_small.gif ├── exit_small.png ├── notvisible.gif ├── notvisible.png ├── expanded_small.gif ├── expanded_small.png ├── movedown_small.gif ├── movedown_small.png ├── moveup_small.gif ├── moveup_small.png ├── visible_small.gif ├── visible_small.png ├── collapsed_small.gif ├── collapsed_small.png ├── notvisible_small.gif └── notvisible_small.png ├── img ├── gui.png ├── Compare.PNG ├── diagram.png └── iesgenExample.png ├── layer ├── __init__.py ├── layerpanel.py ├── operations.py └── layers.py ├── __init__.py ├── util ├── __init__.py ├── menuitems.py ├── fileutils.py ├── mathtuils.py └── uiGeneric.py ├── LICENSE ├── .gitignore ├── README.md └── easyIES.py /ies/__init__.py: -------------------------------------------------------------------------------- 1 | from .ies import * -------------------------------------------------------------------------------- /preview/__init__.py: -------------------------------------------------------------------------------- 1 | from .preview import * -------------------------------------------------------------------------------- /icon/exit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/exit.gif -------------------------------------------------------------------------------- /icon/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/exit.png -------------------------------------------------------------------------------- /img/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/img/gui.png -------------------------------------------------------------------------------- /icon/moveup.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/moveup.gif -------------------------------------------------------------------------------- /icon/moveup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/moveup.png -------------------------------------------------------------------------------- /img/Compare.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/img/Compare.PNG -------------------------------------------------------------------------------- /img/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/img/diagram.png -------------------------------------------------------------------------------- /icon/collapsed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/collapsed.gif -------------------------------------------------------------------------------- /icon/collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/collapsed.png -------------------------------------------------------------------------------- /icon/expanded.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/expanded.gif -------------------------------------------------------------------------------- /icon/expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/expanded.png -------------------------------------------------------------------------------- /icon/movedown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/movedown.gif -------------------------------------------------------------------------------- /icon/movedown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/movedown.png -------------------------------------------------------------------------------- /icon/visible.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/visible.gif -------------------------------------------------------------------------------- /icon/visible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/visible.png -------------------------------------------------------------------------------- /icon/exit_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/exit_small.gif -------------------------------------------------------------------------------- /icon/exit_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/exit_small.png -------------------------------------------------------------------------------- /icon/notvisible.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/notvisible.gif -------------------------------------------------------------------------------- /icon/notvisible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/notvisible.png -------------------------------------------------------------------------------- /layer/__init__.py: -------------------------------------------------------------------------------- 1 | from .layerpanel import * 2 | from .layers import * 3 | from .operations import * 4 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from ies import * 2 | from layer import * 3 | from util import * 4 | from preview import * 5 | -------------------------------------------------------------------------------- /icon/expanded_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/expanded_small.gif -------------------------------------------------------------------------------- /icon/expanded_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/expanded_small.png -------------------------------------------------------------------------------- /icon/movedown_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/movedown_small.gif -------------------------------------------------------------------------------- /icon/movedown_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/movedown_small.png -------------------------------------------------------------------------------- /icon/moveup_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/moveup_small.gif -------------------------------------------------------------------------------- /icon/moveup_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/moveup_small.png -------------------------------------------------------------------------------- /icon/visible_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/visible_small.gif -------------------------------------------------------------------------------- /icon/visible_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/visible_small.png -------------------------------------------------------------------------------- /img/iesgenExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/img/iesgenExample.png -------------------------------------------------------------------------------- /icon/collapsed_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/collapsed_small.gif -------------------------------------------------------------------------------- /icon/collapsed_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/collapsed_small.png -------------------------------------------------------------------------------- /icon/notvisible_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/notvisible_small.gif -------------------------------------------------------------------------------- /icon/notvisible_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickmcdonald/ies-generator/HEAD/icon/notvisible_small.png -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | from .fileutils import * 2 | from .mathtuils import * 3 | from .uiGeneric import * 4 | from .menuitems import * 5 | -------------------------------------------------------------------------------- /util/menuitems.py: -------------------------------------------------------------------------------- 1 | LAYERS = ["Full 360", "Angle Range"] 2 | 3 | OPERATIONS = ["Adjust Intensity", "Interpolate", "Simple Curve", "Noise", "Mask"] 4 | 5 | INTERPMETHODS = [ "Smooth", "Linear", "Sharp", "Root"] 6 | 7 | MIXMETHODS = [ "Override", "Multiply", "Divide", "Add", "Subtract", "Min", "Max"] 8 | 9 | BASEIESTYPES = ["100% Intensity", "50% Intensity", "0% Intensity"] 10 | -------------------------------------------------------------------------------- /util/fileutils.py: -------------------------------------------------------------------------------- 1 | from tkinter import filedialog 2 | from ies import * 3 | import os 4 | import sys 5 | 6 | def resourcePath(rel): 7 | try: 8 | basePath = sys._MEIPASS 9 | except Exception: 10 | basePath = os.path.abspath(".") 11 | return os.path.join(basePath, rel) 12 | 13 | def exportIES(ies, clamp): 14 | filename = filedialog.asksaveasfilename(title="Select Export Location", filetypes=(("ies files","*.ies"),("all files","*.*"))) 15 | if filename and len(filename) > 0: 16 | if filename.endswith(".ies"): 17 | f = open(filename, 'w+') 18 | else: 19 | f = open(filename + ".ies", 'w+') 20 | print(ies.getIESOutput(clamp), file=f) 21 | 22 | def importIES(): 23 | filename = filedialog.askopenfilename(title="Select IES file", filetypes=(("ies files","*.ies"),("all files","*.*"))) 24 | if filename and len(filename) > 0: 25 | f = open(filename, 'r+') 26 | return readIESData(f) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 nickmcdonald 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /layer/layerpanel.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from ies import * 3 | from .layers import * 4 | from util import * 5 | import math 6 | 7 | 8 | class LayersPanel(ScrollFrame): 9 | 10 | def __init__(self, parent): 11 | ScrollFrame.__init__(self, parent) 12 | self.parent = parent 13 | 14 | self.columnconfigure(0, weight=1) 15 | 16 | self.layers = [] 17 | 18 | self.rowCounter = 3 19 | 20 | self.selection = StringVar() 21 | self.selection.set("Add Layer") 22 | self.selection.trace_add('write', self.addLayer) 23 | PanelLabel(self.internalPanel).grid(column=0, row=0) 24 | OptionButton(self.internalPanel, "Add Layer", self.selection, LAYERS).grid(column=0, row=1, sticky=N) 25 | PanelLabel(self.internalPanel).grid(column=0, row=2) 26 | 27 | def addLayer(self, *args): 28 | s = self.selection.get() 29 | if s != "Add Layer": 30 | if s == "Full 360": 31 | mod = Full360(self.internalPanel) 32 | elif s == "Angle Range": 33 | mod = VerticalRange(self.internalPanel) 34 | 35 | mod.grid(column=0,row=self.rowCounter, sticky=EW) 36 | self.layers.append(mod) 37 | self.rowCounter += 1 38 | 39 | self.update() 40 | 41 | def removeLayer(self, layer): 42 | self.layers.remove(layer) 43 | self.update() 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # build 51 | build/ 52 | dist/ 53 | 54 | *.stackdump 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # vscode: 66 | .vscode/ 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # celery beat schedule file 88 | celerybeat-schedule 89 | 90 | # SageMath parsed files 91 | *.sage.py 92 | 93 | # Environments 94 | .env 95 | .venv 96 | env/ 97 | venv/ 98 | ENV/ 99 | env.bak/ 100 | venv.bak/ 101 | 102 | # Spyder project settings 103 | .spyderproject 104 | .spyproject 105 | 106 | # Rope project settings 107 | .ropeproject 108 | 109 | # mkdocs documentation 110 | /site 111 | 112 | # mypy 113 | .mypy_cache/ 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This Project has been depreciated! Please check out its successor [CNDL](https://cndl.io) it is better in every way! 2 | 3 | 4 | # Easy IES 5 | 6 | A free and open source program that you can use to create custom IES files 7 | 8 | These files can be used in game engines like unreal and unity, or other rendering applications to greatly increase the quality of your lighting. 9 | IES files are great, and you can find thousands of them online, but it can be very hard to find one that fits your needs. Other programs 10 | for creating them are either overly complicated paid products, or not very usable. 11 | 12 | ![IES Comparison](https://github.com/nickmcdonald/ies-generator/blob/master/img/Compare.PNG?raw=true "Compare") 13 | 14 | # Interface 15 | 16 | This program allows you to create custom IES files using a simple, yet powerful user interface. 17 | 18 | ![interface](https://github.com/nickmcdonald/ies-generator/blob/master/img/gui.png?raw=true "Interface") 19 | 20 | ## Layers 21 | 22 | Layers are used to organize your project and to select which angles of the light to modify. The layers are then mixed together in order (top to bottom) using the layers mix method. 23 | 24 | ### Full 360 25 | 26 | This is the simplest layer, it applies the operation to all points 27 | 28 | ### Angle Range 29 | 30 | This layer applies the operations to a given range of angles 31 | 32 | ## Operations 33 | 34 | Operations are added to a layer, and applies an effect to the light 35 | 36 | ### Adjust Intensity 37 | 38 | Adjusts the intensity of every point by a percentage 39 | 40 | ### Interpolate 41 | 42 | Blends between 2 intensity values 43 | 44 | ### Simple Curve 45 | 46 | Similar to Interpolate, but adds a middle value and blends between the 3 values 47 | 48 | ### Noise 49 | 50 | Applies noise of a given scale and intensity 51 | 52 | ## Mix Methods 53 | 54 | Each Layer and Operation has its own mix method the mix method is the mathematical 55 | operation that defines how this Layer or Operation blends with the next 56 | 57 | ![Example](https://github.com/nickmcdonald/ies-generator/blob/master/img/iesgenExample.png?raw=true "Examples") 58 | 59 | # Contribution 60 | 61 | Contribution is welcome, feel free to make a pull request or contact me with your ideas. 62 | 63 | If you like Easy IES and would like to support its development, the best way is by a donation. 64 | 65 | [![donate](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8SFL6DVAVMJ8Q) 66 | -------------------------------------------------------------------------------- /util/mathtuils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | from util import * 4 | 5 | def interpolate(a, b, x, method): 6 | if method == "Linear": 7 | return linearInterpolate(a, b, x) 8 | elif method == "Smooth": 9 | return smoothInterpolate(a, b, x) 10 | elif method == "Sharp": 11 | return sharpInterpolate(a, b, x) 12 | elif method == "Root": 13 | return rootInterpolate(a, b, x) 14 | 15 | def linearInterpolate(a, b, x): 16 | return a * (1 - x) + b * x 17 | 18 | def smoothInterpolate(a, b, x): 19 | ft = x * math.pi 20 | f = (1 - math.cos(ft)) / 2 21 | return a * (1 - f) + b * f 22 | 23 | def sharpInterpolate(a, b, x): 24 | return (b - a) * x ** 2 + a 25 | 26 | def rootInterpolate(a, b, x): 27 | return (b - a) * math.sqrt(x) + a 28 | 29 | def getNoise1DProfile(scale, intensity, seed=1): 30 | s = seed 31 | points = [] 32 | for i in range(0, scale): 33 | random.seed(s) 34 | if intensity > 0: 35 | points.append(1-(random.randrange(0,intensity*100,1)/100)) 36 | else: 37 | points.append(1) 38 | s += 1 39 | 40 | points[0] = 1 41 | points[len(points)-1] = 1 42 | return points 43 | 44 | def noise1D(profile, x, method): 45 | for idx, point in enumerate(profile): 46 | x1 = idx / len(profile) 47 | x2 = (idx+1) / len(profile) 48 | if x1 <= x and x <= x2: 49 | if idx+1 < len(profile): 50 | return interpolate(point, profile[idx+1], (x-x1)/(x2-x1), method) 51 | else: 52 | return interpolate(point, profile[idx], (x-x1)/(x2-x1), method) 53 | return 1 54 | 55 | def getNoise2DProfile(xScale, yScale, intensity, seed=1): 56 | s = seed 57 | points = [[]] 58 | for x in range(0, xScale): 59 | for y in range(0, yScale): 60 | random.seed(s) 61 | if intensity > 0: 62 | points[x].append(1-(random.randrange(0,intensity*100,1)/100)) 63 | else: 64 | points[x].append(0) 65 | s += 1 66 | points[x][0] = 1 67 | points[x][len(points[x])-1] = 1 68 | return points 69 | 70 | def noise2D(profile, x, y, method): 71 | for ix in range(0, len(profile)): 72 | for iy in range(0, len(profile[ix])): 73 | x1 = ix / len(profile) 74 | x2 = (ix+1) / len(profile) 75 | y1 = iy / len(profile[ix]) 76 | y2 = (iy+1) / len(profile[ix]) 77 | if x1 <= x and x <= x2 and y1 <= y and y <= y2: 78 | if ix+1 < len(profile) or iy+1 < len(profile[ix]): 79 | i1 = interpolate(profile[ix][iy], profile[ix+1][iy], (x-x1)/(x2-x1), method) 80 | i2 = interpolate(profile[ix][iy+1], profile[ix+1][iy+1], (x-x1)/(x2-x1), method) 81 | return interpolate(i1, i2, (y-y1)/(y2-y1), method) 82 | else: 83 | return 1 84 | return 1 85 | 86 | 87 | def getNoiseProfile(scale, intensity, seed=1): 88 | s = seed 89 | points = [] 90 | for i in range(0, int(scale)): 91 | random.seed(s) 92 | if intensity > 0: 93 | points.append(1-(random.randrange(0,int(intensity*100),1)/100)) 94 | else: 95 | points.append(1) 96 | s += 1 97 | 98 | points[0] = 1 99 | points[len(points)-1] = 1 100 | return points 101 | 102 | def noise(profile, x, method): 103 | for idx, point in enumerate(profile): 104 | x1 = idx / len(profile) 105 | x2 = (idx+1) / len(profile) 106 | if x1 <= x and x <= x2: 107 | if idx+1 < len(profile): 108 | return interpolate(point, profile[idx+1], (x-x1)/(x2-x1), method) 109 | else: 110 | return interpolate(point, profile[idx], (x-x1)/(x2-x1), method) 111 | return 1 112 | -------------------------------------------------------------------------------- /preview/preview.py: -------------------------------------------------------------------------------- 1 | from tkinter import Canvas 2 | from ies import * 3 | from util import * 4 | import math 5 | 6 | 7 | class PreviewRender(PanelFrame): 8 | 9 | def __init__(self, parent, width=500, height=500): 10 | PanelFrame.__init__(self, parent) 11 | self.canvas = Canvas(self, width=width, height=height, bg=PANELCOLOR, highlightthickness=0) 12 | self.canvas.grid(column=0, row=1) 13 | self.columnconfigure(0, weight=1) 14 | self.rowconfigure(0, weight=1) 15 | self.rowconfigure(1, weight=1) 16 | self.width = width 17 | self.height = height 18 | self.ies = None 19 | self.clear() 20 | 21 | self.hAngle = IntVar() 22 | self.hAngle.set(0) 23 | self.hAngle.trace_add('write', self.update) 24 | # self.slider = NumberSlider(self, "View Angle", 0, 360, self.hAngle).grid(column=0,row=0) 25 | 26 | def renderIESPreview(self, ies=None): 27 | self.canvas.delete('all') 28 | if ies != None: 29 | self.ies = ies 30 | 31 | a = self.hAngle.get() 32 | oa = a + 180 33 | if oa > 360: 34 | oa -= 360 35 | 36 | a1 = closest(a, self.ies.angles) 37 | a2 = closest(oa, self.ies.angles) 38 | 39 | for idx, point in enumerate(a1.points): 40 | color = fractionToGrey(point.intensity) 41 | try: 42 | nextAngle = a1.points[idx+1].vAngle 43 | except: 44 | nextAngle = 180 45 | self.canvas.create_polygon([ 46 | self.width/2, self.height/2, 47 | self.width/2 + self.width/2 * math.sin(math.radians(point.vAngle)), self.height/2 + self.height/2 * math.cos(math.radians(point.vAngle)), 48 | self.width/2 + self.width/2 * math.sin(math.radians(nextAngle)), self.height/2 + self.height/2 * math.cos(math.radians(nextAngle)) 49 | ], outline=color, fill=color, width=0) 50 | idx += 1 51 | 52 | for idx, point in enumerate(a2.points): 53 | color = fractionToGrey(point.intensity) 54 | try: 55 | nextAngle = a2.points[idx+1].vAngle 56 | except: 57 | nextAngle = 180 58 | self.canvas.create_polygon([ 59 | self.width/2, self.height/2, 60 | self.width/2 + -self.width/2 * math.sin(math.radians(point.vAngle)), self.height/2 + self.height/2 * math.cos(math.radians(point.vAngle)), 61 | self.width/2 + -self.width/2 * math.sin(math.radians(nextAngle)), self.height/2 + self.height/2 * math.cos(math.radians(nextAngle)) 62 | ], outline=color, fill=color, width=0) 63 | idx += 1 64 | 65 | self.bind("", self.on_resize) 66 | 67 | def on_resize(self,event): 68 | windowx = self.parent.winfo_width() / 2 69 | windowy = self.parent.winfo_height() / 2 70 | if windowx < windowy: 71 | size = windowx 72 | scale = float(windowx)/self.width 73 | else: 74 | size = windowy 75 | scale = float(windowy)/self.height 76 | 77 | self.width = size 78 | self.height = size 79 | 80 | self.canvas.config(width=size, height=size) 81 | self.canvas.scale("all",0,0,scale,scale) 82 | 83 | self.renderIESPreview() 84 | 85 | def update(self, *args): 86 | self.renderIESPreview() 87 | 88 | def clear(self): 89 | self.canvas.delete('all') 90 | 91 | 92 | def closest(angle, angles): 93 | c = angles[0] 94 | for a in angles: 95 | if abs (angle - a.hAngle) < abs (angle - c.hAngle): 96 | c = a 97 | return c 98 | 99 | 100 | def fractionToGrey(x): 101 | if x < 1: 102 | return "#{:02x}{:02x}{:02x}".format(int(x*255),int(x*255),int(x*255)) 103 | else: 104 | return "#{:02x}{:02x}{:02x}".format(int(255),int(255),int(255)) 105 | -------------------------------------------------------------------------------- /easyIES.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | import copy 3 | from preview import * 4 | from layer import * 5 | from ies import * 6 | from util import * 7 | 8 | 9 | DEFAULT_LUMENS = 500 10 | DEFAULT_VRES = 50 11 | DEFAULT_HRES = 1 12 | 13 | 14 | class EasyIESApplication(Tk): 15 | 16 | def __init__(self): 17 | Tk.__init__(self) 18 | 19 | self.title("Easy IES") 20 | self.geometry('900x600') 21 | 22 | self.configure(bg=BGCOLOR) 23 | 24 | self.ies = None 25 | 26 | self.columnconfigure(0, weight=1) 27 | self.columnconfigure(1, weight=1) 28 | self.rowconfigure(0, weight=1) 29 | self.rowconfigure(1, weight=1) 30 | 31 | self.uiPanel = PanelFrame(self) 32 | self.uiPanel.grid(column=0, row=0) 33 | 34 | self.lumens = IntVar() 35 | self.lumens.set(DEFAULT_LUMENS) 36 | self.lumens.trace_add('write', self.update) 37 | self.lumensTI = TextInput(self.uiPanel, "Intensity (Lumens)", self.lumens) 38 | self.lumensTI.grid(column=0, row=0, pady=5) 39 | 40 | self.vRes = IntVar() 41 | self.vRes.set(DEFAULT_VRES) 42 | self.vRes.trace_add('write', self.update) 43 | self.vResTI = TextInput(self.uiPanel, "Resolution", self.vRes) 44 | self.vResTI.grid(column=0, row=1, pady=5) 45 | 46 | self.clamp = BooleanVar() 47 | self.clamp.set(True) 48 | CheckboxInput(self.uiPanel, "Clamp Intensity", self.clamp).grid(column=0, row=3, pady=5) 49 | 50 | ExportButton(self.uiPanel, self.expIES).grid(column=0,row=4, pady=5) 51 | 52 | self.baseIesType = StringVar() 53 | self.baseIesType.set(BASEIESTYPES[2]) 54 | self.baseIesType.trace_add('write', self.selectIesBase) 55 | IesBaseButton(self.uiPanel, self.baseIesType, BASEIESTYPES).grid(column=0,row=5, pady=5) 56 | 57 | self.preview = PreviewRender(self) 58 | self.preview.grid(column=0, row=1, rowspan=10) 59 | 60 | self.layersPanel = LayersPanel(self) 61 | self.layersPanel.grid(column=1, row=0, rowspan=100, sticky=NSEW) 62 | 63 | self.baseIes = None 64 | self.ies = self.baseIes 65 | 66 | self.layersPanel.update() 67 | 68 | def update(self, *args): 69 | self.setIesBase() 70 | self.ies = copy.deepcopy(self.baseIes) 71 | 72 | for layer in self.layersPanel.layers: 73 | layer.apply(self.ies) 74 | 75 | self.preview.renderIESPreview(self.ies) 76 | 77 | def expIES(self): 78 | exportIES(self.ies, self.clamp.get()) 79 | 80 | def selectIesBase(self, *args): 81 | self.setIesBase(selecting=True) 82 | self.update() 83 | 84 | def setIesBase(self, selecting=False): 85 | t = self.baseIesType.get() 86 | if selecting: 87 | self.lumens.set(DEFAULT_LUMENS) 88 | self.vRes.set(DEFAULT_VRES) 89 | 90 | if t == "0% Intensity": 91 | self.baseIes = IesData( 92 | self.lumens.get(), 93 | self.vRes.get(), 94 | 1,0) 95 | self.lumensTI.setEnabled(True) 96 | self.vResTI.setEnabled(True) 97 | elif t == "50% Intensity": 98 | self.baseIes = IesData( 99 | self.lumens.get(), 100 | self.vRes.get(), 101 | 1,0.5) 102 | self.lumensTI.setEnabled(True) 103 | self.vResTI.setEnabled(True) 104 | elif t == "100% Intensity": 105 | self.baseIes = IesData( 106 | self.lumens.get(), 107 | self.vRes.get(), 108 | 1,1) 109 | self.lumensTI.setEnabled(True) 110 | self.vResTI.setEnabled(True) 111 | # elif t == "Import" and selecting: 112 | # self.baseIes = importIES() 113 | # self.lumens.set(self.baseIes.lumens) 114 | # self.vRes.set(self.baseIes.vRes) 115 | # self.lumensTI.setEnabled(False) 116 | # self.vResTI.setEnabled(False) 117 | 118 | 119 | app = EasyIESApplication() 120 | app.mainloop() 121 | -------------------------------------------------------------------------------- /ies/ies.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class IesData: 4 | 5 | def __init__(self, lumens, vRes, hRes, val): 6 | self.lumens = lumens 7 | self.vRes = vRes 8 | self.hRes = hRes 9 | self.angles = [] 10 | 11 | y = 0 12 | while y < 360: 13 | self.angles.append(IesAngle(y, vRes, val)) 14 | y += 360/hRes 15 | 16 | def getIESOutput(self, clamp): 17 | out = "IESNA91\n" 18 | out += "TILT=NONE\n" 19 | out += "1 {0} 1 {1} {2} 1 2 1 1 1\n1.0 1.0 0.0\n\n".format(self.lumens, len(self.angles[0].points), len(self.angles)) 20 | 21 | n = 0 22 | for point in self.angles[0].points: 23 | out += "{0:.2f} ".format(point.vAngle) 24 | if n == 9: 25 | out += "\n" 26 | n = 0 27 | else: 28 | n = n + 1 29 | out += "\n\n" 30 | 31 | n = 0 32 | for angle in self.angles: 33 | out += "{0:.2f} ".format(angle.hAngle) 34 | if n == 9: 35 | out += "\n" 36 | n = 0 37 | else: 38 | n = n + 1 39 | out += "\n0 \n" 40 | 41 | for angle in self.angles: 42 | n = 0 43 | for point in angle.points: 44 | i = point.intensity 45 | if clamp and i > 1: 46 | i = 1 47 | out += "{0:.2f} ".format(self.lumens * i) 48 | if n == 9: 49 | out += "\n" 50 | n = 0 51 | else: 52 | n = n + 1 53 | out += "\n\n" 54 | 55 | return out 56 | 57 | 58 | class IesAngle: 59 | 60 | def __init__(self, hAngle, vRes, intensity): 61 | self.hAngle = hAngle 62 | self.vRes = vRes 63 | self.points = [] 64 | x = 0.00 65 | while x <= 180: 66 | self.points.append(IesPoint(hAngle, x, intensity)) 67 | x += 180/(vRes-1) 68 | 69 | self.points[len(self.points)-1].vAngle = 180 70 | 71 | def updateAngle(self, hAngle): 72 | self.hAngle = hAngle 73 | for point in self.points: 74 | point.hAngle = hAngle 75 | 76 | 77 | class IesPoint: 78 | 79 | def __init__(self, hAngle, vAngle, intensity): 80 | self.hAngle = hAngle 81 | self.vAngle = vAngle 82 | self.intensity = intensity 83 | self.mask = 0 84 | 85 | 86 | def readIESData(inp): 87 | 88 | lines = [line.rstrip('\n') for line in inp] 89 | 90 | version = "" 91 | details = {} 92 | settings = "" 93 | unknownNumbers = "" 94 | vAngleStartIdx = 0 95 | hAngleStartIdx = 0 96 | valsStartIdx = 0 97 | vAngles = [] 98 | hAngles = [] 99 | 100 | for idx, line in enumerate(lines): 101 | l = line.strip() 102 | if l.startswith('IES'): 103 | version = l 104 | elif line.startswith('['): 105 | name = l.split(']')[0].replace('[','') 106 | val = l.split(']')[1] 107 | details[name] = val 108 | elif l.startswith("TILT"): 109 | settings = re.sub(' +', ' ', lines[idx+1]).split(' ') 110 | unknownNumbers = lines[idx+2] 111 | vAngleStartIdx = idx + 3 112 | 113 | lumens = int(settings[1]) 114 | factor = float(settings[2]) 115 | vNums = int(settings[3]) 116 | hNums = int(settings[4]) 117 | unit = settings[6] 118 | # openingSize = tuple(settings[7], settings[8], settings[9]) 119 | 120 | ies = IesData(lumens, vNums, hNums, 0) 121 | 122 | vAnglesRead = 0 123 | for idx in range(vAngleStartIdx, len(lines)): 124 | vals = lines[idx].split() 125 | for val in vals: 126 | vAngles.append(float(val)) 127 | vAnglesRead += 1 128 | if vAnglesRead >= vNums: 129 | hAngleStartIdx = idx+1 130 | break 131 | 132 | hAnglesRead = 0 133 | for idx in range(hAngleStartIdx, len(lines)): 134 | vals = lines[idx].split() 135 | for val in vals: 136 | hAngles.append(float(val)) 137 | hAnglesRead += 1 138 | if hAnglesRead >= hNums: 139 | valsStartIdx = idx+1 140 | break 141 | 142 | brightest = 0 143 | valsIdx = 0 144 | angleIdx = 0 145 | for idx in range(valsStartIdx, len(lines)): 146 | vals = lines[idx].split() 147 | for val in vals: 148 | if float(val) > brightest: 149 | brightest = float(val) 150 | if valsIdx >= vNums: 151 | valsIdx = 0 152 | angleIdx +=1 153 | ies.angles[angleIdx].points[valsIdx].intensity = float(val) 154 | ies.angles[angleIdx].points[valsIdx].vAngle = vAngles[valsIdx] 155 | valsIdx += 1 156 | 157 | ies.lumens = brightest 158 | for angle in ies.angles: 159 | for point in angle.points: 160 | point.intensity /= brightest 161 | 162 | return ies 163 | -------------------------------------------------------------------------------- /layer/operations.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | import math 3 | from ies import * 4 | from util import * 5 | 6 | 7 | class Operation(PanelFrame): 8 | 9 | def __init__(self, parent): 10 | PanelFrame.__init__(self, parent) 11 | self.parent = parent 12 | 13 | self.topBar = PanelFrame(self) 14 | self.topBar.grid(column=0,row=0) 15 | self.topBar.columnconfigure(0, weight=1) 16 | self.topBar.columnconfigure(1, weight=1) 17 | self.topBar.columnconfigure(2, weight=1) 18 | self.topBar.columnconfigure(3, weight=1) 19 | self.topBar.columnconfigure(4, weight=1) 20 | self.details = PanelFrame(self) 21 | self.details.grid(column=0,row=1) 22 | 23 | CollapseButton(self.topBar, self.details).grid(column=0, row=0, sticky=E) 24 | self.titleLabel = PanelLabel(self.topBar, text="Operation") 25 | self.titleLabel.grid(column=1, row=0, sticky=W) 26 | 27 | self.visibility = BooleanVar() 28 | self.visibility.set(True) 29 | VisibilityButton(self.topBar, self.visibility).grid(column=3,row=0, sticky=E) 30 | 31 | DeleteButton(self.topBar, command=self.deleteSelf).grid(column=4, row=0, sticky=W) 32 | 33 | self.update() 34 | 35 | def mix(self, point, value, mix): 36 | if point.mask < 0.5: 37 | if mix == "Multiply": 38 | point.intensity *= value 39 | elif mix == "Override": 40 | point.intensity = value 41 | elif mix == "Divide": 42 | if value != 0: 43 | point.intensity /= value 44 | else: 45 | point.intensity = 1 46 | elif mix == "Add": 47 | point.intensity += value 48 | elif mix == "Subtract": 49 | point.intensity -= value 50 | elif mix == "Min": 51 | point.intensity = min(point.intensity, value) 52 | elif mix == "Max": 53 | point.intensity = max(point.intensity, value) 54 | if point.intensity < 0: 55 | point.intensity = 0 56 | 57 | def deleteSelf(self, *args): 58 | self.parent.parent.removeOperation(self) 59 | self.destroy() 60 | 61 | def update(self, *args): 62 | self.parent.parent.update(args) 63 | 64 | 65 | class AdjustIntensity(Operation): 66 | 67 | def __init__(self, parent): 68 | Operation.__init__(self, parent) 69 | 70 | self.titleLabel['text'] = "Adjust Intensity" 71 | 72 | self.intensity = DoubleVar() 73 | self.intensity.set(80) 74 | self.intensity.trace_add('write', self.update) 75 | NumberSlider(self.details, "Intensity %", 0, 200, self.intensity).grid(column=0,row=0) 76 | 77 | self.update() 78 | 79 | def apply(self, point, mix=MIXMETHODS[0], progression=0): 80 | if self.visibility.get() == True: 81 | self.mix(point, self.intensity.get()/100, mix) 82 | 83 | 84 | class Mask(Operation): 85 | 86 | def __init__(self, parent): 87 | Operation.__init__(self, parent) 88 | 89 | self.titleLabel['text'] = "Mask" 90 | 91 | self.mask = DoubleVar() 92 | self.mask.set(80) 93 | self.mask.trace_add('write', self.update) 94 | NumberSlider(self.details, "Mask", 0, 1, self.mask).grid(column=0,row=0) 95 | 96 | self.update() 97 | 98 | def apply(self, point, mix=MIXMETHODS[0], progression=0): 99 | if self.visibility.get() == True: 100 | point.mask = self.mask.get() 101 | 102 | 103 | class Interpolate(Operation): 104 | 105 | def __init__(self, parent): 106 | Operation.__init__(self, parent) 107 | 108 | self.titleLabel['text'] = "Interpolate" 109 | 110 | self.method = StringVar() 111 | self.method.set(INTERPMETHODS[0]) 112 | self.method.trace_add('write', self.update) 113 | OptionSelector(self.topBar, self.method,INTERPMETHODS).grid(column=2,row=0) 114 | 115 | self.startIntensity = DoubleVar() 116 | self.startIntensity.set(80) 117 | self.startIntensity.trace_add('write', self.update) 118 | NumberSlider(self.details, "Top Intensity %", 0, 200, self.startIntensity).grid(column=0,row=1) 119 | 120 | self.endIntensity = DoubleVar() 121 | self.endIntensity.set(80) 122 | self.endIntensity.trace_add('write', self.update) 123 | NumberSlider(self.details, "Bottom Intensity %", 0, 200, self.endIntensity).grid(column=0,row=2) 124 | 125 | self.update() 126 | 127 | def apply(self, point, mix=MIXMETHODS[0], progression=0): 128 | if self.visibility.get() == True: 129 | a = self.endIntensity.get() / 100 130 | b = self.startIntensity.get() / 100 131 | y = interpolate(a, b, progression, self.method.get()) 132 | self.mix(point, y, mix) 133 | 134 | 135 | class SimpleCurve(Operation): 136 | 137 | def __init__(self, parent): 138 | Operation.__init__(self, parent) 139 | 140 | self.titleLabel['text'] = "Simple Curve" 141 | 142 | self.method = StringVar() 143 | self.method.set(INTERPMETHODS[0]) 144 | self.method.trace_add('write', self.update) 145 | OptionSelector(self.topBar, self.method, INTERPMETHODS).grid(column=2,row=0) 146 | 147 | self.startIntensity = DoubleVar() 148 | self.startIntensity.set(100) 149 | self.startIntensity.trace_add('write', self.update) 150 | NumberSlider(self.details, "Top Intensity %", 0, 200, self.startIntensity).grid(column=0,row=1) 151 | 152 | self.midIntensity = DoubleVar() 153 | self.midIntensity.set(70) 154 | self.midIntensity.trace_add('write', self.update) 155 | NumberSlider(self.details, "Middle Intensity %", 0, 200, self.midIntensity).grid(column=0,row=2) 156 | 157 | self.endIntensity = DoubleVar() 158 | self.endIntensity.set(100) 159 | self.endIntensity.trace_add('write', self.update) 160 | NumberSlider(self.details, "Bottom Intensity %", 0, 200, self.endIntensity).grid(column=0,row=3) 161 | 162 | self.update() 163 | 164 | def apply(self, point, mix=MIXMETHODS[0], progression=0): 165 | if self.visibility.get() == True: 166 | a = self.endIntensity.get() / 100 167 | b = self.midIntensity.get() / 100 168 | c = self.startIntensity.get() / 100 169 | y = 1 170 | if progression < 0.5: 171 | y = interpolate(a, b, progression*2, self.method.get()) 172 | else: 173 | y = interpolate(c, b, (1-progression)*2, self.method.get()) 174 | self.mix(point, y, mix) 175 | 176 | 177 | class Noise(Operation): 178 | 179 | def __init__(self, parent): 180 | Operation.__init__(self, parent) 181 | 182 | self.titleLabel['text'] = "Noise" 183 | 184 | self.method = StringVar() 185 | self.method.set(INTERPMETHODS[0]) 186 | self.method.trace_add('write', self.update) 187 | OptionSelector(self.topBar, self.method, INTERPMETHODS).grid(column=2,row=0) 188 | 189 | self.scale = DoubleVar() 190 | self.scale.set(10) 191 | self.scale.trace_add('write', self.update) 192 | NumberSlider(self.details, "Noise Scale", 0, 50, self.scale).grid(column=0,row=1) 193 | 194 | self.intensity = DoubleVar() 195 | self.intensity.set(50) 196 | self.intensity.trace_add('write', self.update) 197 | NumberSlider(self.details, "Noise Intensity %", 0, 100, self.intensity).grid(column=0,row=2) 198 | 199 | self.update() 200 | 201 | def apply(self, point, mix=MIXMETHODS[0], progression=0): 202 | if self.visibility.get() == True: 203 | profile = getNoiseProfile(self.scale.get(), self.intensity.get()/100, seed=self.scale.get() + self.intensity.get()) 204 | y = noise(profile, progression, self.method.get()) 205 | self.mix(point, y, mix) 206 | -------------------------------------------------------------------------------- /layer/layers.py: -------------------------------------------------------------------------------- 1 | from util import * 2 | from .operations import * 3 | 4 | 5 | class Layer(PanelFrame): 6 | 7 | def __init__(self, parent): 8 | PanelFrame.__init__(self, parent) 9 | self.parent = parent 10 | 11 | self.columnconfigure(0, weight=1) 12 | 13 | self.topBar = PanelFrame(self) 14 | self.topBar.grid(column=0,row=0) 15 | self.details = PanelFrame(self) 16 | self.details.grid(column=0,row=1) 17 | self.details.columnconfigure(0, weight=1) 18 | 19 | CollapseButton(self.topBar, self.details).grid(column=0,row=0) 20 | self.topBar.columnconfigure(0, weight=0) 21 | 22 | self.titleLabel = PanelLabel(self.topBar, text="Layer") 23 | self.titleLabel.grid(column=1,row=0, sticky=W) 24 | self.topBar.columnconfigure(1, weight=1) 25 | 26 | self.selection = StringVar() 27 | self.selection.set("Add Operation") 28 | self.selection.trace_add('write', self.addOperation) 29 | OptionButton(self.topBar, "Add Operation", self.selection, OPERATIONS).grid(column=2,row=0) 30 | self.topBar.columnconfigure(2, weight=1) 31 | self.operations = [] 32 | 33 | self.mix = StringVar() 34 | self.mix.set(MIXMETHODS[0]) 35 | self.mix.trace_add('write', self.update) 36 | OptionSelector(self.topBar, self.mix, MIXMETHODS).grid(column=3,row=0) 37 | self.topBar.columnconfigure(3, weight=1) 38 | 39 | self.visibility = BooleanVar() 40 | self.visibility.set(True) 41 | VisibilityButton(self.topBar, self.visibility).grid(column=4,row=0) 42 | self.topBar.columnconfigure(4, weight=0) 43 | 44 | DeleteButton(self.topBar, self.deleteSelf).grid(column=5,row=0) 45 | self.topBar.columnconfigure(5, weight=0) 46 | 47 | PanelLabel(self).grid(column=0,row=100) 48 | 49 | self.rowCounter = 1 50 | 51 | self.update() 52 | 53 | def deleteSelf(self, *args): 54 | self.parent.parent.parent.removeLayer(self) 55 | self.destroy() 56 | 57 | def addOperation(self, *args): 58 | s = self.selection.get() 59 | if s != "Add Operation": 60 | if s == "Adjust Intensity": 61 | mod = AdjustIntensity(self.details) 62 | elif s == "Interpolate": 63 | mod = Interpolate(self.details) 64 | elif s == "Simple Curve": 65 | mod = SimpleCurve(self.details) 66 | elif s == "Noise": 67 | mod = Noise(self.details) 68 | elif s == "Mask": 69 | mod = Mask(self.details) 70 | 71 | mod.grid(column=0,row=self.rowCounter) 72 | self.operations.append(mod) 73 | self.rowCounter += 1 74 | 75 | self.update() 76 | 77 | def removeOperation(self, op): 78 | self.operations.remove(op) 79 | self.update() 80 | 81 | def apply(self, ies): 82 | pass 83 | 84 | def update(self, *args): 85 | self.parent.parent.update(args) 86 | 87 | 88 | class VerticalRange(Layer): 89 | 90 | def __init__(self, parent): 91 | Layer.__init__(self, parent) 92 | 93 | self.titleLabel["text"] = "Angle Range" 94 | 95 | self.vAngle = DoubleVar() 96 | self.vAngle.set(45) 97 | self.vAngle.trace_add('write', self.update) 98 | NumberSlider(self.details, "Angle", 0, 180, self.vAngle).grid(column=0,row=self.rowCounter) 99 | self.rowCounter += 1 100 | 101 | self.vRange = DoubleVar() 102 | self.vRange.set(90) 103 | self.vRange.trace_add('write', self.update) 104 | NumberSlider(self.details, "Range", 0, 180, self.vRange).grid(column=0,row=self.rowCounter) 105 | self.rowCounter += 1 106 | 107 | def apply(self, ies): 108 | if self.visibility.get() == True: 109 | vAngle = self.vAngle.get() 110 | vRange = self.vRange.get() 111 | for op in self.operations: 112 | for angle in ies.angles: 113 | for point in angle.points: 114 | if point.vAngle >= vAngle and point.vAngle <= vAngle + vRange: 115 | op.apply(point, mix=self.mix.get(), progression=(point.vAngle - vAngle)/vRange) 116 | 117 | 118 | class HorizontalRange(Layer): 119 | 120 | def __init__(self, parent): 121 | Layer.__init__(self, parent) 122 | 123 | self.titleLabel["text"] = "Horizontal Range" 124 | 125 | self.hAngle = DoubleVar() 126 | self.hAngle.set(45) 127 | self.hAngle.trace_add('write', self.update) 128 | NumberSlider(self.details, "Angle", 0, 360, self.hAngle).grid(column=0,row=self.rowCounter) 129 | self.rowCounter += 1 130 | 131 | self.hRange = DoubleVar() 132 | self.hRange.set(90) 133 | self.hRange.trace_add('write', self.update) 134 | NumberSlider(self.details, "Range", 0, 360, self.hRange).grid(column=0,row=self.rowCounter) 135 | self.rowCounter += 1 136 | 137 | def apply(self, ies): 138 | if self.visibility.get() == True: 139 | hAngle = self.hAngle.get() 140 | hRange = self.hRange.get() 141 | for op in self.operations: 142 | for angle in ies.angles: 143 | for point in angle.points: 144 | if point.hAngle >= hAngle and point.hAngle <= hAngle + hRange: 145 | op.apply(point, mix=self.mix.get(), progression=(point.hAngle - hAngle)/hRange) 146 | 147 | 148 | class VHRange(Layer): 149 | 150 | def __init__(self, parent): 151 | Layer.__init__(self, parent) 152 | 153 | self.titleLabel["text"] = "Vertical Horizontal Range" 154 | 155 | self.vAngle = DoubleVar() 156 | self.vAngle.set(45) 157 | self.vAngle.trace_add('write', self.update) 158 | NumberSlider(self.details, "Vertical Angle", 0, 180, self.vAngle).grid(column=0,row=self.rowCounter) 159 | self.rowCounter += 1 160 | 161 | self.vRange = DoubleVar() 162 | self.vRange.set(90) 163 | self.vRange.trace_add('write', self.update) 164 | NumberSlider(self.details, "Vertical Range", 0, 180, self.vRange).grid(column=0,row=self.rowCounter) 165 | self.rowCounter += 1 166 | 167 | self.hAngle = DoubleVar() 168 | self.hAngle.set(45) 169 | self.hAngle.trace_add('write', self.update) 170 | NumberSlider(self.details, "Horizontal Angle", 0, 360, self.hAngle).grid(column=0,row=self.rowCounter) 171 | self.rowCounter += 1 172 | 173 | self.hRange = DoubleVar() 174 | self.hRange.set(90) 175 | self.hRange.trace_add('write', self.update) 176 | NumberSlider(self.details, "Horizontal Range", 0, 360, self.hRange).grid(column=0,row=self.rowCounter) 177 | self.rowCounter += 1 178 | 179 | def apply(self, ies): 180 | if self.visibility.get() == True: 181 | vAngle = self.vAngle.get() 182 | vRange = self.vRange.get() 183 | hAngle = self.hAngle.get() 184 | hRange = self.hRange.get() 185 | for op in self.operations: 186 | for angle in ies.angles: 187 | for point in angle.points: 188 | if point.hAngle >= hAngle and point.hAngle <= hAngle + hRange: 189 | if point.vAngle >= vAngle and point.vAngle <= vAngle + vRange: 190 | hProgress = (point.hAngle - hAngle)/hRange 191 | if hProgress > 0.5: 192 | hProgress = 1 - hProgress 193 | vProgress = (point.vAngle - vAngle)/vRange 194 | if vProgress > 0.5: 195 | vProgress = 1 - vProgress 196 | op.apply(point, mix=self.mix.get(), progression=(hProgress + vProgress)/2) 197 | 198 | 199 | class Full360(Layer): 200 | 201 | def __init__(self, parent): 202 | Layer.__init__(self, parent) 203 | 204 | self.titleLabel["text"] = "Full 360" 205 | 206 | def apply(self, ies): 207 | if self.visibility.get() == True: 208 | for op in self.operations: 209 | for angle in ies.angles: 210 | for point in angle.points: 211 | op.apply(point, mix=self.mix.get(), progression=point.vAngle/180) 212 | -------------------------------------------------------------------------------- /util/uiGeneric.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from util import * 3 | 4 | BGCOLOR = '#444444' 5 | PANELCOLOR = '#333333' 6 | BUTTONCOLOR = '#777777' 7 | TEXTCOLOR = '#ffffff' 8 | 9 | class BaseFrame(Frame): 10 | def __init__(self, parent=None, height=0): 11 | Frame.__init__(self, parent, bg=BGCOLOR, height=height) 12 | self.parent = parent 13 | self.grid(sticky=EW) 14 | 15 | 16 | class PanelFrame(Frame): 17 | def __init__(self, parent=None, bg=PANELCOLOR): 18 | Frame.__init__(self, parent, bg=bg) 19 | self.parent = parent 20 | self.grid(sticky=NSEW, pady=2.5, padx=5) 21 | self.columnconfigure(0, weight=1) 22 | 23 | 24 | class ScrollFrame(PanelFrame): 25 | def __init__(self, parent): 26 | PanelFrame.__init__(self, parent) 27 | 28 | self.bind('', self.onFrameConfigure) 29 | self.columnconfigure(0, weight=1) 30 | self.columnconfigure(1, weight=0) 31 | self.rowconfigure(0, weight=1) 32 | self.canvas = ScrollCanvas(self) 33 | self.canvas.grid(column=0, row=0, rowspan=100, sticky=NSEW) 34 | self.canvas.bind('', self.frameWidth) 35 | self.internalPanel = PanelFrame(self.canvas) 36 | self.internalPanel.grid(column=0, row=0, sticky=NSEW) 37 | self.internalPanel.columnconfigure(0, weight=1) 38 | self.internalPanel.rowconfigure(0, weight=1) 39 | self.vsb = Scrollbar(self, orient='vertical', command=self.canvas.yview) 40 | self.vsb.grid(column=1, row=0, rowspan=10, sticky=NS) 41 | self.canvas.create_window(0, 0, window=self.internalPanel, anchor=N, tags='internalPanel') 42 | self.canvas.configure(yscrollcommand=self.vsb.set, bg=PANELCOLOR, highlightthickness=0) 43 | 44 | def onFrameConfigure(self, event): 45 | self.canvas.configure(scrollregion=self.canvas.bbox('all')) 46 | 47 | def frameWidth(self, event): 48 | self.canvas.itemconfig('internalPanel', width=event.width) 49 | 50 | def update(self, *args): 51 | self.canvas.event_generate('', width=self.canvas.winfo_width()) 52 | self.event_generate('') 53 | self.parent.update(args) 54 | 55 | 56 | class ScrollCanvas(Canvas): 57 | def __init__(self, parent): 58 | Canvas.__init__(self, parent, borderwidth=0) 59 | self.parent = parent 60 | self.grid(sticky=NSEW) 61 | self.columnconfigure(0, weight=1) 62 | self.rowconfigure(0, weight=1) 63 | self.bind('', self.on_resize) 64 | 65 | def on_resize(self,event): 66 | self.configure(width=event.width) 67 | 68 | def update(self, *args): 69 | self.parent.update(args) 70 | 71 | 72 | class PanelLabel(Label): 73 | def __init__(self, parent=None, text=""): 74 | Label.__init__(self, parent, text=text, bg=PANELCOLOR, fg=TEXTCOLOR) 75 | self.grid(sticky=NSEW) 76 | 77 | def setEnabled(self, enabled): 78 | pass 79 | 80 | 81 | class TextInput(PanelFrame): 82 | def __init__(self, parent, label, var): 83 | PanelFrame.__init__(self, parent) 84 | self.columnconfigure(0, weight=1) 85 | self.columnconfigure(1, weight=1) 86 | self.label = PanelLabel(self, text=label) 87 | self.label.grid(column=0, row=0, sticky=E) 88 | self.entry = Entry(self, width=10, textvariable=var) 89 | self.entry.grid(column=1, row=0, sticky=W) 90 | 91 | def setEnabled(self, enabled): 92 | if enabled: 93 | self.entry['state'] = NORMAL 94 | else: 95 | self.entry['state'] = DISABLED 96 | 97 | 98 | class CheckboxInput(PanelFrame): 99 | def __init__(self, parent, label, var): 100 | PanelFrame.__init__(self, parent) 101 | self.columnconfigure(0, weight=1) 102 | self.columnconfigure(1, weight=1) 103 | self.label = PanelLabel(self, text=label) 104 | self.label.grid(column=0, row=0, sticky=E) 105 | 106 | self.cbox = Checkbutton(self, variable=var, 107 | bg=PANELCOLOR, activebackground=PANELCOLOR, 108 | fg=PANELCOLOR, activeforeground=PANELCOLOR, 109 | selectcolor=BUTTONCOLOR, 110 | text='' 111 | ) 112 | 113 | self.cbox.grid(column=1, row=0, sticky=W) 114 | 115 | 116 | class NumberSlider(PanelFrame): 117 | def __init__(self, parent, label, low, high, var, step=1): 118 | PanelFrame.__init__(self, parent) 119 | self.columnconfigure(0, weight=1) 120 | self.columnconfigure(1, weight=3) 121 | self.label = PanelLabel(self, text=label) 122 | self.label.grid(column=0, row=0, sticky=E) 123 | 124 | self.bind('', self.on_resize) 125 | 126 | self.slider = Scale(self, 127 | from_=low, to=high, resolution=step, 128 | width=7, length=150, sliderlength=10, 129 | orient=HORIZONTAL, sliderrelief=FLAT, 130 | bg=PANELCOLOR, fg=TEXTCOLOR, highlightcolor=BUTTONCOLOR, 131 | activebackground=PANELCOLOR, troughcolor=BUTTONCOLOR, 132 | highlightthickness=0, 133 | variable=var) 134 | self.slider.grid(column=1, row=0) 135 | 136 | def on_resize(self,event): 137 | self.slider.configure(length=event.width*0.5) 138 | 139 | def setSliderVals(low, high, step=1): 140 | self.slider.config(from_=low, to=high, resolution=step) 141 | 142 | 143 | class OptionButton(Menubutton): 144 | def __init__(self, parent, label, var, options): 145 | Menubutton.__init__(self, parent, text=label, borderwidth=0, relief=FLAT, 146 | activebackground=BUTTONCOLOR, bg=BUTTONCOLOR, 147 | activeforeground=TEXTCOLOR, fg=TEXTCOLOR, 148 | indicatoron=False) 149 | 150 | self.menu = Menu(self, tearoff=False) 151 | self.configure(menu=self.menu) 152 | for op in options: 153 | self.menu.add_radiobutton(label=op, variable=var, value=op, indicatoron=False) 154 | 155 | 156 | class OptionSelector(OptionMenu): 157 | def __init__(self, parent, var, options): 158 | OptionMenu.__init__(self, parent, var, *options) 159 | self.configure(borderwidth=0, 160 | highlightthickness=0, relief=FLAT, 161 | activebackground=BUTTONCOLOR, bg=BUTTONCOLOR, 162 | activeforeground=TEXTCOLOR, fg=TEXTCOLOR) 163 | 164 | 165 | class BaseButton(Button): 166 | def __init__(self, parent, command, text="", image=None, width=10, height=10, bg=BUTTONCOLOR): 167 | Button.__init__(self, parent, command=command, 168 | text=text, image=image, 169 | width=width, 170 | height=height, 171 | relief=FLAT, 172 | activebackground=bg, bg=bg, 173 | activeforeground=TEXTCOLOR, fg=TEXTCOLOR) 174 | 175 | 176 | class ExportButton(BaseButton): 177 | def __init__(self, parent, command): 178 | BaseButton.__init__(self, parent, text='Export', command=command, width=15, height=1) 179 | 180 | 181 | class IesBaseButton(OptionButton): 182 | def __init__(self, parent, var, options): 183 | OptionButton.__init__(self, parent, 'Set IES Base', var, options) 184 | self.config(width=17, height=1) 185 | 186 | 187 | class DeleteButton(BaseButton): 188 | def __init__(self, parent, command): 189 | self.deleteImage = PhotoImage(file=resourcePath('icon/exit_small.png')) 190 | BaseButton.__init__(self, parent, command=command, image=self.deleteImage) 191 | 192 | 193 | class CollapseButton(BaseButton): 194 | def __init__(self, parent, collapseFrame): 195 | self.collapsedImage = PhotoImage(file=resourcePath('icon/collapsed_small.png')) 196 | self.expandedImage = PhotoImage(file=resourcePath('icon/expanded_small.png')) 197 | BaseButton.__init__(self, parent, command=self.toggle, 198 | image=self.expandedImage, bg=PANELCOLOR) 199 | 200 | self.collapseFrame = collapseFrame 201 | self.collapseFrameRow = int(collapseFrame.grid_info()['row']) 202 | self.collapseFrameColumn = int(collapseFrame.grid_info()['column']) 203 | self.collapsed = False 204 | 205 | def toggle(self): 206 | if self.collapsed: 207 | self.collapseFrame.grid(column=self.collapseFrameColumn,row=self.collapseFrameRow) 208 | self.configure(image=self.expandedImage) 209 | else: 210 | self.collapseFrame.grid_remove() 211 | self.configure(image=self.collapsedImage) 212 | self.collapsed = not self.collapsed 213 | 214 | class VisibilityButton(BaseButton): 215 | def __init__(self, parent, var): 216 | self.visibleImage = PhotoImage(file=resourcePath('icon/visible_small.png')) 217 | self.notVisibleImage = PhotoImage(file=resourcePath('icon/notvisible_small.png')) 218 | self.parent = parent 219 | self.var = var 220 | BaseButton.__init__(self, parent, command=self.toggle, 221 | image=self.visibleImage) 222 | 223 | self.visible = True 224 | 225 | def toggle(self): 226 | self.visible = not self.visible 227 | if self.visible: 228 | self.configure(image=self.visibleImage) 229 | self.var.set(True) 230 | else: 231 | self.configure(image=self.notVisibleImage) 232 | self.var.set(False) 233 | 234 | self.parent.parent.update() --------------------------------------------------------------------------------