├── .gitignore ├── .gitmodules ├── README.md └── addons ├── add_Window.credits.txt ├── add_Window.py ├── cursor_control ├── __init__.py ├── data.py ├── history.py ├── memory.py ├── operators.py └── ui.py ├── edge_fillet.py ├── enhanced_3d_cursor.py ├── inset_outline.py ├── matlib ├── __init__.py ├── categories.xml ├── materials.blend ├── materials.blend1 ├── overwmat.py ├── removemat.py └── sendmat.py ├── mesh_edge_intersection_tools.py ├── mesh_offset_edges.py ├── modules ├── constants_utils.py ├── cursor_utils.py ├── geometry_utils.py ├── mesh_editor_utils.py ├── misc_utils.py └── ui_utils.py ├── multifillet.py ├── node_wrangler.py ├── planks.py ├── render_to_print.py ├── suicidator_city_generator_0_5_7_Free ├── README.txt ├── SCG.jar ├── __init__.py └── lib │ ├── com.objectplanet.image.PngEncoder.jar │ ├── commons-cli-1.2.jar │ ├── jts-1.12.jar │ ├── pyrolite.jar │ └── swing-layout-1.0.4.jar └── sun_position ├── WorldMap.jpg ├── WorldMapLLR.jpg ├── WorldMapLR.jpg ├── __init__.py ├── hdr.py ├── map.py ├── north.py ├── operators.py ├── properties.py ├── sun_calc.py └── ui_sun.py /.gitignore: -------------------------------------------------------------------------------- 1 | #OS Specific files 2 | .directory 3 | .DS 4 | __MACOSX 5 | .kdev4/ 6 | *.kdev4 7 | .idea 8 | 9 | #Ignore KDE's backup files 10 | *~ 11 | 12 | #Blender's cache folder 13 | __pycache__ 14 | 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "addons/sverchok"] 2 | path = addons/sverchok 3 | url = https://github.com/nortikin/sverchok.git 4 | [submodule "addons/object_assembler"] 5 | path = addons/object_assembler 6 | url = https://github.com/ni-ko-o-kin/object_assembler.git 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Blender architecture scripts 2 | ============================ 3 | 4 | This repo contains a collection of Blender scripts and plugins specially useful for architecture. 5 | This repo serves these main purposes: 6 | 7 | 1. Make it easy to find all these scripts which are usually spread into different places (eg. dropbox folders, authors' blogs, blender wiki...) and thus difficult to find. 8 | 2. Provide a backup or a time-proof repository which don't rely on 3rd pary (I've been always concerned that those scripts could be lost sometime). 9 | 3. Benefit from Git and github's social features in order to make it easier to collaborate and improve plugins. 10 | 11 | 12 | Please note that this doesn't mean that all these addons and scripts are necesarilly going to be mantained. This responsability, as well as their credits relies on each script's authors and mantainers. 13 | 14 | [Visit this wiki page](https://github.com/ccamara/blender-architecture-scripts/wiki/List-of-available-addons) to see all the information regarding to the addons that have been included on this repo. 15 | 16 | ## Instructions 17 | 18 | We provide two different ways to use this repo: basic usage (no git knowledge required) and advanced usage for those who want to benefit from all git's features such as cloning, forking, contributing, updating... 19 | 20 | ###Basic usage 21 | 22 | 1. Download this repo's content from the [download link](https://github.com/ccamara/blender-architecture-scripts/archive/master.zip) 23 | 2. Extract it into your blender's scripts' folder (this may be different depending on your operating system, so if in doubt, please check [this page](https://www.blender.org/manual/getting_started/installing/configuration/directories.html) to know where should you place them) 24 | 3. Enable the desired plugin from Blender's properties' dialog box. 25 | 26 | ###Advanced usage 27 | 28 | 1. Navigate to your blender's scripts' folder 29 | 2. Clone this repo using the following command: ```git clone git@github.com:ccamara/blender-architecture-scripts.git``` 30 | 3. In case you want to update your local code from the latest code from this repo you have to execute the following commands 31 | 4. `git pull` to retrieve all the files (Except all the files cloned from other repositories. 32 | 4. Since this repo uses gitsubmodules you'll have to perform some extra commands: `git submodule init` and then `git submodule update` 33 | 34 | ##More information/help 35 | If you have doubts you can always read the [repo's wiki page](https://github.com/ccamara/blender-architecture-scripts/wiki) or check our [issue queue](https://github.com/ccamara/blender-architecture-scripts/issues) 36 | -------------------------------------------------------------------------------- /addons/add_Window.credits.txt: -------------------------------------------------------------------------------- 1 | Credits, source and information: http://blenderartists.org/forum/showthread.php?270422-AddOn-Window-Generator-2 2 | -------------------------------------------------------------------------------- /addons/cursor_control/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | # Blender Add-Ons menu registration (in User Prefs) 21 | bl_info = { 22 | 'name': 'Cursor Control', 23 | 'author': 'Morgan Mörtsell (Seminumerical)', 24 | 'version': (0, 7, 0), 25 | 'blender': (2, 5, 9), 26 | 'api': 39307, 27 | 'location': 'View3D > Properties > Cursor', 28 | 'description': 'Control the Cursor', 29 | 'warning': '', # used for warning icon and text in addons panel 30 | 'wiki_url': 'http://blenderpythonscripts.wordpress.com/', 31 | 'tracker_url': '', 32 | 'category': '3D View'} 33 | 34 | import bpy 35 | 36 | # To support reload properly, try to access a package var, if it's there, reload everything 37 | if "local_var" in locals(): 38 | import imp 39 | imp.reload(data) 40 | imp.reload(ui) 41 | imp.reload(operators) 42 | imp.reload(history) 43 | imp.reload(memory) 44 | else: 45 | from cursor_control import data 46 | from cursor_control import ui 47 | from cursor_control import operators 48 | from cursor_control import history 49 | from cursor_control import memory 50 | 51 | local_var = True 52 | 53 | def register(): 54 | bpy.utils.register_module(__name__) 55 | # Register Cursor Control Structure 56 | bpy.types.Scene.cursor_control = bpy.props.PointerProperty(type=data.CursorControlData, name="") 57 | bpy.types.Scene.cursor_history = bpy.props.PointerProperty(type=history.CursorHistoryData, name="") 58 | bpy.types.Scene.cursor_memory = bpy.props.PointerProperty(type=memory.CursorMemoryData, name="") 59 | # Register menu 60 | bpy.types.VIEW3D_MT_snap.append(ui.menu_callback) 61 | 62 | def unregister(): 63 | # Register menu 64 | bpy.types.VIEW3D_MT_snap.remove(ui.menu_callback) 65 | bpy.utils.unregister_module(__name__) 66 | 67 | 68 | if __name__ == "__main__": 69 | register() 70 | -------------------------------------------------------------------------------- /addons/cursor_control/data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | 21 | 22 | """ 23 | TODO: 24 | 25 | IDEAS: 26 | 27 | LATER: 28 | 29 | ISSUES: 30 | Bugs: 31 | Mites: 32 | 33 | QUESTIONS: 34 | 35 | 36 | """ 37 | 38 | 39 | 40 | import bpy 41 | import bgl 42 | import math 43 | from mathutils import Vector, Matrix 44 | from mathutils import geometry 45 | from misc_utils import * 46 | from constants_utils import * 47 | from cursor_utils import * 48 | from ui_utils import * 49 | from geometry_utils import * 50 | 51 | 52 | PRECISION = 4 53 | 54 | 55 | class CursorControlData(bpy.types.PropertyGroup): 56 | # Step length properties 57 | stepLengthEnable = bpy.props.BoolProperty(name="Use step length",description="Use step length",default=0) 58 | stepLengthMode = bpy.props.EnumProperty(items=[ 59 | ("Mode", "Mode", "Mode"), 60 | ("Absolute", "Absolute", "Absolute"), 61 | ("Proportional", "Proportional", "Proportional")], 62 | default="Proportional") 63 | stepLengthValue = bpy.props.FloatProperty(name="",precision=PRECISION,default=PHI) 64 | # Property for linex result select... 65 | linexChoice = bpy.props.IntProperty(name="",default=-1) 66 | deltaVector = bpy.props.FloatVectorProperty(name="",precision=PRECISION,default=(1,0,0)) 67 | 68 | def hideLinexChoice(self): 69 | self.linexChoice = -1 70 | 71 | def cycleLinexCoice(self,limit): 72 | qc = self.linexChoice + 1 73 | if qc<0: 74 | qc = 1 75 | if qc>=limit: 76 | qc = 0 77 | self.linexChoice = qc 78 | 79 | def setCursor(self,v): 80 | if self.stepLengthEnable: 81 | c = CursorAccess.getCursor() 82 | if((Vector(c)-Vector(v)).length>0): 83 | if self.stepLengthMode=='Absolute': 84 | v = Vector(v)-Vector(c) 85 | v.normalize() 86 | v = v*self.stepLengthValue + Vector(c) 87 | if self.stepLengthMode=='Proportional': 88 | v = (Vector(v)-Vector(c))*self.stepLengthValue + Vector(c) 89 | CursorAccess.setCursor(Vector(v)) 90 | 91 | def guiStates(self,context): 92 | tvs = 0 93 | tes = 0 94 | tfs = 0 95 | edit_mode = False 96 | obj = context.active_object 97 | if (context.mode == 'EDIT_MESH'): 98 | if (obj and obj.type=='MESH' and obj.data): 99 | tvs = obj.data.total_vert_sel 100 | 101 | tes = obj.data.total_edge_sel 102 | tfs = obj.data.total_face_sel 103 | edit_mode = True 104 | return (tvs, tes, tfs, edit_mode) 105 | 106 | def invertDeltaVector(self): 107 | self.deltaVector = Vector([0,0,0])-Vector(self.deltaVector) 108 | 109 | def normalizeDeltaVector(self): 110 | q = Vector(self.deltaVector) 111 | q.normalize() 112 | self.deltaVector = q 113 | 114 | def addDeltaVectorToCursor(self): 115 | c = CursorAccess.getCursor() 116 | CursorAccess.setCursor(Vector(c)+Vector(self.deltaVector)) 117 | 118 | def subDeltaVectorToCursor(self): 119 | c = CursorAccess.getCursor() 120 | CursorAccess.setCursor(Vector(c)-Vector(self.deltaVector)) 121 | -------------------------------------------------------------------------------- /addons/cursor_control/history.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | 21 | 22 | """ 23 | TODO: 24 | 25 | IDEAS: 26 | 27 | LATER: 28 | 29 | ISSUES: 30 | Bugs: 31 | Seg-faults when unregistering addon... 32 | Mites: 33 | * History back button does not light up on first cursor move. 34 | It does light up on the second, or when mouse enters the tool-area 35 | * Switching between local and global view triggers new cursor position in history trace. 36 | * Each consecutive click on the linex operator triggers new cursor position in history trace. 37 | (2011-01-16) Was not able to fix this because of some strange script behaviour 38 | while trying to clear linexChoice from addHistoryLocation 39 | 40 | QUESTIONS: 41 | 42 | 43 | 44 | """ 45 | 46 | 47 | 48 | import bpy 49 | import bgl 50 | import math 51 | from mathutils import Vector, Matrix 52 | from mathutils import geometry 53 | from misc_utils import * 54 | from constants_utils import * 55 | from cursor_utils import * 56 | from ui_utils import * 57 | 58 | 59 | 60 | class CursorHistoryData(bpy.types.PropertyGroup): 61 | # History tracker 62 | historyDraw = bpy.props.BoolProperty(description="Draw history trace in 3D view",default=1) 63 | historyDepth = 144 64 | historyWindow = 12 65 | historyPosition = [-1] # Integer must be in a list or else it can not be written to 66 | historyLocation = [] 67 | #historySuppression = [False] # Boolean must be in a list or else it can not be written to 68 | 69 | def addHistoryLocation(self, l): 70 | if(self.historyPosition[0]==-1): 71 | self.historyLocation.append(l.copy()) 72 | self.historyPosition[0]=0 73 | return 74 | if(l==self.historyLocation[self.historyPosition[0]]): 75 | return 76 | #if self.historySuppression[0]: 77 | #self.historyPosition[0] = self.historyPosition[0] - 1 78 | #else: 79 | #self.hideLinexChoice() 80 | while(len(self.historyLocation)>self.historyPosition[0]+1): 81 | self.historyLocation.pop(self.historyPosition[0]+1) 82 | #self.historySuppression[0] = False 83 | self.historyLocation.append(l.copy()) 84 | if(len(self.historyLocation)>self.historyDepth): 85 | self.historyLocation.pop(0) 86 | self.historyPosition[0] = len(self.historyLocation)-1 87 | #print (self.historyLocation) 88 | 89 | #def enableHistorySuppression(self): 90 | #self.historySuppression[0] = True 91 | 92 | def previousLocation(self): 93 | if(self.historyPosition[0]<=0): 94 | return 95 | self.historyPosition[0] = self.historyPosition[0] - 1 96 | CursorAccess.setCursor(self.historyLocation[self.historyPosition[0]].copy()) 97 | 98 | def nextLocation(self): 99 | if(self.historyPosition[0]<0): 100 | return 101 | if(self.historyPosition[0]+1==len(self.historyLocation)): 102 | return 103 | self.historyPosition[0] = self.historyPosition[0] + 1 104 | CursorAccess.setCursor(self.historyLocation[self.historyPosition[0]].copy()) 105 | 106 | 107 | 108 | class VIEW3D_OT_cursor_previous(bpy.types.Operator): 109 | '''Previous cursor location.''' 110 | bl_idname = "view3d.cursor_previous" 111 | bl_label = "Previous cursor location." 112 | bl_options = {'REGISTER'} 113 | 114 | def modal(self, context, event): 115 | return {'FINISHED'} 116 | 117 | def execute(self, context): 118 | cc = context.scene.cursor_history 119 | cc.previousLocation() 120 | return {'FINISHED'} 121 | 122 | 123 | 124 | class VIEW3D_OT_cursor_next(bpy.types.Operator): 125 | '''Next cursor location.''' 126 | bl_idname = "view3d.cursor_next" 127 | bl_label = "Next cursor location." 128 | bl_options = {'REGISTER'} 129 | 130 | def modal(self, context, event): 131 | return {'FINISHED'} 132 | 133 | def execute(self, context): 134 | cc = context.scene.cursor_history 135 | cc.nextLocation() 136 | return {'FINISHED'} 137 | 138 | 139 | 140 | class VIEW3D_OT_cursor_history_show(bpy.types.Operator): 141 | '''Show cursor trace.''' 142 | bl_idname = "view3d.cursor_history_show" 143 | bl_label = "Show cursor trace." 144 | bl_options = {'REGISTER'} 145 | 146 | def modal(self, context, event): 147 | return {'FINISHED'} 148 | 149 | def execute(self, context): 150 | cc = context.scene.cursor_history 151 | cc.historyDraw = True 152 | BlenderFake.forceRedraw() 153 | return {'FINISHED'} 154 | 155 | 156 | 157 | class VIEW3D_OT_cursor_history_hide(bpy.types.Operator): 158 | '''Hide cursor trace.''' 159 | bl_idname = "view3d.cursor_history_hide" 160 | bl_label = "Hide cursor trace." 161 | bl_options = {'REGISTER'} 162 | 163 | def modal(self, context, event): 164 | return {'FINISHED'} 165 | 166 | def execute(self, context): 167 | cc = context.scene.cursor_history 168 | cc.historyDraw = False 169 | BlenderFake.forceRedraw() 170 | return {'FINISHED'} 171 | 172 | 173 | 174 | class VIEW3D_PT_cursor_history(bpy.types.Panel): 175 | bl_space_type = 'VIEW_3D' 176 | bl_region_type = 'UI' 177 | bl_label = "Cursor History" 178 | bl_default_closed = True 179 | 180 | @classmethod 181 | def poll(self, context): 182 | # Display in object or edit mode. 183 | cc = context.scene.cursor_history 184 | cc.addHistoryLocation(CursorAccess.getCursor()) 185 | if (context.area.type == 'VIEW_3D' and 186 | (context.mode == 'EDIT_MESH' 187 | or context.mode == 'OBJECT')): 188 | return 1 189 | 190 | return 0 191 | 192 | def draw_header(self, context): 193 | layout = self.layout 194 | cc = context.scene.cursor_history 195 | if cc.historyDraw: 196 | GUI.drawIconButton(True, layout, 'RESTRICT_VIEW_OFF', "view3d.cursor_history_hide", False) 197 | else: 198 | GUI.drawIconButton(True, layout, 'RESTRICT_VIEW_ON' , "view3d.cursor_history_show", False) 199 | 200 | def draw(self, context): 201 | layout = self.layout 202 | sce = context.scene 203 | cc = context.scene.cursor_history 204 | 205 | row = layout.row() 206 | row.label("Navigation: ") 207 | GUI.drawIconButton(cc.historyPosition[0]>0, row, 'PLAY_REVERSE', "view3d.cursor_previous") 208 | #if(cc.historyPosition[0]<0): 209 | #row.label(" -- ") 210 | #else: 211 | #row.label(" "+str(cc.historyPosition[0])+" ") 212 | GUI.drawIconButton(cc.historyPosition[0]=len(cc.historyLocation)): 280 | continue 281 | ppp = region3d_get_2d_coordinates(context, cc.historyLocation[ix]) 282 | if(ix_rel<=0): 283 | bgl.glColor4f(0, 0, 0, alpha) 284 | else: 285 | bgl.glColor4f(1, 0, 0, alpha) 286 | bgl.glVertex2f(ppp[0], ppp[1]) 287 | ccc = ccc + 1 288 | bgl.glEnd() 289 | -------------------------------------------------------------------------------- /addons/cursor_control/memory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | 21 | 22 | """ 23 | TODO: 24 | 25 | IDEAS: 26 | Add/Subtract 27 | 28 | LATER: 29 | 30 | ISSUES: 31 | Bugs: 32 | Mites: 33 | CTRL-Z forces memory to world origin (0,0,0)... why?? 34 | Happens only if undo reaches 'default world state' 35 | How to Reproduce: 36 | 1. File->New 37 | 2. Move 3D-cursor 38 | 3. Set memory 39 | 4. Move cube 40 | 5. CTRL-Z 41 | 42 | QUESTIONS: 43 | 44 | 45 | """ 46 | 47 | 48 | 49 | import bpy 50 | import bgl 51 | import math 52 | from mathutils import Vector, Matrix 53 | from mathutils import geometry 54 | from misc_utils import * 55 | from constants_utils import * 56 | from cursor_utils import * 57 | from ui_utils import * 58 | 59 | 60 | 61 | PRECISION = 4 62 | 63 | 64 | 65 | class CursorMemoryData(bpy.types.PropertyGroup): 66 | 67 | savedLocationDraw = bpy.props.BoolProperty(description="Draw SL cursor in 3D view",default=1) 68 | savedLocation = bpy.props.FloatVectorProperty(name="",description="Saved Location",precision=PRECISION) 69 | 70 | 71 | class VIEW3D_OT_cursor_memory_save(bpy.types.Operator): 72 | '''Save cursor location.''' 73 | bl_idname = "view3d.cursor_memory_save" 74 | bl_label = "Save cursor location." 75 | bl_options = {'REGISTER'} 76 | 77 | def modal(self, context, event): 78 | return {'FINISHED'} 79 | 80 | def execute(self, context): 81 | cc = context.scene.cursor_memory 82 | cc.savedLocation = CursorAccess.getCursor() 83 | CursorAccess.setCursor(cc.savedLocation) 84 | return {'FINISHED'} 85 | 86 | 87 | 88 | class VIEW3D_OT_cursor_memory_swap(bpy.types.Operator): 89 | '''Swap cursor location.''' 90 | bl_idname = "view3d.cursor_memory_swap" 91 | bl_label = "Swap cursor location." 92 | bl_options = {'REGISTER'} 93 | 94 | def modal(self, context, event): 95 | return {'FINISHED'} 96 | 97 | def execute(self, context): 98 | location = CursorAccess.getCursor().copy() 99 | cc = context.scene.cursor_memory 100 | CursorAccess.setCursor(cc.savedLocation) 101 | cc.savedLocation = location 102 | return {'FINISHED'} 103 | 104 | 105 | 106 | class VIEW3D_OT_cursor_memory_recall(bpy.types.Operator): 107 | '''Recall cursor location.''' 108 | bl_idname = "view3d.cursor_memory_recall" 109 | bl_label = "Recall cursor location." 110 | bl_options = {'REGISTER'} 111 | 112 | def modal(self, context, event): 113 | return {'FINISHED'} 114 | 115 | def execute(self, context): 116 | cc = context.scene.cursor_memory 117 | CursorAccess.setCursor(cc.savedLocation) 118 | return {'FINISHED'} 119 | 120 | 121 | 122 | class VIEW3D_OT_cursor_memory_show(bpy.types.Operator): 123 | '''Show cursor memory.''' 124 | bl_idname = "view3d.cursor_memory_show" 125 | bl_label = "Show cursor memory." 126 | bl_options = {'REGISTER'} 127 | 128 | def modal(self, context, event): 129 | return {'FINISHED'} 130 | 131 | def execute(self, context): 132 | cc = context.scene.cursor_memory 133 | cc.savedLocationDraw = True 134 | BlenderFake.forceRedraw() 135 | return {'FINISHED'} 136 | 137 | 138 | 139 | class VIEW3D_OT_cursor_memory_hide(bpy.types.Operator): 140 | '''Hide cursor memory.''' 141 | bl_idname = "view3d.cursor_memory_hide" 142 | bl_label = "Hide cursor memory." 143 | bl_options = {'REGISTER'} 144 | 145 | def modal(self, context, event): 146 | return {'FINISHED'} 147 | 148 | def execute(self, context): 149 | cc = context.scene.cursor_memory 150 | cc.savedLocationDraw = False 151 | BlenderFake.forceRedraw() 152 | return {'FINISHED'} 153 | 154 | 155 | 156 | class VIEW3D_PT_cursor_memory(bpy.types.Panel): 157 | bl_space_type = 'VIEW_3D' 158 | bl_region_type = 'UI' 159 | bl_label = "Cursor Memory" 160 | bl_default_closed = True 161 | 162 | @classmethod 163 | def poll(self, context): 164 | # Display in object or edit mode. 165 | if (context.area.type == 'VIEW_3D' and 166 | (context.mode == 'EDIT_MESH' 167 | or context.mode == 'OBJECT')): 168 | return 1 169 | 170 | return 0 171 | 172 | def draw_header(self, context): 173 | layout = self.layout 174 | cc = context.scene.cursor_memory 175 | if cc.savedLocationDraw: 176 | GUI.drawIconButton(True, layout, 'RESTRICT_VIEW_OFF', "view3d.cursor_memory_hide", False) 177 | else: 178 | GUI.drawIconButton(True, layout, 'RESTRICT_VIEW_ON' , "view3d.cursor_memory_show", False) 179 | #layout.prop(sce, "cursor_memory.savedLocationDraw") 180 | 181 | def draw(self, context): 182 | layout = self.layout 183 | sce = context.scene 184 | cc = context.scene.cursor_memory 185 | 186 | row = layout.row() 187 | col = row.column() 188 | row2 = col.row() 189 | GUI.drawIconButton(True, row2, 'FORWARD', "view3d.cursor_memory_save") 190 | row2 = col.row() 191 | GUI.drawIconButton(True, row2, 'FILE_REFRESH', "view3d.cursor_memory_swap") 192 | row2 = col.row() 193 | GUI.drawIconButton(True, row2, 'BACK' , "view3d.cursor_memory_recall") 194 | col = row.column() 195 | col.prop(cc, "savedLocation") 196 | 197 | 198 | 199 | class VIEW3D_PT_cursor_memory_init(bpy.types.Panel): 200 | bl_space_type = 'VIEW_3D' 201 | bl_region_type = 'UI' 202 | bl_label = "Register callback" 203 | bl_default_closed = True 204 | 205 | initDone = False 206 | 207 | @classmethod 208 | def poll(cls, context): 209 | if VIEW3D_PT_cursor_memory_init.initDone: 210 | return 0 211 | print ("Cursor Memory draw-callback registration...") 212 | sce = context.scene 213 | if context.area.type == 'VIEW_3D': 214 | for reg in context.area.regions: 215 | if reg.type == 'WINDOW': 216 | # Register callback for SL-draw 217 | reg.callback_add( 218 | cursor_memory_draw, 219 | (cls,context), 220 | 'POST_PIXEL') 221 | VIEW3D_PT_cursor_memory_init.initDone = True 222 | print ("Cursor Memory draw-callback registered") 223 | # Unregister to prevent double registration... 224 | # Started to fail after v2.57 225 | # bpy.types.unregister(VIEW3D_PT_cursor_memory_init) 226 | else: 227 | print("View3D not found, cannot run operator") 228 | return 0 229 | 230 | def draw_header(self, context): 231 | pass 232 | 233 | def draw(self, context): 234 | pass 235 | 236 | 237 | 238 | def cursor_memory_draw(cls,context): 239 | cc = context.scene.cursor_memory 240 | 241 | draw = 0 242 | if hasattr(cc, "savedLocationDraw"): 243 | draw = cc.savedLocationDraw 244 | 245 | if(draw): 246 | bgl.glEnable(bgl.GL_BLEND) 247 | bgl.glShadeModel(bgl.GL_FLAT) 248 | p1 = Vector(cc.savedLocation) 249 | location = region3d_get_2d_coordinates(context, p1) 250 | alpha = 1-PHI_INV 251 | # Circle 252 | color = ([0.33, 0.33, 0.33], 253 | [1, 1, 1], 254 | [0.33, 0.33, 0.33], 255 | [1, 1, 1], 256 | [0.33, 0.33, 0.33], 257 | [1, 1, 1], 258 | [0.33, 0.33, 0.33], 259 | [1, 1, 1], 260 | [0.33, 0.33, 0.33], 261 | [1, 1, 1], 262 | [0.33, 0.33, 0.33], 263 | [1, 1, 1], 264 | [0.33, 0.33, 0.33], 265 | [1, 1, 1]) 266 | offset = ([-4.480736161291701, -8.939966636005579], 267 | [-0.158097634992133, -9.998750178787843], 268 | [4.195854066857877, -9.077158622037636], 269 | [7.718765411993642, -6.357724476147943], 270 | [9.71288060283854, -2.379065025383466], 271 | [9.783240669628, 2.070797430975971], 272 | [7.915909938224691, 6.110513059466902], 273 | [4.480736161291671, 8.939966636005593], 274 | [0.15809763499209872, 9.998750178787843], 275 | [-4.195854066857908, 9.077158622037622], 276 | [-7.718765411993573, 6.357724476148025], 277 | [-9.712880602838549, 2.379065025383433], 278 | [-9.783240669627993, -2.070797430976005], 279 | [-7.915909938224757, -6.110513059466818]) 280 | bgl.glBegin(bgl.GL_LINE_LOOP) 281 | for i in range(14): 282 | bgl.glColor4f(color[i][0], color[i][1], color[i][2], alpha) 283 | bgl.glVertex2f(location[0]+offset[i][0], location[1]+offset[i][1]) 284 | bgl.glEnd() 285 | 286 | # Crosshair 287 | offset2 = 20 288 | offset = 5 289 | bgl.glColor4f(0, 0, 0, alpha) 290 | bgl.glBegin(bgl.GL_LINE_STRIP) 291 | bgl.glVertex2f(location[0]-offset2, location[1]) 292 | bgl.glVertex2f(location[0]- offset, location[1]) 293 | bgl.glEnd() 294 | bgl.glBegin(bgl.GL_LINE_STRIP) 295 | bgl.glVertex2f(location[0]+ offset, location[1]) 296 | bgl.glVertex2f(location[0]+offset2, location[1]) 297 | bgl.glEnd() 298 | bgl.glBegin(bgl.GL_LINE_STRIP) 299 | bgl.glVertex2f(location[0], location[1]-offset2) 300 | bgl.glVertex2f(location[0], location[1]- offset) 301 | bgl.glEnd() 302 | bgl.glBegin(bgl.GL_LINE_STRIP) 303 | bgl.glVertex2f(location[0], location[1]+ offset) 304 | bgl.glVertex2f(location[0], location[1]+offset2) 305 | bgl.glEnd() 306 | 307 | -------------------------------------------------------------------------------- /addons/cursor_control/ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | 21 | 22 | """ 23 | TODO: 24 | 25 | IDEAS: 26 | 27 | LATER: 28 | 29 | ISSUES: 30 | Bugs: 31 | Mites: 32 | 33 | QUESTIONS: 34 | 35 | 36 | """ 37 | 38 | 39 | 40 | import bpy 41 | import bgl 42 | import math 43 | from mathutils import Vector, Matrix 44 | from mathutils import geometry 45 | from misc_utils import * 46 | from constants_utils import * 47 | from cursor_utils import * 48 | from ui_utils import * 49 | from geometry_utils import * 50 | 51 | 52 | class VIEW3D_PT_cursor(bpy.types.Panel): 53 | bl_space_type = 'VIEW_3D' 54 | bl_region_type = 'UI' 55 | bl_label = "Cursor Target" 56 | bl_default_closed = True 57 | 58 | @classmethod 59 | def poll(self, context): 60 | # Display in object or edit mode. 61 | if (context.area.type == 'VIEW_3D' and 62 | (context.mode == 'EDIT_MESH' 63 | or context.mode == 'OBJECT')): 64 | return 1 65 | 66 | return 0 67 | 68 | def draw_header(self, context): 69 | pass 70 | 71 | def draw(self, context): 72 | layout = self.layout 73 | sce = context.scene 74 | 75 | cc = context.scene.cursor_control 76 | (tvs,tes,tfs,edit_mode) = cc.guiStates(context) 77 | 78 | # Mesh data elements 79 | if(edit_mode): 80 | row = layout.row() 81 | GUI.drawIconButton(tvs>=1 , row, 'STICKY_UVS_DISABLE', "view3d.cursor_to_vertex") 82 | GUI.drawIconButton(tvs==2 or tes>=1, row, 'MESH_DATA' , "view3d.cursor_to_line") 83 | GUI.drawIconButton(tvs==2 or tes>=1, row, 'OUTLINER_OB_MESH' , "view3d.cursor_to_edge") 84 | GUI.drawIconButton(tvs==3 or tfs>=1, row, 'SNAP_FACE' , "view3d.cursor_to_plane") 85 | GUI.drawIconButton(tvs==3 or tfs>=1, row, 'FACESEL' , "view3d.cursor_to_face") 86 | 87 | # Geometry from mesh 88 | if(edit_mode): 89 | row = layout.row() 90 | GUI.drawIconButton(tvs<=3 or tfs==1 , row, 'MOD_MIRROR' , "view3d.cursor_to_sl_mirror") 91 | GUI.drawIconButton(tes==2, row, 'PARTICLE_TIP', "view3d.cursor_to_linex") 92 | GUI.drawIconButton(tvs>1 , row, 'ROTATECENTER', "view3d.cursor_to_vertex_median") #EDITMODE_HLT 93 | GUI.drawIconButton(tvs==3 or tvs==4, row, 'FORCE_FORCE' , "view3d.cursor_to_spherecenter") 94 | GUI.drawIconButton(tvs==3 or tvs==4, row, 'MATERIAL' , "view3d.cursor_to_perimeter") 95 | 96 | # Objects 97 | #row = layout.row() 98 | 99 | #GUI.drawIconButton(context.active_object!=None , row, 'ROTATE' , "view3d.cursor_to_active_object_center") 100 | #GUI.drawIconButton(len(context.selected_objects)>1, row, 'ROTATECOLLECTION', "view3d.cursor_to_selection_midpoint") 101 | #GUI.drawIconButton(len(context.selected_objects)>1, row, 'ROTATECENTER' , "view3d.cursor_to_selection_midpoint") 102 | 103 | # References World Origin, Object Origin, SL and CL 104 | row = layout.row() 105 | GUI.drawIconButton(True , row, 'WORLD_DATA' , "view3d.cursor_to_origin") 106 | GUI.drawIconButton(context.active_object!=None, row, 'ROTACTIVE' , "view3d.cursor_to_active_object_center") 107 | GUI.drawIconButton(True , row, 'CURSOR' , "view3d.cursor_to_sl") 108 | #GUI.drawIconButton(True, row, 'GRID' , "view3d.cursor_sl_recall") 109 | #GUI.drawIconButton(True, row, 'SNAP_INCREMENT', "view3d.cursor_sl_recall") 110 | #row.label("("+str(cc.linexChoice)+")") 111 | cc = context.scene.cursor_control 112 | if cc.linexChoice>=0: 113 | col = row.column() 114 | col.enabled = False 115 | col.prop(cc, "linexChoice") 116 | 117 | # Limit/Clamping Properties 118 | row = layout.row() 119 | row.prop(cc, "stepLengthEnable") 120 | if (cc.stepLengthEnable): 121 | row = layout.row() 122 | row.prop(cc, "stepLengthMode") 123 | row.prop(cc, "stepLengthValue") 124 | row = layout.row() 125 | GUI.drawTextButton(True, row, '1/Phi' , "view3d.cursor_stepval_phinv") 126 | GUI.drawTextButton(True, row, 'Phi' , "view3d.cursor_stepval_phi") 127 | GUI.drawTextButton(True, row, 'Phi²' , "view3d.cursor_stepval_phi2") 128 | GUI.drawIconButton(tvs==2, row, 'EDGESEL' , "view3d.cursor_stepval_vvdist") 129 | 130 | 131 | 132 | class VIEW3D_PT_ccDelta(bpy.types.Panel): 133 | bl_space_type = 'VIEW_3D' 134 | bl_region_type = 'UI' 135 | bl_label = "Cursor Delta" 136 | bl_default_closed = True 137 | 138 | @classmethod 139 | def poll(self, context): 140 | # Display in object or edit mode. 141 | if (context.area.type == 'VIEW_3D' and 142 | (context.mode == 'EDIT_MESH' 143 | or context.mode == 'OBJECT')): 144 | return 1 145 | 146 | return 0 147 | 148 | def draw_header(self, context): 149 | pass 150 | 151 | def draw(self, context): 152 | layout = self.layout 153 | #sce = context.scene 154 | 155 | cc = context.scene.cursor_control 156 | (tvs,tes,tfs,edit_mode) = cc.guiStates(context) 157 | 158 | row = layout.row() 159 | col = row.column(); 160 | GUI.drawIconButton(True , col, 'FF' , "view3d.ccdelta_add") 161 | GUI.drawIconButton(True , col, 'REW' , "view3d.ccdelta_sub") 162 | GUI.drawIconButton(tvs<=2 , col, 'FORWARD' , "view3d.ccdelta_vvdist") 163 | 164 | col = row.column(); 165 | col.prop(cc, "deltaVector") 166 | 167 | col = row.column(); 168 | GUI.drawIconButton(True , col, 'MOD_MIRROR' , "view3d.ccdelta_invert") 169 | GUI.drawIconButton(True , col, 'SNAP_NORMAL' , "view3d.ccdelta_normalize") 170 | 171 | 172 | 173 | class CursorControlMenu(bpy.types.Menu): 174 | """menu""" 175 | bl_idname = "cursor_control_calls" 176 | bl_label = "Cursor Control" 177 | 178 | def draw(self, context): 179 | layout = self.layout 180 | layout.operator_context = 'INVOKE_REGION_WIN' 181 | #layout.operator(VIEW3D_OT_cursor_to_vertex.bl_idname, text = "Vertex") 182 | #layout.operator(VIEW3D_OT_cursor_to_line.bl_idname, text = "Line") 183 | #obj = context.active_object 184 | #if (context.mode == 'EDIT_MESH'): 185 | #if (obj and obj.type=='MESH' and obj.data): 186 | cc = context.scene.cursor_control 187 | (tvs,tes,tfs,edit_mode) = cc.guiStates(context) 188 | 189 | if(edit_mode): 190 | if(tvs>=1): 191 | layout.operator(VIEW3D_OT_cursor_to_vertex.bl_idname, text = "Closest Vertex") 192 | if(tvs==2 or tes>=1): 193 | layout.operator(VIEW3D_OT_cursor_to_line.bl_idname, text = "Closest Line") 194 | if(tvs==2 or tes>=1): 195 | layout.operator(VIEW3D_OT_cursor_to_edge.bl_idname, text = "Closest Edge") 196 | if(tvs==3 or tfs>=1): 197 | layout.operator(VIEW3D_OT_cursor_to_plane.bl_idname, text = "Closest Plane") 198 | if(tvs==3 or tfs>=1): 199 | layout.operator(VIEW3D_OT_cursor_to_face.bl_idname, text = "Closest Face") 200 | 201 | if(edit_mode): 202 | if(tvs<=3 or tfs==1): 203 | layout.operator(VIEW3D_OT_cursor_to_sl_mirror.bl_idname, text = "Mirror") 204 | if(tes==2): 205 | layout.operator(VIEW3D_OT_cursor_to_linex.bl_idname, text = "Line Intersection") 206 | if(tvs>1): 207 | layout.operator(VIEW3D_OT_cursor_to_vertex_median.bl_idname, text = "Vertex Median") 208 | if(tvs==3 or tvs==4): 209 | layout.operator(VIEW3D_OT_cursor_to_spherecenter.bl_idname, text = "Circle Center") 210 | if(tvs==3 or tvs==4): 211 | layout.operator(VIEW3D_OT_cursor_to_perimeter.bl_idname, text = "Circle Perimeter") 212 | 213 | layout.operator(VIEW3D_OT_cursor_to_origin.bl_idname, text = "World Origin") 214 | layout.operator(VIEW3D_OT_cursor_to_active_object_center.bl_idname, text = "Active Object") 215 | layout.operator(VIEW3D_OT_cursor_to_sl.bl_idname, text = "Cursor Memory") 216 | 217 | 218 | 219 | def menu_callback(self, context): 220 | #obj = context.active_object 221 | #if (context.mode == 'EDIT_MESH'): 222 | #if (obj and obj.type=='MESH' and obj.data): 223 | self.layout.menu(CursorControlMenu.bl_idname, icon="PLUGIN") 224 | 225 | -------------------------------------------------------------------------------- /addons/edge_fillet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # ***** BEGIN GPL LICENSE BLOCK ***** 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software Foundation, 18 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | # ***** END GPL LICENCE BLOCK ***** 21 | 22 | # ------ ------ 23 | bl_info = { 24 | 'name': 'edge_fillet', 25 | 'author': '', 26 | 'version': (0, 2, 2), 27 | 'blender': (2, 6, 1), 28 | 'api': 43085, 29 | 'location': 'View3D > Tool Shelf', 30 | 'description': '', 31 | 'warning': '', 32 | 'wiki_url': '', 33 | 'tracker_url': '', 34 | 'category': 'Mesh' } 35 | 36 | # ------ ------ 37 | import bpy 38 | from bpy.props import FloatProperty, IntProperty, BoolProperty 39 | from mathutils import Matrix 40 | from math import cos, pi, degrees, sin, tan 41 | 42 | # ------ ------ 43 | def edit_mode_out(): 44 | bpy.ops.object.mode_set(mode = 'OBJECT') 45 | 46 | def edit_mode_in(): 47 | bpy.ops.object.mode_set(mode = 'EDIT') 48 | 49 | def list_clear_(l): 50 | l[:] = [] 51 | return l 52 | 53 | def get_adj_v_(list_): 54 | tmp = {} 55 | for i in list_: 56 | try: tmp[i[0]].append(i[1]) 57 | except KeyError: tmp[i[0]] = [i[1]] 58 | try: tmp[i[1]].append(i[0]) 59 | except KeyError: tmp[i[1]] = [i[0]] 60 | return tmp 61 | 62 | # ------ ------ 63 | class f_buf(): 64 | an = 0 65 | 66 | # ------ ------ 67 | def f_(me, list_0, adj, n, out, flip, e_mode, radius): 68 | 69 | dict_0 = get_adj_v_(list_0) 70 | list_1 = [[dict_0[i][0], i, dict_0[i][1]] for i in dict_0 if (len(dict_0[i]) == 2)][0] 71 | list_2 = [] 72 | 73 | p_ = list_1[1] 74 | p = (me.vertices[list_1[1]].co).copy() 75 | p1 = (me.vertices[list_1[0]].co).copy() 76 | p2 = (me.vertices[list_1[2]].co).copy() 77 | 78 | vec1 = p - p1 79 | vec2 = p - p2 80 | 81 | ang = vec1.angle(vec2, any) 82 | f_buf.an = round(degrees(ang)) 83 | 84 | # -- -- -- -- 85 | if f_buf.an == 180 or f_buf.an == 0.0: 86 | if e_mode[1] == True: 87 | for e in me.edges: 88 | if e.select: 89 | e.select = False 90 | 91 | elif e_mode[0] == True: 92 | me.vertices[list_1[0]].select = False 93 | me.vertices[list_1[1]].select = False 94 | me.vertices[list_1[2]].select = False 95 | return 96 | 97 | # -- -- -- -- 98 | opp = adj 99 | 100 | if radius == False: 101 | h = adj * (1 / cos(ang * 0.5)) 102 | adj_ = adj 103 | elif radius == True: 104 | h = opp / sin(ang * 0.5) 105 | adj_ = opp / tan(ang * 0.5) 106 | 107 | p3 = p - (vec1.normalized() * adj_) 108 | p4 = p - (vec2.normalized() * adj_) 109 | rp = p - ((p - ((p3 + p4) * 0.5)).normalized() * h) 110 | 111 | vec3 = rp - p3 112 | vec4 = rp - p4 113 | 114 | axis = vec1.cross(vec2) 115 | 116 | if out == False: 117 | if flip == False: 118 | rot_ang = vec3.angle(vec4) 119 | elif flip == True: 120 | rot_ang = vec1.angle(vec2) 121 | elif out == True: 122 | rot_ang = (2 * pi) - vec1.angle(vec2) 123 | 124 | for j in range(n + 1): 125 | new_angle = rot_ang * j / n 126 | mtrx = Matrix.Rotation(new_angle, 3, axis) 127 | if out == False: 128 | if flip == False: 129 | tmp = p4 - rp 130 | tmp1 = mtrx * tmp 131 | tmp2 = tmp1 + rp 132 | elif flip == True: 133 | p3 = p - (vec1.normalized() * opp) 134 | tmp = p3 - p 135 | tmp1 = mtrx * tmp 136 | tmp2 = tmp1 + p 137 | elif out == True: 138 | p4 = p - (vec2.normalized() * opp) 139 | tmp = p4 - p 140 | tmp1 = mtrx * tmp 141 | tmp2 = tmp1 + p 142 | 143 | me.vertices.add(1) 144 | me.vertices[-1].co = tmp2 145 | me.vertices[-1].select = False 146 | list_2.append(me.vertices[-1].index) 147 | 148 | if flip == True: 149 | list_1[1:2] = list_2 150 | else: 151 | list_2.reverse() 152 | list_1[1:2] = list_2 153 | 154 | list_clear_(list_2) 155 | 156 | n1 = len(list_1) 157 | for t in range(n1 - 1): 158 | me.edges.add(1) 159 | me.edges[-1].vertices = [list_1[t], list_1[(t + 1) % n1]] 160 | 161 | me.vertices[list_1[0]].select = False 162 | me.vertices[list_1[-1]].select = False 163 | 164 | if e_mode[1] == True: 165 | for e in me.edges: 166 | if e.select: 167 | e.select = False 168 | 169 | me.vertices.add(1) 170 | me.vertices[-1].co = p 171 | me.edges.add(1) 172 | me.edges[-1].vertices = [me.vertices[-1].index, p_] 173 | 174 | me.update(calc_edges = True) 175 | 176 | # ------ panel 0 ------ 177 | class f_p0(bpy.types.Panel): 178 | bl_space_type = 'VIEW_3D' 179 | bl_region_type = 'TOOLS' 180 | #bl_idname = 'f_p0_id' 181 | bl_label = 'Fillet' 182 | bl_context = 'mesh_edit' 183 | 184 | def draw(self, context): 185 | layout = self.layout 186 | 187 | row = layout.split(0.80) 188 | row.operator('f.op0_id', text = 'Fillet') 189 | row.operator('f.op1_id', text = '?') 190 | 191 | # ------ operator 0 ------ 192 | class f_op0(bpy.types.Operator): 193 | bl_idname = 'f.op0_id' 194 | bl_label = 'Fillet' 195 | bl_options = {'REGISTER', 'UNDO'} 196 | 197 | adj = FloatProperty( name = '', default = 0.1, min = 0.00001, max = 100.0, step = 1, precision = 3 ) 198 | n = IntProperty( name = '', default = 3, min = 1, max = 100, step = 1 ) 199 | out = BoolProperty( name = 'Outside', default = False ) 200 | flip = BoolProperty( name = 'Flip', default = False ) 201 | radius = BoolProperty( name = 'Radius', default = False ) 202 | 203 | def draw(self, context): 204 | layout = self.layout 205 | 206 | if f_buf.an == 180 or f_buf.an == 0.0: 207 | layout.label('Info:') 208 | layout.label('Angle equal to 0 or 180,') 209 | layout.label('can not fillet.') 210 | else: 211 | layout.prop(self, 'radius') 212 | if self.radius == True: 213 | layout.label('Radius:') 214 | elif self.radius == False: 215 | layout.label('Distance:') 216 | layout.prop(self, 'adj') 217 | layout.label('Number of sides:') 218 | layout.prop(self, 'n', slider = True) 219 | if self.n > 1: 220 | row = layout.row(align = False) 221 | row.prop(self, 'out') 222 | if self.out == False: 223 | row.prop(self, 'flip') 224 | 225 | def execute(self, context): 226 | adj = self.adj 227 | n = self.n 228 | out = self.out 229 | flip = self.flip 230 | radius = self.radius 231 | 232 | edit_mode_out() 233 | ob_act = context.active_object 234 | me = ob_act.data 235 | list_0 = [list(e.key) for e in me.edges if e.select] 236 | e_mode = [i for i in bpy.context.tool_settings.mesh_select_mode] 237 | 238 | if len(list_0) != 2: 239 | self.report({'INFO'}, 'Two adjacent edges must be selected.') 240 | edit_mode_in() 241 | return {'CANCELLED'} 242 | else: 243 | if out == True: 244 | flip = False 245 | f_(me, list_0, adj, n, out, flip, e_mode, radius) 246 | 247 | edit_mode_in() 248 | bpy.ops.mesh.delete(type = 'VERT') 249 | return {'FINISHED'} 250 | 251 | # ------ operator 1 ------ 252 | class f_op1(bpy.types.Operator): 253 | bl_idname = 'f.op1_id' 254 | bl_label = '' 255 | 256 | def draw(self, context): 257 | layout = self.layout 258 | layout.label('To use:') 259 | layout.label('Select two adjacent edges and press Fillet button.') 260 | 261 | def execute(self, context): 262 | return {'FINISHED'} 263 | 264 | def invoke(self, context, event): 265 | return context.window_manager.invoke_popup(self, width = 400) 266 | 267 | # ------ operator 2 ------ 268 | class f_op2(bpy.types.Operator): 269 | bl_idname = 'f.op2_id' 270 | bl_label = '' 271 | 272 | def execute(self, context): 273 | bpy.ops.f.op1_id('INVOKE_DEFAULT') 274 | return {'FINISHED'} 275 | 276 | # ------ ------ 277 | class_list = [ f_op0, f_op1, f_op2, f_p0] 278 | 279 | # ------ register ------ 280 | def register(): 281 | for c in class_list: 282 | bpy.utils.register_class(c) 283 | 284 | # ------ unregister ------ 285 | def unregister(): 286 | for c in class_list: 287 | bpy.utils.unregister_class(c) 288 | 289 | # ------ ------ 290 | if __name__ == "__main__": 291 | register() -------------------------------------------------------------------------------- /addons/inset_outline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # ***** BEGIN GPL LICENSE BLOCK ***** 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software Foundation, 18 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | # ***** END GPL LICENCE BLOCK ***** 21 | 22 | # ------ ------ 23 | bl_info = { 24 | 'name': 'inset_edge_chain_loop', 25 | 'author': '', 26 | 'version': (0, 0, 6), 27 | 'blender': (2, 6, 1), 28 | 'api': 43085, 29 | 'location': 'View3D > Tool Shelf', 30 | 'description': '', 31 | 'warning': 'Beta', 32 | 'wiki_url': '', 33 | 'tracker_url': '', 34 | 'category': 'Mesh' } 35 | 36 | # ------ ------ 37 | import bpy 38 | from bpy.props import FloatProperty, BoolProperty 39 | from mathutils.geometry import normal 40 | from mathutils import Vector, Matrix 41 | from math import tan, pi, degrees 42 | 43 | # ------ ------ 44 | def edit_mode_out(): 45 | bpy.ops.object.mode_set(mode = 'OBJECT') 46 | 47 | def edit_mode_in(): 48 | bpy.ops.object.mode_set(mode = 'EDIT') 49 | 50 | def get_adj_v_(list_): 51 | tmp = {} 52 | for i in list_: 53 | try: tmp[i[0]].append(i[1]) 54 | except KeyError: tmp[i[0]] = [i[1]] 55 | try: tmp[i[1]].append(i[0]) 56 | except KeyError: tmp[i[1]] = [i[0]] 57 | return tmp 58 | 59 | def f_1(frst, list_, last): # edge chain 60 | fi = frst 61 | tmp = [frst] 62 | while list_ != []: 63 | for i in list_: 64 | if i[0] == fi: 65 | tmp.append(i[1]) 66 | fi = i[1] 67 | list_.remove(i) 68 | elif i[1] == fi: 69 | tmp.append(i[0]) 70 | fi = i[0] 71 | list_.remove(i) 72 | if tmp[-1] == last: 73 | break 74 | return tmp 75 | 76 | def f_2(frst, list_): # edge loop 77 | fi = frst 78 | tmp = [frst] 79 | while list_ != []: 80 | for i in list_: 81 | if i[0] == fi: 82 | tmp.append(i[1]) 83 | fi = i[1] 84 | list_.remove(i) 85 | elif i[1] == fi: 86 | tmp.append(i[0]) 87 | fi = i[0] 88 | list_.remove(i) 89 | if tmp[-1] == frst: 90 | break 91 | return tmp 92 | 93 | # ------ ------ ------ ------ 94 | def f_(me, list_0, dict_0, opp, list_fl, in_): 95 | 96 | if len(list_fl) == 0: # loop 97 | loop = True 98 | path = False 99 | frst = list_0[0][0] 100 | list_1 = f_2(frst, list_0) 101 | del list_1[-1] 102 | 103 | else: # path 104 | loop = False 105 | path = True 106 | frst = list_fl[0] 107 | last = list_fl[1] 108 | list_1 = f_1(frst, list_0, last) 109 | 110 | n = len(list_1) 111 | for ii in range(n): 112 | p_ = (me.vertices[list_1[ii]].co).copy() 113 | p1_ = (me.vertices[list_1[(ii - 1) % n]].co).copy() 114 | p2_ = (me.vertices[list_1[(ii + 1) % n]].co).copy() 115 | vec1_ = p_ - p1_ 116 | vec2_ = p_ - p2_ 117 | ang = vec1_.angle(vec2_, any) 118 | an = round(degrees(ang)) 119 | 120 | if ang == 0 or ang == 180: 121 | continue 122 | elif ang != 0 or ang != 180: 123 | vec_no = normal(p_, p1_, p2_) 124 | break 125 | 126 | list_2 = [] 127 | list_3 = [] 128 | 129 | for i in range(n): 130 | p = (me.vertices[list_1[i]].co).copy() 131 | p1 = (me.vertices[list_1[(i - 1) % n]].co).copy() 132 | p2 = (me.vertices[list_1[(i + 1) % n]].co).copy() 133 | 134 | me.vertices[list_1[i]].select = False 135 | 136 | if in_ == True: 137 | p3 = p - (vec_no * opp) 138 | else: 139 | p3 = p + (vec_no * opp) 140 | 141 | me.vertices.add(1) 142 | me.vertices[-1].co = p3 143 | 144 | list_2.append(list_1[i]) 145 | list_3.append(me.vertices[-1].index) 146 | 147 | n1 = len(list_2) 148 | if path == True: 149 | for j in range(n1 - 1): 150 | me.faces.add(1) 151 | me.faces[-1].vertices_raw = [ list_2[j], list_2[(j + 1) % n1], list_3[(j + 1) % n1], list_3[j] ] 152 | elif loop == True: 153 | for j in range(n1): 154 | me.faces.add(1) 155 | me.faces[-1].vertices_raw = [ list_2[j], list_2[(j + 1) % n1], list_3[(j + 1) % n1], list_3[j] ] 156 | 157 | # -- -- -- -- 158 | list_4 = [] 159 | n2 = len(list_2) 160 | for k in range(n2): 161 | 162 | q = (me.vertices[list_2[k]].co).copy() 163 | q1 = (me.vertices[list_2[(k - 1) % n2]].co).copy() 164 | q2 = (me.vertices[list_2[(k + 1) % n2]].co).copy() 165 | 166 | vec1 = q - q1 167 | vec2 = q - q2 168 | 169 | q3 = q - (vec1.normalized() * 0.1) 170 | q4 = q - (vec2.normalized() * 0.1) 171 | 172 | ang = vec1.angle(vec2, any) 173 | 174 | if path: 175 | if k == 0: 176 | axis = vec2 177 | elif k == n2 - 1: 178 | axis = -vec1 179 | else: 180 | axis = q3 - q4 181 | 182 | mtrx = Matrix.Rotation((pi * 0.5), 3, axis) 183 | q5 = (me.vertices[list_3[k]].co).copy() 184 | tmp = q5 - q 185 | tmp1 = mtrx * tmp 186 | tmp2 = tmp1 + q 187 | 188 | if k == 0: 189 | list_4.append(tmp2) 190 | elif k == n2 - 1: 191 | list_4.append(tmp2) 192 | else: 193 | vec3 = tmp2 - q 194 | adj = opp / tan(ang / 2) 195 | h = (adj ** 2 + opp ** 2) ** 0.5 196 | q6 = q + (vec3.normalized() * h) 197 | list_4.append(q6) 198 | 199 | elif loop: 200 | 201 | axis = q3 - q4 202 | ang = vec1.angle(vec2, any) 203 | 204 | mtrx = Matrix.Rotation((pi * 0.5), 3, axis) 205 | q5 = (me.vertices[list_3[k]].co).copy() 206 | tmp = q5 - q 207 | tmp1 = mtrx * tmp 208 | tmp2 = tmp1 + q 209 | 210 | vec3 = tmp2 - q 211 | 212 | # -- -- -- -- 213 | d_ = tan(ang / 2) 214 | if d_ == 0: 215 | pass 216 | elif d_ != 0: 217 | adj = opp / tan(ang / 2) 218 | h = (adj ** 2 + opp ** 2) ** 0.5 219 | q6 = q + (vec3.normalized() * h) 220 | me.vertices[list_3[k]].co = q6 221 | # -- -- -- -- 222 | 223 | if len(list_4) == 0: 224 | pass 225 | else: 226 | for kk in range(n2): 227 | me.vertices[list_3[kk]].co = list_4[kk] 228 | 229 | me.update(calc_edges = True) 230 | 231 | # ------ panel 0 ------ 232 | class io_p0(bpy.types.Panel): 233 | 234 | bl_space_type = 'VIEW_3D' 235 | bl_region_type = 'TOOLS' 236 | #bl_idname = 'io_p0_id' 237 | bl_label = 'Inset outline' 238 | bl_context = 'mesh_edit' 239 | 240 | def draw(self, context): 241 | layout = self.layout 242 | layout.operator('io.op0_id', text = 'Inset outline') 243 | 244 | # ------ operator 0 ------ 245 | class io_op0(bpy.types.Operator): 246 | 247 | bl_idname = 'io.op0_id' 248 | bl_label = 'Inset outline' 249 | bl_options = {'REGISTER', 'UNDO'} 250 | 251 | opp = FloatProperty( name = '', default = 0.06, min = -100.0, max = 100.0, step = 1, precision = 3 ) 252 | in_ = BoolProperty( name = 'in / out', default = False ) 253 | 254 | def draw(self, context): 255 | layout = self.layout 256 | layout.label('Inset amount:') 257 | layout.prop(self, 'opp') 258 | #layout.prop(self, 'in_') 259 | 260 | def execute(self, context): 261 | opp = self.opp 262 | in_ = self.in_ 263 | 264 | edit_mode_out() 265 | ob_act = context.active_object 266 | me = ob_act.data 267 | # -- -- -- -- 268 | 269 | list_0 = [list(e.key) for e in me.edges if e.select] 270 | dict_0 = get_adj_v_(list_0) 271 | 272 | list_tmp = [i for i in dict_0 if (len(dict_0[i]) > 2)] 273 | 274 | list_fl = [i for i in dict_0 if (len(dict_0[i]) == 1)] 275 | 276 | if len(list_0) == 0: # check for empty list 277 | self.report({'INFO'}, 'No outline selected') 278 | edit_mode_in() 279 | return 'CANCELLED' 280 | elif len(list_0) != 0: 281 | if len(list_tmp) != 0: # check if any of the vertices has more than two neighbors 282 | self.report({'INFO'}, 'Multiple edge chains not supported') 283 | edit_mode_in() 284 | return 'CANCELLED' 285 | elif len(list_tmp) == 0: 286 | if len(list_fl) > 2: 287 | self.report({'INFO'}, 'Select only one edge chain or edge loop') 288 | elif len(list_fl) <= 2: 289 | f_(me, list_0, dict_0, opp, list_fl, in_) 290 | # -- -- -- -- 291 | edit_mode_in() 292 | return {'FINISHED'} 293 | 294 | # ------ ------ 295 | class_list = [ io_op0,io_p0 ] 296 | 297 | # ------ register ------ 298 | def register(): 299 | for c in class_list: 300 | bpy.utils.register_class(c) 301 | 302 | # ------ unregister ------ 303 | def unregister(): 304 | for c in class_list: 305 | bpy.utils.unregister_class(c) 306 | 307 | # ------ ------ 308 | if __name__ == "__main__": 309 | register() -------------------------------------------------------------------------------- /addons/matlib/categories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /addons/matlib/materials.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/matlib/materials.blend -------------------------------------------------------------------------------- /addons/matlib/materials.blend1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/matlib/materials.blend1 -------------------------------------------------------------------------------- /addons/matlib/overwmat.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | mat = bpy.data.materials["Material"] 3 | mat.name = "tmp" 4 | mat.user_clear() 5 | with bpy.data.libraries.load("untitled.blend") as (data_from, data_to): 6 | data_to.materials = ["Material"] 7 | mat = bpy.data.materials["Material"] 8 | mat.use_fake_user=True 9 | bpy.ops.wm.save_mainfile(filepath="/home/carlinux/.config/blender/2.71/scripts/addons/matlib/materials.blend", check_existing=False, compress=True) 10 | -------------------------------------------------------------------------------- /addons/matlib/removemat.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | mat = bpy.data.materials["MatNode"] 3 | mat.use_fake_user=False 4 | mat.user_clear() 5 | bpy.ops.wm.save_mainfile(filepath="D:\\Blender258\\2.58\\scripts\\addons\\matlib\\materials.blend", check_existing=False, compress=True) 6 | -------------------------------------------------------------------------------- /addons/matlib/sendmat.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | with bpy.data.libraries.load("") as (data_from, data_to): 3 | data_to.materials = ["Test"] 4 | mat = bpy.data.materials["Test"] 5 | mat.use_fake_user=True 6 | bpy.ops.wm.save_mainfile(filepath="D:\\Blender Foundation\\2.62\\scripts\\addons\\matlib\\materials.blend", check_existing=False, compress=True) 7 | -------------------------------------------------------------------------------- /addons/mesh_edge_intersection_tools.py: -------------------------------------------------------------------------------- 1 | ''' 2 | BEGIN GPL LICENSE BLOCK 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | as published by the Free Software Foundation; either version 2 7 | of the License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software Foundation, 16 | Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 | 18 | END GPL LICENCE BLOCK 19 | ''' 20 | 21 | bl_info = { 22 | "name": "Edge tools : tinyCAD VTX", 23 | "author": "zeffii", 24 | "version": (0, 5, 1), 25 | "blender": (2, 56, 0), 26 | "category": "Mesh", 27 | "location": "View3D > EditMode > (w) Specials", 28 | "warning": "Still under development", 29 | "wiki_url": "http://wiki.blender.org/index.php/"\ 30 | "Extensions:2.6/Py/Scripts/Modeling/Edge_Slice", 31 | "tracker_url": "http://projects.blender.org/tracker/"\ 32 | "?func=detail&aid=25227" 33 | } 34 | 35 | """ 36 | parts based on Keith (Wahooney) Boshoff, cursor to intersection script and 37 | Paul Bourke's Shortest Line Between 2 lines, and thanks to PKHG from BA.org 38 | for attempting to explain things to me that i'm not familiar with. 39 | TODO: [ ] allow multi selection ( > 2 ) for Slice/Weld intersection mode 40 | TODO: [ ] streamline this code ! 41 | 42 | 1) Edge Extend To Edge ( T ) 43 | 2) Edge Slice Intersecting ( X ) 44 | 3) Edge Project Converging ( V ) 45 | 46 | """ 47 | 48 | import bpy 49 | import sys 50 | from mathutils import Vector, geometry 51 | from mathutils.geometry import intersect_line_line as LineIntersect 52 | 53 | VTX_PRECISION = 1.0e-5 # or 1.0e-6 ..if you need 54 | 55 | # returns distance between two given points 56 | def mDist(A, B): return (A-B).length 57 | 58 | 59 | # returns True / False if a point happens to lie on an edge 60 | def isPointOnEdge(point, A, B): 61 | eps = ((mDist(A, B) - mDist(point,B)) - mDist(A,point)) 62 | if abs(eps) < VTX_PRECISION: return True 63 | else: 64 | print('distance is ' + str(eps)) 65 | return False 66 | 67 | 68 | # returns the number of edges that a point lies on. 69 | def CountPointOnEdges(point, outer_points): 70 | count = 0 71 | if(isPointOnEdge(point, outer_points[0][0], outer_points[0][1])): count+=1 72 | if(isPointOnEdge(point, outer_points[1][0], outer_points[1][1])): count+=1 73 | return count 74 | 75 | 76 | # takes Vector List and returns tuple of points in expected order. 77 | def edges_to_points(edges): 78 | (vp1, vp2) = (Vector((edges[0][0])), Vector((edges[0][1]))) 79 | (vp3, vp4) = (Vector((edges[1][0])), Vector((edges[1][1]))) 80 | return (vp1,vp2,vp3,vp4) 81 | 82 | 83 | # takes a list of 4 vectors and returns True or False depending on checks 84 | def checkIsMatrixCoplanar(verti): 85 | (v1, v2, v3, v4) = edges_to_points(verti) #unpack 86 | shortest_line = LineIntersect(v1, v2, v3, v4) 87 | if mDist(shortest_line[1], shortest_line[0]) > VTX_PRECISION: return False 88 | else: return True 89 | 90 | 91 | # point = the halfway mark on the shortlest line between two lines 92 | def checkEdges(Edge, obj): 93 | (p1, p2, p3, p4) = edges_to_points(Edge) 94 | line = LineIntersect(p1, p2, p3, p4) 95 | point = ((line[0] + line[1]) / 2) # or point = line[0] 96 | return point 97 | 98 | # returns (object, number of verts, number of edges) && object mode == True 99 | def GetActiveObject(): 100 | bpy.ops.object.mode_set(mode='EDIT') 101 | bpy.ops.mesh.delete(type='EDGE') # removes edges + verts 102 | (vert_count, edge_count) = getVertEdgeCount() 103 | (vert_num, edge_num) = (len(vert_count),len(edge_count)) 104 | 105 | bpy.ops.object.mode_set(mode='OBJECT') # to be sure. 106 | o = bpy.context.active_object 107 | return (o, vert_num, edge_num) 108 | 109 | 110 | def AddVertsToObject(vert_count, o, mvX, mvA, mvB, mvC, mvD): 111 | o.data.vertices.add(5) 112 | pointlist = [mvX, mvA, mvB, mvC, mvD] 113 | for vpoint in range(len(pointlist)): 114 | o.data.vertices[vert_count+vpoint].co = pointlist[vpoint] 115 | 116 | 117 | # Used when the user chooses to slice/Weld, vX is intersection point 118 | def makeGeometryWeld(vX,outer_points): 119 | (o, vert_count, edge_count) = GetActiveObject() 120 | (vA, vB, vC, vD) = edges_to_points(outer_points) 121 | AddVertsToObject(vert_count, o, vA, vX, vB, vC, vD) # o is the object 122 | 123 | oe = o.data.edges 124 | oe.add(4) 125 | oe[edge_count].vertices = [vert_count,vert_count+1] 126 | oe[edge_count+1].vertices = [vert_count+2,vert_count+1] 127 | oe[edge_count+2].vertices = [vert_count+3,vert_count+1] 128 | oe[edge_count+3].vertices = [vert_count+4,vert_count+1] 129 | 130 | 131 | # Used for extending an edge to a point on another edge. 132 | def ExtendEdge(vX, outer_points, count): 133 | (o, vert_count, edge_count) = GetActiveObject() 134 | (vA, vB, vC, vD) = edges_to_points(outer_points) 135 | AddVertsToObject(vert_count, o, vX, vA, vB, vC, vD) 136 | 137 | oe = o.data.edges 138 | oe.add(4) 139 | # Candidate for serious optimization. 140 | if isPointOnEdge(vX, vA, vB): 141 | oe[edge_count].vertices = [vert_count, vert_count+1] 142 | oe[edge_count+1].vertices = [vert_count, vert_count+2] 143 | # find which of C and D is farthest away from X 144 | if mDist(vD, vX) > mDist(vC, vX): 145 | oe[edge_count+2].vertices = [vert_count, vert_count+3] 146 | oe[edge_count+3].vertices = [vert_count+3, vert_count+4] 147 | if mDist(vC, vX) > mDist(vD, vX): 148 | oe[edge_count+2].vertices = [vert_count, vert_count+4] 149 | oe[edge_count+3].vertices = [vert_count+3, vert_count+4] 150 | 151 | if isPointOnEdge(vX, vC, vD): 152 | oe[edge_count].vertices = [vert_count, vert_count+3] 153 | oe[edge_count+1].vertices = [vert_count, vert_count+4] 154 | # find which of A and B is farthest away from X 155 | if mDist(vB, vX) > mDist(vA, vX): 156 | oe[edge_count+2].vertices = [vert_count, vert_count+1] 157 | oe[edge_count+3].vertices = [vert_count+1, vert_count+2] 158 | if mDist(vA, vX) > mDist(vB, vX): 159 | oe[edge_count+2].vertices = [vert_count, vert_count+2] 160 | oe[edge_count+3].vertices = [vert_count+1, vert_count+2] 161 | 162 | 163 | # ProjectGeometry is used to extend two edges to their intersection point. 164 | def ProjectGeometry(vX, opoint): 165 | 166 | def return_distance_checked(X, A, B): 167 | dist1 = mDist(X, A) 168 | dist2 = mDist(X, B) 169 | point_choice = min(dist1, dist2) 170 | if point_choice == dist1: return A, B 171 | else: return B, A 172 | 173 | (o, vert_count, edge_count) = GetActiveObject() 174 | vA, vB = return_distance_checked(vX, Vector((opoint[0][0])), Vector((opoint[0][1]))) 175 | vC, vD = return_distance_checked(vX, Vector((opoint[1][0])), Vector((opoint[1][1]))) 176 | AddVertsToObject(vert_count, o, vX, vA, vB, vC, vD) 177 | 178 | oe = o.data.edges 179 | oe.add(4) 180 | oe[edge_count].vertices = [vert_count, vert_count+1] 181 | oe[edge_count+1].vertices = [vert_count, vert_count+3] 182 | oe[edge_count+2].vertices = [vert_count+1, vert_count+2] 183 | oe[edge_count+3].vertices = [vert_count+3, vert_count+4] 184 | 185 | 186 | def getMeshMatrix(obj): 187 | is_editmode = (obj.mode == 'EDIT') 188 | if is_editmode: 189 | bpy.ops.object.mode_set(mode='OBJECT') 190 | 191 | (edges, meshMatrix) = ([],[]) 192 | mesh = obj.data 193 | verts = mesh.vertices 194 | for e in mesh.edges: 195 | if e.select: 196 | edges.append(e) 197 | 198 | edgenum = 0 199 | for edge_to_test in edges: 200 | p1 = verts[edge_to_test.vertices[0]].co 201 | p2 = verts[edge_to_test.vertices[1]].co 202 | meshMatrix.append([Vector(p1),Vector(p2)]) 203 | edgenum += 1 204 | 205 | return meshMatrix 206 | 207 | 208 | def getVertEdgeCount(): 209 | bpy.ops.object.mode_set(mode='OBJECT') 210 | vert_count = bpy.context.active_object.data.vertices 211 | edge_count = bpy.context.active_object.data.edges 212 | return (vert_count, edge_count) 213 | 214 | 215 | def runCleanUp(): 216 | bpy.ops.object.mode_set(mode='EDIT') 217 | bpy.ops.mesh.select_all(action='TOGGLE') 218 | bpy.ops.mesh.select_all(action='TOGGLE') 219 | bpy.ops.mesh.remove_doubles(threshold=VTX_PRECISION) 220 | bpy.ops.mesh.select_all(action='TOGGLE') #unselect all 221 | 222 | 223 | def initScriptV(context, self): 224 | obj = bpy.context.active_object 225 | meshMatrix = getMeshMatrix(obj) 226 | (vert_count, edge_count) = getVertEdgeCount() 227 | 228 | #need 2 edges to be of any use. 229 | if len(meshMatrix) < 2: 230 | print(str(len(meshMatrix)) +" select, make sure (only) 2 are selected") 231 | return 232 | 233 | #dont go any further if the verts are not coplanar 234 | if checkIsMatrixCoplanar(meshMatrix): print("seems within tolerance, proceed") 235 | else: 236 | print("check your geometry, or decrease tolerance value") 237 | return 238 | 239 | # if we reach this point, the edges are coplanar 240 | # force edit mode 241 | bpy.ops.object.mode_set(mode='EDIT') 242 | vSel = bpy.context.active_object.data.total_vert_sel 243 | 244 | if checkEdges(meshMatrix, obj) == None: print("lines dont intersect") 245 | else: 246 | count = CountPointOnEdges(checkEdges(meshMatrix, obj), meshMatrix) 247 | if count == 0: 248 | ProjectGeometry(checkEdges(meshMatrix, obj), meshMatrix) 249 | runCleanUp() 250 | else: 251 | print("The intersection seems to lie on 1 or 2 edges already") 252 | 253 | 254 | def initScriptT(context, self): 255 | obj = bpy.context.active_object 256 | meshMatrix = getMeshMatrix(obj) 257 | ## force edit mode 258 | bpy.ops.object.mode_set(mode='EDIT') 259 | vSel = bpy.context.active_object.data.total_vert_sel 260 | 261 | if len(meshMatrix) != 2: 262 | print(str(len(meshMatrix)) +" select 2 edges") 263 | else: 264 | count = CountPointOnEdges(checkEdges(meshMatrix, obj), meshMatrix) 265 | if count == 1: 266 | print("Good, Intersection point lies on one of the two edges!") 267 | ExtendEdge(checkEdges(meshMatrix, obj), meshMatrix, count) 268 | runCleanUp() #neutral function, for removing potential doubles 269 | else: 270 | print("Intersection point not on chosen edges") 271 | 272 | 273 | def initScriptX(context, self): 274 | obj = bpy.context.active_object 275 | meshMatrix = getMeshMatrix(obj) 276 | ## force edit mode 277 | bpy.ops.object.mode_set(mode='EDIT') 278 | 279 | if len(meshMatrix) != 2: 280 | print(str(len(meshMatrix)) +" select, make sure (only) 2 are selected") 281 | else: 282 | if checkEdges(meshMatrix, obj) == None: 283 | print("lines dont intersect") 284 | else: 285 | count = CountPointOnEdges(checkEdges(meshMatrix, obj), meshMatrix) 286 | if count == 2: 287 | makeGeometryWeld(checkEdges(meshMatrix, obj), meshMatrix) 288 | runCleanUp() 289 | 290 | 291 | class EdgeIntersections(bpy.types.Operator): 292 | """Makes a weld/slice/extend to intersecting edges/lines""" 293 | bl_idname = 'mesh.intersections' 294 | bl_label = 'Edge tools : tinyCAD VTX' 295 | # bl_options = {'REGISTER', 'UNDO'} 296 | 297 | mode = bpy.props.IntProperty(name = "Mode", 298 | description = "switch between intersection modes", 299 | default = 2) 300 | 301 | @classmethod 302 | def poll(self, context): 303 | obj = context.active_object 304 | return obj != None and obj.type == 'MESH' 305 | 306 | def execute(self, context): 307 | if self.mode == -1: 308 | initScriptV(context, self) 309 | if self.mode == 0: 310 | initScriptT(context, self) 311 | if self.mode == 1: 312 | initScriptX(context, self) 313 | if self.mode == 2: 314 | print("something undefined happened, send me a test case!") 315 | return {'FINISHED'} 316 | 317 | 318 | def menu_func(self, context): 319 | self.layout.operator(EdgeIntersections.bl_idname, text="Edges V Intersection").mode = -1 320 | self.layout.operator(EdgeIntersections.bl_idname, text="Edges T Intersection").mode = 0 321 | self.layout.operator(EdgeIntersections.bl_idname, text="Edges X Intersection").mode = 1 322 | 323 | def register(): 324 | bpy.utils.register_class(EdgeIntersections) 325 | bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_func) 326 | 327 | def unregister(): 328 | bpy.utils.unregister_class(EdgeIntersections) 329 | bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func) 330 | 331 | if __name__ == "__main__": 332 | register() 333 | -------------------------------------------------------------------------------- /addons/mesh_offset_edges.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN GPL LICENSE BLOCK ***** 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ***** END GPL LICENCE BLOCK ***** 19 | 20 | # 21 | 22 | bl_info = { 23 | "name": "Offset Edges", 24 | "author": "Hidesato Ikeya", 25 | "version": (0, 1, 3), 26 | "blender": (2, 68, 0), 27 | "location": "VIEW3D > Edge menu(CTRL-E) > Offset Edges", 28 | "description": "Offset Edges", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Mesh"} 33 | 34 | import math 35 | from math import sin, pi 36 | import bpy 37 | import bmesh 38 | from mathutils import Vector 39 | 40 | Z_UP = Vector((.0, .0, 1.0)) 41 | ANGLE_90 = pi / 2 42 | ANGLE_180 = pi 43 | ANGLE_360 = 2 * pi 44 | 45 | 46 | class OffsetEdges(bpy.types.Operator): 47 | """Offset Edges.""" 48 | bl_idname = "mesh.offset_edges" 49 | bl_label = "Offset Edges" 50 | bl_options = {'REGISTER', 'UNDO'} 51 | 52 | width = bpy.props.FloatProperty( 53 | name="Width", default=.2, precision=3, step=0.05) 54 | geometry_mode = bpy.props.EnumProperty( 55 | items=[('offset', "Offset", "Offset edges"), 56 | ('extrude', "Extrude", "Extrude edges"), 57 | ('move', "Move", "Move selected edges")], 58 | name="Geometory mode", default='offset') 59 | follow_face = bpy.props.BoolProperty( 60 | name="Follow Face", default=False, 61 | description="Offset along faces around") 62 | flip = bpy.props.BoolProperty( 63 | name="Flip", default=False, 64 | description="Flip Direction") 65 | 66 | threshold = bpy.props.FloatProperty( 67 | name="Threshold", default=1.0e-4, step=1.0e-5, 68 | description="Angle threshold which determins folding edges", 69 | options={'HIDDEN'}) 70 | 71 | @classmethod 72 | def poll(self, context): 73 | return context.mode == 'EDIT_MESH' 74 | 75 | def draw(self, context): 76 | layout = self.layout 77 | layout.prop(self, 'geometry_mode', text="") 78 | 79 | layout.prop(self, 'width') 80 | layout.prop(self, 'flip') 81 | layout.prop(self, 'follow_face') 82 | 83 | def create_edgeloops(self, bm): 84 | selected_edges = [] 85 | for e in bm.edges: 86 | if e.select: 87 | co_faces_selected = 0 88 | for f in e.link_faces: 89 | if f.select: 90 | co_faces_selected += 1 91 | else: 92 | if co_faces_selected <= 1: 93 | selected_edges.append(e) 94 | 95 | if not selected_edges: 96 | self.report({'WARNING'}, 97 | "No edges selected.") 98 | return None 99 | 100 | selected_verts = set(v for e in selected_edges for v in e.verts) 101 | 102 | v_es_pairs = dict() 103 | end_verts = set() 104 | for v in selected_verts: 105 | selected_link_edges = \ 106 | tuple(e for e in v.link_edges if e in selected_edges) 107 | len_link = len(selected_link_edges) 108 | 109 | if len_link: 110 | if len_link == 2: 111 | v_es_pairs[v] = selected_link_edges 112 | elif len_link == 1: 113 | v_es_pairs[v] = selected_link_edges 114 | end_verts.add(v) 115 | elif len_link >= 3: 116 | self.report({'WARNING'}, 117 | "Select non-branching edge chains") 118 | return None 119 | 120 | edge_loops = selected_edges.copy() 121 | 122 | self.extended_verts = set() 123 | while end_verts: 124 | v_start = end_verts.pop() 125 | e_start = v_es_pairs[v_start][0] 126 | edge_chain = [(v_start, e_start)] 127 | v_current = e_start.other_vert(v_start) 128 | e_prev = e_start 129 | while v_current not in end_verts: 130 | for e in v_es_pairs[v_current]: 131 | if e != e_prev: 132 | edge_chain.append((v_current, e)) 133 | v_current = e.other_vert(v_current) 134 | e_prev = e 135 | break 136 | end_verts.remove(v_current) 137 | 138 | geom = bmesh.ops.extrude_vert_indiv(bm, verts=[v_start, v_current]) 139 | self.extended_verts.update(geom['verts']) 140 | edge_loops += geom['edges'] 141 | for ex_v in geom['verts']: 142 | link_edge = ex_v.link_edges[0] 143 | if link_edge.other_vert(ex_v) is edge_chain[0][0]: 144 | for v, e in edge_chain: 145 | if e.calc_length() != 0.0: 146 | ex_v.co += v.co - e.other_vert(v).co 147 | break 148 | else: 149 | for v, e in reversed(edge_chain): 150 | if e.calc_length() != 0.0: 151 | ex_v.co += e.other_vert(v).co - v.co 152 | break 153 | edge_loops.append(bm.edges.new(geom['verts'])) 154 | 155 | return edge_loops 156 | 157 | def create_geometry(self, bm, e_loops): 158 | geom_extruded = bmesh.ops.extrude_edge_only(bm, edges=e_loops)['geom'] 159 | 160 | self.offset_verts = offset_verts = \ 161 | [e for e in geom_extruded if isinstance(e, bmesh.types.BMVert)] 162 | self.offset_edges = offset_edges = \ 163 | [e for e in geom_extruded if isinstance(e, bmesh.types.BMEdge)] 164 | self.side_faces = side_faces = \ 165 | [f for f in geom_extruded if isinstance(f, bmesh.types.BMFace)] 166 | bmesh.ops.recalc_face_normals(bm, faces=side_faces) 167 | self.side_edges = side_edges = \ 168 | [e.link_loops[0].link_loop_next.edge for e in offset_edges] 169 | 170 | for f in side_faces: 171 | f.select = True 172 | 173 | extended_verts = self.extended_verts 174 | self.v_v_pairs = v_v_pairs = dict() # keys are offset verts, 175 | # values are original verts 176 | for e in side_edges: 177 | v1, v2 = e.verts 178 | if v1 in offset_verts: 179 | v_offset, v_orig = v1, v2 180 | else: 181 | v_offset, v_orig = v2, v1 182 | v_v_pairs[v_offset] = v_orig 183 | 184 | if v_orig in extended_verts: 185 | extended_verts.add(v_offset) 186 | 187 | self.faces = faces = bmesh.ops.edgeloop_fill( 188 | bm, edges=offset_edges, mat_nr=0, use_smooth=False)['faces'] 189 | 190 | self.l_fn_pairs = l_fn_pairs = dict() # loop - face normal pairs. 191 | for face in faces: 192 | face.loops.index_update() 193 | if face.normal.dot(v_v_pairs[face.verts[0]].normal) < .0: 194 | face.normal_flip() 195 | 196 | for fl in face.loops: 197 | edge = \ 198 | fl.link_loop_radial_next.link_loop_next.link_loop_next.edge 199 | 200 | co = 0 201 | normal = Vector((.0, .0, .0)) 202 | for f in edge.link_faces: 203 | if (f not in side_faces and not f.hide 204 | and f.normal.length): 205 | normal += f.normal 206 | co += 1 207 | if f.select: 208 | l_fn_pairs[fl] = f.normal.copy() 209 | break 210 | else: 211 | if co: 212 | normal.normalize() 213 | l_fn_pairs[fl] = normal 214 | # Be careful, if you flip face normal after 215 | # this line, l_fn_pairs won't work as you expect 216 | # because face.normal_flip() changes loop order in 217 | # the face. 218 | 219 | return faces 220 | 221 | def clean_geometry(self, bm): 222 | bm.normal_update() 223 | 224 | offset_verts = self.offset_verts 225 | offset_edges = self.offset_edges 226 | side_edges = self.side_edges 227 | side_faces = self.side_faces 228 | extended_verts = self.extended_verts 229 | v_v_pairs = self.v_v_pairs 230 | l_fn_pairs = self.l_fn_pairs 231 | 232 | # Align extruded face normals 233 | if self.geometry_mode == 'extrude': 234 | for f in self.faces: 235 | for l in f.loops: 236 | side_face = l.link_loop_radial_next.face 237 | direction = l_fn_pairs.get(l) 238 | if direction: 239 | if side_face.normal.dot(direction) < .0: 240 | side_face.normal_flip() 241 | elif side_face.normal.dot(Z_UP) < .0: 242 | side_face.normal_flip() 243 | 244 | bmesh.ops.delete(bm, geom=self.faces, context=1) 245 | 246 | 247 | if self.geometry_mode != 'extrude': 248 | if self.geometry_mode == 'offset': 249 | bmesh.ops.delete(bm, geom=side_edges+side_faces, context=1) 250 | elif self.geometry_mode == 'move': 251 | for v_target, v_orig in v_v_pairs.items(): 252 | v_orig.co = v_target.co 253 | bmesh.ops.delete( 254 | bm, geom=side_edges+side_faces+offset_edges+offset_verts, 255 | context=1) 256 | extended_verts -= set(offset_verts) 257 | 258 | extended = extended_verts.copy() 259 | for v in extended_verts: 260 | extended.update(v.link_edges) 261 | extended.update(v.link_faces) 262 | bmesh.ops.delete(bm, geom=list(extended), context=1) 263 | 264 | @staticmethod 265 | def skip_zero_width_edges(f_loop, normal=None, reverse=False): 266 | if normal: 267 | normal = normal.normalized() 268 | skip_co = 0 269 | length = f_loop.edge.calc_length() 270 | if length and normal: 271 | # length which is perpendicular to normal 272 | edge = f_loop.vert.co - f_loop.link_loop_next.vert.co 273 | edge -= edge.dot(normal) * normal 274 | length = edge.length 275 | 276 | while length == 0: 277 | f_loop = (f_loop.link_loop_next if not reverse 278 | else f_loop.link_loop_prev) 279 | skip_co += 1 280 | length = f_loop.edge.calc_length() 281 | if length and normal: 282 | edge = f_loop.vert.co - f_loop.link_loop_next.vert.co 283 | edge -= edge.dot(normal) * normal 284 | length = edge.length 285 | 286 | return f_loop, skip_co 287 | 288 | @staticmethod 289 | def get_vector(loop_act, loop_prev, 290 | f_normal_act=None, f_normal_prev=None, 291 | threshold=1.0e-4): 292 | flags = set() 293 | 294 | vec_edge_act = loop_act.link_loop_next.vert.co - loop_act.vert.co 295 | vec_edge_act.normalize() 296 | 297 | vec_edge_prev = loop_prev.vert.co - loop_prev.link_loop_next.vert.co 298 | vec_edge_prev.normalize() 299 | 300 | if f_normal_act and f_normal_prev: 301 | vec_normal = f_normal_act + f_normal_prev 302 | vec_normal.normalize() 303 | elif f_normal_act or f_normal_prev: 304 | vec_normal = (f_normal_act or f_normal_prev).normalized() 305 | else: 306 | vec_normal = loop_act.face.normal.copy() 307 | if vec_normal.length == .0: 308 | vec_normal = Vector((.0, .0, 1.0)) 309 | 310 | # 2d edge vectors are perpendicular to vec_normal 311 | vec_edge_act2d = \ 312 | vec_edge_act - vec_edge_act.dot(vec_normal) * vec_normal 313 | vec_edge_act2d.normalize() 314 | 315 | vec_edge_prev2d = \ 316 | vec_edge_prev - vec_edge_prev.dot(vec_normal) * vec_normal 317 | vec_edge_prev2d.normalize() 318 | 319 | vec_angle2d = vec_edge_act2d.angle(vec_edge_prev2d) 320 | if vec_angle2d < threshold: 321 | # FOLD edges 322 | flags.add('FOLD') 323 | vec_tangent = vec_edge_act2d 324 | vec_angle2d = ANGLE_360 325 | elif vec_angle2d > ANGLE_180 - threshold: 326 | # STRAIGHT edges 327 | flags.add('STRAIGHT') 328 | vec_tangent = vec_edge_act2d.cross(vec_normal) 329 | vec_angle2d = ANGLE_180 330 | else: 331 | direction = vec_edge_act2d.cross(vec_edge_prev2d).dot(vec_normal) 332 | if direction > .0: 333 | # CONVEX corner 334 | flags.add('CONVEX') 335 | vec_tangent = -(vec_edge_act2d + vec_edge_prev2d).normalized() 336 | vec_angle2d = vec_edge_act2d.angle(vec_edge_prev2d) 337 | else: 338 | # CONCAVE corner 339 | flags.add('CONCAVE') 340 | vec_tangent = (vec_edge_act2d + vec_edge_prev2d).normalized() 341 | vec_angle2d = ANGLE_360 - vec_edge_act2d.angle(vec_edge_prev2d) 342 | 343 | if vec_tangent.dot(vec_normal): 344 | # Make vec_tangent perpendicular to vec_normal 345 | vec_tangent -= vec_tangent.dot(vec_normal) * vec_normal 346 | vec_tangent.normalize() 347 | 348 | if f_normal_act and f_normal_prev: 349 | f_angle = f_normal_act.angle(f_normal_prev) 350 | if f_angle > threshold: 351 | f_tangent = f_normal_act.cross(f_normal_prev) 352 | f_tangent.normalize() 353 | #print("vec_tangent:", vec_normal, "\nf_tangent:", f_tangent) 354 | if vec_tangent.dot(f_tangent) < .0: 355 | f_tangent *= -1 356 | vec_tangent = f_tangent 357 | flags.add('FACE_CROSS_LINE') 358 | 359 | if 'FOLD' in flags: 360 | # This case occures when edges are folding 361 | factor_act = factor_prev = 0 362 | elif 'FACE_CROSS_LINE' in flags: 363 | angle_a = vec_tangent.angle(vec_edge_act2d) 364 | angle_p = vec_tangent.angle(vec_edge_prev2d) 365 | angle_sum = vec_angle2d + angle_a + angle_p 366 | if (angle_a < threshold or angle_p < threshold 367 | or angle_sum > ANGLE_360 + threshold): 368 | # For the case in which vec_tangent is not 369 | # between vec_edge_act2d and vec_edge_prev2d. 370 | vec_tangent = vec_edge_act + vec_edge_prev 371 | # Using original vector is more intuitive 372 | # than using 2d edges. 373 | vec_tangent.normalize() 374 | if 'CONVEX' in flags: 375 | vec_tangent *= -1 376 | factor_act = factor_prev = \ 377 | 1.0 / sin(vec_tangent.angle(vec_edge_act)) 378 | else: 379 | factor_act = 1. / sin(vec_tangent.angle(vec_edge_act)) 380 | factor_prev = 1. / sin(vec_tangent.angle(vec_edge_prev)) 381 | 382 | else: 383 | factor_act = 1. / sin(vec_tangent.angle(vec_edge_act)) 384 | factor_prev = 1. / sin(vec_tangent.angle(vec_edge_prev)) 385 | 386 | return vec_tangent, factor_act, factor_prev 387 | 388 | def execute(self, context): 389 | edit_object = context.edit_object 390 | me = edit_object.data 391 | #bm = bmesh.from_edit_mesh(me) # This method causes blender crash 392 | # if an error occured during script 393 | # execution. 394 | bpy.ops.object.editmode_toggle() 395 | bm = bmesh.new() 396 | bm.from_mesh(me) 397 | 398 | e_loops = self.create_edgeloops(bm) 399 | if e_loops is None: 400 | bm.free() 401 | bpy.ops.object.editmode_toggle() 402 | return {'CANCELLED'} 403 | 404 | fs = self.create_geometry(bm, e_loops) 405 | 406 | width = self.width if not self.flip else -self.width 407 | threshold = self.threshold 408 | if self.follow_face: 409 | l_fn_pairs = self.l_fn_pairs 410 | 411 | for f in fs: 412 | f_normal = f.normal.normalized() 413 | 414 | move_vectors = [] 415 | 416 | for f_loop in f.loops: 417 | if self.follow_face: 418 | act_loop, skip_next_co = \ 419 | self.skip_zero_width_edges(f_loop, reverse=False) 420 | 421 | prev_loop = f_loop.link_loop_prev 422 | prev_loop, skip_prev_co = \ 423 | self.skip_zero_width_edges(prev_loop, reverse=True) 424 | 425 | n1 = l_fn_pairs.get(act_loop) 426 | n2 = l_fn_pairs.get(prev_loop) 427 | vectors = self.get_vector( 428 | act_loop, prev_loop, n1, n2, threshold) 429 | else: 430 | act_loop, skip_next_co = \ 431 | self.skip_zero_width_edges(f_loop, f_normal, reverse=False) 432 | 433 | prev_loop = f_loop.link_loop_prev 434 | prev_loop, skip_prev_co = \ 435 | self.skip_zero_width_edges(prev_loop, f_normal, reverse=True) 436 | 437 | vectors = self.get_vector( 438 | act_loop, prev_loop, threshold=threshold) 439 | 440 | move_vectors.append(vectors) 441 | 442 | for f_loop, vecs in zip(f.loops, move_vectors): 443 | vec_tan, factor_act, factor_prev = vecs 444 | f_loop.vert.co += \ 445 | width * min(factor_act, factor_prev) * vec_tan 446 | 447 | self.clean_geometry(bm) 448 | 449 | #bmesh.update_edit_mesh(me) 450 | bm.to_mesh(me) 451 | bm.free() 452 | bpy.ops.object.editmode_toggle() 453 | return {'FINISHED'} 454 | 455 | def invoke(self, context, event): 456 | me = context.edit_object.data 457 | bpy.ops.object.editmode_toggle() 458 | for p in me.polygons: 459 | if p.select: 460 | self.follow_face = True 461 | break 462 | bpy.ops.object.editmode_toggle() 463 | 464 | return self.execute(context) 465 | 466 | 467 | def draw_item(self, context): 468 | self.layout.operator_context = 'INVOKE_DEFAULT' 469 | self.layout.operator_menu_enum('mesh.offset_edges', 'geometry_mode') 470 | 471 | 472 | def register(): 473 | bpy.utils.register_class(OffsetEdges) 474 | bpy.types.VIEW3D_MT_edit_mesh_edges.append(draw_item) 475 | #bpy.types.VIEW3D_PT_tools_meshedit.append(draw_item) 476 | 477 | 478 | def unregister(): 479 | bpy.utils.unregister_class(OffsetEdges) 480 | bpy.types.VIEW3D_MT_edit_mesh_edges.remove(draw_item) 481 | #bpy.types.VIEW3D_PT_tools_meshedit.remove(draw_item) 482 | 483 | 484 | if __name__ == '__main__': 485 | register() 486 | -------------------------------------------------------------------------------- /addons/modules/constants_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | ''' 21 | constants_utils.py 22 | 23 | Useful constants... 24 | 25 | 26 | 27 | ''' 28 | 29 | 30 | 31 | # Golden mean 32 | PHI_INV = 0.61803398874989484820 33 | PHI = 1.61803398874989484820 34 | PHI_SQR = 2.61803398874989484820 35 | -------------------------------------------------------------------------------- /addons/modules/cursor_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | ''' 21 | cursor_utils.py 22 | 23 | Helper methods for accessing the 3D cursor 24 | 25 | 26 | 27 | ''' 28 | 29 | 30 | import bpy 31 | 32 | 33 | class CursorAccess: 34 | 35 | @classmethod 36 | def findSpace(cls): 37 | area = None 38 | for area in bpy.data.window_managers[0].windows[0].screen.areas: 39 | if area.type == 'VIEW_3D': 40 | break 41 | if area.type != 'VIEW_3D': 42 | return None 43 | for space in area.spaces: 44 | if space.type == 'VIEW_3D': 45 | break 46 | if space.type != 'VIEW_3D': 47 | return None 48 | return space 49 | 50 | @classmethod 51 | def setCursor(cls,coordinates): 52 | spc = cls.findSpace() 53 | spc.cursor_location = coordinates 54 | 55 | @classmethod 56 | def getCursor(cls): 57 | spc = cls.findSpace() 58 | return spc.cursor_location 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /addons/modules/geometry_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | ''' 21 | geometry_utils.py 22 | 23 | 3d geometry calculations 24 | 25 | 26 | 27 | ''' 28 | 29 | 30 | from mathutils import Vector, Matrix 31 | from mathutils import geometry 32 | 33 | 34 | # 3D Geometry 35 | class G3: 36 | 37 | @classmethod 38 | def distanceP2P(cls, p1, p2): 39 | return (p1-p2).length 40 | 41 | @classmethod 42 | def closestP2L(cls, p, l1, l2): 43 | vA = p - l1 44 | vL = l2- l1 45 | vL.normalize() 46 | return vL * (vL.dot(vA)) + l1 47 | 48 | @classmethod 49 | def closestP2E(cls, p, e1, e2): 50 | q = G3.closestP2L(p, e1, e2) 51 | de = G3.distanceP2P(e1, e2) 52 | d1 = G3.distanceP2P(q, e1) 53 | d2 = G3.distanceP2P(q, e2) 54 | if d1>de and d1>d2: 55 | q = e2 56 | if d2>de and d2>d1: 57 | q = e1 58 | return q 59 | 60 | @classmethod 61 | def heightP2S(cls, p, sO, sN): 62 | return (p-sO).dot(sN) / sN.dot(sN) 63 | 64 | @classmethod 65 | def closestP2S(cls, p, sO, sN): 66 | k = - G3.heightP2S(p, sO, sN) 67 | q = p+sN*k 68 | return q 69 | 70 | @classmethod 71 | def closestP2F(cls, p, fv, sN): 72 | q = G3.closestP2S(p, fv[0], sN) 73 | #pi = MeshEditor.addVertex(p) 74 | #qi = MeshEditor.addVertex(q) 75 | #MeshEditor.addEdge(pi, qi) 76 | #print ([d0,d1,d2]) 77 | 78 | if len(fv)==3: 79 | h = G3.closestP2L(fv[0], fv[1], fv[2]) 80 | d = (fv[0]-h).dot(q-h) 81 | if d<=0: 82 | return G3.closestP2E(q, fv[1], fv[2]) 83 | h = G3.closestP2L(fv[1], fv[2], fv[0]) 84 | d = (fv[1]-h).dot(q-h) 85 | if d<=0: 86 | return G3.closestP2E(q, fv[2], fv[0]) 87 | h = G3.closestP2L(fv[2], fv[0], fv[1]) 88 | d = (fv[2]-h).dot(q-h) 89 | if d<=0: 90 | return G3.closestP2E(q, fv[0], fv[1]) 91 | return q 92 | if len(fv)==4: 93 | h = G3.closestP2L(fv[0], fv[1], fv[2]) 94 | d = (fv[0]-h).dot(q-h) 95 | if d<=0: 96 | return G3.closestP2E(q, fv[1], fv[2]) 97 | h = G3.closestP2L(fv[1], fv[2], fv[3]) 98 | d = (fv[1]-h).dot(q-h) 99 | if d<=0: 100 | return G3.closestP2E(q, fv[2], fv[3]) 101 | h = G3.closestP2L(fv[2], fv[3], fv[0]) 102 | d = (fv[2]-h).dot(q-h) 103 | if d<=0: 104 | return G3.closestP2E(q, fv[3], fv[0]) 105 | h = G3.closestP2L(fv[3], fv[0], fv[1]) 106 | d = (fv[3]-h).dot(q-h) 107 | if d<=0: 108 | return G3.closestP2E(q, fv[0], fv[1]) 109 | return q 110 | 111 | @classmethod 112 | def medianTriangle(cls, vv): 113 | m0 = (vv[1]+vv[2])/2 114 | m1 = (vv[0]+vv[2])/2 115 | m2 = (vv[0]+vv[1])/2 116 | return [m0, m1, m2] 117 | 118 | @classmethod 119 | def orthoCenter(cls, fv): 120 | try: 121 | h0 = G3.closestP2L(fv[0], fv[1], fv[2]) 122 | h1 = G3.closestP2L(fv[1], fv[0], fv[2]) 123 | #h2 = G3.closestP2L(fm[2], fm[0], fm[1]) 124 | return geometry.intersect_line_line (fv[0], h0, fv[1], h1)[0] 125 | except(RuntimeError, TypeError): 126 | return None 127 | 128 | # Poor mans approach of finding center of circle 129 | @classmethod 130 | def circumCenter(cls, fv): 131 | fm = G3.medianTriangle(fv) 132 | return G3.orthoCenter(fm) 133 | 134 | @classmethod 135 | def ThreePnormal(cls, fv): 136 | n = (fv[1]-fv[0]).cross(fv[2]-fv[0]) 137 | n.normalize() 138 | return n 139 | 140 | @classmethod 141 | def closestP2CylinderAxis(cls, p, fv): 142 | n = G3.ThreePnormal(fv) 143 | c = G3.circumCenter(fv) 144 | if(c==None): 145 | return None 146 | return G3.closestP2L(p, c, c+n) 147 | 148 | # Poor mans approach of finding center of sphere 149 | @classmethod 150 | def centerOfSphere(cls, fv): 151 | try: 152 | if len(fv)==3: 153 | return G3.circumCenter(fv) # Equator 154 | if len(fv)==4: 155 | fv3 = [fv[0],fv[1],fv[2]] 156 | c1 = G3.circumCenter(fv) 157 | n1 = G3.ThreePnormal(fv) 158 | fv3 = [fv[1],fv[2],fv[3]] 159 | c2 = G3.circumCenter(fv3) 160 | n2 = G3.ThreePnormal(fv3) 161 | d1 = c1+n1 162 | d2 = c2+n2 163 | return geometry.intersect_line_line (c1, d1, c2, d2)[0] 164 | except(RuntimeError, TypeError): 165 | return None 166 | 167 | @classmethod 168 | def closestP2Sphere(cls, p, fv): 169 | #print ("G3.closestP2Sphere") 170 | try: 171 | c = G3.centerOfSphere(fv) 172 | if c==None: 173 | return None 174 | pc = p-c 175 | if pc.length == 0: 176 | pc = pc + Vector((1,0,0)) 177 | else: 178 | pc.normalize() 179 | return c + (pc * G3.distanceP2P(c, fv[0])) 180 | except(RuntimeError, TypeError): 181 | return None 182 | 183 | @classmethod 184 | def closestP2Cylinder(cls, p, fv): 185 | #print ("G3.closestP2Sphere") 186 | c = G3.closestP2CylinderAxis(p, fv) 187 | if c==None: 188 | return None 189 | r = (fv[0] - G3.centerOfSphere(fv)).length 190 | pc = p-c 191 | if pc.length == 0: 192 | pc = pc + Vector((1,0,0)) 193 | else: 194 | pc.normalize() 195 | return c + (pc * r) 196 | 197 | #@classmethod 198 | #def closestP2Sphere4(cls, p, fv4): 199 | ##print ("G3.closestP2Sphere") 200 | #fv = [fv4[0],fv4[1],fv4[2]] 201 | #c1 = G3.circumCenter(fv) 202 | #n1 = G3.ThreePnormal(fv) 203 | #fv = [fv4[1],fv4[2],fv4[3]] 204 | #c2 = G3.circumCenter(fv) 205 | #n2 = G3.ThreePnormal(fv) 206 | #d1 = c1+n1 207 | #d2 = c2+n2 208 | #c = geometry.intersect_line_line (c1, d1, c2, d2)[0] 209 | #pc = p-c 210 | #if pc.length == 0: 211 | #pc = pc + Vector((1,0,0)) 212 | #else: 213 | #pc.normalize() 214 | #return c + (pc * G3.distanceP2P(c, fv[0])) 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /addons/modules/mesh_editor_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | ''' 21 | mesh_editor_utils.py 22 | 23 | Helper methods for modifying meshes... 24 | 25 | 26 | 27 | ''' 28 | 29 | 30 | import bpy 31 | 32 | 33 | 34 | # Modify mesh data 35 | class MeshEditor: 36 | 37 | @classmethod 38 | def addVertex(cls, v): 39 | toggle = False 40 | if bpy.context.mode == 'EDIT_MESH': 41 | toggle = True 42 | if toggle: 43 | bpy.ops.object.editmode_toggle() 44 | mesh = bpy.context.active_object.data 45 | mesh.add_geometry (1, 0, 0) 46 | vi = len (mesh.vertices) - 1 47 | mesh.vertices[vi].co = v 48 | mesh.update() 49 | if toggle: 50 | bpy.ops.object.editmode_toggle() 51 | return vi 52 | 53 | @classmethod 54 | def addEdge(cls, vi1, vi2): 55 | toggle = False 56 | if bpy.context.mode == 'EDIT_MESH': 57 | toggle = True 58 | if toggle: 59 | bpy.ops.object.editmode_toggle() 60 | mesh = bpy.context.active_object.data 61 | mesh.add_geometry (0, 1, 0) 62 | ei = len (mesh.edges) - 1 63 | e = mesh.edges[ei] 64 | e.vertices[0] = vi1; 65 | e.vertices[1] = vi2; 66 | mesh.update() 67 | if toggle: 68 | bpy.ops.object.editmode_toggle() 69 | return ei 70 | -------------------------------------------------------------------------------- /addons/modules/misc_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | ''' 21 | misc_util.py 22 | 23 | Miscellaneous helper methods. 24 | 25 | 26 | 27 | ''' 28 | 29 | 30 | 31 | import bpy 32 | from cursor_utils import * 33 | from mathutils import Vector, Matrix 34 | 35 | 36 | 37 | class BlenderFake: 38 | 39 | @classmethod 40 | def forceUpdate(cls): 41 | if bpy.context.mode == 'EDIT_MESH': 42 | bpy.ops.object.mode_set(mode='OBJECT') 43 | bpy.ops.object.mode_set(mode='EDIT') 44 | 45 | @classmethod 46 | def forceRedraw(cls): 47 | CursorAccess.setCursor(CursorAccess.getCursor()) 48 | 49 | 50 | 51 | # Converts 3D coordinates in a 3DRegion 52 | # into 2D screen coordinates for that region. 53 | # Borrowed from Buerbaum Martin (Pontiac) 54 | def region3d_get_2d_coordinates(context, loc_3d): 55 | # Get screen information 56 | mid_x = context.region.width / 2.0 57 | mid_y = context.region.height / 2.0 58 | width = context.region.width 59 | height = context.region.height 60 | 61 | # Get matrices 62 | view_mat = context.space_data.region_3d.perspective_matrix 63 | total_mat = view_mat 64 | 65 | # order is important 66 | vec = total_mat * Vector((loc_3d[0], loc_3d[1], loc_3d[2], 1.0)) 67 | 68 | # dehomogenise 69 | vec = Vector(( 70 | vec[0] / vec[3], 71 | vec[1] / vec[3], 72 | vec[2] / vec[3])) 73 | 74 | x = int(mid_x + vec[0] * width / 2.0) 75 | y = int(mid_y + vec[1] * height / 2.0) 76 | z = vec[2] 77 | 78 | return Vector((x, y, z)) 79 | -------------------------------------------------------------------------------- /addons/modules/ui_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ##### BEGIN GPL LICENSE BLOCK ##### 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ##### END GPL LICENSE BLOCK ##### 19 | 20 | ''' 21 | ui_utils.py 22 | 23 | Some UI utility functions 24 | 25 | 26 | 27 | ''' 28 | 29 | 30 | 31 | class GUI: 32 | 33 | @classmethod 34 | def drawIconButton(cls, enabled, layout, iconName, operator, frame=True): 35 | col = layout.column() 36 | col.enabled = enabled 37 | bt = col.operator(operator, 38 | text='', 39 | icon=iconName, 40 | emboss=frame) 41 | 42 | @classmethod 43 | def drawTextButton(cls, enabled, layout, text, operator, frame=True): 44 | col = layout.column() 45 | col.enabled = enabled 46 | bt = col.operator(operator, 47 | text=text, 48 | emboss=frame) 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /addons/multifillet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # ***** BEGIN GPL LICENSE BLOCK ***** 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software Foundation, 18 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | # ***** END GPL LICENCE BLOCK ***** 21 | 22 | # ------ ------ 23 | bl_info = { 24 | 'name': 'fillet_multiple', 25 | 'author': '', 26 | 'version': (0, 0, 1), 27 | 'blender': (2, 5, 8), 28 | 'api': 38887, 29 | 'location': '', 30 | 'description': '', 31 | 'warning': '', 32 | 'wiki_url': '', 33 | 'tracker_url': '', 34 | 'category': 'Mesh' } 35 | 36 | # ------ ------ 37 | import bpy 38 | from bpy.props import FloatProperty, IntProperty 39 | from mathutils import Vector, Matrix 40 | from math import cos, pi, degrees 41 | 42 | # ------ ------ 43 | def edit_mode_out(): # edit mode out 44 | bpy.ops.object.mode_set(mode = 'OBJECT') 45 | 46 | def edit_mode_in(): # edit mode in 47 | bpy.ops.object.mode_set(mode = 'EDIT') 48 | 49 | def get_mesh_data_(): 50 | edit_mode_out() 51 | ob_act = bpy.context.active_object 52 | me = ob_act.data 53 | edit_mode_in() 54 | return me 55 | 56 | def list_clear_(l): 57 | l[:] = [] 58 | return l 59 | 60 | # ------ ------ ------ ------ 61 | def get_adj_v_(list_): 62 | tmp = {} 63 | for i in list_: 64 | try: tmp[i[0]].append(i[1]) 65 | except KeyError: tmp[i[0]] = [i[1]] 66 | try: tmp[i[1]].append(i[0]) 67 | except KeyError: tmp[i[1]] = [i[0]] 68 | return tmp 69 | 70 | def f_1(frst, list_): # frst - firts vert in path, list_ - list of edges 71 | fi = frst 72 | tmp = [frst] 73 | while list_ != []: 74 | for i in list_: 75 | if i[0] == fi: 76 | tmp.append(i[1]) 77 | fi = i[1] 78 | list_.remove(i) 79 | elif i[1] == fi: 80 | tmp.append(i[0]) 81 | fi = i[0] 82 | list_.remove(i) 83 | return tmp 84 | 85 | # ------ ------ 86 | class fm_buf(): 87 | list_path = [] # path 88 | 89 | # ------ ------ ------ ------ 90 | def f_(me, adj, n): 91 | 92 | list_0 = fm_buf.list_path[:] 93 | #del fm_buf.list_path 94 | #list_clear_(fm_buf.list_path) 95 | 96 | dict_0 = get_adj_v_(list_0) 97 | list_fl = [i for i in dict_0 if (len(dict_0[i]) == 1)] # first and last vertex of path or nothing if loop 98 | 99 | if len(list_fl) == 0: 100 | loop = True 101 | path = False 102 | frst = list_0[0][0] 103 | list_1 = f_1(frst, list_0) # list of vertex indices in the same order as they are in the path 104 | del list_1[-1] # if loop remove last item from list_1 105 | else: 106 | loop = False 107 | path = True 108 | frst = list_fl[0] 109 | list_1 = f_1(frst, list_0) 110 | 111 | v_list = [v.index for v in me.vertices if v.select] 112 | list_2 = v_list[:] 113 | list_3 = [] 114 | list_4 = list_1[:] 115 | 116 | dict_2 = {} 117 | 118 | nn = len(list_1) 119 | for i in list_2: 120 | if i in list_1: 121 | 122 | j = list_1.index(i) # index of a item in list_1 123 | dict_2[list_1[j]] = [] 124 | p = (me.vertices[list_1[j]].co).copy() 125 | p1 = (me.vertices[list_1[(j - 1) % nn]].co).copy() 126 | p2 = (me.vertices[list_1[(j + 1) % nn]].co).copy() 127 | 128 | vec1 = p - p1 129 | vec2 = p - p2 130 | 131 | ang = vec1.angle(vec2, any) 132 | 133 | h = adj * (1 / cos(ang * 0.5)) 134 | 135 | p3 = p - (vec1.normalized() * adj) 136 | p4 = p - (vec2.normalized() * adj) 137 | rp = p - ((p - ((p3 + p4) * 0.5)).normalized() * h) 138 | 139 | vec3 = rp - p3 140 | vec4 = rp - p4 141 | 142 | axis = vec1.cross(vec2) 143 | 144 | rot_ang = vec3.angle(vec4) 145 | 146 | for o in range(n + 1): 147 | new_angle = rot_ang * o / n 148 | mtrx = Matrix.Rotation(new_angle, 3, axis) 149 | tmp = p4 - rp 150 | tmp1 = mtrx*tmp 151 | tmp2 = tmp1 + rp 152 | me.vertices.add(1) 153 | me.vertices[-1].co = tmp2 154 | me.vertices[-1].select = False 155 | list_3.append(me.vertices[-1].index) 156 | 157 | k = list_4.index(i) 158 | list_3.reverse() 159 | list_4[k:(k + 1)] = list_3 160 | list_clear_(list_3) 161 | 162 | n1 = len(list_4) 163 | for t in range(n1): 164 | a = list_4[t] 165 | b = list_4[(t + 1) % n1] 166 | me.edges.add(1) 167 | me.edges[-1].vertices = [a, b] 168 | 169 | 170 | 171 | # ------ panel 0 ------ 172 | class fm_p0(bpy.types.Panel): 173 | 174 | bl_space_type = 'VIEW_3D' 175 | bl_region_type = 'TOOLS' 176 | #bl_idname = 'fm_p0_id' 177 | bl_label = 'Fillet multiple' 178 | bl_context = 'mesh_edit' 179 | 180 | def draw(self, context): 181 | layout = self.layout 182 | row = layout.split(0.60) 183 | row.label('Store data :') 184 | row.operator('fm.op0_id', text = 'Path') 185 | layout.operator('fm.op1_id', text = 'Fillet multiple') 186 | 187 | # ------ operator 0 ------ 188 | class fm_op0(bpy.types.Operator): 189 | bl_idname = 'fm.op0_id' 190 | bl_label = '' 191 | 192 | def execute(self, context): 193 | me = get_mesh_data_() 194 | list_clear_(fm_buf.list_path) 195 | fm_buf.list_path = [ list(e.key) for e in me.edges if e.select ] 196 | bpy.ops.mesh.select_all(action = 'DESELECT') 197 | return {'FINISHED'} 198 | 199 | # ------ operator 1 ------ 200 | class fm_op1(bpy.types.Operator): 201 | 202 | bl_idname = 'fm.op1_id' 203 | bl_label = '' 204 | bl_options = {'REGISTER', 'UNDO'} 205 | 206 | adj = FloatProperty( default = 0.08, min = 0.00001, max = 100.0, step = 1, precision = 3 ) 207 | n = IntProperty( name = '', default = 4, min = 3, max = 100, step = 1 ) 208 | 209 | def execute(self, context): 210 | 211 | adj = self.adj 212 | n = self.n 213 | 214 | edit_mode_out() 215 | ob_act = context.active_object 216 | me = ob_act.data 217 | # -- -- -- -- 218 | 219 | f_(me, adj, n) 220 | 221 | # -- -- -- -- 222 | edit_mode_in() 223 | bpy.ops.mesh.delete(type = 'VERT') 224 | return {'FINISHED'} 225 | 226 | # ------ ------ 227 | class_list = [ fm_p0, fm_op0, fm_op1] 228 | 229 | # ------ register ------ 230 | def register(): 231 | for c in class_list: 232 | bpy.utils.register_class(c) 233 | 234 | # ------ unregister ------ 235 | def unregister(): 236 | for c in class_list: 237 | bpy.utils.unregister_class(c) 238 | 239 | # ------ ------ 240 | if __name__ == "__main__": 241 | register() -------------------------------------------------------------------------------- /addons/render_to_print.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | # 20 | 21 | bl_info = { 22 | "name": "Render to Print", 23 | "author": "Marco Crippa , Dealga McArdle", 24 | "version": (0, 2), 25 | "blender": (2, 58, 0), 26 | "location": "Render > Render to Print", 27 | "description": "Set the size of the render for a print", 28 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" 29 | "Scripts/Render/Render to Print", 30 | "tracker_url": "https://projects.blender.org/tracker/index.php?" 31 | "func=detail&aid=24219", 32 | "category": "Render"} 33 | 34 | 35 | import math 36 | import bpy 37 | from bpy.types import Panel, Operator, Scene, PropertyGroup 38 | from bpy.props import (IntProperty, 39 | FloatProperty, 40 | StringProperty, 41 | EnumProperty, 42 | PointerProperty, 43 | ) 44 | 45 | 46 | paper_presets = ( 47 | ("custom_1_1", "custom", ""), 48 | ("A0_84.1_118.9", "A0 (84.1x118.9 cm)", ""), 49 | ("A1_59.4_84.1", "A1 (59.4x84.1 cm)", ""), 50 | ("A2_42.0_59.4", "A2 (42.0x59.4 cm)", ""), 51 | ("A3_29.7_42.0", "A3 (29.7 42.0 cm)", ""), 52 | ("A4_21.0_29.7", "A4 (21.0x29.7 cm)", ""), 53 | ("A5_14.8_21.0", "A5 (14.8x21.0 cm)", ""), 54 | ("A6_10.5_14.8", "A6 (10.5x14.8 cm)", ""), 55 | ("A7_7.4_10.5", "A7 (7.4x10.5 cm)", ""), 56 | ("A8_5.2_7.4", "A8 (5.2x7.4 cm)", ""), 57 | ("A9_3.7_5.2", "A9 (3.7x5.2 cm)", ""), 58 | ("A10_2.6_3.7", "A10 (2.6x3.7 cm)", ""), 59 | 60 | ("B0_100.0_141.4", "B0 (100.0x141.4 cm)", ""), 61 | ("B1_70.7_100.0", "B1 (70.7x100.0 cm)", ""), 62 | ("B2_50.0_70.7", "B2 (50.0x70.7 cm)", ""), 63 | ("B3_35.3_50.0", "B3 (35.3x50.0 cm)", ""), 64 | ("B4_25.0_35.3", "B4 (25.0x35.3 cm)", ""), 65 | ("B5_17.6_25.0", "B5 (17.6x25.0 cm)", ""), 66 | ("B6_12.5_17.6", "B6 (12.5x17.6 cm)", ""), 67 | ("B7_8.8_12.5", "B7 (8.8x12.5 cm)", ""), 68 | ("B8_6.2_8.8", "B8 (6.2x8.8 cm)", ""), 69 | ("B9_4.4_6.2", "B9 (4.4x6.2 cm)", ""), 70 | ("B10_3.1_4.4", "B10 (3.1x4.4 cm)", ""), 71 | 72 | ("C0_91.7_129.7", "C0 (91.7x129.7 cm)", ""), 73 | ("C1_64.8_91.7", "C1 (64.8x91.7 cm)", ""), 74 | ("C2_45.8_64.8", "C2 (45.8x64.8 cm)", ""), 75 | ("C3_32.4_45.8", "C3 (32.4x45.8 cm)", ""), 76 | ("C4_22.9_32.4", "C4 (22.9x32.4 cm)", ""), 77 | ("C5_16.2_22.9", "C5 (16.2x22.9 cm)", ""), 78 | ("C6_11.4_16.2", "C6 (11.4x16.2 cm)", ""), 79 | ("C7_8.1_11.4", "C7 (8.1x11.4 cm)", ""), 80 | ("C8_5.7_8.1", "C8 (5.7x8.1 cm)", ""), 81 | ("C9_4.0_5.7", "C9 (4.0x5.7 cm)", ""), 82 | ("C10_2.8_4.0", "C10 (2.8x4.0 cm)", ""), 83 | 84 | ("Letter_21.6_27.9", "Letter (21.6x27.9 cm)", ""), 85 | ("Legal_21.6_35.6", "Legal (21.6x35.6 cm)", ""), 86 | ("Legal junior_20.3_12.7", "Legal junior (20.3x12.7 cm)", ""), 87 | ("Ledger_43.2_27.9", "Ledger (43.2x27.9 cm)", ""), 88 | ("Tabloid_27.9_43.2", "Tabloid (27.9x43.2 cm)", ""), 89 | 90 | ("ANSI C_43.2_55.9", "ANSI C (43.2x55.9 cm)", ""), 91 | ("ANSI D_55.9_86.4", "ANSI D (55.9x86.4 cm)", ""), 92 | ("ANSI E_86.4_111.8", "ANSI E (86.4x111.8 cm)", ""), 93 | 94 | ("Arch A_22.9_30.5", "Arch A (22.9x30.5 cm)", ""), 95 | ("Arch B_30.5_45.7", "Arch B (30.5x45.7 cm)", ""), 96 | ("Arch C_45.7_61.0", "Arch C (45.7x61.0 cm)", ""), 97 | ("Arch D_61.0_91.4", "Arch D (61.0x91.4 cm)", ""), 98 | ("Arch E_91.4_121.9", "Arch E (91.4x121.9 cm)", ""), 99 | ("Arch E1_76.2_106.7", "Arch E1 (76.2x106.7 cm)", ""), 100 | ("Arch E2_66.0_96.5", "Arch E2 (66.0x96.5 cm)", ""), 101 | ("Arch E3_68.6_99.1", "Arch E3 (68.6x99.1 cm)", ""), 102 | ) 103 | 104 | 105 | def paper_enum_parse(idname): 106 | tipo, dim_w, dim_h = idname.split("_") 107 | return tipo, float(dim_w), float(dim_h) 108 | 109 | 110 | paper_presets_data = {idname: paper_enum_parse(idname) 111 | for idname, name, descr in paper_presets} 112 | 113 | 114 | def update_settings_cb(self, context): 115 | # annoying workaround for recursive call 116 | if update_settings_cb.level is False: 117 | update_settings_cb.level = True 118 | pixels_from_print(self) 119 | update_settings_cb.level = False 120 | 121 | update_settings_cb.level = False 122 | 123 | 124 | class RenderPrintSertings(PropertyGroup): 125 | unit_from = EnumProperty( 126 | name="Set from", 127 | description="Set from", 128 | items=( 129 | ("CM_TO_PIXELS", "CM -> Pixel", "Centermeters to Pixels"), 130 | ("PIXELS_TO_CM", "Pixel -> CM", "Pixels to Centermeters") 131 | ), 132 | default="CM_TO_PIXELS", 133 | ) 134 | orientation = EnumProperty( 135 | name="Page Orientation", 136 | description="Set orientation", 137 | items=( 138 | ("Portrait", "Portrait", "Portrait"), 139 | ("Landscape", "Landscape", "Landscape") 140 | ), 141 | default="Portrait", 142 | update=update_settings_cb, 143 | ) 144 | preset = EnumProperty( 145 | name="Select Preset", 146 | description="Select from preset", 147 | items=paper_presets, 148 | default="custom_1_1", 149 | update=update_settings_cb, 150 | ) 151 | dpi = IntProperty( 152 | name="DPI", 153 | description="Dots per Inch", 154 | default=300, 155 | min=72, max=1800, 156 | update=update_settings_cb, 157 | ) 158 | width_cm = FloatProperty( 159 | name="Width", 160 | description="Width in CM", 161 | default=5.0, 162 | min=1.0, max=100000.0, 163 | update=update_settings_cb, 164 | ) 165 | height_cm = FloatProperty( 166 | name="Height", 167 | description="Height in CM", 168 | default=3.0, 169 | min=1.0, max=100000.0, 170 | update=update_settings_cb, 171 | ) 172 | width_px = IntProperty( 173 | name="Pixel Width", 174 | description="Pixel Width", 175 | default=900, 176 | min=4, max=10000, 177 | update=update_settings_cb, 178 | ) 179 | height_px = IntProperty( 180 | name="Pixel Height", 181 | description="Pixel Height", 182 | default=600, 183 | min=4, max=10000, 184 | update=update_settings_cb, 185 | ) 186 | 187 | 188 | def pixels_from_print(ps): 189 | tipo, dim_w, dim_h = paper_presets_data[ps.preset] 190 | 191 | if ps.unit_from == "CM_TO_PIXELS": 192 | if tipo == "custom": 193 | dim_w = ps.width_cm 194 | dim_h = ps.height_cm 195 | ps.width_cm = dim_w 196 | ps.height_cm = dim_h 197 | elif tipo != "custom" and ps.orientation == "Landscape": 198 | ps.width_cm = dim_h 199 | ps.height_cm = dim_w 200 | elif tipo != "custom" and ps.orientation == "Portrait": 201 | ps.width_cm = dim_w 202 | ps.height_cm = dim_h 203 | 204 | ps.width_px = math.ceil((ps.width_cm * ps.dpi) / 2.54) 205 | ps.height_px = math.ceil((ps.height_cm * ps.dpi) / 2.54) 206 | else: 207 | if tipo != "custom" and ps.orientation == "Landscape": 208 | ps.width_cm = dim_h 209 | ps.height_cm = dim_w 210 | ps.width_px = math.ceil((ps.width_cm * ps.dpi) / 2.54) 211 | ps.height_px = math.ceil((ps.height_cm * ps.dpi) / 2.54) 212 | elif tipo != "custom" and ps.orientation == "Portrait": 213 | ps.width_cm = dim_w 214 | ps.height_cm = dim_h 215 | ps.width_px = math.ceil((ps.width_cm * ps.dpi) / 2.54) 216 | ps.height_px = math.ceil((ps.height_cm * ps.dpi) / 2.54) 217 | 218 | ps.width_cm = (ps.width_px / ps.dpi) * 2.54 219 | ps.height_cm = (ps.height_px / ps.dpi) * 2.54 220 | 221 | 222 | class RENDER_PT_print(Panel): 223 | bl_label = "Render to Print" 224 | bl_space_type = 'PROPERTIES' 225 | bl_region_type = 'WINDOW' 226 | bl_context = 'render' 227 | 228 | def draw(self, context): 229 | 230 | layout = self.layout 231 | scene = context.scene 232 | ps = scene.print_settings 233 | 234 | row = layout.row(align=True) 235 | row1 = layout.row(align=True) 236 | row2 = layout.row(align=True) 237 | row3 = layout.row(align=True) 238 | row4 = layout.row(align=True) 239 | row5 = layout.row(align=True) 240 | row6 = layout.row(align=True) 241 | row7 = layout.row(align=True) 242 | col = layout.column(align=True) 243 | 244 | row.prop(ps, "unit_from") 245 | row1.prop(ps, "orientation") 246 | row2.prop(ps, "preset") 247 | 248 | col.separator() 249 | row3.prop(ps, "width_cm") 250 | row3.separator() 251 | row3.prop(ps, "height_cm") 252 | col.separator() 253 | row4.prop(ps, "dpi") 254 | col.separator() 255 | row5.prop(ps, "width_px") 256 | row5.separator() 257 | row5.prop(ps, "height_px") 258 | 259 | col.separator() 260 | row6.label("Inch Width: %.2f" % (ps.width_cm / 2.54)) 261 | row6.label("Inch Height: %.2f" % (ps.height_cm / 2.54)) 262 | col.separator() 263 | 264 | row7.operator("render.apply_size", icon="RENDER_STILL") 265 | 266 | # this if else deals with hiding UI elements when logic demands it. 267 | tipo = paper_presets_data[ps.preset][0] 268 | 269 | if tipo != "custom": 270 | row.active = False 271 | row.enabled = False 272 | 273 | if ps.unit_from == "CM_TO_PIXELS": 274 | row5.active = False 275 | row5.enabled = False 276 | 277 | if tipo == "custom": 278 | row3.active = True 279 | row3.enabled = True 280 | row1.active = False 281 | row1.enabled = False 282 | elif tipo != "custom" and ps.orientation == "Landscape": 283 | row3.active = False 284 | row3.enabled = False 285 | row1.active = True 286 | row1.enabled = True 287 | elif tipo != "custom" and ps.orientation == "Portrait": 288 | row3.active = False 289 | row3.enabled = False 290 | row1.active = True 291 | row1.enabled = True 292 | else: 293 | row3.active = False 294 | row3.enabled = False 295 | 296 | if tipo == "custom": 297 | row1.active = False 298 | row1.enabled = False 299 | elif tipo != "custom" and ps.orientation == "Landscape": 300 | row1.active = True 301 | row1.enabled = True 302 | row5.active = False 303 | row5.enabled = False 304 | elif tipo != "custom" and ps.orientation == "Portrait": 305 | row1.active = True 306 | row1.enabled = True 307 | row5.active = False 308 | row5.enabled = False 309 | 310 | 311 | class RENDER_OT_apply_size(Operator): 312 | bl_idname = "render.apply_size" 313 | bl_label = "Apply Print to Render" 314 | bl_description = "Set the render dimension" 315 | 316 | def execute(self, context): 317 | 318 | scene = context.scene 319 | ps = scene.print_settings 320 | 321 | pixels_from_print(ps) 322 | 323 | render = scene.render 324 | render.resolution_x = ps.width_px 325 | render.resolution_y = ps.height_px 326 | 327 | return {'FINISHED'} 328 | 329 | 330 | def register(): 331 | bpy.utils.register_class(RENDER_OT_apply_size) 332 | bpy.utils.register_class(RENDER_PT_print) 333 | bpy.utils.register_class(RenderPrintSertings) 334 | 335 | Scene.print_settings = PointerProperty(type=RenderPrintSertings) 336 | 337 | 338 | def unregister(): 339 | bpy.utils.unregister_class(RENDER_OT_apply_size) 340 | bpy.utils.unregister_class(RENDER_PT_print) 341 | bpy.utils.unregister_class(RenderPrintSertings) 342 | del Scene.print_settings 343 | 344 | 345 | if __name__ == "__main__": 346 | register() 347 | -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/README.txt: -------------------------------------------------------------------------------- 1 | This program, Suicidator City Generator (SCG) is copyright Arnaud Couturier. 2 | It allows you to quickly create 3d cities in the 3D software Blender. 3 | 4 | Official website: 5 | http://cgchan.com/suicidator 6 | 7 | Licence for the free version: 8 | You are free to use and redistribute Suicidator City Generator Free as you like, provided you keep it unaltered. 9 | You cannot resell it, claim it as your own, nor change this license. You are not allowed to reverse-engineer the code. 10 | 11 | Licence for the full (paid) version: 12 | The copy of Suicidator City Generator Full you purchase can be used by only you. 13 | You cannot modify it nor redistribute it. You are not allowed to reverse-engineer the code. 14 | 15 | //--------------------------------------------------------------------------------------------------------------------------------------------- 16 | //--------------------------------------------------------------------------------------------------------------------------------------------- 17 | //--------------------------------------------------------------------------------------------------------------------------------------------- 18 | 19 | ####### QUICK START 20 | In order to start using SCG, follow these 3 steps: 21 | 22 | 23 | ####### (1) Install the latest version of Blender 24 | Get it from http://www.blender.org/download/get-blender/ 25 | SCG will probably not run with previous versions of Blender. 26 | If SCG is not compatible with a particular version of Blender, you will see it in the list of addons, but you won't be able to activate it. 27 | Check the SCG website for SCG compatibility updates as new version of Blender and SCG come out. 28 | 29 | 30 | 31 | ####### (2) Install / upgrade Java 32 | SCG requires Java 5 minimum (sometimes also referred to as Java 1.5, JRE 5 or J2SE 1.5) 33 | The more recent your Java version, the faster SCG will run. 34 | Java 6 (which is the same as Java 1.6) and above is recommended. 35 | The procedure to get Java depends on your operating system 36 | 37 | - Windows: go to http://java.com, and download the latest version of Java. Restart your computer. 38 | - MacOS: use your update center. See http://gephi.org/users/install-java-6-mac-os-x-leopard/ 39 | - Linux: use your package manager (example apt-get or aptitude in Ubuntu) 40 | 41 | To check whether Java is correctly installed, open a terminal, then type "java -version". 42 | The terminal in Windows is located in "Start menu/programs/accessories/console or terminal" 43 | If you see the version of Java you have, you can continue to step 3 below. 44 | 45 | If you have a message saying the Java command is unknown, then Java is not correctly installed, and SCG won't work. 46 | Follow the steps below to fix it. 47 | The likely cause is your PATH environment variable is not updated. 48 | PATH is used by your system to know where it can find many programs, including Java. 49 | 50 | To update your PATH on Windows XP (should be somewhat identical on Vista/7), 51 | right clic on the "My Computer" desktop icon -> properties -> advanced tab -> environment variables (bottom). 52 | In "system variables", look for the PATH variable, select it, click "Edit". 53 | Now, be careful not to erase anything, or not to make typing mistakes, or you may seriously break your system! 54 | If you do a mistake, always clic "Cancel" to go back one step. 55 | At the end of the PATH value, add a semicolon (;) as a separator, then put the full path to your Java bin folder. 56 | The Java bin folder (bin for binary, where all the Java commands are) should be located in 57 | C:\Program Files\Java\bin 58 | C:\Program Files\Java\jre6\bin, or something similar. 59 | You know you've found it when you can see lots of .exe and .dll files, including "java.exe" in that folder. 60 | So you can search your computer for "java.exe" if you can't locate the Java bin folder. 61 | Once you've found the java bin folder, add its path (for example C:\Program Files\Java\jre6\bin) to the PATH variable, and validate your change. 62 | Restart your computer, and check "java -version" in a console again, and it should work. 63 | 64 | 65 | 66 | 67 | ####### (3) Install the addon in Blender 68 | You must copy the whole SCG folder in the Blender addons folder. 69 | The Blender addons folder should be "Blender install directory/blender version/scripts/addons" 70 | For example, mine on Windows XP is "C:\Program Files\Blender\2.62\scripts\addons" 71 | So in the end, you should have something like 72 | C:\Program Files\Blender\2.62\scripts\addons\suicidator_city_generator_0_5_1_Free 73 | 74 | At this stage, SCG will be seen by Blender, but not enabled by default. 75 | To enable SCG in Blender, open the Blender preferences (shortcut: CTRL+ALT+U) -> addons tab 76 | Type "suicidator" in the search box (top left), you should see SCG listed. 77 | Check its checkbox. 78 | 79 | It is possible to have multiple versions of SCG (free, full, 0.5, 0.6 ...) 80 | but only one version can be registered at a time in Blender. 81 | You must first disable the current SCG, then enable the other version of SCG you want. 82 | More information about Blender addons in general on the Blender wiki: http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Add-Ons 83 | 84 | Once the addon is registered, open the toolshelf (shortcut "t") in the 3D view. 85 | You should see the SCG panel at the bottom. 86 | 87 | To use SCG: 88 | 1) Set your city options 89 | 2) Launch the generation process 90 | 91 | You'll find all the SCG output textures in your Blender temporary folder ("temp"), usually at C:\tmp (windows) or /tmp (Mac, Linux). 92 | To know where it is located, see 93 | http://wiki.blender.org/index.php/Doc:2.6/Manual/Preferences/File 94 | 95 | 96 | 97 | 98 | This was just an introduction. Check the online manual for more info. 99 | 100 | //--------------------------------------------------------------------------------------------------------------------------------------------- 101 | //--------------------------------------------------------------------------------------------------------------------------------------------- 102 | //--------------------------------------------------------------------------------------------------------------------------------------------- 103 | 104 | CREDITS 105 | SCG would not be possible without the follwoing libraries (used legally) 106 | - JTS, by the great Martin Davis! 107 | - pyrolite, by Irmen de Jong 108 | - Apache commons-cli 109 | - pngEncoder, by ObjectPlanet 110 | 111 | 112 | 113 | Happy city building :) 114 | Arnaud Couturier (piiichan) 115 | couturier.arnaud@gmail.com 116 | -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/SCG.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/suicidator_city_generator_0_5_7_Free/SCG.jar -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Suicidator City Generator's Blender addon 3 | Official website: http://cgchan.com/suicidator 4 | Author: Arnaud Couturier (piiichan) couturier.arnaud@gmail.com 5 | Licence: you can modify and reditribute this file as you wish. 6 | """ 7 | 8 | import bpy 9 | import bmesh 10 | import subprocess 11 | import os 12 | import time 13 | import sys 14 | import imp 15 | import webbrowser 16 | import re 17 | import pickle 18 | import math 19 | import random 20 | 21 | 22 | SCG_JAR_FILE_PATH = os.path.join( 23 | os.path.dirname(__file__), 24 | 'SCG.jar') 25 | 26 | bl_info = { 27 | "name": "Suicidator City Generator", 28 | "author": "Arnaud Couturier (piiichan)", 29 | "version": (0,5,7), 30 | "blender": (2, 6, 3), 31 | "api": 45996, 32 | "location": "View3D > Tool Shelf > 3D Nav", 33 | "description": "Build large and detailed cities very easily. Buy the Pro version for even more details and options.", 34 | "category": "3D View"} 35 | 36 | 37 | #-------------------------------------------------------------------------------------------------------------------- 38 | 39 | # launches JAR 40 | # returns (stdout,stderr,javaIsRecognized) if possible, (None,None) otherwise 41 | # args = CLI array, only give VM and program arguments 42 | def execJAR(cliArg, Xms=64, Xmx=64): 43 | stdOut = stdErr = None 44 | javaIsRecognized = False 45 | try: 46 | command = ['java','-Xms'+str(Xms)+'m','-Xmx'+str(Xmx)+'m','-jar',SCG_JAR_FILE_PATH] 47 | command.extend(cliArg) 48 | javaProcess = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 49 | javaIsRecognized = True 50 | stdOut, stdErr = javaProcess.communicate() 51 | except Exception as e: 52 | print("Warning: \"java\" command unknown! ("+str(e)+") It the java bin folder in your PATH environment variable? Trying to execute JAR file without the java command as last chance...") 53 | try: 54 | javaProcess = subprocess.Popen('"'+SCG_JAR_FILE_PATH+'" '+''.join([" "+arg for arg in cliArg]), stdout=subprocess.PIPE, shell=True) 55 | stdOut, stdErr = javaProcess.communicate() 56 | except Exception as e: 57 | print("Cannot execute Java ("+str(e)+"). It is needed. Have you installed it?") 58 | return stdOut, stdErr, javaIsRecognized 59 | 60 | #-------------------------------------------------------------------------------------------------------------------- 61 | 62 | # check java 63 | JAVA_VERSION_STRING = None 64 | JAVA_VERSION = None 65 | try: 66 | javaOutput, javaError, javaIsRecognized = execJAR(['-showJavaVersion']) 67 | 68 | #JAVA_IS_DETECTED = len(exceptions) <= 0 69 | 70 | JAVA_VERSION_STRING = javaOutput.decode('UTF-8') 71 | JAVA_VERSION = JAVA_VERSION_STRING.split(".") 72 | JAVA_VERSION[0] = int(re.sub(r'[^0-9]','',JAVA_VERSION[0])) 73 | JAVA_VERSION[1] = int(re.sub(r'[^0-9]','',JAVA_VERSION[1])) 74 | JAVA_VERSION[2] = int(re.sub(r'[^0-9]','',JAVA_VERSION[2])) 75 | 76 | except Exception as e: 77 | print("Couldn't get java version from string '"+JAVA_VERSION_STRING+"': "+str(e)) 78 | 79 | 80 | meshOps = bpy.ops.mesh 81 | objectOps = bpy.ops.object 82 | sys.path.append(bpy.app.tempdir) 83 | 84 | 85 | #-------------------------------------------------------------------------------------------------------------------- 86 | 87 | def getOrCreateMesh(name): 88 | mesh = None 89 | try: 90 | mesh = bpy.data.meshes[name] 91 | except KeyError: 92 | mesh = bpy.data.meshes.new(name) 93 | return mesh 94 | 95 | #-------------------------------------------------------------------------------------------------------------------- 96 | 97 | # returns the new mesh assigned to the object 98 | def clearMeshOfObject(objectName): 99 | if objectName not in bpy.data.objects.keys(): 100 | return 101 | initialMode = bpy.context.mode 102 | set_mode('OBJECT') 103 | object = bpy.data.objects[objectName] 104 | 105 | # create a new mesh, rename and delete old one 106 | oldMesh = object.data 107 | objectMeshName = object.data.name 108 | object.data.name = object.data.name + '_old' 109 | newMesh = bpy.data.meshes.new(objectMeshName) 110 | object.data = newMesh 111 | try: 112 | bpy.data.meshes.remove(oldMesh) 113 | except Error: 114 | pass 115 | set_mode(initialMode) 116 | object.hide = object.hide_select = object.hide_render = False 117 | return newMesh 118 | 119 | #-------------------------------------------------------------------------------------------------------------------- 120 | 121 | def getOrCreateCurve(name): 122 | curve = None 123 | try: 124 | curve = bpy.data.curves[name] 125 | except: 126 | curve = bpy.data.curves.new(name, 'CURVE') 127 | return curve 128 | 129 | #-------------------------------------------------------------------------------------------------------------------- 130 | 131 | def getOrCreateObject(name, data, scale=1): 132 | obj = None 133 | try: 134 | obj = bpy.data.objects[name] 135 | obj.data = data 136 | except KeyError: 137 | obj = bpy.data.objects.new(name, data) 138 | bpy.context.scene.objects.link(obj) 139 | obj.scale = (scale, scale, scale) 140 | return obj 141 | 142 | #-------------------------------------------------------------------------------------------------------------------- 143 | 144 | def set_mode(mode): 145 | if bpy.context.mode != mode: 146 | bpy.ops.object.mode_set(mode=mode) 147 | 148 | #-------------------------------------------------------------------------------------------------------------------- 149 | 150 | def hide_SCG_objects(): 151 | for object in bpy.data.objects: 152 | if object.name.startswith('SCG_'): 153 | object.hide = object.hide_render = True 154 | #-------------------------------------------------------------------------------------------------------------------- 155 | 156 | def scale_SCG_objects_around_origin(scale): 157 | for object in bpy.data.objects: 158 | if object.parent is None and object.name.startswith('SCG_'): 159 | object.scale = scale, scale, scale 160 | object.location.x /= scale 161 | object.location.y /= scale 162 | object.location.z /= scale 163 | 164 | #-------------------------------------------------------------------------------------------------------------------- 165 | 166 | def delete_SCG_objects(): 167 | initialMode = bpy.context.mode 168 | set_mode('OBJECT') 169 | bpy.ops.object.select_all(action='DESELECT') 170 | for object in bpy.data.objects: 171 | if object.name.startswith('SCG_'): 172 | object.select = True 173 | if object.hide: 174 | object.hide = False 175 | bpy.ops.object.delete() 176 | set_mode(initialMode) 177 | 178 | #-------------------------------------------------------------------------------------------------------------------- 179 | 180 | def getOrCreateMaterial(name, specular_intensity=.05, specular_hardness=100): 181 | material = None 182 | try: 183 | material = bpy.data.materials[name] 184 | except KeyError: 185 | material = bpy.data.materials.new(name) 186 | material.specular_intensity = specular_intensity 187 | return material 188 | 189 | #-------------------------------------------------------------------------------------------------------------------- 190 | 191 | def set_faces_material_index(mesh, faceIndicesByMatName, matIndicesByMatName): 192 | for materialName, materialFaces in faceIndicesByMatName.items(): 193 | matIndex = matIndicesByMatName[materialName] 194 | for faceNb in materialFaces: 195 | mesh.tessfaces[faceNb].material_index = matIndex 196 | 197 | #-------------------------------------------------------------------------------------------------------------------- 198 | 199 | # returns material index 200 | def add_material_to_mesh(material, mesh): 201 | 202 | for i in range(len(mesh.materials)): 203 | if mesh.materials[i] == material: 204 | return i 205 | 206 | for i in range(len(mesh.materials)): 207 | if mesh.materials[i] == None: 208 | mesh.materials[i] = material 209 | return i 210 | 211 | mesh.materials.append(material) 212 | return len(mesh.materials)-1 213 | 214 | #-------------------------------------------------------------------------------------------------------------------- 215 | 216 | def clear_material_nodes(material): 217 | try: 218 | for node in material.node_tree.nodes: 219 | material.node_tree.nodes.remove(node) 220 | except: 221 | pass 222 | 223 | #-------------------------------------------------------------------------------------------------------------------- 224 | 225 | def clear_material_slots(material): 226 | if material is None: 227 | return 228 | for i in range(len(material.texture_slots)): 229 | material.texture_slots.clear(i) 230 | 231 | #-------------------------------------------------------------------------------------------------------------------- 232 | 233 | def getOrLoadImage(name, url, reload=False): 234 | img = None 235 | try: 236 | img = bpy.data.images[name] 237 | if reload: 238 | img.reload() 239 | except KeyError: 240 | try: 241 | img = bpy.data.images.load(filepath=url) 242 | except: 243 | pass 244 | return img 245 | 246 | #-------------------------------------------------------------------------------------------------------------------- 247 | 248 | # returns uv layer 249 | def get_or_create_uv_layer(uvLayerName, mesh): 250 | if uvLayerName in mesh.tessface_uv_textures: 251 | return mesh.tessface_uv_textures[uvLayerName] 252 | newUVLayerName = mesh.uv_textures.new().name 253 | uvLayer = mesh.tessface_uv_textures[newUVLayerName] 254 | uvLayer.name = uvLayerName 255 | return uvLayer 256 | 257 | #-------------------------------------------------------------------------------------------------------------------- 258 | 259 | # Method useful for backward compatibility because shader nodes' names changed with Blender 2.67 260 | # node_type: 261 | def add_material_node(target_node_tree, node_type): 262 | result_node = None 263 | alternative_node_type = None 264 | 265 | if node_type == "GEOMETRY": 266 | alternative_node_type = "ShaderNodeGeometry" 267 | elif node_type == "MATERIAL_EXT": 268 | alternative_node_type = "ShaderNodeExtendedMaterial" 269 | elif node_type == "MIX_RGB": 270 | alternative_node_type = "ShaderNodeMixRGB" 271 | if node_type == "MATERIAL": 272 | alternative_node_type = "ShaderNodeMaterial" 273 | elif node_type == "TEXTURE": 274 | alternative_node_type = "ShaderNodeTexture" 275 | elif node_type == "OUTPUT": 276 | alternative_node_type = "ShaderNodeOutput" 277 | 278 | try: 279 | result_node = target_node_tree.nodes.new(type = node_type) 280 | except RuntimeError: 281 | result_node = target_node_tree.nodes.new(type = alternative_node_type) 282 | return result_node 283 | 284 | #-------------------------------------------------------------------------------------------------------------------- 285 | 286 | def unwrap_mesh_from_data(mesh, uvLayer, facesMaterials, facesData): 287 | 288 | unwrappedFaces = facesData.keys() 289 | 290 | for faceNb, face in enumerate(mesh.polygons): 291 | 292 | if faceNb not in unwrappedFaces: 293 | continue 294 | 295 | faceData = facesData[faceNb] 296 | faceUVs = uvLayer.data[faceNb] 297 | 298 | faceUVs.uv1 = faceData[0], faceData[1] 299 | faceUVs.uv2 = faceData[2], faceData[3] 300 | faceUVs.uv3 = faceData[4], faceData[5] 301 | faceUVs.uv4 = faceData[6], faceData[7] 302 | 303 | #-------------------------------------------------------------------------------------------------------------------- 304 | 305 | def getOrCreateTexture(name, type): 306 | texture = None 307 | try: 308 | texture = bpy.data.textures[name] 309 | except KeyError: 310 | texture = bpy.data.textures.new(name=name, type=type) 311 | return texture 312 | 313 | #-------------------------------------------------------------------------------------------------------------------- 314 | 315 | def add_texture_to_material(texture, material): 316 | for textureSlot in material.texture_slots.values(): 317 | if textureSlot != None and textureSlot.texture == texture: 318 | return textureSlot 319 | newTextureSlot = material.texture_slots.add() 320 | newTextureSlot.texture = texture 321 | return newTextureSlot 322 | 323 | #-------------------------------------------------------------------------------------------------------------------- 324 | 325 | class SCG_show_manual_op(bpy.types.Operator): 326 | bl_idname = "object.scg_show_website" 327 | bl_description = "If you need help" 328 | bl_label = "" 329 | 330 | def execute(self, context): 331 | webbrowser.open_new_tab("http://arnaud.ile.nc/sce") 332 | return {'FINISHED'} 333 | 334 | #-------------------------------------------------------------------------------------------------------------------- 335 | 336 | class SCG_open_options_op(bpy.types.Operator): 337 | bl_idname = "object.scg_open_options" 338 | bl_description = "Set the generator options: city size, complexity, input and output" 339 | bl_label = "" 340 | 341 | def execute(self, context): 342 | javaOutput, javaError, javaIsRecognized = execJAR(['-showOptions']) 343 | return {'FINISHED'} 344 | 345 | #-------------------------------------------------------------------------------------------------------------------- 346 | 347 | class SCG_activate_glsl_op(bpy.types.Operator): 348 | bl_idname = "object.scg_activate_glsl" 349 | bl_description = "Activates GLSL shading, so you can preview your city materials. Good graphics card needed: ATI or NVIDIA" 350 | bl_label = "" 351 | 352 | def execute(self, context): 353 | bpy.context.scene.game_settings.material_mode = 'GLSL' 354 | bpy.context.space_data.viewport_shade = 'TEXTURED' 355 | return {'FINISHED'} 356 | 357 | #-------------------------------------------------------------------------------------------------------------------- 358 | 359 | class SCG_delete_objects_op(bpy.types.Operator): 360 | bl_idname = "object.scg_delete_objects" 361 | bl_description = "Remove all objects created by SCG. Useful if your scene gets cluttered by all the objects SCG creates" 362 | bl_label = "" 363 | 364 | def execute(self, context): 365 | delete_SCG_objects() 366 | return {'FINISHED'} 367 | 368 | #-------------------------------------------------------------------------------------------------------------------- 369 | 370 | class SCG_build_city_op(bpy.types.Operator): 371 | bl_idname = "object.scg_build_city" 372 | bl_description = "Build the city, according to your settings. May take some time. Experiment with small cities first." 373 | bl_label = "" 374 | 375 | def execute(self, context): 376 | print("SCG starts city generation...") 377 | startTime = time.time() 378 | 379 | # delete previous output 380 | try: 381 | os.remove(os.path.join(bpy.app.tempdir, 'SCG_output.py')) 382 | except Exception as e: 383 | print("Could not remove previous SCG output because: "+str(e)) 384 | 385 | 386 | javaOutput, javaError, javaIsRecognized = execJAR(['-generateCity', '-outputDir', '"'+bpy.app.tempdir+'"'], 64, bpy.context.scene.SCG_java_heap_max_size) 387 | print('java part done in '+str(time.time() - startTime)) 388 | print('java said: ') 389 | try: 390 | print(javaOutput.decode(encoding='UTF-8')) 391 | print(javaError.decode(encoding='UTF-8')) 392 | except Exception as e: 393 | pass 394 | 395 | hide_SCG_objects() 396 | 397 | 398 | file, pathname, description = imp.find_module('SCG_output', [bpy.app.tempdir]) 399 | SCG_output = imp.load_module('SCG_output', file, pathname, description) 400 | scale_SCG_objects_around_origin(.01) 401 | 402 | print("SCG made your city in "+str(time.time() - startTime)) 403 | return {'FINISHED'} 404 | 405 | #-------------------------------------------------------------------------------------------------------------------- 406 | 407 | class SCGPanel(bpy.types.Panel): 408 | bl_idname = "VIEW3D_PT_scg" 409 | bl_space_type = "VIEW_3D" 410 | bl_region_type = "TOOLS" 411 | bl_label = "Suicidator City Generator "+str(bl_info["version"]) 412 | 413 | def draw(self, context): 414 | layout = self.layout 415 | #view = context.space_data 416 | 417 | 418 | 419 | row = layout.row(align=True) 420 | #if not JAVA_IS_DETECTED: 421 | #row.label("Warning: Java can't be detected, SCG may not work", icon="ERROR") 422 | if not JAVA_VERSION or len(JAVA_VERSION) < 3: 423 | row.label("Java not found, SCG may not work", icon="ERROR") 424 | elif JAVA_VERSION[1] < 5: 425 | row.label("Error: Java is too old ("+str(JAVA_VERSION[1])+"."+str(JAVA_VERSION[2])+") SCG won't run", icon="ERROR") 426 | elif JAVA_VERSION[1] == 5: 427 | row.label("Java is ok but very old ("+str(JAVA_VERSION[1])+"."+str(JAVA_VERSION[2])+")", icon="FILE_TICK") 428 | elif JAVA_VERSION[1] == 6 and JAVA_VERSION[2] < 26: 429 | row.label("Java is ok but old ("+str(JAVA_VERSION[1])+"."+str(JAVA_VERSION[2])+")", icon="FILE_TICK") 430 | elif JAVA_VERSION[1] == 6 and JAVA_VERSION[2] >= 26: 431 | row.label("Java ok ("+str(JAVA_VERSION[1])+"."+str(JAVA_VERSION[2])+")", icon="FILE_TICK") 432 | elif JAVA_VERSION[1] >= 7: 433 | row.label("Java is optimal ("+str(JAVA_VERSION[1])+"."+str(JAVA_VERSION[2])+")", icon="FILE_TICK") 434 | else: 435 | row.label("Java OK "+str(JAVA_VERSION[1])) 436 | 437 | 438 | row = layout.row() 439 | row.prop(bpy.context.scene, "SCG_java_heap_max_size", slider=True) 440 | row = layout.row() 441 | row.operator("object.scg_show_website", text="Visit website") 442 | row = layout.row() 443 | row.operator("object.scg_delete_objects", text="Delete all SCG objects") 444 | 445 | row = layout.row() 446 | row.operator("object.scg_open_options", text="Set city options") 447 | row = layout.row() 448 | #row.alignment = 'LEFT' 449 | row.operator("object.scg_build_city", text="BUILD CITY", icon="OBJECT_DATAMODE") 450 | row = layout.row() 451 | row.operator("object.scg_activate_glsl", text="Preview city textures (GLSL)", icon="FACESEL_HLT") 452 | #row = layout.row() 453 | #row.label("Buy Pro for better cities") 454 | 455 | #-------------------------------------------------------------------------------------------------------------------- 456 | 457 | def register(): 458 | bpy.types.Scene.SCG_java_heap_max_size = bpy.props.IntProperty(default=256, name="Max potential RAM (MB)", description="Warning: increase ONLY if SCG needs more RAM and if your system has that much free memory. It's not even guaranteed values above 1200 will work.", min=128, max=4096) 459 | bpy.utils.register_module(__name__) 460 | 461 | def unregister(): 462 | del bpy.types.Scene.SCG_java_heap_max_size 463 | bpy.utils.unregister_module(__name__) 464 | 465 | if __name__ == "__main__": 466 | register() -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/lib/com.objectplanet.image.PngEncoder.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/suicidator_city_generator_0_5_7_Free/lib/com.objectplanet.image.PngEncoder.jar -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/lib/commons-cli-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/suicidator_city_generator_0_5_7_Free/lib/commons-cli-1.2.jar -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/lib/jts-1.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/suicidator_city_generator_0_5_7_Free/lib/jts-1.12.jar -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/lib/pyrolite.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/suicidator_city_generator_0_5_7_Free/lib/pyrolite.jar -------------------------------------------------------------------------------- /addons/suicidator_city_generator_0_5_7_Free/lib/swing-layout-1.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/suicidator_city_generator_0_5_7_Free/lib/swing-layout-1.0.4.jar -------------------------------------------------------------------------------- /addons/sun_position/WorldMap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/sun_position/WorldMap.jpg -------------------------------------------------------------------------------- /addons/sun_position/WorldMapLLR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/sun_position/WorldMapLLR.jpg -------------------------------------------------------------------------------- /addons/sun_position/WorldMapLR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccamara/blender-architecture-scripts/4e94644b6918ea810045eae8147c2d6a9d902da9/addons/sun_position/WorldMapLR.jpg -------------------------------------------------------------------------------- /addons/sun_position/__init__.py: -------------------------------------------------------------------------------- 1 | ### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | # -------------------------------------------------------------------------- 20 | # The sun positioning algorithms are based on the National Oceanic 21 | # and Atmospheric Administration's (NOAA) Solar Position Calculator 22 | # which rely on calculations of Jean Meeus' book "Astronomical Algorithms." 23 | # Use of NOAA data and products are in the public domain and may be used 24 | # freely by the public as outlined in their policies at 25 | # www.nws.noaa.gov/disclaimer.php 26 | # -------------------------------------------------------------------------- 27 | # The world map images have been composited from two NASA images. 28 | # NASA's image use policy can be found at: 29 | # http://www.nasa.gov/audience/formedia/features/MP_Photo_Guidelines.html 30 | # -------------------------------------------------------------------------- 31 | 32 | # 33 | 34 | bl_info = { 35 | "name": "Sun Position", 36 | "author": "Michael Martin", 37 | "version": (3, 0, 1), 38 | "blender": (2, 6, 5), 39 | "api": 53207, 40 | "location": "World > Sun Position", 41 | "description": "Show sun position with objects and/or sky texture", 42 | "warning": "", 43 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" \ 44 | "Scripts/3D_interaction/Sun_Position", 45 | "tracker_url": "https://projects.blender.org/tracker/" \ 46 | "index.php?func=detail&aid=29714", 47 | "category": "3D View"} 48 | 49 | import bpy 50 | from . properties import * 51 | from . ui_sun import * 52 | from . map import SunPos_Help 53 | from . hdr import SunPos_HdrHelp 54 | 55 | ############################################################################ 56 | 57 | 58 | def register(): 59 | bpy.utils.register_class(SunPosSettings) 60 | bpy.types.Scene.SunPos_property = ( 61 | bpy.props.PointerProperty(type=SunPosSettings, 62 | name="Sun Position", 63 | description="Sun Position Settings")) 64 | bpy.utils.register_class(SunPosPreferences) 65 | bpy.types.Scene.SunPos_pref_property = ( 66 | bpy.props.PointerProperty(type=SunPosPreferences, 67 | name="Sun Position Preferences", 68 | description="SP Preferences")) 69 | 70 | bpy.utils.register_class(SunPos_OT_Controller) 71 | bpy.utils.register_class(SunPos_OT_Preferences) 72 | bpy.utils.register_class(SunPos_OT_PreferencesDone) 73 | bpy.utils.register_class(SunPos_OT_DayRange) 74 | bpy.utils.register_class(SunPos_OT_SetObjectGroup) 75 | bpy.utils.register_class(SunPos_OT_ClearObjectGroup) 76 | bpy.utils.register_class(SunPos_OT_TimePlace) 77 | bpy.utils.register_class(SunPos_OT_Map) 78 | bpy.utils.register_class(SunPos_OT_Hdr) 79 | bpy.utils.register_class(SunPos_Panel) 80 | bpy.utils.register_class(SunPos_OT_MapChoice) 81 | bpy.utils.register_class(SunPos_Help) 82 | bpy.utils.register_class(SunPos_HdrHelp) 83 | 84 | 85 | def unregister(): 86 | bpy.utils.unregister_class(SunPos_HdrHelp) 87 | bpy.utils.unregister_class(SunPos_Help) 88 | bpy.utils.unregister_class(SunPos_OT_MapChoice) 89 | bpy.utils.unregister_class(SunPos_Panel) 90 | bpy.utils.unregister_class(SunPos_OT_Hdr) 91 | bpy.utils.unregister_class(SunPos_OT_Map) 92 | bpy.utils.unregister_class(SunPos_OT_TimePlace) 93 | bpy.utils.unregister_class(SunPos_OT_ClearObjectGroup) 94 | bpy.utils.unregister_class(SunPos_OT_SetObjectGroup) 95 | bpy.utils.unregister_class(SunPos_OT_DayRange) 96 | bpy.utils.unregister_class(SunPos_OT_PreferencesDone) 97 | bpy.utils.unregister_class(SunPos_OT_Preferences) 98 | bpy.utils.unregister_class(SunPos_OT_Controller) 99 | del bpy.types.Scene.SunPos_pref_property 100 | bpy.utils.unregister_class(SunPosPreferences) 101 | del bpy.types.Scene.SunPos_property 102 | bpy.utils.unregister_class(SunPosSettings) 103 | -------------------------------------------------------------------------------- /addons/sun_position/north.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bgl 3 | import math 4 | 5 | from . properties import Sun 6 | 7 | 8 | class NorthClass: 9 | 10 | def __init__(self): 11 | self.handler = None 12 | self.isActive = False 13 | 14 | def refresh_screen(self): 15 | bpy.context.scene.cursor_location.x += 0.0 16 | 17 | def activate(self, context): 18 | 19 | if context.area.type == 'PROPERTIES': 20 | self.handler = bpy.types.SpaceView3D.draw_handler_add( 21 | DrawNorth_callback, 22 | (self, context), 'WINDOW', 'POST_PIXEL') 23 | self.isActive = True 24 | self.refresh_screen() 25 | return True 26 | return False 27 | 28 | def deactivate(self): 29 | if self.handler is not None: 30 | bpy.types.SpaceView3D.draw_handler_remove(self.handler, 'WINDOW') 31 | self.handler = None 32 | self.isActive = False 33 | self.refresh_screen() 34 | 35 | Sun.SP.ShowNorth = False 36 | Sun.ShowNorth = False 37 | 38 | 39 | North = NorthClass() 40 | 41 | 42 | def DrawNorth_callback(self, context): 43 | 44 | if not Sun.SP.ShowNorth and North.isActive: 45 | North.deactivate() 46 | return 47 | 48 | # ------------------------------------------------------------------ 49 | # Set up the compass needle using the current north offset angle 50 | # less 90 degrees. This forces the unit circle to begin at the 51 | # 12 O'clock instead of 3 O'clock position. 52 | # ------------------------------------------------------------------ 53 | color = (0.2, 0.6, 1.0, 0.7) 54 | radius = 100 55 | angle = -(Sun.NorthOffset - math.pi / 2) 56 | x = math.cos(angle) * radius 57 | y = math.sin(angle) * radius 58 | 59 | p1, p2 = (0, 0, 0), (x, y, 0) # Start & end of needle 60 | 61 | #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 62 | # Thanks to Buerbaum Martin for the following which draws openGL 63 | # lines. ( From his script space_view3d_panel_measure.py ) 64 | #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 65 | # ------------------------------------------------------------------ 66 | # Convert the Perspective Matrix of the current view/region. 67 | # ------------------------------------------------------------------ 68 | view3d = bpy.context 69 | region = view3d.region_data 70 | perspMatrix = region.perspective_matrix 71 | tempMat = [perspMatrix[j][i] for i in range(4) for j in range(4)] 72 | perspBuff = bgl.Buffer(bgl.GL_FLOAT, 16, tempMat) 73 | 74 | # --------------------------------------------------------- 75 | # Store previous OpenGL settings. 76 | # --------------------------------------------------------- 77 | MatrixMode_prev = bgl.Buffer(bgl.GL_INT, [1]) 78 | bgl.glGetIntegerv(bgl.GL_MATRIX_MODE, MatrixMode_prev) 79 | MatrixMode_prev = MatrixMode_prev[0] 80 | 81 | # Store projection matrix 82 | ProjMatrix_prev = bgl.Buffer(bgl.GL_DOUBLE, [16]) 83 | bgl.glGetFloatv(bgl.GL_PROJECTION_MATRIX, ProjMatrix_prev) 84 | 85 | # Store Line width 86 | lineWidth_prev = bgl.Buffer(bgl.GL_FLOAT, [1]) 87 | bgl.glGetFloatv(bgl.GL_LINE_WIDTH, lineWidth_prev) 88 | lineWidth_prev = lineWidth_prev[0] 89 | 90 | # Store GL_BLEND 91 | blend_prev = bgl.Buffer(bgl.GL_BYTE, [1]) 92 | bgl.glGetFloatv(bgl.GL_BLEND, blend_prev) 93 | blend_prev = blend_prev[0] 94 | 95 | line_stipple_prev = bgl.Buffer(bgl.GL_BYTE, [1]) 96 | bgl.glGetFloatv(bgl.GL_LINE_STIPPLE, line_stipple_prev) 97 | line_stipple_prev = line_stipple_prev[0] 98 | 99 | # Store glColor4f 100 | color_prev = bgl.Buffer(bgl.GL_FLOAT, [4]) 101 | bgl.glGetFloatv(bgl.GL_COLOR, color_prev) 102 | 103 | # --------------------------------------------------------- 104 | # Prepare for 3D drawing 105 | # --------------------------------------------------------- 106 | bgl.glLoadIdentity() 107 | bgl.glMatrixMode(bgl.GL_PROJECTION) 108 | bgl.glLoadMatrixf(perspBuff) 109 | 110 | bgl.glEnable(bgl.GL_BLEND) 111 | bgl.glEnable(bgl.GL_LINE_STIPPLE) 112 | 113 | # ------------------ 114 | # and draw the line 115 | # ------------------ 116 | width = 2 117 | bgl.glLineWidth(width) 118 | bgl.glColor4f(color[0], color[1], color[2], color[3]) 119 | bgl.glBegin(bgl.GL_LINE_STRIP) 120 | bgl.glVertex3f(p1[0], p1[1], p1[2]) 121 | bgl.glVertex3f(p2[0], p2[1], p2[2]) 122 | bgl.glEnd() 123 | 124 | # --------------------------------------------------------- 125 | # Restore previous OpenGL settings 126 | # --------------------------------------------------------- 127 | bgl.glLoadIdentity() 128 | bgl.glMatrixMode(MatrixMode_prev) 129 | bgl.glLoadMatrixf(ProjMatrix_prev) 130 | bgl.glLineWidth(lineWidth_prev) 131 | 132 | if not blend_prev: 133 | bgl.glDisable(bgl.GL_BLEND) 134 | if not line_stipple_prev: 135 | bgl.glDisable(bgl.GL_LINE_STIPPLE) 136 | 137 | bgl.glColor4f(color_prev[0], 138 | color_prev[1], 139 | color_prev[2], 140 | color_prev[3]) 141 | -------------------------------------------------------------------------------- /addons/sun_position/operators.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import datetime 3 | 4 | from . properties import * 5 | from . sun_calc import Move_sun 6 | from . north import * 7 | from . map import Map 8 | from . hdr import Hdr 9 | 10 | # --------------------------------------------------------------------------- 11 | 12 | 13 | class ControlClass: 14 | 15 | region = None 16 | handler = None 17 | 18 | def callback(self, os, context): 19 | if Sun.SP.IsActive: 20 | if self.panel_changed(): 21 | Move_sun() 22 | else: 23 | self.remove_handler() 24 | 25 | def activate(self, context): 26 | if context.area.type == 'PROPERTIES': 27 | if Display.ENABLE: 28 | Display.setAction('PANEL') 29 | Sun.SP.IsActive = True 30 | self.region = context.region 31 | self.add_handler(context) 32 | return {'RUNNING_MODAL'} 33 | else: 34 | Display.setAction('ENABLE') 35 | Sun.SP.IsActive = False 36 | Map.deactivate() 37 | Hdr.deactivate() 38 | return {'FINISHED'} 39 | else: 40 | self.report({'WARNING'}, "Context not available") 41 | return {'CANCELLED'} 42 | 43 | def add_handler(self, context): 44 | self.handler = bpy.types.SpaceView3D.draw_handler_add(self.callback, 45 | (self, context), 'WINDOW', 'POST_PIXEL') 46 | 47 | def remove_handler(self): 48 | if self.handler: 49 | bpy.types.SpaceView3D.draw_handler_remove(self.handler, 'WINDOW') 50 | self.handler = None 51 | 52 | def panel_changed(self): 53 | rv = False 54 | sp = Sun.SP 55 | 56 | if not Sun.UseDayMonth and sp.Day_of_year != Sun.Day_of_year: 57 | dt = (datetime.date(sp.Year, 1, 1) + 58 | datetime.timedelta(sp.Day_of_year - 1)) 59 | Sun.Day = dt.day 60 | Sun.Month = dt.month 61 | Sun.Day_of_year = sp.Day_of_year 62 | sp.Day = dt.day 63 | sp.Month = dt.month 64 | rv = True 65 | elif (sp.Day != Sun.Day or 66 | sp.Month != Sun.Month): 67 | try: 68 | dt = datetime.date(sp.Year, sp.Month, sp.Day) 69 | sp.Day_of_year = dt.timetuple().tm_yday 70 | Sun.Day = sp.Day 71 | Sun.Month = sp.Month 72 | Sun.Day_of_year = sp.Day_of_year 73 | rv = True 74 | except: 75 | pass 76 | 77 | if Sun.PP.UsageMode == "HDR": 78 | if sp.BindToSun != Sun.BindToSun: 79 | Sun.BindToSun = sp.BindToSun 80 | if Sun.BindToSun: 81 | nt = bpy.context.scene.world.node_tree.nodes 82 | envTex = nt.get(sp.HDR_texture) 83 | if envTex: 84 | if envTex.type == "TEX_ENVIRONMENT": 85 | Sun.Bind.tex_location = envTex.texture_mapping.rotation 86 | Sun.Bind.azStart = sp.HDR_azimuth 87 | obj = bpy.context.scene.objects.get(Sun.SunObject) 88 | Sun.HDR_texture = sp.HDR_texture 89 | Sun.Elevation = sp.HDR_elevation 90 | Sun.Azimuth = sp.HDR_azimuth 91 | Sun.Bind.elevation = sp.HDR_elevation 92 | Sun.Bind.azimuth = sp.HDR_azimuth 93 | Sun.SunDistance = sp.SunDistance 94 | return True 95 | if (sp.HDR_elevation != Sun.Bind.elevation or 96 | sp.HDR_azimuth != Sun.Bind.azimuth or 97 | sp.SunDistance != Sun.SunDistance): 98 | Sun.Elevation = sp.HDR_elevation 99 | Sun.Azimuth = sp.HDR_azimuth 100 | Sun.Bind.elevation = sp.HDR_elevation 101 | Sun.Bind.azimuth = sp.HDR_azimuth 102 | Sun.SunDistance = sp.SunDistance 103 | return True 104 | return False 105 | 106 | if (rv or sp.Time != Sun.Time or 107 | sp.TimeSpread != Sun.TimeSpread or 108 | sp.SunDistance != Sun.SunDistance or 109 | sp.Latitude != Sun.Latitude or 110 | sp.Longitude != Sun.Longitude or 111 | sp.UTCzone != Sun.UTCzone or 112 | sp.Year != Sun.Year or 113 | sp.UseSkyTexture != Sun.UseSkyTexture or 114 | sp.SkyTexture != Sun.SkyTexture or 115 | sp.HDR_texture != Sun.HDR_texture or 116 | sp.UseSunObject != Sun.UseSunObject or 117 | sp.SunObject != Sun.SunObject or 118 | sp.UseObjectGroup != Sun.UseObjectGroup or 119 | sp.ObjectGroup != Sun.ObjectGroup or 120 | sp.DaylightSavings != Sun.DaylightSavings or 121 | sp.ShowRefraction != Sun.ShowRefraction or 122 | sp.ShowNorth != Sun.ShowNorth or 123 | sp.NorthOffset != Sun.NorthOffset): 124 | 125 | Sun.Time = sp.Time 126 | Sun.TimeSpread = sp.TimeSpread 127 | Sun.SunDistance = sp.SunDistance 128 | Sun.Latitude = sp.Latitude 129 | Sun.Longitude = sp.Longitude 130 | Sun.UTCzone = sp.UTCzone 131 | Sun.Year = sp.Year 132 | Sun.UseSkyTexture = sp.UseSkyTexture 133 | Sun.SkyTexture = sp.SkyTexture 134 | Sun.HDR_texture = sp.HDR_texture 135 | Sun.UseSunObject = sp.UseSunObject 136 | Sun.SunObject = sp.SunObject 137 | Sun.UseObjectGroup = sp.UseObjectGroup 138 | Sun.ObjectGroup = sp.ObjectGroup 139 | Sun.DaylightSavings = sp.DaylightSavings 140 | Sun.ShowRefraction = sp.ShowRefraction 141 | Sun.ShowNorth = sp.ShowNorth 142 | Sun.NorthOffset = sp.NorthOffset 143 | return True 144 | return False 145 | 146 | 147 | Controller = ControlClass() 148 | 149 | # --------------------------------------------------------------------------- 150 | 151 | 152 | class SunPos_OT_Controller(bpy.types.Operator): 153 | bl_idname = "world.sunpos_controller" 154 | bl_label = "Sun panel event handler" 155 | bl_description = "Enable sun panel" 156 | 157 | def __del__(self): 158 | Stop_all_handlers() 159 | Controller.remove_handler() 160 | Display.setAction('ENABLE') 161 | Sun.SP.IsActive = False 162 | 163 | def modal(self, context, event): 164 | 165 | if Display.PANEL: 166 | 167 | if Sun.SP.ShowMap: 168 | if not Map.isActive: 169 | if not Map.activate(context): 170 | Sun.SP.ShowMap = False 171 | elif Map.isActive: 172 | Map.deactivate() 173 | 174 | if Sun.SP.ShowHdr: 175 | if not Hdr.isActive: 176 | Sun.SP.BindToSun = False 177 | if not Hdr.activate(context): 178 | Sun.SP.ShowHdr = False 179 | elif Hdr.isActive: 180 | Hdr.deactivate() 181 | 182 | if Sun.SP.ShowNorth: 183 | if not North.isActive: 184 | North.activate(context) 185 | elif North.isActive: 186 | North.deactivate() 187 | 188 | return {'PASS_THROUGH'} 189 | 190 | Display.refresh() 191 | return {'FINISHED'} 192 | 193 | def invoke(self, context, event): 194 | 195 | Sun.verify_ObjectGroup() 196 | Map.init(Sun.PP.MapLocation) 197 | Hdr.init() 198 | retval = Controller.activate(context) 199 | if retval != {'RUNNING_MODAL'}: 200 | return retval 201 | 202 | context.window_manager.modal_handler_add(self) 203 | Sun.PreBlend_handler = SunPos_new_blendfile 204 | bpy.app.handlers.load_pre.append(SunPos_new_blendfile) 205 | Sun.Frame_handler = Frame_handler 206 | bpy.app.handlers.frame_change_pre.append(Frame_handler) 207 | 208 | Display.setAction('PANEL') 209 | Sun.SP.IsActive = True 210 | 211 | return {'RUNNING_MODAL'} 212 | 213 | ############################################################################ 214 | 215 | 216 | class SunPos_OT_Map(bpy.types.Operator): 217 | bl_idname = "sunpos.map" 218 | bl_label = "World map" 219 | 220 | def modal(self, context, event): 221 | if Map.view3d_area != context.area or not Sun.SP.ShowMap: 222 | Map.deactivate() 223 | Display.refresh() 224 | return {'FINISHED'} 225 | elif not Display.PANEL: 226 | Stop_all_handlers() 227 | return {'FINISHED'} 228 | return Map.event_controller(context, event) 229 | 230 | def invoke(self, context, event): 231 | context.window_manager.modal_handler_add(self) 232 | Display.refresh() 233 | return {'RUNNING_MODAL'} 234 | 235 | ############################################################################ 236 | 237 | 238 | class SunPos_OT_Hdr(bpy.types.Operator): 239 | bl_idname = "sunpos.hdr" 240 | bl_label = "HDR map" 241 | 242 | def modal(self, context, event): 243 | if Hdr.view3d_area != context.area or not Sun.SP.ShowHdr: 244 | Hdr.deactivate() 245 | Display.refresh() 246 | return {'FINISHED'} 247 | elif not Display.PANEL: 248 | Stop_all_handlers() 249 | return {'FINISHED'} 250 | return Hdr.event_controller(context, event) 251 | 252 | def invoke(self, context, event): 253 | context.window_manager.modal_handler_add(self) 254 | Display.refresh() 255 | return {'RUNNING_MODAL'} 256 | 257 | ############################################################################ 258 | 259 | 260 | def SunPos_new_blendfile(context): 261 | Stop_all_handlers() 262 | Cleanup_objects() 263 | 264 | 265 | def Cleanup_callback(self, context): 266 | Stop_all_handlers() 267 | Cleanup_objects() 268 | 269 | 270 | def Cleanup_objects(): 271 | try: 272 | Sun.SP.UseObjectGroup = False 273 | Sun.UseObjectGroup = False 274 | except: 275 | pass 276 | del Sun.Selected_objects[:] 277 | del Sun.Selected_names[:] 278 | Display.setAction('ENABLE') 279 | Sun.SP.IsActive = False 280 | 281 | 282 | def Stop_all_handlers(): 283 | North.deactivate() 284 | Map.deactivate() 285 | Hdr.deactivate() 286 | 287 | if Sun.Frame_handler is not None: 288 | try: 289 | bpy.app.handlers.frame_change_pre.remove(Frame_handler) 290 | except: 291 | pass 292 | Sun.Frame_handler = None 293 | 294 | if Sun.PreBlend_handler is not None: 295 | try: 296 | bpy.app.handlers.load_pre.remove(SunPos_new_blendfile) 297 | except: 298 | pass 299 | Sun.PreBlend_handler = None 300 | 301 | ############################################################################ 302 | # The Frame_handler is called while rendering when the scene changes 303 | # to make sure objects are updated according to any keyframes. 304 | ############################################################################ 305 | 306 | 307 | def Frame_handler(context): 308 | Controller.panel_changed() 309 | Move_sun() 310 | -------------------------------------------------------------------------------- /addons/sun_position/properties.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class DisplayAction: 5 | ENABLE = True 6 | PANEL = False 7 | PREFERENCES = False 8 | 9 | def __init__(self): 10 | self.invert_zoom_wheel = False 11 | self.invert_mouse_zoom = False 12 | 13 | def setAction(self, VAL): 14 | if VAL == 'ENABLE': 15 | self.ENABLE = True 16 | self.PANEL = self.PREFERENCES = False 17 | elif VAL == 'PANEL': 18 | self.PANEL = True 19 | self.ENABLE = self.PREFERENCES = False 20 | else: 21 | self.PREFERENCES = True 22 | self.ENABLE = self.PANEL = False 23 | 24 | def refresh(self): 25 | # Touching the cursor forces a screen refresh 26 | bpy.context.scene.cursor_location.x += 0.0 27 | 28 | 29 | Display = DisplayAction() 30 | Display.setAction('ENABLE') 31 | 32 | ############################################################################ 33 | # SunClass is used for storing and comparing changes in panel values 34 | # as well as general traffic direction. 35 | ############################################################################ 36 | 37 | 38 | class SunClass: 39 | 40 | class TazEl: 41 | time = 0.0 42 | azimuth = 0.0 43 | elevation = 0.0 44 | 45 | class CLAMP: 46 | tex_location = None 47 | elevation = 0.0 48 | azimuth = 0.0 49 | azStart = 0.0 50 | azDiff = 0.0 51 | 52 | Sunrise = TazEl() 53 | Sunset = TazEl() 54 | SolarNoon = TazEl() 55 | RiseSetOK = False 56 | 57 | Bind = CLAMP() 58 | BindToSun = False 59 | PlaceLabel = "Time & Place" 60 | PanelLabel = "Panel Location" 61 | MapName = "WorldMapLR.jpg" 62 | MapLocation = 'VIEWPORT' 63 | 64 | Latitude = 0.0 65 | Longitude = 0.0 66 | Elevation = 0.0 67 | Azimuth = 0.0 68 | AzNorth = 0.0 69 | Phi = 0.0 70 | Theta = 0.0 71 | LX = 0.0 72 | LY = 0.0 73 | LZ = 0.0 74 | 75 | Month = 0 76 | Day = 0 77 | Year = 0 78 | Day_of_year = 0 79 | Time = 0.0 80 | 81 | UTCzone = 0 82 | SunDistance = 0.0 83 | TimeSpread = 23.50 84 | UseDayMonth = True 85 | DaylightSavings = False 86 | ShowRiseSet = True 87 | ShowRefraction = True 88 | NorthOffset = 0.0 89 | 90 | UseSunObject = False 91 | SunObject = "Sun" 92 | 93 | UseSkyTexture = False 94 | SkyTexture = "Sky Texture" 95 | HDR_texture = "Environment Texture" 96 | 97 | UseObjectGroup = False 98 | ObjectGroup = 'ECLIPTIC' 99 | Selected_objects = [] 100 | Selected_names = [] 101 | ObjectGroup_verified = False 102 | 103 | PreBlend_handler = None 104 | Frame_handler = None 105 | SP = None 106 | PP = None 107 | Counter = 0 108 | 109 | # ---------------------------------------------------------------------- 110 | # There are times when the object group needs to be deleted such as 111 | # when opening a new file or when objects might be deleted that are 112 | # part of the group. If such is the case, delete the object group. 113 | # ---------------------------------------------------------------------- 114 | 115 | def verify_ObjectGroup(self): 116 | if not self.ObjectGroup_verified: 117 | if len(self.Selected_names) > 0: 118 | names = [x.name for x in bpy.data.objects] 119 | for x in self.Selected_names: 120 | if not x in names: 121 | del self.Selected_objects[:] 122 | del self.Selected_names[:] 123 | break 124 | self.ObjectGroup_verified = True 125 | 126 | Sun = SunClass() 127 | 128 | ############################################################################ 129 | # Sun panel properties 130 | ############################################################################ 131 | 132 | 133 | class SunPosSettings(bpy.types.PropertyGroup): 134 | 135 | from bpy.props import StringProperty, EnumProperty, \ 136 | IntProperty, FloatProperty 137 | 138 | IsActive = bpy.props.BoolProperty( 139 | description="True if panel enabled.", 140 | default=False) 141 | 142 | ShowMap = bpy.props.BoolProperty( 143 | description="Show world map.", 144 | default=False) 145 | 146 | DaylightSavings = bpy.props.BoolProperty( 147 | description="Daylight savings time adds 1 hour to standard time.", 148 | default=0) 149 | 150 | ShowRefraction = bpy.props.BoolProperty( 151 | description="Show apparent sun position due to refraction.", 152 | default=1) 153 | 154 | ShowNorth = bpy.props.BoolProperty( 155 | description="Draws line pointing north.", 156 | default=0) 157 | 158 | Latitude = FloatProperty( 159 | attr="", 160 | name="Latitude", 161 | description="Latitude: (+) Northern (-) Southern", 162 | soft_min=-90.000, soft_max=90.000, step=3.001, 163 | default=40.000, precision=3) 164 | 165 | Longitude = FloatProperty( 166 | attr="", 167 | name="Longitude", 168 | description="Longitude: (-) West of Greenwich (+) East of Greenwich", 169 | soft_min=-180.000, soft_max=180.000, 170 | step=3.001, default=1.000, precision=3) 171 | 172 | Month = IntProperty( 173 | attr="", 174 | name="Month", 175 | description="", 176 | min=1, max=12, default=6) 177 | 178 | Day = IntProperty( 179 | attr="", 180 | name="Day", 181 | description="", 182 | min=1, max=31, default=21) 183 | 184 | Year = IntProperty( 185 | attr="", 186 | name="Year", 187 | description="", 188 | min=1800, max=4000, default=2012) 189 | 190 | Day_of_year = IntProperty( 191 | attr="", 192 | name="Day of year", 193 | description="", 194 | min=1, max=366, default=1) 195 | 196 | UTCzone = IntProperty( 197 | attr="", 198 | name="UTC zone", 199 | description="Time zone: Difference from Greenwich England in hours.", 200 | min=0, max=12, default=0) 201 | 202 | Time = FloatProperty( 203 | attr="", 204 | name="Time", 205 | description="", 206 | precision=4, 207 | soft_min=0.00, soft_max=23.9999, step=1.00, default=12.00) 208 | 209 | NorthOffset = FloatProperty( 210 | attr="", 211 | name="", 212 | description="North offset in degrees or radians " 213 | "from scene's units settings", 214 | unit="ROTATION", 215 | soft_min=-3.14159265, soft_max=3.14159265, step=10.00, default=0.00) 216 | 217 | SunDistance = FloatProperty( 218 | attr="", 219 | name="Distance", 220 | description="Distance to sun from XYZ axes intersection.", 221 | unit="LENGTH", 222 | soft_min=1, soft_max=3000.00, step=10.00, default=50.00) 223 | 224 | UseSunObject = bpy.props.BoolProperty( 225 | description="Enable sun positioning of named lamp or mesh", 226 | default=False) 227 | 228 | SunObject = StringProperty( 229 | default="Sun", 230 | name="theSun", 231 | description="Name of sun object") 232 | 233 | UseSkyTexture = bpy.props.BoolProperty( 234 | description="Enable use of Cycles' " 235 | "sky texture. World nodes must be enabled, " 236 | "then set color to Sky Texture.", 237 | default=False) 238 | 239 | SkyTexture = StringProperty( 240 | default="Sky Texture", 241 | name="sunSky", 242 | description="Name of sky texture to be used") 243 | 244 | ShowHdr = bpy.props.BoolProperty( 245 | description="Click to set sun location on environment texture", 246 | default=False) 247 | 248 | HDR_texture = StringProperty( 249 | default="Environment Texture", 250 | name="envSky", 251 | description="Name of texture to use. World nodes must be enabled " 252 | "and color set to Environment Texture") 253 | 254 | HDR_azimuth = FloatProperty( 255 | attr="", 256 | name="Rotation", 257 | description="Rotation angle of sun and environment texture " 258 | "in degrees or radians from scene's units settings", 259 | unit="ROTATION", 260 | step=1.00, default=0.00, precision=3) 261 | 262 | HDR_elevation = FloatProperty( 263 | attr="", 264 | name="Elevation", 265 | description="Elevation angle of sun", 266 | step=3.001, 267 | default=0.000, precision=3) 268 | 269 | BindToSun = bpy.props.BoolProperty( 270 | description="If true, Environment texture moves with sun.", 271 | default=False) 272 | 273 | UseObjectGroup = bpy.props.BoolProperty( 274 | description="Allow a group of objects to be positioned.", 275 | default=False) 276 | 277 | TimeSpread = FloatProperty( 278 | attr="", 279 | name="Time Spread", 280 | description="Time period in which to spread object group", 281 | precision=4, 282 | soft_min=1.00, soft_max=24.00, step=1.00, default=23.00) 283 | 284 | ObjectGroup = EnumProperty( 285 | name="Display type", 286 | description="Show object group on ecliptic or as analemma", 287 | items=( 288 | ('ECLIPTIC', "On the Ecliptic", ""), 289 | ('ANALEMMA', "As and Analemma", ""), 290 | ), 291 | default='ECLIPTIC') 292 | 293 | Location = StringProperty( 294 | default="view3d", 295 | name="location", 296 | description="panel location") 297 | 298 | 299 | ############################################################################ 300 | # Preference panel properties 301 | ############################################################################ 302 | 303 | 304 | class SunPosPreferences(bpy.types.PropertyGroup): 305 | from bpy.props import StringProperty, EnumProperty 306 | 307 | UsageMode = EnumProperty( 308 | name="Usage mode", 309 | description="operate in normal mode or environment texture mode", 310 | items=( 311 | ('NORMAL', "Normal", ""), 312 | ('HDR', "Sun + HDR texture", ""), 313 | ), 314 | default='NORMAL') 315 | 316 | MapLocation = EnumProperty( 317 | name="Map location", 318 | description="Display map in viewport or world panel", 319 | items=( 320 | ('VIEWPORT', "Viewport", ""), 321 | ('PANEL', "Panel", ""), 322 | ), 323 | default='VIEWPORT') 324 | 325 | UseOneColumn = bpy.props.BoolProperty( 326 | description="Set panel to use one column.", 327 | default=False) 328 | 329 | UseTimePlace = bpy.props.BoolProperty( 330 | description="Show time/place presets.", 331 | default=False) 332 | 333 | UseObjectGroup = bpy.props.BoolProperty( 334 | description="Use object group option.", 335 | default=True) 336 | 337 | ShowDMS = bpy.props.BoolProperty( 338 | description="Show lat/long degrees,minutes,seconds labels.", 339 | default=True) 340 | 341 | ShowNorth = bpy.props.BoolProperty( 342 | description="Show north offset choice and slider.", 343 | default=True) 344 | 345 | ShowRefraction = bpy.props.BoolProperty( 346 | description="Show sun refraction choice.", 347 | default=True) 348 | 349 | ShowAzEl = bpy.props.BoolProperty( 350 | description="Show azimuth and solar elevation info.", 351 | default=True) 352 | 353 | ShowDST = bpy.props.BoolProperty( 354 | description="Show daylight savings time choice.", 355 | default=True) 356 | 357 | ShowRiseSet = bpy.props.BoolProperty( 358 | description="Show sunrise and sunset.", 359 | default=True) 360 | 361 | MapName = StringProperty( 362 | default="WorldMap.jpg", 363 | name="WorldMap", 364 | description="Name of world map") 365 | -------------------------------------------------------------------------------- /addons/sun_position/sun_calc.py: -------------------------------------------------------------------------------- 1 | from mathutils import * 2 | import math 3 | import datetime 4 | 5 | from . properties import * 6 | 7 | Degrees = "\xb0" 8 | 9 | 10 | def format_time(theTime, UTCzone, daylightSavings, longitude): 11 | hh = str(int(theTime)) 12 | min = (theTime - int(theTime)) * 60 13 | sec = int((min - int(min)) * 60) 14 | mm = "0" + str(int(min)) if min < 10 else str(int(min)) 15 | ss = "0" + str(sec) if sec < 10 else str(sec) 16 | 17 | zone = UTCzone 18 | if(longitude < 0): 19 | zone *= -1 20 | if daylightSavings: 21 | zone += 1 22 | gt = int(theTime) - zone 23 | 24 | if gt < 0: 25 | gt = 24 + gt 26 | elif gt > 23: 27 | gt = gt - 24 28 | gt = str(gt) 29 | 30 | return ("Local: " + hh + ":" + mm + ":" + ss, 31 | "UTC: " + gt + ":" + mm + ":" + ss) 32 | 33 | 34 | def format_hms(theTime): 35 | hh = str(int(theTime)) 36 | min = (theTime - int(theTime)) * 60 37 | sec = int((min - int(min)) * 60) 38 | mm = "0" + str(int(min)) if min < 10 else str(int(min)) 39 | ss = "0" + str(sec) if sec < 10 else str(sec) 40 | 41 | return (hh + ":" + mm + ":" + ss) 42 | 43 | 44 | def format_lat_long(latLong, isLatitude): 45 | hh = str(abs(int(latLong))) 46 | min = abs((latLong - int(latLong)) * 60) 47 | sec = abs(int((min - int(min)) * 60)) 48 | mm = "0" + str(int(min)) if min < 10 else str(int(min)) 49 | ss = "0" + str(sec) if sec < 10 else str(sec) 50 | if latLong == 0: 51 | coordTag = " " 52 | else: 53 | if isLatitude: 54 | coordTag = " N" if latLong > 0 else " S" 55 | else: 56 | coordTag = " E" if latLong > 0 else " W" 57 | 58 | return hh + Degrees + " " + mm + "' " + ss + '"' + coordTag 59 | 60 | ############################################################################ 61 | # 62 | # PlaceSun() will cycle through all the selected objects of type LAMP or 63 | # MESH and call setSunPosition to place them in the sky. 64 | # 65 | ############################################################################ 66 | 67 | 68 | def Move_sun(): 69 | if Sun.PP.UsageMode == "HDR": 70 | Sun.Theta = math.pi / 2 - degToRad(Sun.Elevation) 71 | Sun.Phi = -Sun.Azimuth 72 | 73 | locX = math.sin(Sun.Phi) * math.sin(-Sun.Theta) * Sun.SunDistance 74 | locY = math.sin(Sun.Theta) * math.cos(Sun.Phi) * Sun.SunDistance 75 | locZ = math.cos(Sun.Theta) * Sun.SunDistance 76 | 77 | try: 78 | nt = bpy.context.scene.world.node_tree.nodes 79 | envTex = nt.get(Sun.HDR_texture) 80 | if Sun.Bind.azDiff and envTex.texture_mapping.rotation.z == 0.0: 81 | envTex.texture_mapping.rotation.z = Sun.Bind.azDiff 82 | 83 | if envTex and Sun.BindToSun: 84 | az = Sun.Azimuth 85 | if Sun.Bind.azStart < az: 86 | taz = az - Sun.Bind.azStart 87 | else: 88 | taz = -(Sun.Bind.azStart - az) 89 | envTex.texture_mapping.rotation.z += taz 90 | Sun.Bind.azStart = az 91 | 92 | obj = bpy.context.scene.objects.get(Sun.SunObject) 93 | 94 | try: 95 | obj.location = locX, locY, locZ 96 | except: 97 | pass 98 | 99 | if obj.type == 'LAMP': 100 | obj.rotation_euler = ( 101 | (math.radians(Sun.Elevation - 90), 0, -Sun.Azimuth)) 102 | except: 103 | pass 104 | return True 105 | 106 | totalObjects = len(Sun.Selected_objects) 107 | 108 | localTime = Sun.Time 109 | if Sun.Longitude > 0: 110 | zone = Sun.UTCzone * -1 111 | else: 112 | zone = Sun.UTCzone 113 | if Sun.DaylightSavings: 114 | zone -= 1 115 | 116 | northOffset = radToDeg(Sun.NorthOffset) 117 | 118 | if Sun.ShowRiseSet: 119 | calcSunrise_Sunset(1) 120 | calcSunrise_Sunset(0) 121 | 122 | getSunPosition(None, localTime, Sun.Latitude, Sun.Longitude, 123 | northOffset, zone, Sun.Month, Sun.Day, Sun.Year, 124 | Sun.SunDistance) 125 | 126 | if Sun.UseSkyTexture: 127 | try: 128 | nt = bpy.context.scene.world.node_tree.nodes 129 | sunTex = nt.get(Sun.SkyTexture) 130 | if sunTex: 131 | locX = math.sin(Sun.Phi) * math.sin(-Sun.Theta) 132 | locY = math.sin(Sun.Theta) * math.cos(Sun.Phi) 133 | locZ = math.cos(Sun.Theta) 134 | sunTex.texture_mapping.rotation.z = 0.0 135 | sunTex.sun_direction = locX, locY, locZ 136 | 137 | except: 138 | pass 139 | 140 | if Sun.UseSunObject: 141 | try: 142 | obj = bpy.context.scene.objects.get(Sun.SunObject) 143 | setSunPosition(obj, Sun.SunDistance) 144 | if obj.type == 'LAMP': 145 | obj.rotation_euler = ( 146 | (math.radians(Sun.Elevation - 90), 0, 147 | math.radians(-Sun.AzNorth))) 148 | except: 149 | pass 150 | 151 | if totalObjects < 1 or not Sun.UseObjectGroup: 152 | return False 153 | 154 | if Sun.ObjectGroup == 'ECLIPTIC': 155 | # Ecliptic 156 | if totalObjects > 1: 157 | timeIncrement = Sun.TimeSpread / (totalObjects - 1) 158 | localTime = localTime + timeIncrement * (totalObjects - 1) 159 | else: 160 | timeIncrement = Sun.TimeSpread 161 | 162 | for obj in Sun.Selected_objects: 163 | mesh = obj.type 164 | if mesh == 'LAMP' or mesh == 'MESH': 165 | getSunPosition(obj, 166 | localTime, 167 | Sun.Latitude, Sun.Longitude, 168 | northOffset, zone, 169 | Sun.Month, Sun.Day, Sun.Year, 170 | Sun.SunDistance) 171 | setSunPosition(obj, Sun.SunDistance) 172 | localTime = localTime - timeIncrement 173 | if mesh == 'LAMP': 174 | obj.rotation_euler = ( 175 | (math.radians(Sun.Elevation - 90), 0, 176 | math.radians(-Sun.AzNorth))) 177 | else: 178 | # Analemma 179 | dayIncrement = 365 / totalObjects 180 | day = Sun.Day_of_year + dayIncrement * (totalObjects - 1) 181 | for obj in Sun.Selected_objects: 182 | mesh = obj.type 183 | if mesh == 'LAMP' or mesh == 'MESH': 184 | dt = (datetime.date(Sun.Year, 1, 1) + 185 | datetime.timedelta(day - 1)) 186 | getSunPosition(obj, localTime, 187 | Sun.Latitude, Sun.Longitude, 188 | northOffset, zone, dt.month, dt.day, 189 | Sun.Year, Sun.SunDistance) 190 | setSunPosition(obj, Sun.SunDistance) 191 | day -= dayIncrement 192 | if mesh == 'LAMP': 193 | obj.rotation_euler = ( 194 | (math.radians(Sun.Elevation - 90), 0, 195 | math.radians(-Sun.AzNorth))) 196 | 197 | return True 198 | 199 | ############################################################################ 200 | # 201 | # Calculate the actual position of the sun based on input parameters. 202 | # 203 | # The sun positioning algorithms below are based on the National Oceanic 204 | # and Atmospheric Administration's (NOAA) Solar Position Calculator 205 | # which rely on calculations of Jean Meeus' book "Astronomical Algorithms." 206 | # Use of NOAA data and products are in the public domain and may be used 207 | # freely by the public as outlined in their policies at 208 | # www.nws.noaa.gov/disclaimer.php 209 | # 210 | # The calculations of this script can be verified with those of NOAA's 211 | # using the Azimuth and Solar Elevation displayed in the SunPos_Panel. 212 | # NOAA's web site is: 213 | # http://www.esrl.noaa.gov/gmd/grad/solcalc 214 | ############################################################################ 215 | 216 | 217 | def getSunPosition(obj, localTime, latitude, longitude, northOffset, 218 | utcZone, month, day, year, distance): 219 | 220 | longitude *= -1 # for internal calculations 221 | utcTime = localTime + utcZone # Set Greenwich Meridian Time 222 | 223 | if latitude > 89.93: # Latitude 90 and -90 gives 224 | latitude = degToRad(89.93) # erroneous results so nudge it 225 | elif latitude < -89.93: 226 | latitude = degToRad(-89.93) 227 | else: 228 | latitude = degToRad(latitude) 229 | 230 | t = julianTimeFromY2k(utcTime, year, month, day) 231 | 232 | e = degToRad(obliquityCorrection(t)) 233 | L = apparentLongitudeOfSun(t) 234 | solarDec = sunDeclination(e, L) 235 | eqtime = calcEquationOfTime(t) 236 | 237 | timeCorrection = (eqtime - 4 * longitude) + 60 * utcZone 238 | trueSolarTime = ((utcTime - utcZone) * 60.0 + timeCorrection) % 1440 239 | 240 | hourAngle = trueSolarTime / 4.0 - 180.0 241 | if hourAngle < -180.0: 242 | hourAngle += 360.0 243 | 244 | csz = (math.sin(latitude) * math.sin(solarDec) + 245 | math.cos(latitude) * math.cos(solarDec) * 246 | math.cos(degToRad(hourAngle))) 247 | if csz > 1.0: 248 | csz = 1.0 249 | elif csz < -1.0: 250 | csz = -1.0 251 | 252 | zenith = math.acos(csz) 253 | 254 | azDenom = math.cos(latitude) * math.sin(zenith) 255 | 256 | if abs(azDenom) > 0.001: 257 | azRad = ((math.sin(latitude) * 258 | math.cos(zenith)) - math.sin(solarDec)) / azDenom 259 | if abs(azRad) > 1.0: 260 | azRad = -1.0 if (azRad < 0.0) else 1.0 261 | azimuth = 180.0 - radToDeg(math.acos(azRad)) 262 | if hourAngle > 0.0: 263 | azimuth = -azimuth 264 | else: 265 | azimuth = 180.0 if (latitude > 0.0) else 0.0 266 | 267 | if azimuth < 0.0: 268 | azimuth = azimuth + 360.0 269 | 270 | exoatmElevation = 90.0 - radToDeg(zenith) 271 | 272 | if exoatmElevation > 85.0: 273 | refractionCorrection = 0.0 274 | else: 275 | te = math.tan(degToRad(exoatmElevation)) 276 | if exoatmElevation > 5.0: 277 | refractionCorrection = ( 278 | 58.1 / te - 0.07 / (te ** 3) + 0.000086 / (te ** 5)) 279 | elif (exoatmElevation > -0.575): 280 | s1 = (-12.79 + exoatmElevation * 0.711) 281 | s2 = (103.4 + exoatmElevation * (s1)) 282 | s3 = (-518.2 + exoatmElevation * (s2)) 283 | refractionCorrection = 1735.0 + exoatmElevation * (s3) 284 | else: 285 | refractionCorrection = -20.774 / te 286 | 287 | refractionCorrection = refractionCorrection / 3600 288 | 289 | if Sun.ShowRefraction: 290 | solarElevation = 90.0 - (radToDeg(zenith) - refractionCorrection) 291 | else: 292 | solarElevation = 90.0 - radToDeg(zenith) 293 | 294 | solarAzimuth = azimuth + northOffset 295 | 296 | Sun.AzNorth = solarAzimuth 297 | 298 | Sun.Theta = math.pi / 2 - degToRad(solarElevation) 299 | Sun.Phi = degToRad(solarAzimuth) * -1 300 | Sun.Azimuth = azimuth 301 | Sun.Elevation = solarElevation 302 | 303 | 304 | def setSunPosition(obj, distance): 305 | 306 | locX = math.sin(Sun.Phi) * math.sin(-Sun.Theta) * distance 307 | locY = math.sin(Sun.Theta) * math.cos(Sun.Phi) * distance 308 | locZ = math.cos(Sun.Theta) * distance 309 | 310 | #---------------------------------------------- 311 | # Update selected object in viewport 312 | #---------------------------------------------- 313 | try: 314 | obj.location = locX, locY, locZ 315 | except: 316 | pass 317 | 318 | 319 | def calcSunriseSetUTC(rise, jd, latitude, longitude): 320 | t = calcTimeJulianCent(jd) 321 | eqTime = calcEquationOfTime(t) 322 | solarDec = calcSunDeclination(t) 323 | hourAngle = calcHourAngleSunrise(latitude, solarDec) 324 | if not rise: 325 | hourAngle = -hourAngle 326 | delta = longitude + radToDeg(hourAngle) 327 | timeUTC = 720 - (4.0 * delta) - eqTime 328 | return timeUTC 329 | 330 | 331 | def calcSunDeclination(t): 332 | e = degToRad(obliquityCorrection(t)) 333 | L = apparentLongitudeOfSun(t) 334 | solarDec = sunDeclination(e, L) 335 | return solarDec 336 | 337 | 338 | def calcHourAngleSunrise(lat, solarDec): 339 | latRad = degToRad(lat) 340 | HAarg = (math.cos(degToRad(90.833)) / 341 | (math.cos(latRad) * math.cos(solarDec)) 342 | - math.tan(latRad) * math.tan(solarDec)) 343 | if HAarg < -1.0: 344 | HAarg = -1.0 345 | elif HAarg > 1.0: 346 | HAarg = 1.0 347 | HA = math.acos(HAarg) 348 | return HA 349 | 350 | 351 | def calcSolarNoon(jd, longitude, timezone, dst): 352 | t = calcTimeJulianCent(jd - longitude / 360.0) 353 | eqTime = calcEquationOfTime(t) 354 | noonOffset = 720.0 - (longitude * 4.0) - eqTime 355 | newt = calcTimeJulianCent(jd + noonOffset / 1440.0) 356 | eqTime = calcEquationOfTime(newt) 357 | 358 | nv = 780.0 if dst else 720.0 359 | noonLocal = (nv - (longitude * 4.0) - eqTime + (timezone * 60.0)) % 1440 360 | Sun.SolarNoon.time = noonLocal / 60.0 361 | 362 | 363 | def calcSunrise_Sunset(rise): 364 | if Sun.Longitude > 0: 365 | zone = Sun.UTCzone * -1 366 | else: 367 | zone = Sun.UTCzone 368 | 369 | jd = getJulianDay(Sun.Year, Sun.Month, Sun.Day) 370 | timeUTC = calcSunriseSetUTC(rise, jd, Sun.Latitude, Sun.Longitude) 371 | newTimeUTC = calcSunriseSetUTC(rise, jd + timeUTC / 1440.0, 372 | Sun.Latitude, Sun.Longitude) 373 | timeLocal = newTimeUTC + (-zone * 60.0) 374 | tl = timeLocal / 60.0 375 | getSunPosition(None, tl, Sun.Latitude, Sun.Longitude, 0.0, 376 | zone, Sun.Month, Sun.Day, Sun.Year, 377 | Sun.SunDistance) 378 | if Sun.DaylightSavings: 379 | timeLocal += 60.0 380 | tl = timeLocal / 60.0 381 | if tl < 0.0: 382 | tl += 24.0 383 | elif tl > 24.0: 384 | tl -= 24.0 385 | if rise: 386 | Sun.Sunrise.time = tl 387 | Sun.Sunrise.azimuth = Sun.Azimuth 388 | Sun.Sunrise.elevation = Sun.Elevation 389 | calcSolarNoon(jd, Sun.Longitude, -zone, Sun.DaylightSavings) 390 | getSunPosition(None, Sun.SolarNoon.time, Sun.Latitude, Sun.Longitude, 391 | 0.0, zone, Sun.Month, Sun.Day, Sun.Year, 392 | Sun.SunDistance) 393 | Sun.SolarNoon.elevation = Sun.Elevation 394 | else: 395 | Sun.Sunset.time = tl 396 | Sun.Sunset.azimuth = Sun.Azimuth 397 | Sun.Sunset.elevation = Sun.Elevation 398 | 399 | ########################################################################## 400 | ## Get the elapsed julian time since 1/1/2000 12:00 gmt 401 | ## Y2k epoch (1/1/2000 12:00 gmt) is Julian day 2451545.0 402 | ########################################################################## 403 | 404 | 405 | def julianTimeFromY2k(utcTime, year, month, day): 406 | century = 36525.0 # Days in Julian Century 407 | epoch = 2451545.0 # Julian Day for 1/1/2000 12:00 gmt 408 | jd = getJulianDay(year, month, day) 409 | return ((jd + (utcTime / 24)) - epoch) / century 410 | 411 | 412 | def getJulianDay(year, month, day): 413 | if month <= 2: 414 | year -= 1 415 | month += 12 416 | A = math.floor(year / 100) 417 | B = 2 - A + math.floor(A / 4.0) 418 | jd = (math.floor((365.25 * (year + 4716.0))) + 419 | math.floor(30.6001 * (month + 1)) + day + B - 1524.5) 420 | return jd 421 | 422 | 423 | def calcTimeJulianCent(jd): 424 | t = (jd - 2451545.0) / 36525.0 425 | return t 426 | 427 | 428 | def sunDeclination(e, L): 429 | return (math.asin(math.sin(e) * math.sin(L))) 430 | 431 | 432 | def calcEquationOfTime(t): 433 | epsilon = obliquityCorrection(t) 434 | ml = degToRad(meanLongitudeSun(t)) 435 | e = eccentricityEarthOrbit(t) 436 | m = degToRad(meanAnomalySun(t)) 437 | y = math.tan(degToRad(epsilon) / 2.0) 438 | y = y * y 439 | sin2ml = math.sin(2.0 * ml) 440 | cos2ml = math.cos(2.0 * ml) 441 | sin4ml = math.sin(4.0 * ml) 442 | sinm = math.sin(m) 443 | sin2m = math.sin(2.0 * m) 444 | etime = (y * sin2ml - 2.0 * e * sinm + 4.0 * e * y * 445 | sinm * cos2ml - 0.5 * y ** 2 * sin4ml - 1.25 * e ** 2 * sin2m) 446 | return (radToDeg(etime) * 4) 447 | 448 | 449 | def obliquityCorrection(t): 450 | ec = obliquityOfEcliptic(t) 451 | omega = 125.04 - 1934.136 * t 452 | return (ec + 0.00256 * math.cos(degToRad(omega))) 453 | 454 | 455 | def obliquityOfEcliptic(t): 456 | return ((23.0 + 26.0 / 60 + (21.4480 - 46.8150) / 3600 * t - 457 | (0.00059 / 3600) * t ** 2 + (0.001813 / 3600) * t ** 3)) 458 | 459 | 460 | def trueLongitudeOfSun(t): 461 | return (meanLongitudeSun(t) + equationOfSunCenter(t)) 462 | 463 | 464 | def calcSunApparentLong(t): 465 | o = trueLongitudeOfSun(t) 466 | omega = 125.04 - 1934.136 * t 467 | lamb = o - 0.00569 - 0.00478 * math.sin(degToRad(omega)) 468 | return lamb 469 | 470 | 471 | def apparentLongitudeOfSun(t): 472 | return (degToRad(trueLongitudeOfSun(t) - 0.00569 - 0.00478 * 473 | math.sin(degToRad(125.04 - 1934.136 * t)))) 474 | 475 | 476 | def meanLongitudeSun(t): 477 | return (280.46646 + 36000.76983 * t + 0.0003032 * t ** 2) % 360 478 | 479 | 480 | def equationOfSunCenter(t): 481 | m = degToRad(meanAnomalySun(t)) 482 | c = ((1.914602 - 0.004817 * t - 0.000014 * t ** 2) * math.sin(m) + 483 | (0.019993 - 0.000101 * t) * math.sin(m * 2) + 484 | 0.000289 * math.sin(m * 3)) 485 | return c 486 | 487 | 488 | def meanAnomalySun(t): 489 | return (357.52911 + t * (35999.05029 - 0.0001537 * t)) 490 | 491 | 492 | def eccentricityEarthOrbit(t): 493 | return (0.016708634 - 0.000042037 * t - 0.0000001267 * t ** 2) 494 | 495 | 496 | def degToRad(angleDeg): 497 | return (math.pi * angleDeg / 180.0) 498 | 499 | 500 | def radToDeg(angleRad): 501 | return (180.0 * angleRad / math.pi) 502 | --------------------------------------------------------------------------------