├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── Init.py ├── InitGui.py ├── LICENSE ├── OpticalObject.py ├── OpticsWorkbench.py ├── Plot.py ├── README.md ├── README_de.md ├── README_zh_TW.md ├── Ray.py ├── SunRay.py ├── apply_translations.sh ├── examples ├── Concave_mirror_grating_thorlabs_1.PNG ├── Concave_mirror_grating_thorlabs_2.PNG ├── Emitter.png ├── RayHits.png ├── beamsplitter_by_using_ignored_elements.PNG ├── ccd_xyplot.png ├── dispersion.png ├── echelle_example.PNG ├── example1.py ├── example2D.png ├── example3D.py ├── example_candle.png ├── example_candle.py ├── example_candle2D.png ├── example_dispersion.py ├── example_hierarchy2D.png ├── example_hierarchy2D.py ├── example_hierarchy3D.png ├── example_hierarchy3D.py ├── example_semi.png ├── example_semi.py ├── plot3Dexample1.png ├── plot3Dexample2.png ├── screenshot3D.png ├── simple_reflection_grating_set_of_planes.PNG └── simple_transmission_grating.PNG ├── icons ├── Anonymous_Lightbulb_Lit.svg ├── Anonymous_Lightbulb_Off.svg ├── ExportCSV.svg ├── absorber.svg ├── emitter.svg ├── grating.svg ├── lens.svg ├── mirror.svg ├── ray.svg ├── rayarray.svg ├── raygridfocal.svg ├── raysun.svg ├── scatter3D.svg ├── sun.svg └── sun3D.svg ├── optics_workbench_icon.svg ├── package.xml ├── translations ├── OpticsWorkbench.ts ├── OpticsWorkbench_de.qm ├── OpticsWorkbench_de.ts ├── OpticsWorkbench_es-AR.qm ├── OpticsWorkbench_es-AR.ts ├── OpticsWorkbench_es-ES.qm ├── OpticsWorkbench_es-ES.ts ├── OpticsWorkbench_zh-TW.qm ├── OpticsWorkbench_zh-TW.ts └── update_translation.sh ├── update_translations.sh └── wavelength_to_rgb ├── .gitattributes ├── README.rst ├── gentable.py └── rgb.py /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: "3 3 * * 0" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ python ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v2 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # macos Finder 132 | .DS_Store -------------------------------------------------------------------------------- /Init.py: -------------------------------------------------------------------------------- 1 | # FreeCAD init script of the PythonWorkbenchTemplate module 2 | # (c) 2020 Christian Bergmann LGPL 3 | -------------------------------------------------------------------------------- /InitGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __title__ = 'FreeCAD Optics Workbench - Init file' 4 | __author__ = 'Christian Bergmann' 5 | __url__ = ['http://www.freecadweb.org'] 6 | __doc__ = 'Optics Workbench workbench' 7 | __version__ = '0.0.1' 8 | 9 | 10 | class OpticsWorkbench (Workbench): 11 | def __init__(self): 12 | import os 13 | import OpticsWorkbench 14 | import FreeCADGui 15 | 16 | translate = FreeCAD.Qt.translate 17 | translations_path = os.path.join(OpticsWorkbench.get_module_path(), "translations") 18 | FreeCADGui.addLanguagePath(translations_path) 19 | FreeCADGui.updateLocale() 20 | 21 | self.__class__.MenuText = 'Optics' 22 | self.__class__.ToolTip = translate("Workbench", 'Ray Tracing Simulation') 23 | self.__class__.Icon = os.path.join(OpticsWorkbench.get_module_path(), 'optics_workbench_icon.svg') 24 | 25 | def Initialize(self): 26 | '''This function is executed when FreeCAD starts''' 27 | # import here all the needed files that create your FreeCAD commands 28 | import Ray 29 | import OpticalObject 30 | import Plot 31 | from examples import example1, example3D, example_dispersion, example_candle, example_semi, example_hierarchy2D, example_hierarchy3D 32 | from PySide.QtCore import QT_TRANSLATE_NOOP 33 | 34 | rays = [QT_TRANSLATE_NOOP('Workbench', 'Ray (monochrome)'), 35 | QT_TRANSLATE_NOOP('Workbench', 'Ray (sun light)'), 36 | QT_TRANSLATE_NOOP('Workbench', 'Beam'), 37 | QT_TRANSLATE_NOOP('Workbench', '2D Radial Beam'), 38 | QT_TRANSLATE_NOOP('Workbench', 'Spherical Beam'), 39 | QT_TRANSLATE_NOOP('Workbench', 'Grid Focal Beam')] 40 | optics = [QT_TRANSLATE_NOOP('Workbench', 'Emitter'), 41 | QT_TRANSLATE_NOOP('Workbench', 'Mirror'), 42 | QT_TRANSLATE_NOOP('Workbench', 'Grating'), 43 | QT_TRANSLATE_NOOP('Workbench', 'Absorber'), 44 | QT_TRANSLATE_NOOP('Workbench', 'Lens')] 45 | actions = [QT_TRANSLATE_NOOP('Workbench', 'Off'), QT_TRANSLATE_NOOP('Workbench', 'Start')] 46 | analysis= [QT_TRANSLATE_NOOP('Workbench', 'RayHits'), QT_TRANSLATE_NOOP('Workbench', 'Hits2CSV')] 47 | separator = ['Separator'] 48 | examples = [QT_TRANSLATE_NOOP('Workbench', 'Example2D'), 49 | QT_TRANSLATE_NOOP('Workbench', 'Example3D'), 50 | QT_TRANSLATE_NOOP('Workbench', 'ExampleDispersion'), 51 | QT_TRANSLATE_NOOP('Workbench', 'ExampleCandle'), 52 | QT_TRANSLATE_NOOP('Workbench', 'ExampleSemi'), 53 | QT_TRANSLATE_NOOP('Workbench', 'ExampleHierarchy2D'), 54 | QT_TRANSLATE_NOOP('Workbench', 'ExampleHierarchy3D')] 55 | self.list = rays + separator + optics + separator + actions + separator + analysis #A list of command names created in the line above 56 | self.menu = self.list + separator + examples 57 | 58 | self.appendToolbar(self.__class__.MenuText, self.list) # creates a new toolbar with your commands 59 | self.appendMenu(self.__class__.MenuText, self.menu) # creates a new menu 60 | 61 | def Activated(self): 62 | '''This function is executed when the workbench is activated''' 63 | return 64 | 65 | def Deactivated(self): 66 | '''This function is executed when the workbench is deactivated''' 67 | return 68 | 69 | def ContextMenu(self, recipient): 70 | '''This is executed whenever the user right-clicks on screen''' 71 | # 'recipient' will be either 'view' or 'tree' 72 | self.appendContextMenu(self.__class__.MenuText, self.list) # add commands to the context menu 73 | 74 | def GetClassName(self): 75 | # this function is mandatory if this is a full python workbench 76 | return 'Gui::PythonWorkbench' 77 | 78 | 79 | Gui.addWorkbench(OpticsWorkbench()) 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /OpticsWorkbench.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | from FreeCAD import Vector, Rotation, activeDocument 5 | import Ray 6 | import OpticalObject 7 | import SunRay 8 | from numpy import linspace 9 | from importlib import reload 10 | import Plot 11 | 12 | 13 | def recompute(): 14 | activeDocument().recompute() 15 | 16 | def get_module_path(): 17 | ''' Returns the current module path. 18 | Determines where this file is running from, so works regardless of whether 19 | the module is installed in the app's module directory or the user's app data folder. 20 | (The second overrides the first.) 21 | ''' 22 | return os.path.dirname(__file__) 23 | 24 | 25 | def makeRay(position = Vector(0, 0, 0), 26 | direction = Vector(1, 0, 0), 27 | power = True, 28 | beamNrColumns = 1, 29 | beamNrRows = 1, 30 | beamDistance = 0.1, 31 | spherical = False, 32 | hideFirst = False, 33 | maxRayLength = 1000000, 34 | maxNrReflections = 200, 35 | wavelength = 580, 36 | order = 0, 37 | coneAngle = 360, 38 | ignoredElements=[], 39 | baseShape = None, 40 | focalPoint = Vector(0, 0, 100), 41 | rayBundleType = ''): 42 | reload(Ray) 43 | '''Python command to create a light ray.''' 44 | name = 'Ray' 45 | if beamNrColumns * beamNrRows > 1: 46 | name = 'Beam' 47 | if baseShape: 48 | name = 'Emitter' 49 | 50 | fp = activeDocument().addObject('Part::FeaturePython', name) 51 | fp.Placement.Base = position 52 | fp.Placement.Rotation = Rotation(Vector(1, 0, 0), direction) 53 | Ray.RayWorker(fp, power, spherical, beamNrColumns, beamNrRows, beamDistance, hideFirst, maxRayLength, maxNrReflections, wavelength, order, coneAngle, ignoredElements, baseShape, focalPoint, rayBundleType) 54 | Ray.RayViewProvider(fp.ViewObject) 55 | recompute() 56 | return fp 57 | 58 | 59 | def makeSunRay(position = Vector(0, 0, 0), 60 | direction = Vector(1, 0, 0), 61 | power = True, 62 | beamNrColumns = 1, 63 | beamNrRows = 1, 64 | beamDistance = 0.1, 65 | spherical = False, 66 | hideFirst = False, 67 | maxRayLength = 1000000, 68 | maxNrReflections = 900, 69 | wavelength_from = 450, 70 | wavelength_to = 750, 71 | num_rays = 70, 72 | order = 1, 73 | ignoredElements=[]): 74 | reload(SunRay) 75 | rays = [] 76 | for l in linspace(wavelength_from, wavelength_to, num_rays): 77 | ray = makeRay(position = position, 78 | direction = direction, 79 | power = power, 80 | beamNrColumns=beamNrColumns, 81 | beamNrRows=beamNrRows, 82 | beamDistance=beamDistance, 83 | spherical=spherical, 84 | hideFirst = hideFirst, 85 | maxRayLength = maxRayLength, 86 | maxNrReflections = maxNrReflections, 87 | wavelength = l, 88 | order = order, 89 | ignoredElements = ignoredElements) 90 | ray.ViewObject.LineWidth = 1 91 | rays.append(ray) 92 | 93 | fp = activeDocument().addObject('Part::FeaturePython','SunRay') 94 | SunRay.SunRayWorker(fp, rays) 95 | SunRay.SunRayViewProvider(fp.ViewObject) 96 | recompute() 97 | return fp 98 | 99 | # reload(Ray) 100 | # doc = activeDocument() 101 | # rays = [] 102 | # for l in linspace(wavelength_from, wavelength_to, num_rays): 103 | # ray = makeRay(position = position, 104 | # direction = direction, 105 | # power = power, 106 | # beamNrColumns=beamNrColumns, 107 | # beamNrRows=beamNrRows, 108 | # beamDistance=beamDistance, 109 | # spherical=spherical, 110 | # hideFirst = hideFirst, 111 | # maxRayLength = maxRayLength, 112 | # maxNrReflections = maxNrReflections, 113 | # wavelength = l, 114 | # order = order) 115 | # ray.ViewObject.LineWidth = 1 116 | # rays.append(ray) 117 | 118 | # group = doc.addObject('App::DocumentObjectGroup','SunRay') 119 | # group.Group = rays 120 | # recompute() 121 | 122 | 123 | def restartAll(): 124 | for obj in activeDocument().Objects: 125 | if isRay(obj): 126 | obj.Power = True 127 | obj.touch() 128 | 129 | recompute() 130 | 131 | def allOff(): 132 | for obj in activeDocument().Objects: 133 | if isRay(obj): 134 | obj.Power = False 135 | 136 | elif Ray.isOpticalObject(obj): 137 | for a in dir(obj): 138 | if a.startswith('HitsFrom') or a.startswith('HitCoordsFrom') or a.startswith('EnergyFrom'): 139 | obj.removeProperty(a) 140 | 141 | recompute() 142 | 143 | def makeMirror(base = [], collectStatistics = False, transparency=0): 144 | #reload(OpticalObject) 145 | '''All FreeCAD objects in base will be optical mirrors.''' 146 | fp = activeDocument().addObject('Part::FeaturePython', 'Mirror') 147 | OpticalObject.OpticalObjectWorker(fp, base, type = 'mirror', collectStatistics = collectStatistics, transparency = transparency) 148 | OpticalObject.OpticalObjectViewProvider(fp.ViewObject) 149 | recompute() 150 | return fp 151 | 152 | def makeAbsorber(base = [], collectStatistics = False, transparency=0): 153 | #reload(OpticalObject) 154 | '''All FreeCAD objects in base will be optical light absorbers.''' 155 | fp = activeDocument().addObject('Part::FeaturePython', 'Absorber') 156 | OpticalObject.OpticalObjectWorker(fp, base, type = 'absorber', collectStatistics = collectStatistics, transparency = transparency) 157 | OpticalObject.OpticalObjectViewProvider(fp.ViewObject) 158 | recompute() 159 | return fp 160 | 161 | def makeLens(base = [], RefractionIndex = 0, material = 'Quartz', collectStatistics = False, transparency=100): 162 | #reload(OpticalObject) 163 | '''All FreeCAD objects in base will be optical lenses.''' 164 | fp = activeDocument().addObject('Part::FeaturePython', 'Lens') 165 | OpticalObject.LensWorker(fp, base, RefractionIndex, material, collectStatistics, transparency = transparency) 166 | OpticalObject.OpticalObjectViewProvider(fp.ViewObject) 167 | recompute() 168 | return fp 169 | 170 | def makeGrating(base=[], RefractionIndex=1, material='', lpm = 500, GratingType = "reflection", GratingLinesPlane = Vector(0,1,0), order = 1, collectStatistics = False): 171 | #reload(OpticalObject) 172 | '''All FreeCAD objects in base will be diffraction gratings.''' 173 | fp = activeDocument().addObject('Part::FeaturePython', 'Grating') 174 | OpticalObject.GratingWorker(fp, base, RefractionIndex, material, lpm, GratingType, GratingLinesPlane, order, collectStatistics) 175 | OpticalObject.OpticalObjectViewProvider(fp.ViewObject) 176 | recompute() 177 | return fp 178 | 179 | def isRay(obj): 180 | return hasattr(obj, 'Power') and hasattr(obj, 'BeamNrColumns') 181 | 182 | def plot_xy(absorber): 183 | import numpy as np 184 | import matplotlib.pyplot as plt 185 | 186 | coords = [] 187 | attr_names = [attr for attr in dir(absorber) if attr.startswith('HitCoordsFrom')] 188 | coords_per_beam = [getattr(absorber, attr) for attr in attr_names] 189 | all_coords = np.array([coord for coords in coords_per_beam for coord in coords]) 190 | print("attr_names", attr_names) 191 | print("coords_per_beam", coords_per_beam) 192 | print("all_coords", all_coords) 193 | 194 | x = -all_coords[:,1] 195 | y = all_coords[:,2] 196 | 197 | if len(all_coords) > 0: 198 | plt.scatter(x, y) 199 | plt.show() 200 | 201 | 202 | def drawPlot(selectedObjList): 203 | ## Create the list of selected absorbers; if none then skip 204 | Plot.PlotRayHits.plot3D(selectedObjList) 205 | 206 | 207 | def Hits2CSV(): 208 | 209 | sheet = activeDocument().addObject('Spreadsheet::Sheet', 'RayHits') 210 | 211 | sheet.set('A1','Absorber') 212 | sheet.set('B1','Ray') 213 | sheet.set('C1','X-axis') 214 | sheet.set('D1','Y-axis') 215 | sheet.set('E1','Z-axis') 216 | sheet.set('F1','Energy %') 217 | row = 1 218 | 219 | coords_per_beam = [] 220 | for eachObject in activeDocument().Objects: 221 | if Ray.isOpticalObject(eachObject): 222 | #all_coords = np.array([coord for coords in coords_per_beam for coord in coords]) 223 | for attr in dir(eachObject): 224 | if attr.startswith('HitCoordsFrom'): 225 | lastrow = row 226 | coords_per_beam = getattr(eachObject, attr) 227 | for co in coords_per_beam: 228 | row += 1 229 | sheet.set('A' + str(row), eachObject.Label) 230 | sheet.set('B' + str(row), attr[13:]) 231 | sheet.set('C' + str(row), str(co[0])) 232 | sheet.set('D' + str(row), str(co[1])) 233 | sheet.set('E' + str(row), str(co[2])) 234 | 235 | eneryname = 'EnergyFrom' + attr[13:] 236 | if hasattr(eachObject, eneryname): 237 | energy = getattr(eachObject, eneryname) 238 | row = lastrow 239 | for e in energy: 240 | row += 1 241 | sheet.set('F' + str(row), str(e)) 242 | 243 | sheet.recompute() 244 | sheet.ViewObject.doubleClicked() 245 | 246 | -------------------------------------------------------------------------------- /Plot.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import FreeCADGui as Gui 4 | from FreeCAD import activeDocument 5 | import os 6 | import Ray 7 | 8 | from PySide.QtCore import QT_TRANSLATE_NOOP 9 | 10 | _icondir_ = os.path.join(os.path.dirname(__file__), 'icons') 11 | 12 | class PlotRayHits(): 13 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 14 | def plot3D(selectedObjList): 15 | 16 | # Figure out the selected absorber; if multiple absorbers selected, or multiple objects then loop through them all 17 | # and accumulate data from all of them 18 | 19 | #print("Selected Objects: ", len(selectedObjList)) 20 | if len(selectedObjList) >0: 21 | coords = [] 22 | attr_names=[] 23 | coords_per_beam = [] 24 | for eachObject in selectedObjList: 25 | #print("Looping through: ", eachObject.Label) 26 | try: 27 | if Ray.isOpticalObject(eachObject): 28 | #coords = [] 29 | attr_names[len(attr_names):] = [attr for attr in dir(eachObject) if attr.startswith('HitCoordsFrom')] 30 | coords_per_beam[len(coords_per_beam):] = [getattr(eachObject, attr) for attr in attr_names] 31 | #all_coords = np.array([coord for coords in coords_per_beam for coord in coords]) 32 | else: 33 | print ("Ignoring: ",eachObject.Label) 34 | except: 35 | print ("Ignoring: ",eachObject.Label) 36 | all_coords = np.array([coord for coords in coords_per_beam for coord in coords]) 37 | if len(all_coords) > 0: 38 | x = all_coords[:,0] 39 | y = all_coords[:,1] 40 | z = all_coords[:,2] 41 | 42 | startx = x[0] 43 | starty = y[0] 44 | startz = z[0] 45 | xpresent = False 46 | ypresent = False 47 | zpresent = False 48 | for co in all_coords: 49 | if abs(startx - co[0]) > Ray.EPSILON: xpresent = True 50 | if abs(starty - co[1]) > Ray.EPSILON: ypresent = True 51 | if abs(startz - co[2]) > Ray.EPSILON: zpresent = True 52 | 53 | fig = plt.figure() 54 | 55 | if xpresent and ypresent and zpresent: 56 | ax = fig.add_subplot(projection='3d') 57 | ax.scatter(x, y, z) 58 | ax.set_xlabel('X-axis') 59 | ax.set_ylabel('Y-axis') 60 | ax.set_zlabel('Z-axis') 61 | else: 62 | ax = fig.add_subplot() 63 | if not zpresent: 64 | ax.scatter(x, y) 65 | ax.set_xlabel('X-axis') 66 | ax.set_ylabel('Y-axis') 67 | elif not ypresent: 68 | ax.scatter(x, z) 69 | ax.set_xlabel('X-axis') 70 | ax.set_ylabel('Z-axis') 71 | else: 72 | ax.scatter(y, z) 73 | ax.set_xlabel('Y-axis') 74 | ax.set_ylabel('Z-axis') 75 | 76 | plt.show() 77 | else: 78 | print("No ray hits were found") 79 | 80 | def Activated(self): 81 | '''Will be called when the feature is executed.''' 82 | # Generate commands in the FreeCAD python console to plot ray hits for selected absorber 83 | Gui.doCommand('import OpticsWorkbench') 84 | Gui.doCommand('selectedObjList = FreeCADGui.Selection.getSelection()') 85 | Gui.doCommand('OpticsWorkbench.drawPlot(selectedObjList)') 86 | 87 | 88 | def IsActive(self): 89 | '''Here you can define if the command must be active or not (greyed) if certain conditions 90 | are met or not. This function is optional.''' 91 | if activeDocument(): 92 | return(True) 93 | else: 94 | return(False) 95 | 96 | def GetResources(self): 97 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 98 | return {'Pixmap' : os.path.join(_icondir_, 'scatter3D.svg'), 99 | 'Accel' : '', # a default shortcut (optional) 100 | 'MenuText': QT_TRANSLATE_NOOP('RayHits', '2D/3D Plot'), 101 | 'ToolTip' : QT_TRANSLATE_NOOP('RayHits', 'Show selected absorbers ray hits in scatter plot') } 102 | 103 | 104 | class RayHits2CSV(): 105 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 106 | 107 | def Activated(self): 108 | '''Will be called when the feature is executed.''' 109 | # Generate commands in the FreeCAD python console to plot ray hits for selected absorber 110 | Gui.doCommand('import OpticsWorkbench') 111 | Gui.doCommand('OpticsWorkbench.Hits2CSV()') 112 | 113 | 114 | def IsActive(self): 115 | '''Here you can define if the command must be active or not (greyed) if certain conditions 116 | are met or not. This function is optional.''' 117 | if activeDocument(): 118 | return(True) 119 | else: 120 | return(False) 121 | 122 | def GetResources(self): 123 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 124 | return {'Pixmap' : os.path.join(_icondir_, 'ExportCSV.svg'), 125 | 'Accel' : '', # a default shortcut (optional) 126 | 'MenuText': QT_TRANSLATE_NOOP('Hits2CSV', 'Ray Hits to Spreadsheet'), 127 | 'ToolTip' : QT_TRANSLATE_NOOP('Hits2CSV', 'Export Ray Hits to Spreadsheet') } 128 | 129 | 130 | Gui.addCommand('RayHits', PlotRayHits()) 131 | Gui.addCommand('Hits2CSV', RayHits2CSV()) 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![WorkbenchIcon](./optics_workbench_icon.svg) Optics Workbench 2 | 3 | [ >deutsch< ](README_de.md) 4 | 5 | [ >中文< ](README_zh_TW.md) 6 | 7 | Geometrical optics for FreeCAD. 8 | Performs simple raytracing through your FreeCAD objects. 9 | 10 | ![screenshot](./examples/example2D.png) 11 | 12 | ## Installation 13 | 14 | ### Auto Installation 15 | 16 | Optics workbench is available through the FreeCAD [Addon Manager](https://wiki.freecad.org/AddonManager) 17 | 18 | ### Manual Installation 19 | 20 |
21 | Expand to view manual installation instructions 22 | 23 | ```bash 24 | cd ~/.FreeCAD/Mod/ 25 | git clone https://github.com/chbergmann/OpticsWorkbench.git 26 | ``` 27 | 28 |
29 | 30 | #### Important Note 31 | Once Optics workbench is installed, FreeCAD will need to be restarted. When you restart FreeCAD, "Optics Workbench" workbench should now show up in the [workbench dropdown list](https://freecad.org/wiki/Std_Workbench). 32 | 33 | ## Getting started 34 | - Create some FreeCAD design objects. For 2D simulation Sketches will do the job. 35 | - Select one or more of your design objects and then create Optical Mirror to let your objects act as mirrors 36 | - Select one or more of your design objects and then create Optical Absorber to let your objects act as black walls for the light rays 37 | - Select one or more of your design objects and then create Optical Lenses to let your objects act as lenses. Lenses should be closed shapes. 38 | Select a material in the Lens properties or provide a refraction index. 39 | - Add some source of light (Ray, Beam). 40 | 41 | ## Tools 42 | ### ![RayIcon](./icons/ray.svg) Ray (monochrome) 43 | A single ray for raytracing 44 | Parameters: 45 | - `Power`: On or Off 46 | - `RayBundleType`: `parallel` line or rectangle of beams into one direction, `spherical` all rays start at the same point, `focal` All rays are passing a focal point 47 | - `BeamNrColumns`: Number of rays in a beam per column 48 | - `BeamNrRows`: Number of rays in a beam per row 49 | - `BeamDistance`: Distance between two beams 50 | - `HideFirstPart`: Hide the first part of every ray that comes from the source and goes to the first point of reflection/refraction/nirvana 51 | - `MaxRayLength`: Maximum length of a ray 52 | - `MaxNrReflections`: Maximum number of reflections. This prevents endless loops if the ray is inside a mirror box. 53 | - `Ignored Optical Elements`: List of optical objects that will be ignored by the ray/beam. 54 | - `Base`: If a shape is selected here, an optical emitter will be created. 55 | - `FocalPoint`: set RayBundleType to `focal` to direct all rays to this point 56 | 57 | ### ![SunRayIcon](./icons/raysun.svg) Ray (sun light) 58 | A number of rays with different wavelengths of visible light. 59 | The rays overlap. If they hit a lens, they will be dispersed. See [Example - Dispersion](#-example---dispersion) below. 60 | 61 | ### ![2D Beam](./icons/rayarray.svg) 2D Beam 62 | A row of multiple rays for raytracing 63 | Parameters: 64 | * Ray. `BeamNrColumns` must be > 1 and `RayBundleType=parallel` to get a beam 65 | 66 | ### ![Radial Beam](./icons/sun.svg) 2D Radial Beam 67 | Rays coming from one point going to all directions in a 2D plane 68 | Parameters: 69 | * Ray. `BeamNrColumns` must be > 1 and `BeamNrRows=1` and `RayBundleType=spherical` to get a radial beam 70 | 71 | ### ![Spherical Beam](./icons/sun3D.svg) Spherical Beam 72 | Rays coming from one point going to all directions 73 | Parameters: 74 | * Ray. `BeamNrColumns` and `BeamNrRows` must be > 1 `RayBundleType=spherical` to get a spherical beam 75 | 76 | ### ![Grid Focal Beam](./icons/raygridfocal.svg) Grid Focal Beam 77 | Rays coming from a grid going to a focal point 78 | Parameters: 79 | * Ray. `BeamNrColumns` and `BeamNrRows` must be > 1 `RayBundleType=focal` to get a spherical beam 80 | 81 | ### ![Optical Emitter](./icons/emitter.svg) Optical Emitter 82 | The FreeCAD objects in parameter Base will act as emitters 83 | Select some FreeCAD objects, faces or edges, then create Optical Emitter 84 | ![screenshot](./examples/Emitter.png) 85 | Edges can also be selected as Base: 86 | ![screenshot](./examples/example_candle2D.png) 87 | 88 | The parameters are the same as in the Ray tool. 89 | Parameters with different meaning for Emitter: 90 | - `Power`: On or Off 91 | - `Spherical`: (will be ignored for this feature) 92 | - `BeamNrColumns`: Number of rays per column distributed over every selected surface 93 | - `BeamNrRows`: Number of rays per row distributed over every selected surface 94 | - `BeamDistance`: (will be ignored for this feature) 95 | - `Base`: Base shape for optical emitter. If a solid is selected, rays will be created on every face. You can also select faces or edges separately. 96 | 97 | ### ![Optical Mirror](./icons/mirror.svg) Optical Mirror 98 | The FreeCAD objects in parameter Base will act as mirrors 99 | * Select some FreeCAD objects, then create Optical Mirror 100 | Parameters: 101 | - `Transparency`: Percentage of light that passes through the semi transparent mirror 102 | - `collectStatistics` see [Statistics](#Statistics) 103 | 104 | ### ![Optical Absorber](./icons/absorber.svg) Optical Absorber 105 | The FreeCAD objects in parameter `Base` will swallow the rays of light. 106 | * Select some FreeCAD objects 107 | * Create Optical Absorber 108 | Parameters: 109 | - `Transparency`: Percentage of light that passes through the semi transparent absorber 110 | - `collectStatistics` see [Statistics](#Statistics) 111 | 112 | 113 | ### ![Diffraction grating](./icons/grating.svg) Diffraction grating 114 | The FreeCAD objects in parameter `Base` will do simple 1D grating simulation to the very superb OpticsWorkbench. 115 | Raytracing of simple 1D gratings is done following [Ludwig 1973](https://doi.org/10.1364/JOSA.63.001105) 116 | 117 | For this approach, rays now have the additional attribute `order`, which is taken into account when hitting an object specified as optical grating. Utilizing this it is possible to simulate multiple orders of diffraction at one grating by generating rays with the same wavelength but in a different order. 118 | 119 | Gratings are defined by their: 120 | * type: 121 | * reflection 122 | * transmission with diffraction at 1st surface 123 | * transmission with diffraction at 2nd surface 124 | * line spacing 125 | * line direction as specified by a hypothetical set of planes intersecting the body and generating the lines as intersecion cuts 126 | * attribute "order" 127 | Additionally, for transmission gratings a refractive index should be provided. 128 | 129 | Diffraction at a grating object can be specified to be calculated using the order defined by the ray, or by the hit grating, allowing for multiple diffractions of different orders at multiple gratings being hit in the path of a single ray. 130 | **Note** that due to the specific type of this approach to simulate diffraction gratings, one quickly ends up with a large quantity of rays or even sunrays, which consequently heavily increases calculation time. 131 | **Also note** that bugs in the code might of course be present, however in my testing diffraction (at least for reflection and transmission gratings without taking into account different indices of refactions) is simulated accurately. 132 | 133 | ![screenshot](./examples/simple_reflection_grating_set_of_planes.PNG) 134 | *Above: illustrates a simple reflection grating with 500 lpm hit by sunray. Planes with normal 010 indicate the set of intersecting planes used to define the grating lines direction.* 135 | 136 | ![screenshot](./examples/simple_transmission_grating.PNG) 137 | *Above: shows the same body, defined as transmission grating. Note that the diffraction happens at the 2nd surface as specified by the grating type. Differences in refractive indices are taken into account.* 138 | 139 | ![screenshot](./examples/echelle_example.PNG) 140 | *Above: an example of a simple echelle spectrometer using a R2 52.91 lpm grating and a set of sunrays from order -47 to -82 (each order comprises ~5-10 nm, sampled by 15 rays around a center wavelength from blue to red) and a flint glass prism. Collimation and camera optics are thorlabs STEP files and a transparent absorber shows the resulting echelle spectrum. Entrance into the spectrometer design is by a 50 mu slit. This is an example with very long calculation time due to the high number of rays. **Note** that the sign of the order is not intuitive. If an error occurs stating that complex numbers are not supported, while diffraction with this order is considered valid by the user, try to change the sign of the order.* 141 | 142 | see also [Statistics](#Statistics) 143 | 144 | ### ![Optical Lens](./icons/lens.svg) Optical Lens 145 | The FreeCAD objects in parameter Base will act as lenses 146 | * Select some FreeCAD objects 147 | * create Optical Lens 148 | Parameters: 149 | - `Material`: contains a list with pre defined refraction indexes ans Sellmeier coefficients. 150 | - `Refration Index`: has to be provided if no material is selected 151 | - `Sellmeier`: wavelength dependent refraction index coefficents 152 | - `Transparency`: Percentage of light that passes through the lens. The rest will be mirrored at the outside 153 | - `collectStatistics` see [Statistics](#Statistics) 154 | 155 | ### ![Off](./icons/Anonymous_Lightbulb_Off.svg) Switch off lights 156 | Switches off all Rays and Beams 157 | 158 | ### ![On](./icons/Anonymous_Lightbulb_Lit.svg) (Re)start simulation 159 | Switches on and recalculates all Rays and Beams 160 | 161 | ## Statistics 162 | The optical objects have a parameter `collectStatistics`. If true, some statistics will be collected on every start of simulation: 163 | - `Hits From Ray/Beam...`: This is a counter of how many rays have hit this mirror (read only) 164 | - `Hit coordinates from ...`: records the position of each LIGHT RAY when it hits (read only). This way, it is possible to visualize the image on the absorber in a XY diagram. 165 | - `Energy from ...`: records the energy level of evey coordinate. Relevant if you are using semi transparent objects. 166 | 167 | ### ![plot3D](./icons/scatter3D.svg) 2D/3D Plot 168 | Select one or more optical objects with parameter `collectStatistics` = true and display the location rays hitting them on a scatter graph. It will ignore objects other than absorbers. To only display hits from select beam sources turn off the power for the beams to be ignored. Toggling beams or absorbers visibility in the document tree does not affect the 3D scatter plot output. 169 | If coordinates in all 3 dimensions are present, a 3D plot will be shown, otherwise you will see a 2D plot only. 170 | 171 | ![screenshot](./examples/plot3Dexample1.png) ![screenshot](./examples/plot3Dexample2.png) 172 | 173 | 174 | ### ![CSVexport](./icons/ExportCSV.svg) CSV Ray Hits Export 175 | Creates a spreadsheet with the coordinates of all hits of all beams in all optical objects with parameter `collectStatistics` = true. 176 | Go to the Spreadsheet workbench for doing further data processing or export the data to a file. 177 | 178 | ![screenshot](./examples/RayHits.png) 179 | 180 | ### ![Example](./optics_workbench_icon.svg) Example - 2D 181 | ![screenshot](./examples/example2D.png) 182 | 183 | ### ![Example](./optics_workbench_icon.svg) Example - 3D 184 | ![screenshot](./examples/screenshot3D.png) 185 | 186 | ### ![Example](./optics_workbench_icon.svg) Example - Dispersion 187 | ![screenshot](https://pad.ccc-p.org/uploads/upload_210b4dd5466d2837eb76d5e63688a5c1.png) 188 | 189 | ### ![Example](./optics_workbench_icon.svg) Example - Candle 190 | ![screenshot](./examples/example_candle.png) 191 | 192 | ### ![Example](./optics_workbench_icon.svg) Example - Semi transparency 193 | ![screenshot](./examples/example_semi.png) 194 | 195 | ## Issues and Troubleshooting 196 | see [issues on Github](https://github.com/chbergmann/OpticsWorkbench/issues) 197 | 198 | ## Discussion 199 | Please offer feedback or connect with the developer via the [dedicated FreeCAD forum thread](https://forum.freecad.org/viewtopic.php?f=8&t=59860). 200 | 201 | ## License 202 | GNU Lesser General Public License v3.0 ([LICENSE](LICENSE)) 203 | -------------------------------------------------------------------------------- /README_de.md: -------------------------------------------------------------------------------- 1 | # ![WorkbenchIcon](./optics_workbench_icon.svg) Optics Workbench 2 | 3 | [ >english< ](README.md) 4 | 5 | [ >中文< ](README_zh_TW.md) 6 | 7 | Geometrische Optik für FreeCAD. 8 | Simuliert Lichtstrahlen durch deine FreeCAD Objekte. 9 | 10 | 11 | ![screenshot](./examples/example2D.png) 12 | 13 | ## Installation 14 | 15 | ### Auto Installation 16 | 17 | Optics workbench kann mit Hilfe des [Addon Manager](https://wiki.freecad.org/AddonManager) installiert werden 18 | 19 | ### Manuelle Installation 20 | 21 |
22 | 23 | ```bash 24 | cd ~/.FreeCAD/Mod/ 25 | git clone https://github.com/chbergmann/OpticsWorkbench.git 26 | ``` 27 | 28 |
29 | 30 | #### Wichtiger Hinweis 31 | Nach der Installation der Optics Workbench muss FreeCAD neu gestartet werden. 32 | "Optics" ist danach auswählbar in der [workbench dropdown list](https://freecad.org/wiki/Std_Workbench). 33 | 34 | ## Einstieg in Optics workbench 35 | - Erstelle FreeCAD Objekte. Für 2D Simulationen können das Sketche sein. 36 | - Wähle ein oder mehrere FreeCAD objekte aus und klicke dann auf Spiegel, Linse, Absorber oder optisches Gitter, um das Objekt als solches zu verwenden. 37 | - Erstelle eine Lichtquelle (Lichtstrahl, Strahlenbündel, Sonnenstrahl) 38 | 39 | ## Tools 40 | ### ![RayIcon](./icons/ray.svg) Lichtstrahl (monochrom) 41 | Ein einzelner Lichtstrahl für die optische Simulation. 42 | Parameter: 43 | - `Power`: An oder aus 44 | - `RayBundleType`: `parallel`: alle Lichtstrahlen gehen in die selbe Richtng, `spherical`: Punktförmige Lichtquelle, `focal`: alle Lichtstrahlen schneiden sich in FocalPoint 45 | - `BeamNrColumns`: Anzahl Lichtstrahlen pro Spalte 46 | - `BeamNrRows`: Anzahl Lichtstrahlen pro Reihe 47 | - `BeamDistance`: Abstand zwischen Strahlen 48 | - `Cone Angle`: Lichtkegel Öffnungswinkel 49 | - `HideFirstPart`: Den ersten Teil von jedem Lichtstrahl ausblenden 50 | - `MaxRayLength`: Maximale Länge 51 | - `MaxNrReflections`: Maximale Anzahl von Reflexionen, um Endlosschleifen in geschlossenen Spiegeln zu vermeiden 52 | - `Ignored Optical Elements`: Liste von Ojekten, die ignoriert werden sollen 53 | - `Base`: Falls vorhanden, wird ein Emitter auf Basis dieses Elements generiert 54 | - `FocalPoint`: mit `RayBundleType=focal`: schneiden sich alle Lichtstrahlen in diesem Punkt 55 | 56 | ### ![SunRayIcon](./icons/raysun.svg) Sonnenstrahl 57 | Ein Bündel sich überlappender Lichtstrahlen mit verschiedenen Wellenlängen im sichtbaren Bereich. 58 | Siehe [Beispiel - Lichtbrechung](#-Beispiel---Lichtbrechung). 59 | 60 | ### ![2D Beam](./icons/rayarray.svg) 2D Strahlenbündel 61 | Lichtstrahlen in einer Reihe angeordnet. 62 | Parameter: 63 | - `BeamNrColumns` muss größer als 1 sein, um ein Strahlenbündel zu bekommen 64 | - `Spherical`: Punktförmige Lichtquelle, muss False sein 65 | 66 | ### ![Radial Beam](./icons/sun.svg) 2D punktförmige Lichtquelle 67 | Punktförmige, radiale Lichtquelle in einer Ebene. 68 | Parameter: 69 | - `BeamNrColumns` Anzahl Strahlen 70 | - `BeamNrRows` muss 1 sein 71 | - `Spherical`: Punktförmige Lichtquelle, muss True sein 72 | - `Cone Angle`: Lichtkegel Öffnungswinkel 73 | 74 | ### ![Grid Focal Beam](./icons/raygridfocal.svg) Fokussierte Lichtquelle 75 | Alle Strahlen des Bündels schneiden ich in FocalPoint 76 | Parameters: 77 | * Ray. `BeamNrColumns` and `BeamNrRows` must be > 1 `RayBundleType=focal` to get a spherical beam 78 | 79 | ### ![Spherical Beam](./icons/sun3D.svg) Optischer Emitter 80 | Punktförmige Lichtquelle im 3D Raum. 81 | Parameter: 82 | - `BeamNrColumns` Anzahl Strahlen = `BeamNrColumns` * `BeamNrRows` 83 | - `BeamNrRows` muss größer 1 sein 84 | - `Spherical`: Punktförmige Lichtquelle, muss True sein 85 | - `Cone Angle`: Lichtkegel Öffnungswinkel 86 | 87 | ### ![Optical Emitter](./icons/emitter.svg) Optischer Emitter 88 | Die FreeCAD Objekte im Parameter Base werden optische Emitter. 89 | Wähle ein FreeCAD Objekt, oder eine Oberfläche aus, dann Optischer Emitter. 90 | ![screenshot](./examples/Emitter.png) 91 | Edges can also be selected as Base: 92 | ![screenshot](./examples/example_candle2D.png) 93 | 94 | Parameter wie in Lichtstrahl (monochrom): 95 | - `Power`: An oder Aus 96 | - `BeamNrColumns`: Anzahl Lichtstrahlen in einer Spalte auf der selektierten Oberfläche 97 | - `BeamNrRows`: Anzahl Lichtstrahlen in einer Reihe auf der selektierten Oberfläche 98 | - `Base`: Basis Objekt für optischen Emitter. Bei Festkörpern werden Lichtstrahlen von allen Oberflächen aus generiert. Es ist auch möglich, einzelene Oberflächen auszuwählen. 99 | 100 | ### ![Optical Mirror](./icons/mirror.svg) Optischer Spiegel 101 | Die FreeCAD Objekte im Parameter Base werden optische Spiegel. 102 | * Wähle ein oder mehrere FreeCAD Objekte aus, dann Optischer Spiegel. 103 | Parameter: 104 | - `Transparency`: Prozentsatz des Lichts, das den Spiegel passiert. Der Rest wird an der Oberfläche reflektiert 105 | - `collectStatistics` siehe [Statistik](#Statistik) 106 | 107 | ### ![Optical Absorber](./icons/absorber.svg) Optischer Absorber 108 | Die FreeCAD Objekte im Parameter Base verschlucken alle auftreffenden Lichtstrahlen. 109 | * Wähle ein oder mehrere FreeCAD Objekte aus, dann Optischer Absorber. 110 | Parameter: 111 | - `Transparency`: Prozentsatz des Lichts, das den Absorber passiert 112 | - `collectStatistics` siehe [Statistik](#Statistik) 113 | 114 | ### ![Diffraction grating](./icons/grating.svg) Optisches Gitter 115 | Die FreeCAD Objekte im Parameter Base werden optische Gitter. 116 | Simulation von einfachen 1D optischen Gittern nach [Ludwig 1973](https://doi.org/10.1364/JOSA.63.001105) 117 | 118 | For this approach, rays now have the additional attribute `order`, which is taken into account when hitting an object specified as optical grating. Utilizing this it is possible to simulate multiple orders of diffraction at one grating by generating rays with the same wavelength but in a different order. 119 | 120 | Gratings are defined by their: 121 | * type: 122 | * reflection 123 | * transmission with diffraction at 1st surface 124 | * transmission with diffraction at 2nd surface 125 | * line spacing 126 | * line direction as specified by a hypothetical set of planes intersecting the body and generating the lines as intersecion cuts 127 | * attribute "order" 128 | Additionally, for transmission gratings a refractive index should be provided. 129 | 130 | Diffraction at a grating object can be specified to be calculated using the order defined by the ray, or by the hit grating, allowing for multiple diffractions of different orders at multiple gratings being hit in the path of a single ray. 131 | **Note** that due to the specific type of this approach to simulate diffraction gratings, one quickly ends up with a large quantity of rays or even sunrays, which consequently heavily increases calculation time. 132 | **Also note** that bugs in the code might of course be present, however in my testing diffraction (at least for reflection and transmission gratings without taking into account different indices of refactions) is simulated accurately. 133 | 134 | ![screenshot](./examples/simple_reflection_grating_set_of_planes.PNG) 135 | *Above: illustrates a simple reflection grating with 500 lpm hit by sunray. Planes with normal 010 indicate the set of intersecting planes used to define the grating lines direction.* 136 | 137 | ![screenshot](./examples/simple_transmission_grating.PNG) 138 | *Above: shows the same body, defined as transmission grating. Note that the diffraction happens at the 2nd surface as specified by the grating type. Differences in refractive indices are taken into account.* 139 | 140 | ![screenshot](./examples/echelle_example.PNG) 141 | *Above: an example of a simple echelle spectrometer using a R2 52.91 lpm grating and a set of sunrays from order -47 to -82 (each order comprises ~5-10 nm, sampled by 15 rays around a center wavelength from blue to red) and a flint glass prism. Collimation and camera optics are thorlabs STEP files and a transparent absorber shows the resulting echelle spectrum. Entrance into the spectrometer design is by a 50 mu slit. This is an example with very long calculation time due to the high number of rays. **Note** that the sign of the order is not intuitive. If an error occurs stating that complex numbers are not supported, while diffraction with this order is considered valid by the user, try to change the sign of the order.* 142 | 143 | 144 | ### ![Optical Lens](./icons/lens.svg) Optische Linse 145 | Die FreeCAD Objekte im Parameter Base werden optische Linsen. 146 | Wähle ein oder mehrere FreeCAD Objekte aus, dann Optische Linse. 147 | Parameter: 148 | - `RefractionIndex` Brechungsindex 149 | - `Sellmeier` Brechungsindex in Abhängigkeit der Wellenlänge 150 | - `Material` Die Auswahl eines Material stellt Brechungsindex und Sellmeier Parameter ein 151 | - `Transparency`: Prozentsatz des Lichts, das die Linse passiert. Der Rest wird an der Oberfläche reflektiert 152 | - `collectStatistics` siehe [Statistik](#Statistik) 153 | 154 | ### ![Off](./icons/Anonymous_Lightbulb_Off.svg) Licht ausschalten 155 | Alle Lichtquellen deaktivieren. 156 | 157 | ### ![On](./icons/Anonymous_Lightbulb_Lit.svg) Simulation neu starten 158 | Alle Lichtstrahlen neu berechnen. 159 | 160 | ## Statistik 161 | Die optischen Objekte haben einen Parameter `collectStatistics`. Wenn true, werden Statistiken von auftreffenden Lichtstrahlen gesammelt: 162 | - `Hits From Ray/Beam...` Anzahl Strahl-Treffer (schreibgeschützt) 163 | - `Hit coordinates from ... ` Koordination vom Strahl-Treffer (schreibgeschützt) 164 | 165 | 166 | ### ![plot3D](./icons/scatter3D.svg) 2D/3D Plot 167 | Zeigt eine Graphik mit den Strahl-Treffer Koordinaten von ausgewählten optischen Objekten mit dem Parameter `collectStatistics` = true 168 | 169 | ![screenshot](./examples/plot3Dexample1.png) ![screenshot](./examples/plot3Dexample2.png) 170 | 171 | 172 | ### ![CSVexport](./icons/ExportCSV.svg) CSV Ray Hits Export 173 | Exportiert die Statistik von ausgewählten optischen Objekten mit dem Parameter `collectStatistics` = true in eine Tabelle. 174 | Weitere Datenverarbeitung und Speichern in Datei ist dann über die Spreadsheet workbench möglich. 175 | 176 | ![screenshot](./examples/RayHits.png) 177 | 178 | ### ![Example](./optics_workbench_icon.svg) Beispiel - 2D 179 | ![screenshot](./examples/example2D.png) 180 | 181 | ### ![Example](./optics_workbench_icon.svg) Beispiel - 3D 182 | ![screenshot](./examples/screenshot3D.png) 183 | 184 | ### ![Example](./optics_workbench_icon.svg) Beispiel - Lichtbrechung 185 | ![screenshot](https://pad.ccc-p.org/uploads/upload_210b4dd5466d2837eb76d5e63688a5c1.png) 186 | 187 | ### ![Example](./optics_workbench_icon.svg) Beispiel - Emitter 188 | ![screenshot](./examples/example_candle.png) 189 | - 190 | ### ![Example](./optics_workbench_icon.svg) Beispiel - Halb-Transparenz 191 | ![screenshot](./examples/example_semi.png) 192 | 193 | ## Fehlersuche und Report (englisch) 194 | siehe [issues on Github](https://github.com/chbergmann/OpticsWorkbench/issues) 195 | 196 | ## Diskussion (englisch) 197 | Please offer feedback or connect with the developer via the [dedicated FreeCAD forum thread](https://forum.freecad.org/viewtopic.php?f=8&t=59860). 198 | 199 | ## Lizenz 200 | GNU Lesser General Public License v3.0 ([LICENSE](LICENSE)) 201 | -------------------------------------------------------------------------------- /README_zh_TW.md: -------------------------------------------------------------------------------- 1 | # ![WorkbenchIcon](./optics_workbench_icon.svg) Optics Workbench 2 | 3 | [ >deutsch< ](README_de.md) 4 | 5 | [ >English< ](README.md) 6 | 7 | FreeCAD 的幾何光學模組。 8 | 通過你的 FreeCAD 物件進行簡單的光線追蹤。 9 | 10 | ![screenshot](./examples/example2D.png) 11 | 12 | ## 安裝 13 | 14 | ### 自動安裝 15 | 16 | Optics 工作台可通過 FreeCAD 的 [Addon Manager](https://wiki.freecad.org/AddonManager) 安裝。 17 | 18 | 19 | ### 手動安裝 20 | 21 |
22 | 點擊展開顯示手動安裝說明 23 | 24 | ```bash 25 | cd ~/.FreeCAD/Mod/ 26 | git clone https://github.com/chbergmann/OpticsWorkbench.git 27 | ``` 28 | 29 | 如果是macOS下,你的FreeCAD Mod路徑可能如下 30 | 31 | ``` 32 | cd ~/Library/Application\ Support/FreeCAD/Mod/ 33 | git clone https://github.com/chbergmann/OpticsWorkbench.git 34 | ``` 35 | 36 | 如果是Windows路徑可能如下 37 | 38 | ``` 39 | C:\Program Files\FreeCAD X.YY 40 | ``` 41 | 42 |
43 | 44 | #### 重要提醒 45 | 一旦安裝完成,必須重新啟動 FreeCAD。當重新啟動 FreeCAD 後,"Optics Workbench" 應該會顯示在 [工作台下拉選單](https://freecad.org/wiki/Std_Workbench) 中。 46 | 47 | ## 開始使用 48 | - 創建一些 FreeCAD 設計物件。對於 2D 模擬,草圖就能完成工作。 49 | - 選擇一個或多個設計物件,然後創建光學鏡面,使物件充當鏡子。 50 | - 選擇一個或多個設計物件,然後創建光學吸收體,使物件充當光線的黑牆。 51 | - 選擇一個或多個設計物件,然後創建光學透鏡,使物件充當透鏡。透鏡應該是封閉形狀。 52 | - 在透鏡屬性中選擇一個材料或提供折射率。 53 | - 添加一些光源(光線、光束)。 54 | 55 | ## 工具 56 | ### ![RayIcon](./icons/ray.svg) 光線 (單色) 57 | 單一光線進行光線追蹤 58 | 參數: 59 | - 功率:開啟或關閉 60 | - 球面:False = 光束單向,True = 從中心向所有方向發射的光線 61 | - BeamNrColumns:每列光線的數量 62 | - BeamNrRows:每行光線的數量 63 | - BeamDistance:兩束光線之間的距離 64 | - 隱藏第一部分:隱藏從光源出發到達第一個反射/折射/消失點的光線部分 65 | - 最大光線長度:光線的最大長度 66 | - 最大反射次數:光線的最大反射次數。防止光線進入鏡盒內無限循環。 67 | - 忽略光學元件:光線將忽略的光學物件列表。 68 | - 基礎:如果在此處選擇了一個形狀,將創建一個光學發射器。 69 | 70 | ### ![SunRayIcon](./icons/raysun.svg) 光線 (陽光) 71 | 多條不同波長的可見光光線。 72 | 光線會重疊。如果它們撞擊到透鏡,則會發生色散。請參見 [範例 - 色散](#-example---dispersion) 下方。 73 | 74 | ### ![2D Beam](./icons/rayarray.svg) 2D 光束 75 | 多條光線組成的光束進行光線追蹤 76 | 參數: 77 | * 光線:BeamNrColumns 必須大於 1 才能生成光束 78 | 79 | ### ![Radial Beam](./icons/sun.svg) 2D 放射性光束 80 | 從一點向所有方向發射的光線 81 | 參數: 82 | * 光線:BeamNrColumns 必須大於 1 且 BeamNrRows=1 且 Spherical=True 以生成放射性光束 83 | 84 | ### ![Spherical Beam](./icons/sun3D.svg) 球面光束 85 | 從一點向所有方向發射的光線 86 | 參數: 87 | * 光線:BeamNrColumns 和 BeamNrRows 必須大於 1 且 Spherical=True 才能生成球面光束 88 | 89 | ### ![Optical Emitter](./icons/emitter.svg) 光學發射器 90 | FreeCAD 物件將作為光學發射器 91 | 選擇一些 FreeCAD 物件、面或邊,然後創建光學發射器 92 | ![screenshot](./examples/Emitter.png) 93 | 邊也可以作為基礎選擇: 94 | ![screenshot](./examples/example_candle2D.png) 95 | 96 | 參數與光線工具相同。 97 | 具有不同含義的參數: 98 | - 功率:開啟或關閉 99 | - 球面:(此功能將忽略此參數) 100 | - BeamNrColumns:每列光線在每個選定表面上的分佈數量 101 | - BeamNrRows:每行光線在每個選定表面上的分佈數量 102 | - BeamDistance:(此功能將忽略此參數) 103 | - 基礎:用於光學發射器的基礎形狀。如果選擇固體,則會在每個面上創建光線。你也可以分別選擇面或邊。 104 | 105 | ### ![Optical Mirror](./icons/mirror.svg) 光學鏡面 106 | FreeCAD 物件將作為鏡子 107 | * 選擇一些 FreeCAD 物件,然後創建光學鏡面 108 | 請參見 [統計](#Statistics) 109 | 110 | ### ![Optical Absorber](./icons/absorber.svg) 光學吸收體 111 | FreeCAD 物件將吸收光線 112 | * 選擇一些 FreeCAD 物件 113 | * 創建光學吸收體 114 | 請參見 [統計](#Statistics) 115 | 116 | ### ![Diffraction grating](./icons/grating.svg) 衍射光柵 117 | FreeCAD 物件將進行簡單的一維光柵模擬。 118 | 對簡單的一維光柵進行光線追蹤,依據 [Ludwig 1973](https://doi.org/10.1364/JOSA.63.001105) 進行。 119 | 120 | 此方法中,光線有額外的屬性順序,當撞擊指定為光學光柵的物件時會被考慮。利用此屬性,可以在單一光柵上模擬多次不同次序的衍射。 121 | 122 | 光柵根據以下屬性進行定義: 123 | * 類型: 124 | * 反射 125 | * 透射,且在第一面發生衍射 126 | * 透射,且在第二面發生衍射 127 | * 線間距 128 | * 線方向,由一組假設的平面定義,這些平面與物體交集並生成光柵線 129 | * 屬性 "順序" 130 | 此外,對於透射光柵,應提供折射率。 131 | 132 | 衍射光柵的計算方式可以基於光線定義的順序進行,也可以基於撞擊的光柵進行,這樣就能在多個光柵上同時計算不同的衍射順序。 133 | **注意** 由於此方法模擬光柵的方式,光線數量會迅速增多,尤其是使用陽光等高數量光源時,計算時間也會顯著增加。 134 | **同時注意**,由於代碼中的漏洞,衍射的模擬可能會有所不準,但在測試中反射和透射光柵的模擬是準確的。 135 | 136 | ![screenshot](./examples/simple_reflection_grating_set_of_planes.PNG) 137 | *上圖:顯示一個簡單的反射光柵,500 lpm 被陽光照射。平面法線為 010,顯示定義光柵線方向的交集平面集合。* 138 | 139 | ![screenshot](./examples/simple_transmission_grating.PNG) 140 | *上圖:顯示相同的物體,定義為透射光柵。請注意,衍射發生在第二面,這是光柵類型的規定。* 141 | 142 | ![screenshot](./examples/echelle_example.PNG) 143 | *上圖:顯示一個簡單的 echelle 分光儀,使用 R2 52.91 lpm 光柵,並對來自順序 -47 到 -82 的陽光進行衍射。每個順序包含大約 5-10 nm,並從藍光到紅光分別採樣。* 144 | 145 | 請參見 [統計](#Statistics) 146 | 147 | ### ![Optical Lens](./icons/lens.svg) 光學透鏡 148 | FreeCAD 物件將作為透鏡 149 | * 選擇一些 FreeCAD 物件 150 | * 創建光學透鏡 151 | 必須提供折射率。參數「材料」包含一個預定義折射率的列表。 152 | 請參見 [統計](#Statistics) 153 | 154 | ### ![Off](./icons/Anonymous_Lightbulb_Off.svg) 關閉光源 155 | 關閉所有光線和光束 156 | 157 | ### ![On](./icons/Anonymous_Lightbulb_Lit.svg) (重新)啟動模擬 158 | 啟動並重新計算所有光線和光束 159 | 160 | ## Statistics 161 | 光學物件有一個參數 `collectStatistics`。如果設為 `true`,則每次開始模擬時會收集一些統計資料: 162 | - `Hits From Ray/Beam...` 這是計數器,顯示有多少光線擊中了此鏡面(只讀) 163 | - `Hit coordinates from ...` 記錄每條光線擊中物體的位置(只讀)。這樣可以在吸收體上以 XY 圖表方式視覺化光線的影像。 164 | 165 | ### ![plot3D](./icons/scatter3D.svg) 2D/3D 圖表 166 | 選擇一個或多個屬性為 `collectStatistics` = true 的光學物件,並顯示光線擊中它們的位置。將會顯示散佈圖,並忽略非吸收物體。若只顯示選定光束源的擊中位置,請將光束的功率設為關閉,以忽略這些光束。切換文件樹中的光束或吸收體的可見性不會影響 3D 散佈圖的顯示。 167 | 如果有三個維度的座標,將顯示 3D 圖表,否則只顯示 2D 圖表。 168 | 169 | ![screenshot](./examples/plot3Dexample1.png) ![screenshot](./examples/plot3Dexample2.png) 170 | 171 | 172 | ### ![CSVexport](./icons/ExportCSV.svg) CSV 光線擊中匯出 173 | 創建一個電子表格,列出所有光束擊中所有吸收體的座標。 174 | 前往「試算表工作台」進行進一步數據處理,或將數據匯出為檔案。 175 | 176 | ![screenshot](./examples/RayHits.png) 177 | 178 | ### ![Example](./optics_workbench_icon.svg) 範例 - 2D 179 | ![screenshot](./examples/example2D.png) 180 | 181 | ### ![Example](./optics_workbench_icon.svg) 範例 - 3D 182 | ![screenshot](./examples/screenshot3D.png) 183 | 184 | ### ![Example](./optics_workbench_icon.svg) 範例 - 色散 185 | ![screenshot](https://pad.ccc-p.org/uploads/upload_210b4dd5466d2837eb76d5e63688a5c1.png) 186 | 187 | ### ![Example](./optics_workbench_icon.svg) 範例 - 蠟燭 188 | ![screenshot](./examples/example_candle.png) 189 | 190 | ## 問題與疑難排解 191 | 請參見 [Github 問題](https://github.com/chbergmann/OpticsWorkbench/issues) 192 | 193 | ## 討論 194 | 請提供反饋或通過 [專用 FreeCAD 討論區](https://forum.freecad.org/viewtopic.php?f=8&t=59860) 與開發者聯繫。 195 | 196 | ## 授權 197 | GNU 輕量級公共許可證 v3.0 ([授權條款](LICENSE)) -------------------------------------------------------------------------------- /SunRay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __title__ = 'SunRay' 4 | __author__ = 'Christian Bergmann' 5 | __license__ = 'LGPL 3.0' 6 | 7 | import os 8 | import FreeCADGui as Gui 9 | import FreeCAD 10 | translate = FreeCAD.Qt.translate 11 | 12 | from PySide.QtCore import QT_TRANSLATE_NOOP 13 | 14 | _icondir_ = os.path.join(os.path.dirname(__file__), 'icons') 15 | __doc__ = translate('Sun Ray', 'Declare your FreeCAD objects to be optical mirrors, lenses or absorbers') 16 | 17 | class SunRayWorker: 18 | def __init__(self, 19 | fp, # an instance of Part::FeaturePython 20 | rays = []): 21 | fp.addProperty('App::PropertyLinkList', 'Rays', 'SunRay', 22 | translate('Sun Ray', 'Create rays of different wavelength')).Rays = rays 23 | fp.Proxy = self 24 | self.update = True 25 | 26 | def execute(self, fp): 27 | if not self.update: return 28 | self.update = False 29 | self.displace(fp) 30 | self.update = True 31 | 32 | def onChanged(self, fp, prop): 33 | proplist = ['Rays'] 34 | if prop in proplist: 35 | self.execute(fp) 36 | 37 | def displace(self, fp): 38 | for ray in fp.Rays: 39 | if ray.Placement != fp.Placement: 40 | ray.Placement = fp.Placement 41 | 42 | 43 | class SunRayViewProvider: 44 | def __init__(self, vobj): 45 | '''Set this object to the proxy object of the actual view provider''' 46 | vobj.Proxy = self 47 | self.Object = vobj.Object 48 | 49 | def getIcon(self): 50 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 51 | return os.path.join(_icondir_, 'raysun.svg') 52 | 53 | def attach(self, vobj): 54 | '''Setup the scene sub-graph of the view provider, this method is mandatory''' 55 | self.Object = vobj.Object 56 | self.onChanged(vobj, '') 57 | 58 | def updateData(self, fp, prop): 59 | '''If a property of the handled feature has changed we have the chance to handle this here''' 60 | pass 61 | 62 | def claimChildren(self): 63 | '''Return a list of objects that will be modified by this feature''' 64 | return self.Object.Rays 65 | 66 | def onDelete(self, feature, subelements): 67 | '''Here we can do something when the feature will be deleted''' 68 | return True 69 | 70 | def onChanged(self, fp, prop): 71 | '''Here we can do something when a single property got changed''' 72 | pass 73 | 74 | def __getstate__(self): 75 | '''When saving the document this object gets stored using Python's json module.\ 76 | Since we have some un-serializable parts here we must define this method\ 77 | to return a tuple of all serializable objects or None.''' 78 | return None 79 | 80 | def __setstate__(self, state): 81 | '''When restoring the serialized object from document we have the chance to set some internals here.\ 82 | Since no data were serialized nothing needs to be done here.''' 83 | return None 84 | 85 | 86 | 87 | class RaySun(): 88 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 89 | 90 | def Activated(self): 91 | Gui.doCommand('import OpticsWorkbench') 92 | Gui.doCommand('OpticsWorkbench.makeSunRay()') 93 | 94 | def IsActive(self): 95 | '''Here you can define if the command must be active or not (greyed) if certain conditions 96 | are met or not. This function is optional.''' 97 | if FreeCAD.ActiveDocument: 98 | return(True) 99 | else: 100 | return(False) 101 | 102 | def GetResources(self): 103 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 104 | return {'Pixmap' : os.path.join(_icondir_, 'raysun.svg'), 105 | 'Accel' : '', # a default shortcut (optional) 106 | 'MenuText': QT_TRANSLATE_NOOP('Sun Ray', 'Ray (sun light)'), 107 | 'ToolTip' : QT_TRANSLATE_NOOP('Sun Ray', 'Declare your FreeCAD objects to be optical mirrors') } 108 | 109 | 110 | Gui.addCommand('Sun Ray', RaySun()) 111 | -------------------------------------------------------------------------------- /apply_translations.sh: -------------------------------------------------------------------------------- 1 | for f in translations/*_*.ts 2 | do 3 | lrelease "$f" 4 | done 5 | -------------------------------------------------------------------------------- /examples/Concave_mirror_grating_thorlabs_1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/Concave_mirror_grating_thorlabs_1.PNG -------------------------------------------------------------------------------- /examples/Concave_mirror_grating_thorlabs_2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/Concave_mirror_grating_thorlabs_2.PNG -------------------------------------------------------------------------------- /examples/Emitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/Emitter.png -------------------------------------------------------------------------------- /examples/RayHits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/RayHits.png -------------------------------------------------------------------------------- /examples/beamsplitter_by_using_ignored_elements.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/beamsplitter_by_using_ignored_elements.PNG -------------------------------------------------------------------------------- /examples/ccd_xyplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/ccd_xyplot.png -------------------------------------------------------------------------------- /examples/dispersion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/dispersion.png -------------------------------------------------------------------------------- /examples/echelle_example.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/echelle_example.PNG -------------------------------------------------------------------------------- /examples/example1.py: -------------------------------------------------------------------------------- 1 | from FreeCAD import Vector 2 | import Sketcher 3 | import Part 4 | import FreeCAD as App 5 | import FreeCADGui as Gui 6 | import OpticsWorkbench 7 | import os 8 | from PySide.QtCore import QT_TRANSLATE_NOOP 9 | 10 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 11 | _exname_ = QT_TRANSLATE_NOOP('Example2D', 'Example - 2D') 12 | 13 | def createSketch_Sketch_Mirror1(doc): 14 | Sketch_Mirror1 = doc.addObject('Sketcher::SketchObject', 'Sketch_Mirror1') 15 | Sketch_Mirror1.addGeometry(Part.LineSegment(Vector (11.836481, 6.837696, 0.0), Vector (15.17516, -0.6152189999999997, 0.0))) 16 | Sketch_Mirror1.addGeometry(Part.Circle(Vector(0.00, -5.56, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 17 | Sketch_Mirror1.toggleConstruction(1) 18 | Sketch_Mirror1.addGeometry(Part.Circle(Vector(0.00, -15.18, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 19 | Sketch_Mirror1.toggleConstruction(2) 20 | Sketch_Mirror1.addGeometry(Part.Circle(Vector(7.19, -16.35, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 21 | Sketch_Mirror1.toggleConstruction(3) 22 | Sketch_Mirror1.addGeometry(Part.BSplineCurve([Vector(0.00, -5.56, 0.00), Vector(0.00, -15.18, 0.00), Vector(7.19, -16.35, 0.00)])) 23 | Sketch_Mirror1.addGeometry(Part.Point(Vector(0.00, -5.56, 0.00))) 24 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('PointOnObject', 4, 1, -2)) 25 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('Equal', 1, 2)) 26 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('PointOnObject', 2, 3, -2)) 27 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('Equal', 1, 3)) 28 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', 1, 3, 4, 0)) 29 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', 2, 3, 4, 1)) 30 | Sketch_Mirror1.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', 3, 3, 4, 2)) 31 | return Sketch_Mirror1 32 | 33 | def createSketch_Sketch_Box(doc): 34 | Sketch_Box = doc.addObject('Sketcher::SketchObject', 'Sketch_Box') 35 | Sketch_Box.addGeometry(Part.LineSegment(Vector (-0.14878900000000073, 11.406045000000002, 0.0), Vector (40.0, 11.406045000000002, 0.0))) 36 | Sketch_Box.addGeometry(Part.LineSegment(Vector (40.0, 11.406045000000002, 0.0), Vector (40.0, -21.699192, 0.0))) 37 | Sketch_Box.addGeometry(Part.LineSegment(Vector (40.0, -21.699192, 0.0), Vector (-0.14878900000000073, -21.699192, 0.0))) 38 | Sketch_Box.addGeometry(Part.LineSegment(Vector (-0.14878900000000073, -21.699192, 0.0), Vector (-0.14878900000000073, 11.406045000000002, 0.0))) 39 | Sketch_Box.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1)) 40 | Sketch_Box.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1)) 41 | Sketch_Box.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 3, 1)) 42 | Sketch_Box.addConstraint(Sketcher.Constraint('Coincident', 3, 2, 0, 1)) 43 | Sketch_Box.addConstraint(Sketcher.Constraint('Horizontal', 0)) 44 | Sketch_Box.addConstraint(Sketcher.Constraint('Horizontal', 2)) 45 | Sketch_Box.addConstraint(Sketcher.Constraint('Vertical', 1)) 46 | Sketch_Box.addConstraint(Sketcher.Constraint('Vertical', 3)) 47 | Sketch_Box.ViewObject.DiffuseColor = [(0.00, 0.00, 0.00, 0.00)] 48 | Sketch_Box.ViewObject.LineColor = (0.00, 0.00, 0.00, 0.00) 49 | Sketch_Box.ViewObject.LineColorArray = [(0.00, 0.00, 0.00, 0.00)] 50 | Sketch_Box.ViewObject.PointColor = (0.00, 0.00, 0.00, 0.00) 51 | Sketch_Box.ViewObject.PointColorArray = [(0.00, 0.00, 0.00, 0.00)] 52 | Sketch_Box.ViewObject.ShapeColor = (0.00, 0.00, 0.00, 0.00) 53 | return Sketch_Box 54 | 55 | def createSketch_Sketch_Mirror2(doc): 56 | Sketch_Mirror2 = doc.addObject('Sketcher::SketchObject', 'Sketch_Mirror2') 57 | Sketch_Mirror2.addGeometry(Part.ArcOfCircle(Part.Circle(Vector(56.12, -5.69, 0.00), Vector (0.0, 0.0, 1.0), 10.64), 0.6054907273817108, 6.799308361137584)) 58 | return Sketch_Mirror2 59 | 60 | def createSketch_Sketch_Lens(doc): 61 | Sketch_Lens = doc.addObject('Sketcher::SketchObject', 'Sketch_Lens') 62 | Sketch_Lens.addGeometry(Part.LineSegment(Vector (11.566624790355402, -6.79, 0.0), Vector (11.566624790355402, -16.79, 0.0))) 63 | Sketch_Lens.addGeometry(Part.ArcOfCircle(Part.Circle(Vector(8.25, -11.79, 0.00), Vector (0.0, 0.0, 1.0), 6.00), 5.298074523841841, 7.268296090517332)) 64 | Sketch_Lens.addConstraint(Sketcher.Constraint('Vertical', 0)) 65 | Sketch_Lens.addConstraint(Sketcher.Constraint('Coincident', 1, 1, 0, 2)) 66 | Sketch_Lens.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 0, 1)) 67 | Sketch_Lens.addConstraint(Sketcher.Constraint('Radius', 1, 6.00)) 68 | Sketch_Lens.addConstraint(Sketcher.Constraint('DistanceY', 0, 2, 0, 1, 10.00)) 69 | return Sketch_Lens 70 | 71 | def createSketch_Sketch_Prism(doc): 72 | Sketch_Prism = doc.addObject('Sketcher::SketchObject', 'Sketch_Prism') 73 | Sketch_Prism.addGeometry(Part.LineSegment(Vector (21.126467, -13.53020124766231, 0.0), Vector (21.126467, -21.018505, 0.0))) 74 | Sketch_Prism.addGeometry(Part.LineSegment(Vector (21.126467, -21.018505, 0.0), Vector (31.579980991773073, -17.27435312383116, 0.0))) 75 | Sketch_Prism.addGeometry(Part.LineSegment(Vector (31.579980991773073, -17.27435312383116, 0.0), Vector (21.126467, -13.53020124766231, 0.0))) 76 | Sketch_Prism.addConstraint(Sketcher.Constraint('Vertical', 0)) 77 | Sketch_Prism.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1)) 78 | Sketch_Prism.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1)) 79 | Sketch_Prism.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 0, 1)) 80 | Sketch_Prism.addConstraint(Sketcher.Constraint('Equal', 1, 2)) 81 | return Sketch_Prism 82 | 83 | def make_optics(): 84 | App.newDocument() 85 | doc = App.activeDocument() 86 | 87 | Sketch_Mirror1 = createSketch_Sketch_Mirror1(doc) 88 | Sketch_Box1 = createSketch_Sketch_Box(doc) 89 | Sketch_Box2 = createSketch_Sketch_Box(doc) 90 | Sketch_Box2.Placement.Base.x += 41 91 | Sketch_Mirror2 = createSketch_Sketch_Mirror2(doc) 92 | Sketch_Lens = createSketch_Sketch_Lens(doc) 93 | Sketch_Prism = createSketch_Sketch_Prism(doc) 94 | 95 | OpticsWorkbench.makeMirror([Sketch_Mirror1, Sketch_Mirror2]) 96 | OpticsWorkbench.makeAbsorber([Sketch_Box1, Sketch_Box2], True) 97 | OpticsWorkbench.makeLens([Sketch_Lens, Sketch_Prism], material='NBK7/Window glass') 98 | 99 | doc.recompute() 100 | OpticsWorkbench.makeRay(Vector(75.00, 0.00, 0.00), Vector(-1,0,0)) 101 | OpticsWorkbench.makeRay(beamNrColumns=50) 102 | 103 | doc.recompute() 104 | 105 | class Example1(): 106 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 107 | 108 | def Activated(self): 109 | make_optics() 110 | Gui.runCommand('Std_OrthographicCamera',1) 111 | Gui.SendMsgToActiveView("ViewFit") 112 | 113 | def IsActive(self): 114 | return(True) 115 | 116 | def GetResources(self): 117 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 118 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 119 | 'Accel' : "", # a default shortcut (optional) 120 | 'MenuText': _exname_, 121 | 'ToolTip' : '' } 122 | 123 | Gui.addCommand('Example2D', Example1()) 124 | -------------------------------------------------------------------------------- /examples/example2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/example2D.png -------------------------------------------------------------------------------- /examples/example3D.py: -------------------------------------------------------------------------------- 1 | import FreeCAD as App 2 | import FreeCADGui as Gui 3 | import os 4 | from FreeCAD import Vector, Placement, Rotation 5 | import FreeCAD as app 6 | import OpticsWorkbench 7 | from PySide.QtCore import QT_TRANSLATE_NOOP 8 | 9 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 10 | 11 | def make_Test3D(): 12 | App.newDocument("Example 3D") 13 | doc = app.activeDocument() 14 | 15 | Cube = doc.addObject('Part::Box', 'Cube') 16 | Cube.Placement = Placement(Vector(20.00, -3.17, -2.00), Rotation (-0.0, -0.0, 0.25881904510252074, 0.9659258262890684)) 17 | 18 | Sphere = doc.addObject('Part::Sphere', 'Sphere') 19 | Sphere.Placement = Placement(Vector(3.00, -19.00, 0.00), Rotation (0.0, 0.0, 0.0, 1.0)) 20 | 21 | Cone = doc.addObject('Part::Cone', 'Cone') 22 | Cone.Placement = Placement(Vector(68.90, -46.50, -2.30), Rotation (0.0, 0.0, 0.0, 1.0)) 23 | 24 | Cylinder = doc.addObject('Part::Cylinder', 'Cylinder') 25 | Cylinder.Height = 50.0 26 | Cylinder.Placement = Placement(Vector(40.00, -26.00, -19.00), Rotation (0.0, 0.0, 0.0, 1.0)) 27 | Cylinder.Radius = 50.0 28 | Cylinder.ViewObject.Transparency = 90 29 | 30 | Sphere001 = doc.addObject('Part::Sphere', 'Sphere001') 31 | Sphere001.Radius = 20.0 32 | Sphere001.Visibility = False 33 | Sphere001.ViewObject.Visibility = False 34 | 35 | Cube002 = doc.addObject('Part::Box', 'Cube002') 36 | Cube002.Height = 40.0 37 | Cube002.Length = 40.0 38 | Cube002.Placement = Placement(Vector(-20.00, -25.00, -20.00), Rotation (0.0, 0.0, 0.0, 1.0)) 39 | Cube002.Visibility = False 40 | Cube002.Width = 40.0 41 | Cube002.ViewObject.Visibility = False 42 | 43 | HalfSphere = doc.addObject('Part::Cut', 'HalfSphere') 44 | HalfSphere.Base = Sphere001 45 | HalfSphere.Placement = Placement(Vector(25.86, -24.61, 0.00), Rotation (0.0, 0.0, -0.793353340291235, 0.6087614290087205)) 46 | HalfSphere.Tool = Cube002 47 | HalfSphere.ViewObject.DiffuseColor = [(0.80, 0.80, 0.80, 0.00), (0.80, 0.80, 0.80, 0.00)] 48 | HalfSphere.ViewObject.Transparency = 75 49 | 50 | doc.recompute() 51 | 52 | OpticsWorkbench.makeMirror([Cube, Sphere, Cone], True) 53 | OpticsWorkbench.makeAbsorber([Cylinder]) 54 | OpticsWorkbench.makeLens([HalfSphere]) 55 | 56 | doc.recompute() 57 | OpticsWorkbench.makeRay(beamNrColumns=20, beamNrRows=10, beamDistance=0.05) 58 | 59 | doc.recompute() 60 | 61 | 62 | class Example3D(): 63 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 64 | 65 | def Activated(self): 66 | make_Test3D() 67 | Gui.activeDocument().activeView().viewDimetric() 68 | Gui.SendMsgToActiveView("ViewFit") 69 | 70 | def IsActive(self): 71 | return(True) 72 | 73 | def GetResources(self): 74 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 75 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 76 | 'Accel' : "", # a default shortcut (optional) 77 | 'MenuText': QT_TRANSLATE_NOOP('Example3D', 'Example - 3D'), 78 | 'ToolTip' : '' } 79 | 80 | Gui.addCommand('Example3D', Example3D()) 81 | -------------------------------------------------------------------------------- /examples/example_candle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/example_candle.png -------------------------------------------------------------------------------- /examples/example_candle.py: -------------------------------------------------------------------------------- 1 | from FreeCAD import Vector, Placement, Rotation 2 | import Sketcher 3 | import Part 4 | import FreeCAD as App 5 | import FreeCADGui as Gui 6 | import OpticsWorkbench 7 | import os 8 | from PySide.QtCore import QT_TRANSLATE_NOOP 9 | 10 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 11 | 12 | def createSketch_Sketch(doc): 13 | Sketch = doc.addObject('Sketcher::SketchObject', 'Sketch') 14 | geo0 = Sketch.addGeometry(Part.Circle(Vector(0.00, 12.01, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 15 | Sketch.toggleConstruction(geo0) 16 | geo1 = Sketch.addGeometry(Part.Circle(Vector(-1.00, 12.42, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 17 | Sketch.toggleConstruction(geo1) 18 | geo2 = Sketch.addGeometry(Part.Circle(Vector(-1.43, 15.03, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 19 | Sketch.toggleConstruction(geo2) 20 | geo3 = Sketch.addGeometry(Part.Circle(Vector(-0.15, 15.56, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 21 | Sketch.toggleConstruction(geo3) 22 | geo4 = Sketch.addGeometry(Part.Circle(Vector(0.00, 16.62, 0.00), Vector (0.0, 0.0, 1.0), 1.00)) 23 | Sketch.toggleConstruction(geo4) 24 | geo5 = Sketch.addGeometry(Part.BSplineCurve([Vector(0.00, 12.01, 0.00), Vector(-1.00, 12.42, 0.00), Vector(-1.43, 15.03, 0.00), Vector(-0.15, 15.56, 0.00), Vector(0.00, 16.62, 0.00)])) 25 | Sketch.addConstraint(Sketcher.Constraint('Weight', geo0, 1.00)) 26 | Sketch.addConstraint(Sketcher.Constraint('PointOnObject', geo5, 1, -2)) 27 | Sketch.addConstraint(Sketcher.Constraint('Equal', geo0, geo1)) 28 | Sketch.addConstraint(Sketcher.Constraint('Equal', geo0, geo2)) 29 | Sketch.addConstraint(Sketcher.Constraint('Equal', geo0, geo3)) 30 | Sketch.addConstraint(Sketcher.Constraint('Equal', geo0, geo4)) 31 | Sketch.addConstraint(Sketcher.Constraint('PointOnObject', geo5, 2, -2)) 32 | Sketch.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', geo0, 3, geo5, 0)) 33 | Sketch.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', geo1, 3, geo5, 1)) 34 | Sketch.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', geo2, 3, geo5, 2)) 35 | Sketch.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', geo3, 3, geo5, 3)) 36 | Sketch.addConstraint(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint', geo4, 3, geo5, 4)) 37 | Sketch.Placement = Placement(Vector(0.00, 0.00, 0.00), Rotation (0.7071067811865475, 0.0, 0.0, 0.7071067811865476)) 38 | Sketch.Visibility = False 39 | Sketch.ViewObject.Visibility = False 40 | return Sketch 41 | 42 | 43 | def make_candle(): 44 | App.newDocument("Candle Example") 45 | doc = App.activeDocument() 46 | 47 | Candle1 = doc.addObject('Part::Cylinder', 'Candle1') 48 | Candle1.Radius = 3.0 49 | 50 | Candle2 = doc.addObject('Part::Cylinder', 'Candle2') 51 | Candle2.Height = 12.5 52 | Candle2.Radius = 0.2 53 | 54 | Sketch = createSketch_Sketch(doc) 55 | 56 | Revolve = doc.addObject('Part::Revolution', 'Flame') 57 | Revolve.Source = Sketch 58 | Revolve.ViewObject.LineColor = (1.00, 1.00, 1.00, 1.00) 59 | Revolve.ViewObject.LineColorArray = [(1.00, 1.00, 1.00, 1.00)] 60 | Revolve.ViewObject.PointColor = (1.00, 1.00, 1.00, 1.00) 61 | Revolve.ViewObject.PointColorArray = [(1.00, 1.00, 1.00, 1.00)] 62 | 63 | OpticsWorkbench.makeAbsorber([Candle1, Candle2]) 64 | OpticsWorkbench.makeRay(beamNrColumns=10, beamNrRows=10, baseShape=Revolve, maxRayLength=100) 65 | 66 | doc.recompute() 67 | 68 | 69 | class ExampleCandle(): 70 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 71 | 72 | def Activated(self): 73 | make_candle() 74 | Gui.activeDocument().activeView().viewIsometric() 75 | 76 | def IsActive(self): 77 | return(True) 78 | 79 | def GetResources(self): 80 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 81 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 82 | 'Accel' : "", # a default shortcut (optional) 83 | 'MenuText': QT_TRANSLATE_NOOP('ExampleCandle', 'Example - Candle'), 84 | 'ToolTip' : '' } 85 | 86 | Gui.addCommand('ExampleCandle', ExampleCandle()) -------------------------------------------------------------------------------- /examples/example_candle2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/example_candle2D.png -------------------------------------------------------------------------------- /examples/example_dispersion.py: -------------------------------------------------------------------------------- 1 | from FreeCAD import Vector 2 | import Sketcher 3 | import Part 4 | import FreeCAD as App 5 | import FreeCADGui as Gui 6 | import OpticsWorkbench 7 | import os 8 | from PySide.QtCore import QT_TRANSLATE_NOOP 9 | 10 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 11 | 12 | def create_prism(doc): 13 | Sketch_Prism = doc.addObject('Sketcher::SketchObject', 'Sketch_Prism') 14 | add_line = lambda a,b: Sketch_Prism.addGeometry(Part.LineSegment(Vector (a), Vector(b))) 15 | a = (1.13, 0.26, 0.0) 16 | b = (1.50, -.38, 0.0) 17 | c = (0.76, -.38, 0.0) 18 | add_line(a, b) 19 | add_line(b, c) 20 | add_line(c, a) 21 | 22 | add_constraint = lambda *args: Sketch_Prism.addConstraint(Sketcher.Constraint(*args)) 23 | add_constraint('Coincident', 0, 2, 1, 1) 24 | add_constraint('Coincident', 1, 2, 2, 1) 25 | add_constraint('Coincident', 2, 2, 0, 1) 26 | add_constraint('Equal', 1, 2) 27 | add_constraint('Equal', 0, 1) 28 | Sketch_Prism.Visibility = False 29 | 30 | prism = doc.addObject('PartDesign::Pad','prism') 31 | prism.Profile= Sketch_Prism 32 | prism.Midplane = 1 # symmetric 33 | prism.Length = 0.2 34 | 35 | prism.ViewObject.Transparency = 70 36 | prism.ViewObject.LineWidth = 1 37 | prism.ViewObject.LineMaterial.Transparency = 30 38 | prism.ViewObject.PointMaterial.Transparency = 30 39 | return prism 40 | 41 | 42 | def make_optics(): 43 | App.newDocument("Dispersion Example") 44 | doc = App.activeDocument() 45 | 46 | prism = create_prism(doc) 47 | doc.recompute() 48 | OpticsWorkbench.makeLens(prism, material='PMMA (plexiglass)').Label = 'Refractor' 49 | OpticsWorkbench.makeSunRay(maxRayLength=2.0) 50 | doc.recompute() 51 | 52 | class ExampleDispersion(): 53 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 54 | 55 | def Activated(self): 56 | make_optics() 57 | Gui.activeDocument().activeView().viewTop() 58 | Gui.SendMsgToActiveView("ViewFit") 59 | 60 | def IsActive(self): 61 | return(True) 62 | 63 | def GetResources(self): 64 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 65 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 66 | 'Accel' : "", # a default shortcut (optional) 67 | 'MenuText': QT_TRANSLATE_NOOP('ExampleDispersion', 'Example - Dispersion'), 68 | 'ToolTip' : '' } 69 | 70 | Gui.addCommand('ExampleDispersion', ExampleDispersion()) 71 | -------------------------------------------------------------------------------- /examples/example_hierarchy2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/example_hierarchy2D.png -------------------------------------------------------------------------------- /examples/example_hierarchy2D.py: -------------------------------------------------------------------------------- 1 | from FreeCAD import Vector, Rotation 2 | import Sketcher 3 | import Part 4 | import FreeCAD as App 5 | import FreeCADGui as Gui 6 | import OpticsWorkbench 7 | import os 8 | from PySide.QtCore import QT_TRANSLATE_NOOP 9 | 10 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 11 | _exname_ = QT_TRANSLATE_NOOP('ExampleHierarchy2D', 'Example - Hierarchy 2D') 12 | 13 | def makeLens(doc, name): 14 | sketch = doc.addObject('Sketcher::SketchObject', name + '_sketch') 15 | circle = Part.Circle(Vector(2, 0, 0), Vector(0, 0, 1), 5) 16 | arc = Part.ArcOfCircle(circle, 2, 4) 17 | sketch.addGeometry(arc) 18 | line = Part.LineSegment(Vector(0, 4.5, 0), Vector(0, -4.5, 0)) 19 | sketch.addGeometry(line) 20 | sketch.addConstraint(Sketcher.Constraint('Radius', 0, 5)) 21 | sketch.addConstraint(Sketcher.Constraint('PointOnObject', 0, 1, -2)) 22 | sketch.addConstraint(Sketcher.Constraint('PointOnObject', 0, 2, -2)) 23 | sketch.addConstraint(Sketcher.Constraint('Coincident', 1, 1, 0, 1)) 24 | sketch.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 2)) 25 | sketch.addConstraint(Sketcher.Constraint('PointOnObject', 0, 3, -1)) 26 | sketch.addConstraint(Sketcher.Constraint('DistanceX', 0, 3, 2)) 27 | sketch.Placement.Base = Vector(0, 19, 0) 28 | sketch.Placement.Rotation = Rotation(-42, 0, 0) 29 | lens = OpticsWorkbench.makeLens([sketch]) 30 | lens.Label = name 31 | return lens 32 | 33 | def makeMirror(doc, name): 34 | sketch = doc.addObject('Sketcher::SketchObject', name + '_sketch') 35 | sketch.addGeometry(Part.LineSegment(Vector(10.0, 0.0, 0.0), Vector(12.0, 10.0, 0.0))) 36 | mirror = OpticsWorkbench.makeMirror([sketch]) 37 | mirror.Label = name 38 | return mirror 39 | 40 | def makeRay(name): 41 | ray = OpticsWorkbench.makeRay(Vector(0, 0, 0), Vector(2.0, 1.0, 0), beamNrColumns=10, beamDistance=0.4) 42 | ray.Placement.Base = Vector(0, -1.5, 0) 43 | ray.Label = name 44 | return ray 45 | 46 | def createRayInsideMirrorInside(doc): 47 | obj = doc.addObject('App::Part', 'ray_inside_mirror_inside') 48 | mirror = makeMirror(doc, 'mirror_a') 49 | obj.addObject(mirror) 50 | lens = makeLens(doc, 'lens_a') 51 | obj.addObject(lens) 52 | ray = makeRay('ray_a') 53 | obj.addObject(ray) 54 | obj.Placement.Base += Vector(-30.0, -30.0, 0.0) 55 | ray.MaxRayLength = 24.0 56 | return obj 57 | 58 | def createRayOutsideMirrorOutside(doc): 59 | obj = doc.addObject('App::DocumentObjectGroup', 'ray_outside_mirror_outside') 60 | mirror = makeMirror(doc, 'mirror_b') 61 | mirror.Base[0].Placement.Base += Vector(30.0, -30.0, 0.0) 62 | obj.addObject(mirror) 63 | lens = makeLens(doc, 'lens_b') 64 | lens.Base[0].Placement.Base += Vector(30.0, -30.0, 0.0) 65 | obj.addObject(lens) 66 | ray = makeRay('ray_b') 67 | ray.Placement.Base += Vector(30.0, -30.0, 0.0) 68 | obj.addObject(ray) 69 | ray.MaxRayLength = 24.0 70 | return obj 71 | 72 | def createRayInsideMirrorOutside(doc): 73 | obj = doc.addObject('App::DocumentObjectGroup', 'ray_inside_mirror_outside') 74 | obj_inside = doc.addObject('App::Part', 'ray_inside') 75 | obj_inside.Placement.Base = Vector(-30.0, 30.0, 0.0) 76 | obj.addObject(obj_inside) 77 | mirror = makeMirror(doc, 'mirror_c') 78 | mirror.Base[0].Placement.Base += Vector(-30.0, 30.0, 0.0) 79 | obj.addObject(mirror) 80 | lens = makeLens(doc, 'lens_c') 81 | lens.Base[0].Placement.Base += Vector(-30.0, 30.0, 0.0) 82 | obj.addObject(lens) 83 | ray = makeRay('ray_c') 84 | obj_inside.addObject(ray) 85 | ray.MaxRayLength = 24.0 86 | return obj 87 | 88 | def createRayOutsideMirrorInside(doc): 89 | obj = doc.addObject('App::DocumentObjectGroup', 'ray_outside_mirror_inside') 90 | obj_inside = doc.addObject('App::Part', 'mirror_inside') 91 | obj_inside.Placement.Base = Vector(30.0, 30.0, 0.0) 92 | obj.addObject(obj_inside) 93 | mirror = makeMirror(doc, 'mirror_d') 94 | obj_inside.addObject(mirror) 95 | lens = makeLens(doc, 'lens_d') 96 | obj_inside.addObject(lens) 97 | ray = makeRay('ray_d') 98 | ray.Placement.Base += Vector(30.0, 30.0, 0.0) 99 | obj.addObject(ray) 100 | ray.MaxRayLength = 24.0 101 | return obj 102 | 103 | def make_optics(): 104 | App.newDocument() 105 | doc = App.activeDocument() 106 | 107 | createRayInsideMirrorInside(doc) 108 | createRayOutsideMirrorOutside(doc) 109 | createRayInsideMirrorOutside(doc) 110 | createRayOutsideMirrorInside(doc) 111 | 112 | doc.recompute() 113 | 114 | class ExampleHierarchy2D(): 115 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 116 | 117 | def Activated(self): 118 | make_optics() 119 | Gui.runCommand('Std_OrthographicCamera',1) 120 | Gui.SendMsgToActiveView("ViewFit") 121 | 122 | def IsActive(self): 123 | return(True) 124 | 125 | def GetResources(self): 126 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 127 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 128 | 'Accel' : "", # a default shortcut (optional) 129 | 'MenuText': _exname_, 130 | 'ToolTip' : '' } 131 | 132 | Gui.addCommand('ExampleHierarchy2D', ExampleHierarchy2D()) 133 | -------------------------------------------------------------------------------- /examples/example_hierarchy3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/example_hierarchy3D.png -------------------------------------------------------------------------------- /examples/example_hierarchy3D.py: -------------------------------------------------------------------------------- 1 | from FreeCAD import Vector, Rotation 2 | import Sketcher 3 | import Part 4 | import FreeCAD as App 5 | import FreeCADGui as Gui 6 | from BOPTools import BOPFeatures 7 | import OpticsWorkbench 8 | import os 9 | from PySide.QtCore import QT_TRANSLATE_NOOP 10 | 11 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 12 | _exname_ = QT_TRANSLATE_NOOP('ExampleHierarchy3D', 'Example - Hierarchy 3D') 13 | 14 | def makeLens(doc, name): 15 | sphere = doc.addObject('Part::Sphere', name + '_sphere') 16 | box = doc.addObject('Part::Box', name + '_box') 17 | box.Placement.Base = Vector(-2, -5, -5) 18 | bp = BOPFeatures.BOPFeatures(doc) 19 | cut = bp.make_cut([sphere.Label, box.Label]) 20 | cut.Label = name + '_cut' 21 | cut.Placement.Base = Vector(0, 10, 0) 22 | cut.Placement.Rotation = Rotation(-42, 0, 0) 23 | lens = OpticsWorkbench.makeLens([cut]) 24 | lens.Label = name 25 | return lens 26 | 27 | def makeMirror(doc, name): 28 | box = doc.addObject('Part::Box', name + '_box') 29 | box.Length = 2.0 30 | box.Placement.Base = Vector(5, -2.0, -5.0) 31 | box.Placement.Rotation = Rotation(-10, 0, 0) 32 | mirror = OpticsWorkbench.makeMirror([box]) 33 | mirror.Label = name 34 | return mirror 35 | 36 | def makeRay(name): 37 | ray = OpticsWorkbench.makeRay(Vector(0, 0, 0), Vector(2.0, 1.0, 0), beamNrColumns=10, beamNrRows=10, beamDistance=0.4) 38 | ray.Placement.Base = Vector(0, -1.5, -2) 39 | ray.Label = name 40 | return ray 41 | 42 | def createRayInsideMirrorInside(doc): 43 | obj = doc.addObject('App::Part', 'ray_inside_mirror_inside') 44 | mirror = makeMirror(doc, 'mirror_a') 45 | obj.addObject(mirror) 46 | lens = makeLens(doc, 'lens_a') 47 | obj.addObject(lens) 48 | ray = makeRay('ray_a') 49 | obj.addObject(ray) 50 | obj.Placement.Base = Vector(-30.0, -30.0, 0.0) 51 | ray.MaxRayLength = 24.0 52 | return obj 53 | 54 | def createRayOutsideMirrorOutside(doc): 55 | obj = doc.addObject('App::DocumentObjectGroup', 'ray_outside_mirror_outside') 56 | mirror = makeMirror(doc, 'mirror_b') 57 | mirror.Base[0].Placement.Base += Vector(30.0, -30.0, 0.0) 58 | obj.addObject(mirror) 59 | lens = makeLens(doc, 'lens_b') 60 | lens.Base[0].Placement.Base += Vector(30.0, -30.0, 0.0) 61 | obj.addObject(lens) 62 | ray = makeRay('ray_b') 63 | ray.Placement.Base += Vector(30.0, -30.0, 0.0) 64 | obj.addObject(ray) 65 | ray.MaxRayLength = 24.0 66 | return obj 67 | 68 | def createRayInsideMirrorOutside(doc): 69 | obj = doc.addObject('App::DocumentObjectGroup', 'ray_inside_mirror_outside') 70 | obj_inside = doc.addObject('App::Part', 'ray_inside') 71 | obj_inside.Placement.Base = Vector(-30.0, 30.0, 0.0) 72 | obj.addObject(obj_inside) 73 | mirror = makeMirror(doc, 'mirror_c') 74 | mirror.Base[0].Placement.Base += Vector(-30.0, 30.0, 0.0) 75 | obj.addObject(mirror) 76 | lens = makeLens(doc, 'lens_c') 77 | lens.Base[0].Placement.Base += Vector(-30.0, 30.0, 0.0) 78 | obj.addObject(lens) 79 | ray = makeRay('ray_c') 80 | obj_inside.addObject(ray) 81 | ray.MaxRayLength = 24.0 82 | return obj 83 | 84 | def createRayOutsideMirrorInside(doc): 85 | obj = doc.addObject('App::DocumentObjectGroup', 'ray_outside_mirror_inside') 86 | obj_inside = doc.addObject('App::Part', 'mirror_inside') 87 | obj_inside.Placement.Base = Vector(30.0, 30.0, 0.0) 88 | obj.addObject(obj_inside) 89 | mirror = makeMirror(doc, 'mirror_d') 90 | obj_inside.addObject(mirror) 91 | lens = makeLens(doc, 'lens_d') 92 | obj_inside.addObject(lens) 93 | ray = makeRay('ray_d') 94 | ray.Placement.Base += Vector(30.0, 30.0, 0.0) 95 | obj.addObject(ray) 96 | ray.MaxRayLength = 24.0 97 | return obj 98 | 99 | def make_optics(): 100 | App.newDocument() 101 | doc = App.activeDocument() 102 | 103 | createRayInsideMirrorInside(doc) 104 | createRayOutsideMirrorOutside(doc) 105 | createRayInsideMirrorOutside(doc) 106 | createRayOutsideMirrorInside(doc) 107 | 108 | doc.recompute() 109 | 110 | class ExampleHierarchy3D(): 111 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 112 | 113 | def Activated(self): 114 | make_optics() 115 | Gui.runCommand('Std_OrthographicCamera',1) 116 | Gui.SendMsgToActiveView("ViewFit") 117 | 118 | def IsActive(self): 119 | return(True) 120 | 121 | def GetResources(self): 122 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 123 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 124 | 'Accel' : "", # a default shortcut (optional) 125 | 'MenuText': _exname_, 126 | 'ToolTip' : '' } 127 | 128 | Gui.addCommand('ExampleHierarchy3D', ExampleHierarchy3D()) 129 | -------------------------------------------------------------------------------- /examples/example_semi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/example_semi.png -------------------------------------------------------------------------------- /examples/example_semi.py: -------------------------------------------------------------------------------- 1 | from FreeCAD import Vector, Placement, Rotation 2 | import Sketcher 3 | import Part 4 | import FreeCAD as App 5 | import FreeCADGui as Gui 6 | import OpticsWorkbench 7 | import os 8 | from PySide.QtCore import QT_TRANSLATE_NOOP 9 | 10 | _icondir_ = os.path.join(os.path.dirname(__file__), '..') 11 | _exname_ = QT_TRANSLATE_NOOP('ExampleSemi', 'Example - Semi transparent') 12 | 13 | def createSketch_Sketch(doc): 14 | Sketch = doc.addObject('Sketcher::SketchObject', 'Sketch') 15 | geo0 = Sketch.addGeometry(Part.LineSegment(Vector (17.57187, 4.200229, 0.0), Vector (3.394269999999999, -5.662041000000004, 0.0))) 16 | Sketch.AttacherEngine = 'Engine Plane' 17 | return Sketch 18 | 19 | def createSketch_Sketch002(doc): 20 | Sketch002 = doc.addObject('Sketcher::SketchObject', 'Sketch002') 21 | geo0 = Sketch002.addGeometry(Part.LineSegment(Vector (35.877303000000005, 5.425019999999973, 0.0), Vector (49.675442217789794, -5.392686170185302, 0.0))) 22 | geo1 = Sketch002.addGeometry(Part.ArcOfCircle(Part.Circle(Vector(36.78444246898194, -7.626625831826473, 0.0), Vector (0.0, 0.0, 1.0), 13.083132688145355), 0.1715903905166062, 1.640188568091698)) 23 | Sketch002.addConstraint(Sketcher.Constraint('Coincident', geo1, 2, geo0, 1)) 24 | Sketch002.addConstraint(Sketcher.Constraint('Coincident', geo1, 1, geo0, 2)) 25 | Sketch002.AttacherEngine = 'Engine Plane' 26 | return Sketch002 27 | 28 | def createSketch_Sketch003(doc): 29 | Sketch003 = doc.addObject('Sketcher::SketchObject', 'Sketch003') 30 | geo0 = Sketch003.addGeometry(Part.LineSegment(Vector (-5.804982, 35.19952, 0.0), Vector (-5.804982, -28.56389, 0.0))) 31 | geo1 = Sketch003.addGeometry(Part.LineSegment(Vector (-5.804982, -28.56389, 0.0), Vector (72.434807, -28.56389, 0.0))) 32 | geo2 = Sketch003.addGeometry(Part.LineSegment(Vector (72.434807, -28.56389, 0.0), Vector (72.434807, 35.19952, 0.0))) 33 | geo3 = Sketch003.addGeometry(Part.LineSegment(Vector (72.434807, 35.19952, 0.0), Vector (-5.804981999999995, 35.19952, 0.0))) 34 | Sketch003.addConstraint(Sketcher.Constraint('Coincident', geo0, 2, geo1, 1)) 35 | Sketch003.addConstraint(Sketcher.Constraint('Coincident', geo1, 2, geo2, 1)) 36 | Sketch003.addConstraint(Sketcher.Constraint('Coincident', geo2, 2, geo3, 1)) 37 | Sketch003.addConstraint(Sketcher.Constraint('Coincident', geo3, 2, geo0, 1)) 38 | Sketch003.addConstraint(Sketcher.Constraint('Vertical', geo0)) 39 | Sketch003.addConstraint(Sketcher.Constraint('Vertical', geo2)) 40 | Sketch003.addConstraint(Sketcher.Constraint('Horizontal', geo1)) 41 | Sketch003.addConstraint(Sketcher.Constraint('Horizontal', geo3)) 42 | Sketch003.AttacherEngine = 'Engine Plane' 43 | Sketch003.ViewObject.LineColor = (0.0, 0.0, 0.0, 0.0) 44 | Sketch003.ViewObject.LineColorArray = [(0.0, 0.0, 0.0, 0.0)] 45 | return Sketch003 46 | 47 | 48 | def make_semi(): 49 | App.newDocument(_exname_) 50 | doc = App.activeDocument() 51 | 52 | Sketch = createSketch_Sketch(doc) 53 | Mirror_semi40 = OpticsWorkbench.makeMirror([Sketch], True, 40) 54 | Mirror_semi40.Label = "Mirror_semi40" 55 | 56 | Cube = doc.addObject('Part::Box', 'Cube') 57 | Cube.Height = 2.0 58 | Cube.Length = 2.0 59 | Cube.Placement = Placement(Vector(27.0, -5.0, -1.0), Rotation (0.0, 0.0, 0.0, 1.0)) 60 | Cube.ViewObject.Transparency = 50 61 | Absorber_semi50 = OpticsWorkbench.makeAbsorber([Cube], True, 50) 62 | Absorber_semi50.Label = "Absorber_semi50" 63 | 64 | Sketch002 = createSketch_Sketch002(doc) 65 | Lens_semi80 = OpticsWorkbench.makeLens([Sketch002], material='NBK7/Window glass', collectStatistics=True, transparency=80) 66 | Lens_semi80.Label = "Lens_semi80" 67 | 68 | Sketch003 = createSketch_Sketch003(doc) 69 | FullAbsorber = OpticsWorkbench.makeAbsorber([Sketch003], True, 0) 70 | FullAbsorber.Label = "FullAbsorber" 71 | 72 | doc.recompute() 73 | OpticsWorkbench.makeRay(beamNrColumns=3, beamDistance = 0.2) 74 | doc.recompute() 75 | OpticsWorkbench.Hits2CSV() 76 | doc.recompute() 77 | Gui.ActiveDocument=doc 78 | 79 | 80 | class ExampleSemi(): 81 | '''This class will be loaded when the workbench is activated in FreeCAD. You must restart FreeCAD to apply changes in this class''' 82 | 83 | def Activated(self): 84 | make_semi() 85 | Gui.runCommand('Std_OrthographicCamera',1) 86 | Gui.SendMsgToActiveView("ViewFit") 87 | 88 | def IsActive(self): 89 | return(True) 90 | 91 | def GetResources(self): 92 | '''Return the icon which will appear in the tree view. This method is optional and if not defined a default icon is shown.''' 93 | return {'Pixmap' : os.path.join(_icondir_, 'optics_workbench_icon.svg'), 94 | 'Accel' : "", # a default shortcut (optional) 95 | 'MenuText': _exname_, 96 | 'ToolTip' : '' } 97 | 98 | Gui.addCommand('ExampleSemi', ExampleSemi()) -------------------------------------------------------------------------------- /examples/plot3Dexample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/plot3Dexample1.png -------------------------------------------------------------------------------- /examples/plot3Dexample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/plot3Dexample2.png -------------------------------------------------------------------------------- /examples/screenshot3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/screenshot3D.png -------------------------------------------------------------------------------- /examples/simple_reflection_grating_set_of_planes.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/simple_reflection_grating_set_of_planes.PNG -------------------------------------------------------------------------------- /examples/simple_transmission_grating.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/examples/simple_transmission_grating.PNG -------------------------------------------------------------------------------- /icons/Anonymous_Lightbulb_Lit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 39 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | [maxwxyz] 51 | 52 | 53 | https://www.freecad.org/wiki/index.php?title=Artwork 54 | 55 | 56 | FreeCAD 57 | 58 | 59 | FreeCAD/src/ 60 | 61 | 62 | FreeCAD LGPL2+ 63 | 64 | 65 | 2024 66 | 67 | 68 | 69 | 72 | 78 | 82 | 86 | 90 | 94 | 95 | 103 | 109 | 113 | 117 | 121 | 125 | 126 | 135 | 142 | 146 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /icons/Anonymous_Lightbulb_Off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | [maxwxyz] 28 | 29 | 30 | https://www.freecad.org/wiki/index.php?title=Artwork 31 | 32 | 33 | FreeCAD 34 | 35 | 36 | FreeCAD/src/ 37 | 38 | 39 | FreeCAD LGPL2+ 40 | 41 | 42 | 2024 43 | 44 | 45 | 46 | 52 | 56 | 60 | 64 | 68 | 69 | 77 | 83 | 87 | 91 | 95 | 99 | 100 | 109 | 116 | 120 | 124 | 125 | -------------------------------------------------------------------------------- /icons/ExportCSV.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 37 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | [maxwxyz] 49 | 50 | 51 | https://www.freecad.org/wiki/index.php?title=Artwork 52 | 53 | 54 | FreeCAD 55 | 56 | 57 | FreeCAD/src/ 58 | 59 | 60 | FreeCAD LGPL2+ 61 | 62 | 63 | 2024 64 | 65 | 66 | 67 | 70 | 77 | 86 | 90 | 94 | 98 | 102 | 106 | 110 | 115 | 119 | 123 | 127 | 131 | 135 | 139 | 143 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /icons/absorber.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /icons/emitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | [maxwxyz] 28 | 29 | 30 | https://www.freecad.org/wiki/index.php?title=Artwork 31 | 32 | 33 | FreeCAD 34 | 35 | 36 | FreeCAD/src/ 37 | 38 | 39 | FreeCAD LGPL2+ 40 | 41 | 42 | 2024 43 | 44 | 45 | 46 | 49 | 53 | 57 | 61 | 65 | 66 | 70 | 74 | 78 | 82 | 83 | 87 | 91 | 95 | 99 | 100 | 104 | 108 | 112 | 116 | 117 | 121 | 125 | 129 | 133 | 134 | 138 | 142 | 146 | 150 | 151 | 155 | 159 | 163 | 167 | 168 | 172 | 176 | 180 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /icons/grating.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 121 | 125 | 129 | 133 | 137 | 141 | 145 | 149 | 153 | 157 | 161 | 165 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /icons/lens.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 121 | 125 | 129 | 133 | 137 | 141 | 145 | 149 | 153 | 157 | 161 | 165 | 169 | 176 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /icons/mirror.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /icons/ray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 100 | 104 | 108 | 112 | 113 | 119 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /icons/rayarray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 100 | 104 | 108 | 112 | 113 | 119 | 124 | 127 | 131 | 135 | 139 | 140 | 146 | 151 | 154 | 158 | 162 | 166 | 167 | 173 | 178 | 181 | 185 | 189 | 193 | 194 | 200 | 205 | 208 | 212 | 216 | 220 | 221 | 227 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /icons/raygridfocal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /icons/raysun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 121 | 125 | 129 | 133 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /icons/scatter3D.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 118 | 124 | 130 | 136 | 141 | 146 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /icons/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 100 | 104 | 108 | 112 | 113 | 116 | 120 | 124 | 128 | 129 | 132 | 136 | 140 | 144 | 145 | 148 | 152 | 156 | 160 | 161 | 164 | 170 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /icons/sun3D.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 42 | 44 | 48 | 52 | 53 | 55 | 59 | 63 | 64 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | [maxwxyz] 76 | 77 | 78 | https://www.freecad.org/wiki/index.php?title=Artwork 79 | 80 | 81 | FreeCAD 82 | 83 | 84 | FreeCAD/src/ 85 | 86 | 87 | FreeCAD LGPL2+ 88 | 89 | 90 | 2024 91 | 92 | 93 | 94 | 97 | 101 | 105 | 109 | 113 | 117 | 121 | 125 | 129 | 133 | 137 | 141 | 145 | 149 | 153 | 157 | 161 | 165 | 169 | 175 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Optics 4 | Geometrical optics for FreeCAD. Performs simple raytracing through your FreeCAD objects. 5 | 1.2.2 6 | 2025-06-01 7 | Christi 8 | LGPL-2.1 9 | https://github.com/chbergmann/OpticsWorkbench 10 | https://github.com/chbergmann/OpticsWorkbench/README.md 11 | optics_workbench_icon.svg 12 | 13 | 14 | 15 | Optics 16 | OpticsWorkbench 17 | ./ 18 | 0.19.0 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /translations/OpticsWorkbench_de.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/translations/OpticsWorkbench_de.qm -------------------------------------------------------------------------------- /translations/OpticsWorkbench_es-AR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/translations/OpticsWorkbench_es-AR.qm -------------------------------------------------------------------------------- /translations/OpticsWorkbench_es-ES.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/translations/OpticsWorkbench_es-ES.qm -------------------------------------------------------------------------------- /translations/OpticsWorkbench_zh-TW.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chbergmann/OpticsWorkbench/e0da97cde9e661ef3df7195483a5a0f58d48df54/translations/OpticsWorkbench_zh-TW.qm -------------------------------------------------------------------------------- /translations/update_translation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # -------------------------------------------------------------------------------------------------- 4 | # 5 | # Create, update and release translation files. 6 | # 7 | # Supported locales on FreeCAD <2024-11-28, FreeCADGui.supportedLocales(), total=44>: 8 | # {'English': 'en', 'Afrikaans': 'af', 'Arabic': 'ar', 'Basque': 'eu', 'Belarusian': 'be', 9 | # 'Bulgarian': 'bg', 'Catalan': 'ca', 'Chinese Simplified': 'zh-CN', 10 | # 'Chinese Traditional': 'zh-TW', 'Croatian': 'hr', 'Czech': 'cs', 'Danish': 'da', 11 | # 'Dutch': 'nl', 'Filipino': 'fil', 'Finnish': 'fi', 'French': 'fr', 'Galician': 'gl', 12 | # 'Georgian': 'ka', 'German': 'de', 'Greek': 'el', 'Hungarian': 'hu', 'Indonesian': 'id', 13 | # 'Italian': 'it', 'Japanese': 'ja', 'Kabyle': 'kab', 'Korean': 'ko', 'Lithuanian': 'lt', 14 | # 'Norwegian': 'no', 'Polish': 'pl', 'Portuguese': 'pt-PT', 'Portuguese, Brazilian': 'pt-BR', 15 | # 'Romanian': 'ro', 'Russian': 'ru', 'Serbian': 'sr', 'Serbian, Latin': 'sr-CS', 'Slovak': 'sk', 16 | # 'Slovenian': 'sl', 'Spanish': 'es-ES', 'Spanish, Argentina': 'es-AR', 'Swedish': 'sv-SE', 17 | # 'Turkish': 'tr', 'Ukrainian': 'uk', 'Valencian': 'val-ES', 'Vietnamese': 'vi'} 18 | # 19 | # NOTE: PREPARATION 20 | # - Install Qt tools 21 | # Debian-based (e.g., Ubuntu): $ sudo apt-get install qttools5-dev-tools pyqt6-dev-tools 22 | # Fedora-based: $ sudo dnf install qt6-linguist qt6-devel 23 | # Arch-based: $ sudo pacman -S qt6-tools python-pyqt6 24 | # - Make the script executable 25 | # $ chmod +x update_translation.sh 26 | # - The script has to be executed within the `translations` directory. 27 | # Executing the script with no flags invokes the help. 28 | # $ ./update_translation.sh 29 | # 30 | # NOTE: WORKFLOW TRANSLATOR (LOCAL) 31 | # - Execute the script passing the `-u` flag plus locale code as argument 32 | # Only update the file(s) you're translating! 33 | # $ ./update_translation.sh -u es-ES 34 | # - Do the translation via Qt Linguist and use `File>Release` 35 | # - If releasing with the script execute it passing the `-r` flag 36 | # plus locale code as argument 37 | # $ ./update_translation.sh -r es-ES 38 | # 39 | # NOTE: WORKFLOW MAINTAINER (CROWDIN) 40 | # - Execute the script passing the '-U' flag 41 | # $ ./update_translation.sh -U 42 | # - Once done, download the translated files, copy them to `translations` 43 | # - Upload the updated file to CrowdIn and wait for translators do their thing ;-) 44 | # and release all the files to update the changes 45 | # $ ./update_translation.sh -R 46 | # 47 | # -------------------------------------------------------------------------------------------------- 48 | 49 | supported_locales=( 50 | "en" "af" "ar" "eu" "be" "bg" "ca" "zh-CN" "zh-TW" "hr" 51 | "cs" "da" "nl" "fil" "fi" "fr" "gl" "ka" "de" "el" 52 | "hu" "id" "it" "ja" "kab" "ko" "lt" "no" "pl" "pt-PT" 53 | "pt-BR" "ro" "ru" "sr" "sr-CS" "sk" "sl" "es-ES" "es-AR" "sv-SE" 54 | "tr" "uk" "val-ES" "vi" 55 | ) 56 | 57 | is_locale_supported() { 58 | local locale="$1" 59 | for supported_locale in "${supported_locales[@]}"; do 60 | [ "$supported_locale" == "$locale" ] && return 0 61 | done 62 | return 1 63 | } 64 | 65 | update_locale() { 66 | local locale="$1" 67 | local u=${locale:+_} # Conditional underscore 68 | FILES="../*.py" 69 | 70 | # NOTE: Execute the right command depending on: 71 | # - if it's a locale file or the main, agnostic one 72 | [ ! -f "${WB}${u}${locale}.ts" ] && action="Creating" || action="Updating" 73 | echo -e "\033[1;34m\n\t<<< ${action} '${WB}${u}${locale}.ts' file >>>\n\033[m" 74 | if [ "$u" == "" ]; then 75 | eval $LUPDATE "$FILES" -ts "${WB}.ts" # locale-agnostic file 76 | else 77 | eval $LUPDATE "$FILES" -source-language en_US -target-language "${locale//-/_}" \ 78 | -ts "${WB}_${locale}.ts" 79 | fi 80 | } 81 | 82 | help() { 83 | echo -e "\nDescription:" 84 | echo -e "\tCreate, update and release translation files." 85 | echo -e "\nUsage:" 86 | echo -e "\t./update_translation.sh [-R] [-U] [-r ] [-u ]" 87 | echo -e "\nFlags:" 88 | echo -e " -R\n\tRelease all translations (qm files)" 89 | echo -e " -U\n\tUpdate all translations (ts files)" 90 | echo -e " -r \n\tRelease the specified locale" 91 | echo -e " -u \n\tUpdate strings for the specified locale" 92 | } 93 | 94 | # Main function ------------------------------------------------------------------------------------ 95 | 96 | LUPDATE=/usr/lib/qt6/bin/lupdate # from Qt6 97 | # LUPDATE=lupdate # from Qt5 98 | LRELEASE=/usr/lib/qt6/bin/lrelease # from Qt6 99 | # LRELEASE=lrelease # from Qt5 100 | WB="OpticsWorkbench" 101 | 102 | sed -i '3s/-/_/' ${WB}*.ts # Enforce underscore on locales 103 | sed -i '3s/\"en\"/\"en_US\"/g' ${WB}*.ts # Use en_US 104 | 105 | if [ $# -eq 1 ]; then 106 | if [ "$1" == "-R" ]; then 107 | find . -type f -name '*_*.ts' | while IFS= read -r file; do 108 | # Release all locales 109 | $LRELEASE -nounfinished "$file" 110 | echo 111 | done 112 | elif [ "$1" == "-U" ]; then 113 | for locale in "${supported_locales[@]}"; do 114 | update_locale "$locale" 115 | done 116 | elif [ "$1" == "-u" ]; then 117 | update_locale # Update main file (agnostic) 118 | else 119 | help 120 | fi 121 | elif [ $# -eq 2 ]; then 122 | LOCALE="$2" 123 | if is_locale_supported "$LOCALE"; then 124 | if [ "$1" == "-r" ]; then 125 | # Release locale (creation of *.qm file from *.ts file) 126 | $LRELEASE -nounfinished "${WB}_${LOCALE}.ts" 127 | elif [ "$1" == "-u" ]; then 128 | # Update main & locale files 129 | update_locale 130 | update_locale "$LOCALE" 131 | fi 132 | else 133 | echo "Verify your language code. Case sensitive." 134 | echo "If it's correct, ask a maintainer to add support for your language on FreeCAD." 135 | echo -e "\nSupported locales, '\033[1;34mFreeCADGui.supportedLocales()\033[m': \033[1;33m" 136 | for locale in $(printf "%s\n" "${supported_locales[@]}" | sort); do 137 | echo -n "$locale " 138 | done 139 | echo 140 | fi 141 | else 142 | help 143 | fi 144 | -------------------------------------------------------------------------------- /update_translations.sh: -------------------------------------------------------------------------------- 1 | for f in $(ls translations/*.ts); do 2 | pylupdate5 *.py examples/*.py -ts $f 3 | done -------------------------------------------------------------------------------- /wavelength_to_rgb/.gitattributes: -------------------------------------------------------------------------------- 1 | *.py filter=kw 2 | *.c filter=kw 3 | *.sh filter=kw 4 | 5 | -------------------------------------------------------------------------------- /wavelength_to_rgb/README.rst: -------------------------------------------------------------------------------- 1 | Converting visible wavelengths to (r, g, b) 2 | ########################################### 3 | 4 | :date: 2017-09-17 5 | :tags: python, wavelength, RGB 6 | :author: Roland Smith 7 | 8 | .. Last modified: 2018-04-17T19:50:43+0200 9 | 10 | The ``rgb`` module contains a function that converts from a wavelength in nm 11 | to an 3-tuple of (R,G,B) values, each in the range 0--255. 12 | 13 | .. PELICAN_END_SUMMARY 14 | 15 | License 16 | ------- 17 | 18 | The ``gentable.py`` script is licensed under the MIT licensce. 19 | Its output ``rgb.py`` is automatically generated and thus not copyrightable. 20 | 21 | 22 | Notes 23 | ----- 24 | 25 | The algorithm is based on `Dan Bruton's work`_. The first time I came across 26 | it was in a Pascal translation of the original Fortran code. 27 | 28 | .. _Dan Bruton's work: http://www.physics.sfasu.edu/astro/color/spectra.html 29 | 30 | Since the conversion uses a fixed function over a relatively small range of 31 | values, I pre-compute the results for every integer value of the wavelength 32 | between 380 and 780 nm and record them in a ``bytes`` object. Three bytes are 33 | used for a single wavelength in the sequence red, green, blue. 34 | This ``bytes`` object is compressed with zlib and then encoded in base64. 35 | 36 | The code is generated as follows. 37 | 38 | .. code-block:: console 39 | 40 | python3 gentable.py > rgb.py 41 | 42 | The compression reduces the amount of data approximately by half. Encoding 43 | adds to the length, but the encoded string is significantly shorter than the 44 | compressed data in string form. 45 | 46 | .. code-block:: python 47 | 48 | In [1]: from gentable import wavelen2rgb 49 | 50 | In [2]: clrs = [] 51 | 52 | In [3]: for wl in range(380, 781): 53 | ...: clrs += wavelen2rgb(wl) 54 | ...: 55 | 56 | In [4]: raw = bytes(clrs) 57 | 58 | In [5]: len(raw) 59 | Out[5]: 1203 60 | 61 | In [6]: import zlib 62 | 63 | In [7]: compressed = zlib.compress(raw, 9) 64 | 65 | In [8]: len(compressed) 66 | Out[8]: 653 67 | 68 | In [9]: import base64 69 | 70 | In [10]: enc = base64.b64encode(compressed) 71 | 72 | In [11]: len(enc) 73 | Out[11]: 872 74 | 75 | In [12]: len(str(compressed)) 76 | Out[12]: 1811 77 | 78 | In [13]: len(str(enc)) 79 | Out[13]: 875 80 | 81 | There is a one-time cost for decoding the table. As can be seen below, this 82 | cost is not very high. 83 | 84 | .. code-block:: python 85 | 86 | In [14]: %timeit -r 1 -n 1 zlib.decompress(base64.b64decode(enc)) 87 | 1 loop, best of 1: 132 µs per loop 88 | 89 | The table look-up is significantly faster than doing the conversion directly. 90 | 91 | .. code-block:: python 92 | 93 | In [1]: from rgb import rgb 94 | 95 | In [2]: %timeit [rgb(n) for n in range(400, 501)] 96 | 1000 loops, best of 3: 205 µs per loop 97 | 98 | In [3]: from gentable import wavelen2rgb 99 | 100 | In [4]: %timeit [wavelen2rgb(n) for n in range(400, 501)] 101 | 1000 loops, best of 3: 592 µs per loop 102 | 103 | 104 | -------------------------------------------------------------------------------- /wavelength_to_rgb/gentable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # file: gentable.py 3 | # vim:fileencoding=utf-8:fdm=marker:ft=python 4 | # 5 | # Copyright © 2018 R.F. Smith . 6 | # SPDX-License-Identifier: MIT 7 | # Created: 2017-09-17T14:01:14+0200 8 | # Last modified: 2018-07-08T12:53:20+0200 9 | '''Generates a Python table to convert wavelenths in nm to (R,G,B) tuples.''' 10 | 11 | import base64 12 | import zlib 13 | 14 | 15 | def wavelen2rgb(nm): 16 | """ 17 | Converts a wavelength between 380 and 780 nm to an RGB color tuple. 18 | 19 | Argument: 20 | nm: Wavelength in nanometers. 21 | 22 | Returns: 23 | a 3-tuple (red, green, blue) of integers in the range 0-255. 24 | """ 25 | 26 | def adjust(color, factor): 27 | if color < 0.01: 28 | return 0 29 | max_intensity = 255 30 | gamma = 0.80 31 | rv = int(round(max_intensity * (color * factor)**gamma)) 32 | if rv < 0: 33 | return 0 34 | if rv > max_intensity: 35 | return max_intensity 36 | return rv 37 | 38 | if nm < 380 or nm > 780: 39 | raise ValueError('wavelength out of range') 40 | red = 0.0 41 | green = 0.0 42 | blue = 0.0 43 | # Calculate intensities in the different wavelength bands. 44 | if nm < 440: 45 | red = -(nm - 440.0) / (440.0 - 380.0) 46 | blue = 1.0 47 | elif nm < 490: 48 | green = (nm - 440.0) / (490.0 - 440.0) 49 | blue = 1.0 50 | elif nm < 510: 51 | green = 1.0 52 | blue = -(nm - 510.0) / (510.0 - 490.0) 53 | elif nm < 580: 54 | red = (nm - 510.0) / (580.0 - 510.0) 55 | green = 1.0 56 | elif nm < 645: 57 | red = 1.0 58 | green = -(nm - 645.0) / (645.0 - 580.0) 59 | else: 60 | red = 1.0 61 | # Let the intensity fall off near the vision limits. 62 | if nm < 420: 63 | factor = 0.3 + 0.7 * (nm - 380.0) / (420.0 - 380.0) 64 | elif nm < 701: 65 | factor = 1.0 66 | else: 67 | factor = 0.3 + 0.7 * (780.0 - nm) / (780.0 - 700.0) 68 | # Return the calculated values in an (R,G,B) tuple. 69 | return (adjust(red, factor), adjust(green, factor), adjust(blue, factor)) 70 | 71 | 72 | def binclrs(): 73 | clrs = [] 74 | for wl in range(380, 781): 75 | clrs += wavelen2rgb(wl) 76 | raw = bytes(clrs) 77 | return base64.b64encode(zlib.compress(raw, 9)) 78 | 79 | 80 | def split_len(seq, length): 81 | return [seq[i:i + length] for i in range(0, len(seq), length)] 82 | 83 | 84 | def bintable(): 85 | pieces = split_len(binclrs(), 65) 86 | start = pieces.pop(0) 87 | start = f'_ctbl = {start}' 88 | pieces = [f' {p}' for p in pieces] 89 | pieces.insert(0, start) 90 | hdr = """# This code has been automatically generated by the gentable.py script. 91 | # You can find this script at https://github.com/rsmith-nl/wavelength_to_rgb 92 | # The algorithm is based on Dan Bruton's work in 93 | # http://www.physics.sfasu.edu/astro/color/spectra.html 94 | """ 95 | print(hdr) 96 | print('import base64') 97 | print('import zlib\n') 98 | print(' \\\n'.join(pieces)) 99 | print('_ctbl = zlib.decompress(base64.b64decode(_ctbl))') 100 | fs = r''' 101 | 102 | def rgb(nm): 103 | """ 104 | Converts a wavelength between 380 and 780 nm to an RGB color tuple. 105 | 106 | Argument: 107 | nm: Wavelength in nanometers. It is rounded to the nearest integer. 108 | 109 | Returns: 110 | a 3-tuple (red, green, blue) of integers in the range 0-255. 111 | """ 112 | nm = int(round(nm)) 113 | if nm < 380 or nm > 780: 114 | raise ValueError('wavelength out of range') 115 | nm = (nm - 380) * 3 116 | return _ctbl[nm:nm + 3]''' 117 | print(fs) 118 | 119 | 120 | if __name__ == '__main__': 121 | bintable() 122 | -------------------------------------------------------------------------------- /wavelength_to_rgb/rgb.py: -------------------------------------------------------------------------------- 1 | # This code has been automatically generated by the gentable.py script. 2 | # You can find this script at https://github.com/rsmith-nl/wavelength_to_rgb 3 | # The algorithm is based on Dan Bruton's work in 4 | # http://www.physics.sfasu.edu/astro/color/spectra.html 5 | 6 | import base64 7 | import zlib 8 | 9 | _ctbl = b'eNrV0ltr0AUAxuHn1rqoiAqSiA6EEJ3ogBUJeeFFRUdJOjOyWau1bLZpGztnM1oyy' \ 10 | b'2yYmcmMETPNlixNVpmssobKMJuYDVnNGksz0zTe5C9BXyF4v8Dz4y1RMlPpLGVlKs' \ 11 | b'pVVqh+Vu0cDVVa5mqdp61Ge63FdTrqLWuwolFno64m3U3WNutp1ttsY7O+Jpub9Df' \ 12 | b'a2migwY56O+sM1dpTY3iekblGq4zNcWC2QxWOlDtWJqVSIg/JfTJd7pRbZZpMlZtk' \ 13 | b'slwtl8skuUjOk3PkDDlFnNipcWZMjAtjUlwR18aNcXNMi9virpgRD0ZJlMZTMTuqo' \ 14 | b'ibqoyUWxCuxKJbEm/F2dEZXrI4PYn1siL7YHP3xTWyLwfg+9sRwjMT+GI/f4884Fj' \ 15 | b'mxP2S/7JVB+Vr6pEe65C1ZJC9KjTxduKcX1smF74TMhDgtzopz4/y4uGBdFlfFdXF' \ 16 | b'DTImpBe6WuD3ujnvj/ni4ID4WTxTKZ6IyqgtoXTTGC9EaL8fCgvt6dPwrXhmrCnR3' \ 17 | b'rIl18VH0xicF/fPYEl/G1hiI7UWA72KoaPBj7Iufigxj8VtR4nAcjeMnYxyXo3JYD' \ 18 | b'sq4/CqjMiLD8oPsll1Fp+0yUNTqly/kU9kkG2S9fChrpLtIuErekeWyVN6Q16Rd2m' \ 19 | b'SBzJcmqSvqVkulVMiTMkselUfkAZkh98gd/znZFLlerpEr5VK5RC6QiXK2nC4TTv7' \ 20 | b'sf7C/OcZfHOEwhzjIAcYZ4xdG+ZkR9jHMXvawmyF2sZNBdrCNAb5lK1/RzxY28xl9' \ 21 | b'bGIjH9PLenpYx1rep5v36OJdOlnJCpazjKV0sITFvEo7C2njJVqZTwtNNFBHLc9Tz' \ 22 | b'XNUMpsKyinjcUqZSQn/AJ7p9HY=' 23 | _ctbl = zlib.decompress(base64.b64decode(_ctbl)) 24 | 25 | 26 | def rgb(nm): 27 | """ 28 | Converts a wavelength between 380 and 780 nm to an RGB color tuple. 29 | 30 | Argument: 31 | nm: Wavelength in nanometers. It is rounded to the nearest integer. 32 | 33 | Returns: 34 | a 3-tuple (red, green, blue) of integers in the range 0-255. 35 | """ 36 | nm = int(round(nm)) 37 | if nm < 380 or nm > 780: 38 | raise ValueError('wavelength out of range') 39 | nm = (nm - 380) * 3 40 | return _ctbl[nm:nm + 3] 41 | --------------------------------------------------------------------------------