├── LICENSE ├── README.md └── three_point_arch.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Three Point Arch Tool 2 | A Blender add-on for creating arches. 3 | 4 | Please see the [releases branch](https://github.com/n-Burn/three_point_arch/tree/releases) to get the 5 | latest stable release and other releases of three point arch. 6 | -------------------------------------------------------------------------------- /three_point_arch.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | END GPL LICENSE BLOCK 19 | 20 | #============================================================================ 21 | 22 | [Stage] [Event that ends stage] 23 | * Stage 0 - No points placed, add-on just launched and is initializing 24 | * Stage 1 - 1st point placed 25 | * Stage 2 - 2nd point placed (1st to 2nd point is arc width) 26 | * Stage 3 - 3rd point placed (to create planar surface to align arc to) 27 | * Stage 4 - 1st arch edge created 28 | * Stage 5 - Make faces from 1st arch (with extrude > scale) 29 | * Stage 6 - Make arch faces into solid (by extruding faces from Stage 5) 30 | * Exit add-on 31 | 32 | Note: When the add-on is running it will proceed through Stage 0 to Stage 1 33 | with no pause in execution. Likewise, once Stage 3 completes, the add-on 34 | will proceed through Stage 4 to Stage 5 with no pause in execution. 35 | Both of these previously mentioned stage intervals (0 to 1 and 3 to 4) 36 | can essentially be considered a single stage. 37 | 38 | To-Do 39 | [X] Option to change number of edges on arch (pause with space to break modal?) 40 | [X] Option to show measurements? 41 | [?] Better shortcut info, make sure helpdisplay up to date 42 | [_] Option to change color scheme? NP Station color scheme? 43 | [?] Better DPI argument handling for font drawing 44 | [_] Make OBJECT / EDIT mode switching less error prone (wrap in function?) 45 | [X] Find out why arch mesh isn't deleted if addon exited during extrudes 46 | [?] Find way to display measurments for last 2 extrudes 47 | [X] Make workaround in case space is pressed while extuding 48 | [X] Add shadowing for measurement text 49 | 50 | Possible To-Do 51 | [_] Make a new basic text class and have HelpText inherit it? 52 | [X] Turn distances and segment count display during pause into classes? 53 | [_] Option to manually set distance between arch edges (spacebar pause menu?) 54 | [_] Option to "roll back" arch distance? 55 | [_] Option to add an arch "base/support wall" before/after creating arch? 56 | [_] Option to change arch types (circular, equilateral, parabolic, etc) 57 | [_] Use curves instead of vertex plotting? 58 | [_] Option to have normals either inside or outside? 59 | [_] Make previous measurments always visible (like with np_float_box) 60 | [_] Add texture to arch before exiting? 61 | [_] 62 | ''' 63 | 64 | bl_info = { 65 | "name": "Three Point Arch Tool", 66 | "author": "nBurn", 67 | "version": (0, 0, 4), 68 | "blender": (2, 70, 0), 69 | "location": "View3D > Tools Panel", 70 | "description": "Tool for creating arches", 71 | "category": "Mesh" 72 | } 73 | 74 | # Additional credits: 75 | # Help \ shortcut menu system adapted from NP Station 76 | 77 | from copy import deepcopy 78 | from math import pi, degrees, radians, sin 79 | 80 | import bpy 81 | import bmesh 82 | import bgl 83 | import blf 84 | from mathutils import geometry, Quaternion, Vector 85 | from bpy_extras import view3d_utils 86 | from bpy_extras.view3d_utils import location_3d_to_region_2d as loc3d_to_reg2d 87 | from bpy_extras.view3d_utils import region_2d_to_vector_3d as reg2d_to_vec3d 88 | from bpy_extras.view3d_utils import region_2d_to_location_3d as reg2d_to_loc3d 89 | from bpy_extras.view3d_utils import region_2d_to_origin_3d as reg2d_to_org3d 90 | from bpy.props import IntProperty, BoolProperty 91 | 92 | #print("Loaded: Three Point Arc Tool\n") # debug 93 | 94 | # "Constant" values 95 | ( 96 | X, 97 | Y, 98 | Z, 99 | PLACE_1ST, 100 | PLACE_2ND, 101 | PLACE_3RD, 102 | ARCH_EXTRUDE_1, 103 | ARCH_EXTRUDE_2, 104 | EXIT 105 | ) = range(9) 106 | 107 | 108 | class Colr: 109 | red = 1.0, 0.0, 0.0, 0.5 110 | green = 0.0, 1.0, 0.0, 0.5 111 | blue = 0.0, 0.0, 1.0, 0.5 112 | white = 1.0, 1.0, 1.0, 1.0 113 | grey = 1.0, 1.0, 1.0, 0.4 114 | black = 0.0, 0.0, 0.0, 1.0 115 | brown = 0.15, 0.15, 0.15, 0.20 116 | 117 | 118 | # Defines the settings part in the addons tab: 119 | class TPARCH_prefs(bpy.types.AddonPreferences): 120 | bl_idname = __name__ 121 | 122 | np_scale_dist = bpy.props.FloatProperty( 123 | name='', 124 | description='Distance multiplier (for example, for cm use 100)', 125 | default=100, 126 | min=0, 127 | step=100, 128 | precision=2) 129 | 130 | ''' 131 | np_col_scheme = bpy.props.EnumProperty( 132 | name ='', 133 | items = ( 134 | ('csc_default_grey', 'Blender_Default_NP_GREY',''), 135 | ('csc_school_marine', 'NP_school_paper_NP_MARINE',''), 136 | ('def_blender_gray', 'TP_Arch_Default_Theme','')), 137 | default = 'def_blender_gray', 138 | #default = 'csc_default_grey', 139 | description = 'Choose the overall addon color scheme, according to " + \ 140 | "your current Blender theme') 141 | ''' 142 | 143 | np_suffix_dist = bpy.props.EnumProperty( 144 | name='', 145 | items=(("'", "'", ''), ('"', '"', ''), (' thou', 'thou', ''), 146 | (' km', 'km', ''), (' m', 'm', ''), (' cm', 'cm', ''), 147 | (' mm', 'mm', ''), (' nm', 'nm', ''), ('None', 'None', '')), 148 | default=' cm', 149 | description='Add a unit extension after the numerical distance ') 150 | 151 | segm_cnt = IntProperty( 152 | name="Arch segments", 153 | description="Number of segments in arch", 154 | min=2, 155 | default=16) 156 | 157 | extr_enabled = BoolProperty(name="Enable extrude", 158 | description="Extrude arch after edge creation", 159 | default=True) 160 | 161 | def draw(self, context): 162 | layout = self.layout 163 | # split 50 / 50, then split 50 to 60 / 40 164 | row1 = layout.row() 165 | r1_sl = row1.split(percentage=0.6) # 60% of 50% 166 | r1_sl.label(text="Unit scale for distance") 167 | r1_sl.prop(self, "np_scale_dist") # 40% of 50% 168 | r1_s2 = row1.split(percentage=0.6) # 60% of 50% 169 | r1_s2.label(text="Unit suffix for distance") 170 | r1_s2.prop(self, "np_suffix_dist") 171 | 172 | row2 = layout.row() 173 | r2_sl = row2.split(percentage=0.5) 174 | r2_sl.prop(self, "segm_cnt") # 50% 175 | r2_sl.prop(self, "extr_enabled", text="Enable extrude") 176 | #r2_sl_s = r2_sl.split(percentage=0.3) 177 | #r2_sl_s.label(text="Color scheme") 178 | #r2_sl_s.prop(self, "np_col_scheme") 179 | 180 | #row3 = layout.row() 181 | #r3_sl = row3.split(percentage=0.5) 182 | #r3_sl.prop(self, "extr_enabled", text="Enable extrude") 183 | 184 | 185 | def backup_blender_settings(): 186 | backup = [ 187 | deepcopy(bpy.context.tool_settings.use_snap), 188 | deepcopy(bpy.context.tool_settings.snap_element), 189 | deepcopy(bpy.context.tool_settings.snap_target), 190 | deepcopy(bpy.context.space_data.pivot_point), 191 | deepcopy(bpy.context.space_data.transform_orientation), 192 | deepcopy(bpy.context.space_data.show_manipulator), 193 | deepcopy(bpy.context.scene.cursor_location), 194 | deepcopy(bpy.context.tool_settings.mesh_select_mode[:])] 195 | return backup 196 | 197 | 198 | def init_blender_settings(): 199 | bpy.context.tool_settings.use_snap = False 200 | bpy.context.tool_settings.snap_element = 'VERTEX' 201 | bpy.context.tool_settings.snap_target = 'CLOSEST' 202 | bpy.context.space_data.pivot_point = 'ACTIVE_ELEMENT' 203 | bpy.context.space_data.transform_orientation = 'GLOBAL' 204 | bpy.context.space_data.show_manipulator = False 205 | bpy.context.tool_settings.mesh_select_mode = True, False, False 206 | return 207 | 208 | 209 | def restore_blender_settings(backup): 210 | bpy.context.tool_settings.use_snap = deepcopy(backup[0]) 211 | bpy.context.tool_settings.snap_element = deepcopy(backup[1]) 212 | bpy.context.tool_settings.snap_target = deepcopy(backup[2]) 213 | bpy.context.space_data.pivot_point = deepcopy(backup[3]) 214 | bpy.context.space_data.transform_orientation = deepcopy(backup[4]) 215 | bpy.context.space_data.show_manipulator = deepcopy(backup[5]) 216 | bpy.context.scene.cursor_location = deepcopy(backup[6]) 217 | bpy.context.tool_settings.mesh_select_mode = deepcopy(backup[7]) 218 | return 219 | 220 | 221 | class DrawMeanDistance: 222 | def __init__(self, sz, settings): 223 | self.reg = bpy.context.region 224 | self.rv3d = bpy.context.region_data 225 | self.dpi = bpy.context.user_preferences.system.dpi 226 | self.size = sz 227 | self.txtcolr = settings["col_num_main"] 228 | self.shdcolr = settings["col_num_shadow"] 229 | self.shdoffs = -1, -1 # shadow offset 230 | self.font_id = 0 231 | 232 | def draw(self, pts, meas_mult, meas_suff): 233 | pts_3d = [] 234 | for i in range(len(pts)): 235 | if type(pts[i]) is not Vector: 236 | pts_3d.append(Vector(pts[i])) 237 | else: 238 | pts_3d.append(pts[i]) 239 | 240 | p1_2d = loc3d_to_reg2d(self.reg, self.rv3d, pts_3d[0]) 241 | p2_2d = loc3d_to_reg2d(self.reg, self.rv3d, pts_3d[1]) 242 | 243 | draw_line_2D(p1_2d, p2_2d, Colr.white) 244 | 245 | if p1_2d is None or p2_2d is None: 246 | p1_2d = p2_2d = 0.0, 0.0 247 | 248 | def get_pts_mean(locs2d, max_val): 249 | res = 0 250 | for i in locs2d: 251 | if i > max_val: 252 | res += max_val 253 | elif i > 0: 254 | res += i 255 | return res / 2 256 | 257 | mean_x = get_pts_mean((p1_2d[X], p2_2d[X]), self.reg.width) 258 | mean_y = get_pts_mean((p1_2d[Y], p2_2d[Y]), self.reg.height) 259 | offset = 5, 5 260 | shdblr = 3 # shadow blur 261 | dist_loc = mean_x + offset[X], mean_y + offset[Y] 262 | dist_3d = meas_mult * (pts_3d[1] - pts_3d[0]).length 263 | dist_3d_rnd = abs(round(dist_3d, 2)) 264 | dist = str(dist_3d_rnd) + meas_suff 265 | #print("self.txtcolr", self.txtcolr) # debug 266 | 267 | if dist_3d_rnd != 0: 268 | blf.enable(self.font_id, blf.SHADOW) 269 | blf.shadow(self.font_id, shdblr, *self.shdcolr) 270 | blf.shadow_offset(self.font_id, *self.shdoffs) 271 | 272 | bgl.glColor4f(*self.txtcolr) 273 | blf.size(self.font_id, self.size, self.dpi) 274 | blf.position(self.font_id, dist_loc[0], dist_loc[1], 0) 275 | blf.draw(self.font_id, dist) 276 | 277 | blf.disable(self.font_id, blf.SHADOW) 278 | 279 | # To-Do : if displaying measurements for multiple dimensions 280 | # at once is needed, may have to uncomment below return 281 | # statement so measuresments can be stored externally. 282 | #return (dist_3d_rnd, dist) 283 | 284 | 285 | class DrawSegmCounter: 286 | def __init__(self, settings): 287 | self.dpi = bpy.context.user_preferences.system.dpi 288 | self.desc_str = "Arch segments" 289 | self.desc_size = 16 290 | self.seg_cnt_size = 32 291 | self.desc_co_offs = None 292 | self.seg_cnt_co_offs = None 293 | self.desc_colr = settings["col_font_instruct_main"] 294 | self.seg_cnt_colr = settings["col_font_instruct_main"] 295 | self.shdcolr = settings["col_font_instruct_shadow"] 296 | self.shdoffs = -1, -1 # shadow offset 297 | self.font_id = 0 298 | 299 | d_b_offs = Vector((10, 24)) # description base offset 300 | c_b_offs = Vector((5, 6)) # count base offset 301 | blf.size(self.font_id, self.desc_size, self.dpi) 302 | desc_dim = Vector(blf.dimensions(self.font_id, self.desc_str)) 303 | 304 | #desc_x_hf = 305 | self.desc_co_offs = Vector((-desc_dim[X] - d_b_offs[X], d_b_offs[Y])) 306 | self.seg_cnt_co_offs = Vector((c_b_offs[X], desc_dim[Y] + c_b_offs[Y])) 307 | 308 | def draw(self, cnt, co): 309 | if co is None: 310 | return 311 | desc_co = co + self.desc_co_offs 312 | seg_cnt_co = desc_co + self.seg_cnt_co_offs 313 | 314 | bgl.glColor4f(*self.desc_colr) 315 | blf.size(self.font_id, self.desc_size, self.dpi) 316 | blf.position(self.font_id, desc_co[0], desc_co[1], 0) 317 | blf.draw(self.font_id, self.desc_str) 318 | 319 | bgl.glColor4f(*self.seg_cnt_colr) 320 | blf.size(self.font_id, self.seg_cnt_size, self.dpi) 321 | blf.position(self.font_id, seg_cnt_co[0], seg_cnt_co[1], 0) 322 | blf.draw(self.font_id, str(cnt)) 323 | 324 | 325 | class HelpText: 326 | def get_size(self): 327 | blf.size(self.font_id, self.size, self.dpi) 328 | self.wid, self.hgt = blf.dimensions(self.font_id, self.origtxt) 329 | 330 | def __init__(self, text, size, h_a, colr, shdcolr): 331 | self.dpi = bpy.context.user_preferences.system.dpi 332 | self.origtxt = text # original text string 333 | self.disptxt = [text] # displayed text 334 | self.size = size 335 | self.colr = colr # color / colour 336 | self.shad = False # text shadow enabled? 337 | self.shad_colr = shdcolr 338 | #self.horz_aln = h_a # horizontal alignment (L/R/C, C only for instr) 339 | self.wid = 0 # width 340 | self.hgt = 0 # height 341 | self.font_id = 0 342 | self.crop = False 343 | self.viz = True # visible 344 | self.pos = [] # position 345 | self.ovrfl_pos = [] 346 | self.ovrfl = False 347 | 348 | self.get_size() 349 | 350 | 351 | def draw_wrapper(self): 352 | if self.viz: 353 | if self.shad: 354 | shdblur = 3 # shadow blur 355 | shdoffs = -1, -1 356 | blf.enable(self.font_id, blf.SHADOW) 357 | blf.shadow(self.font_id, shdblur, *self.shad_colr) 358 | blf.shadow_offset(self.font_id, shdoffs[X], shdoffs[Y]) 359 | self.draw() 360 | blf.disable(self.font_id, blf.SHADOW) 361 | else: 362 | self.draw() 363 | 364 | def draw(self): 365 | bgl.glColor4f(*self.colr) 366 | blf.size(self.font_id, self.size, self.dpi) 367 | p1 = self.pos 368 | blf.position(self.font_id, p1[0], p1[1], 0) 369 | blf.draw(self.font_id, self.disptxt[0]) 370 | if self.ovrfl: 371 | p2 = self.ovrfl_pos 372 | blf.position(self.font_id, p2[0], p2[1], 0) 373 | blf.draw(self.font_id, self.disptxt[1]) 374 | 375 | 376 | class HelpBar: 377 | #def __init__(self, col , disp=True): 378 | def __init__(self): 379 | #self.disp_bar = False # display bar? 380 | self.colr = None # bar color 381 | self.help_txts = [] # help text objects 382 | self.txtcnt = 0 # text count 383 | self.barcnt = 1 # bar count 384 | #self.wid = 0 # bar width 385 | self.max_txt_wid = 0 # max text width 386 | self.hgt = 0 # bar height 387 | self.bndry = [ [], [] ] # bar positions 388 | self.viz = True # is bar or its contents visible 389 | self.x_off = 0 # x offset 390 | self.y_off = 0 391 | self.bar_y_off = 3 # y offset 392 | self.extend = False 393 | #self.crop = False 394 | 395 | def clear(self): 396 | self.help_txts = [] 397 | self.txtcnt = 0 398 | self.barcnt = 1 399 | self.extend = False 400 | 401 | def set_sizes(self, barwid): 402 | if self.help_txts != []: 403 | dpi = self.help_txts[0].dpi 404 | font_id = 0 405 | sizes = [i.size for i in self.help_txts] 406 | max_size = max(sizes) 407 | blf.size(font_id, max_size, dpi) 408 | x, max_y = blf.dimensions(0, "Tgp") 409 | y = blf.dimensions(0, "T")[1] 410 | hgt_mult = 1.6 # multiplier for bar heigt 411 | self.hgt = int(max_y * hgt_mult) 412 | self.x_off = int(x / 2) 413 | hgtoff_mult = (2 - hgt_mult) / 2 414 | self.y_off = int((max_y * hgtoff_mult) + (max_y - y)) 415 | self.max_txt_wid = barwid - (self.x_off * 2) 416 | 417 | # make sure text fits, if not don't display 418 | tot_width = 0 419 | for i in self.help_txts: 420 | tot_width += i.wid 421 | i.viz = True 422 | if tot_width > self.max_txt_wid: 423 | for i in self.help_txts: 424 | if i.wid < int(self.max_txt_wid / 2): 425 | i.viz = True 426 | else: 427 | i.vis = False 428 | 429 | ''' 430 | tot_w = 0 431 | for i in self.help_txts: tot_w += i.wid + self.x_off 432 | if tot_w > self.max_txt_wid: 433 | self.extend = True 434 | if tot_w > (self.max_txt_wid * 2): 435 | self.crop = True 436 | ''' 437 | 438 | def fit(self): 439 | helptxt = self.help_txts[0] 440 | helptxt.viz = True 441 | dpi = self.help_txts[0].dpi 442 | if helptxt.wid < self.max_txt_wid: 443 | self.barcnt = 1 444 | helptxt.disptxt = [helptxt.origtxt] 445 | helptxt.ovrfl = False 446 | self.extend = False 447 | else: 448 | font_id = helptxt.font_id 449 | str_segs = helptxt.origtxt.split(',') 450 | out_str = [''] 451 | str_cnt = 0 # text string count 452 | seg_cnt = len(str_segs) 453 | re_add = ([','] * (seg_cnt - 1)) + [''] 454 | blf.size(font_id, helptxt.size, dpi) 455 | blf_dim = blf.dimensions 456 | for i in range(seg_cnt): 457 | tmp_str = str_segs[i] + re_add[i] 458 | tmp_wid = blf_dim(font_id, tmp_str)[0] 459 | os_wid = blf_dim(font_id, out_str[str_cnt])[0] 460 | if (os_wid + tmp_wid) > self.max_txt_wid: 461 | if str_cnt > 0: 462 | break 463 | else: 464 | out_str.append('') 465 | tmp_str = tmp_str.strip() 466 | str_cnt += 1 467 | out_str[str_cnt] += tmp_str 468 | helptxt.disptxt = out_str 469 | self.barcnt = 2 470 | helptxt.ovrfl = True 471 | self.extend = True 472 | 473 | # get bar coordinates 474 | def get_bar_co(self, lf, ri, bt, tp): 475 | return [(lf, bt), (lf, tp), (ri, tp), (ri, bt)] 476 | 477 | def set_txt_pos(self, bx_lf, bx_ri, bx_bt): 478 | if self.txtcnt > 0: # left align 479 | self.help_txts[0].pos = bx_lf + self.x_off, bx_bt + self.y_off 480 | if self.txtcnt > 1: # right align 481 | ht_1_wid = self.help_txts[1].wid 482 | self.help_txts[1].pos = bx_ri - self.x_off - ht_1_wid, bx_bt + self.y_off 483 | if self.extend: 484 | self.help_txts[0].ovrfl_pos = self.help_txts[0].pos 485 | bx_bt2 = bx_bt + self.hgt + self.bar_y_off 486 | self.help_txts[0].pos = bx_lf + self.x_off, bx_bt2 + self.y_off 487 | 488 | def set_co(self, left, right, btm): # bndry = boundary 489 | top = btm + self.hgt 490 | self.bndry[0] = self.get_bar_co(left, right, btm, top) 491 | self.set_txt_pos(left, right, btm) 492 | if self.extend: 493 | btm2 = top + self.bar_y_off 494 | top2 = btm2 + self.hgt 495 | self.bndry[1] = self.get_bar_co(left, right, btm2, top2) 496 | 497 | def draw(self): 498 | bgl.glColor4f(*self.colr) 499 | for b in range(self.barcnt): 500 | bgl.glBegin(bgl.GL_TRIANGLE_FAN) 501 | for co in self.bndry[b]: 502 | bgl.glVertex2f(*co) 503 | bgl.glEnd() 504 | 505 | for i in self.help_txts: 506 | i.draw_wrapper() 507 | 508 | 509 | class HelpDisplay: 510 | def __init__(self, reg, settings): 511 | self.reg = reg # region tools width 512 | self.rtoolsw = 0 # region tools width 513 | self.ruiw = 0 # region UI width 514 | self.rgwid = 0 # region width 515 | self.rghgt = 0 # region width 516 | #self.barcnt = 2 # bar count 517 | #self.siz = None # text sizes 518 | #self.txtcolr = None # text colors 519 | self.pos = None # text positions 520 | self.instr = None # instructions 521 | self.bartop = HelpBar() # bar top 522 | self.barbot = HelpBar() 523 | self.bar_w = 0 # bar width 524 | self.viz = False # is gui visible? 525 | self.dispbars = False # display bars? 526 | self.settings = settings 527 | 528 | self.bartop.colr = self.settings["col_field_keys_aff"] 529 | self.barbot.colr = self.settings["col_field_keys_neg"] 530 | 531 | # always have to do full update after string change as 532 | # there is no guarantee string will not be split 533 | def clear_str(self): 534 | self.instr = None 535 | self.bartop.help_txts = [] 536 | self.bartop.txtcnt = 0 537 | self.barbot.help_txts = [] 538 | self.barbot.txtcnt = 0 539 | 540 | def add_str(self, help_typ, txt, size, align, colr=None, shdcolr=None): 541 | if help_typ == "INS": 542 | colr = self.settings["col_font_instruct_main"] 543 | shdcolr = self.settings["col_font_instruct_shadow"] 544 | self.instr = HelpText( 545 | txt, size, align, colr, shdcolr) 546 | self.instr.shad = True 547 | elif help_typ == "TOP": 548 | colr = self.settings["col_font_keys"] 549 | self.bartop.help_txts.append(HelpText( 550 | txt, size, align, colr, shdcolr)) 551 | self.bartop.txtcnt += 1 552 | elif help_typ == "BOT": 553 | colr = self.settings["col_font_keys"] 554 | self.barbot.help_txts.append(HelpText( 555 | txt, size, align, colr, shdcolr)) 556 | self.barbot.txtcnt += 1 557 | 558 | def new_vals(self): 559 | rtoolsw = 0 560 | ruiw = 0 561 | system = bpy.context.user_preferences.system 562 | if system.use_region_overlap: 563 | if system.window_draw_method in ('TRIPLE_BUFFER', 'AUTOMATIC'): 564 | area = bpy.context.area 565 | for r in area.regions: 566 | if r.type == 'TOOLS': 567 | rtoolsw = r.width 568 | elif r.type == 'UI': 569 | ruiw = r.width 570 | 571 | if self.rtoolsw != rtoolsw or self.ruiw != ruiw or \ 572 | self.rgwid != self.reg.width or self.rghgt != self.reg.height: 573 | self.rtoolsw = rtoolsw 574 | self.ruiw = ruiw 575 | self.rgwid = self.reg.width 576 | self.rghgt = self.reg.height 577 | return True 578 | else: 579 | return False 580 | 581 | def update(self): 582 | logo_w = 30 583 | reg_w, reg_h = self.rgwid, self.rghgt 584 | r_tools_w, r_ui_w = self.rtoolsw, self.ruiw 585 | offs_x = 60 # to avoid blocking xyz graphic (110 on tablet, desk 60) 586 | offs_y = 40 # 46 587 | bar_bar_y_offs = 3 588 | min_view_w = logo_w * 5 # view3d width minimum 589 | min_view_h = logo_w * 5 # view3d height minimum 590 | min_gui_w = 480 # bar width minimum 591 | min_gui_h = 280 # bar height minimum 592 | view_w = reg_w - r_tools_w - r_ui_w 593 | self.bar_w = (view_w - offs_x) * 0.96 594 | 595 | if view_w > min_view_w and reg_h > min_view_h: 596 | self.viz = True 597 | if view_w > min_gui_w and reg_h > min_gui_h: 598 | self.dispbars = True 599 | 600 | # set bar properties 601 | for i in (self.bartop, self.barbot): 602 | i.set_sizes(self.bar_w) 603 | self.bartop.fit() 604 | 605 | left_brdr = r_tools_w + offs_x # left border 606 | bar_x_beg = int(left_brdr + ((view_w - self.bar_w - offs_x) / 2)) 607 | bar_x_end = bar_x_beg + self.bar_w 608 | self.barbot.set_co(bar_x_beg, bar_x_end, offs_y) 609 | 610 | barbot_y = self.barbot.bndry[0][1][1] + bar_bar_y_offs 611 | self.bartop.set_co(bar_x_beg, bar_x_end, barbot_y) 612 | 613 | # set instructions properties 614 | instr_max = (view_w - logo_w) * 0.9 615 | if self.instr.wid < instr_max: 616 | self.instr.viz = True 617 | else: 618 | self.instr.viz = False 619 | 620 | top_of_top_bar = self.bartop.bndry[0][1][1] 621 | instr_x = r_tools_w + view_w / 2 - self.instr.wid / 2 + logo_w / 2 622 | self.instr.pos = [ 623 | int(instr_x), 624 | int(top_of_top_bar + (self.bartop.hgt * 2))] 625 | 626 | else: 627 | self.dispbars = False 628 | 629 | else: 630 | self.viz = False 631 | 632 | def draw(self): 633 | if self.new_vals(): 634 | self.update() 635 | if self.viz: 636 | #font_id = 0 637 | #draw_logo() 638 | 639 | if self.dispbars: 640 | self.instr.draw_wrapper() 641 | self.bartop.draw() 642 | self.barbot.draw() 643 | 644 | 645 | def get_rotated_pt(piv_co, mov_co, ang_rad, piv_norm): 646 | mov_aligned = mov_co - piv_co 647 | rot_val = Quaternion(piv_norm, ang_rad) 648 | mov_aligned.rotate(rot_val) 649 | return mov_aligned + piv_co 650 | 651 | 652 | def draw_pt_2D(pt_co, pt_color): 653 | if pt_co is not None: 654 | bgl.glEnable(bgl.GL_BLEND) 655 | bgl.glPointSize(10) 656 | bgl.glColor4f(*pt_color) 657 | bgl.glBegin(bgl.GL_POINTS) 658 | bgl.glVertex2f(*pt_co) 659 | bgl.glEnd() 660 | return 661 | 662 | 663 | def draw_line_2D(pt_co_1, pt_co_2, pt_color): 664 | if None not in (pt_co_1, pt_co_2): 665 | bgl.glEnable(bgl.GL_BLEND) 666 | bgl.glPointSize(7) 667 | bgl.glColor4f(*pt_color) 668 | bgl.glBegin(bgl.GL_LINE_STRIP) 669 | bgl.glVertex2f(*pt_co_1) 670 | bgl.glVertex2f(*pt_co_2) 671 | bgl.glEnd() 672 | return 673 | 674 | 675 | def draw_circ_arch_3D(steps, pts, orig, ang_meas, piv_norm, color, reg, rv3d): 676 | orig2d = loc3d_to_reg2d(reg, rv3d, orig) 677 | # returns None when 3d point is not inside active 3D View 678 | if orig2d is not None: 679 | draw_pt_2D(orig2d, Colr.white) 680 | ang_incr = abs(ang_meas / steps) 681 | bgl.glColor4f(*color) 682 | bgl.glBegin(bgl.GL_LINE_STRIP) 683 | curr_ang = 0.0 684 | while curr_ang <= ang_meas: 685 | new_pt = get_rotated_pt(orig, pts[0], curr_ang, piv_norm) 686 | new_pt2d = loc3d_to_reg2d(reg, rv3d, new_pt) 687 | if new_pt2d is not None: 688 | bgl.glVertex2f(new_pt2d[X], new_pt2d[Y]) 689 | curr_ang = curr_ang + ang_incr 690 | new_pt2d = loc3d_to_reg2d(reg, rv3d, pts[1]) 691 | if new_pt2d is not None: 692 | bgl.glVertex2f(new_pt2d[X], new_pt2d[Y]) 693 | bgl.glEnd() 694 | return 695 | 696 | 697 | # Refreshes mesh drawing in 3D view and updates mesh coordinate 698 | # data so ref_pts are drawn at correct locations. 699 | # Using editmode_toggle to do this seems hackish, but editmode_toggle seems 700 | # to be the only thing that updates both drawing and coordinate info. 701 | def editmode_refresh(ed_type): 702 | if ed_type == "EDIT_MESH": 703 | bpy.ops.object.editmode_toggle() 704 | bpy.ops.object.editmode_toggle() 705 | 706 | 707 | def add_pt(self, co): 708 | self.pts.append(co) 709 | self.pt_cnt += 1 710 | 711 | 712 | # === PointFind code === 713 | class SnapPoint(): 714 | def __init__(self): 715 | self.mode = "OBJECT" 716 | self.point = None 717 | self.ob = bpy.context.scene.objects 718 | #self.pt_cnt = 0 719 | 720 | # todo : move outside SnapPoint ? 721 | def get_mouse_3d(self, mouse_loc): 722 | region = bpy.context.region 723 | rv3d = bpy.context.region_data 724 | 725 | # make sure converted mouse location is visible from the 3D view, 726 | # if not, use less accurate alternative for getting mouses 3D coordinates 727 | mouse_vec3d = reg2d_to_vec3d(region, rv3d, mouse_loc) 728 | enterloc = reg2d_to_loc3d(region, rv3d, mouse_loc, mouse_vec3d) 729 | test2d = loc3d_to_reg2d(region, rv3d, enterloc) 730 | if test2d is None: 731 | persp_md_fix = mouse_vec3d / 5 732 | enterloc = reg2d_to_org3d(region, rv3d, mouse_loc) + persp_md_fix 733 | 734 | return enterloc 735 | 736 | def create(self, ms_loc_2d, ed_type): 737 | ms_loc_3d = self.get_mouse_3d(ms_loc_2d) 738 | if ed_type == 'OBJECT': 739 | bpy.ops.object.add(type='MESH', location=ms_loc_3d) 740 | self.point = bpy.context.object 741 | bpy.ops.transform.translate('INVOKE_DEFAULT') 742 | 743 | # Makes sure only the "guide point" object or vert 744 | # added with create is grabbed. 745 | def grab(self, ed_type, sel_backup=None): 746 | if ed_type == 'OBJECT': 747 | bpy.ops.object.select_all(action='DESELECT') 748 | self.point.select = True 749 | elif ed_type == 'EDIT_MESH': 750 | bpy.ops.mesh.select_all(action='DESELECT') 751 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 752 | bm.verts[-1].select = True 753 | editmode_refresh(ed_type) 754 | bpy.ops.transform.translate('INVOKE_DEFAULT') 755 | 756 | # todo : make "move then grab" function? 757 | # Makes sure only the "guide point" object or vert 758 | # added with create is grabbed. 759 | def mouse_grab(self, ms_loc_2d, ed_type, sel_backup=None): 760 | ms_loc_3d = self.get_mouse_3d(ms_loc_2d) 761 | if ed_type == 'OBJECT': 762 | bpy.ops.object.select_all(action='DESELECT') 763 | self.point.select = True 764 | self.point.location = ms_loc_3d 765 | ''' 766 | elif ed_type == 'EDIT_MESH': 767 | bpy.ops.mesh.select_all(action='DESELECT') 768 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 769 | bm.verts[-1].select = True 770 | inver_mw = bpy.context.edit_object.matrix_world.inverted() 771 | local_co = inver_mw * ms_loc_3d 772 | bm.verts[-1].co = local_co 773 | editmode_refresh(ed_type) 774 | ''' 775 | #snap_co = self.get_co(ms_loc_2d) 776 | #print("dist moved:", (snap_co - ms_loc_3d).length) # debug 777 | bpy.ops.transform.translate('INVOKE_DEFAULT') 778 | 779 | # Makes sure only the "guide point" object or vert 780 | # added with create is deleted. 781 | def remove(self, ed_type, sel_backup=None): 782 | if ed_type == 'OBJECT': 783 | bpy.ops.object.select_all(action='DESELECT') 784 | self.point.select = True 785 | bpy.ops.object.delete() 786 | ''' 787 | elif ed_type == 'EDIT_MESH': 788 | bpy.ops.mesh.select_all(action='DESELECT') 789 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 790 | bm.verts[-1].select = True 791 | editmode_refresh(ed_type) 792 | bpy.ops.mesh.delete(type='VERT') 793 | ''' 794 | self.point = None 795 | #sel_backup.restore_selected(ed_type) 796 | 797 | def get_co(self, ed_type): 798 | if self.mode == 'OBJECT': 799 | return self.point.location.copy() 800 | 801 | def move(self, ed_type, new_co): 802 | if ed_type == 'OBJECT': 803 | self.point.location = new_co.copy() 804 | 805 | 806 | def exit_addon(self): 807 | if self.curr_ed_type == 'EDIT_MESH': 808 | bpy.ops.object.editmode_toggle() 809 | self.curr_ed_type = bpy.context.mode 810 | if self.force_quit: 811 | self.force_quit = False 812 | self.snap.remove(self.curr_ed_type, self.sel_backup) 813 | #print("self.curr_ed_type", self.curr_ed_type) # debug 814 | #print("self.stage", self.stage) # debug 815 | #print("self.force_quit", self.force_quit) # debug 816 | restore_blender_settings(self.settings_backup) 817 | #print("\n\nAdd-On Exited!\n") # debug 818 | 819 | 820 | def warp_cursor(self, context, dest_co): 821 | if dest_co is None: 822 | return 823 | area = context.area 824 | win = None 825 | for r in area.regions: 826 | if r.type == "WINDOW": 827 | win = r 828 | break 829 | if win is not None: 830 | warpco = dest_co[0] + win.x, dest_co[1] + win.y 831 | context.window.cursor_warp(*warpco) 832 | 833 | 834 | # called when self.stage == PLACE_3RD 835 | def update_arch(self, snap): 836 | if self.paused: 837 | return 838 | 839 | self.piv_norm = geometry.normal(self.pts[0], self.cent, snap) 840 | 841 | # exit function if piv_norm or snap have values that prevent rotations 842 | if self.piv_norm == Vector() or snap in self.pts: 843 | self.bad_input = True 844 | return 845 | else: 846 | self.bad_input = False 847 | 848 | # create pos and neg endpoints for determining where to create arch 849 | rot_pos, rot_neg = self.mov_aligned.copy(), self.mov_aligned.copy() 850 | rot_pos.rotate(Quaternion(self.piv_norm, self.rad90)) 851 | rot_neg.rotate(Quaternion(self.piv_norm,-self.rad90)) 852 | rot_pos = rot_pos + self.cent 853 | rot_neg = rot_neg + self.cent 854 | 855 | hgt = (snap - self.cent).length 856 | radius = None 857 | if hgt != 0: 858 | radius = (hgt / 2) + (self.wid**2) / (8 * hgt) 859 | else: 860 | radius = 0 861 | cen_to_piv = radius - hgt 862 | scale = cen_to_piv / (self.wid / 2) 863 | circ_cen_p = self.cent.lerp(rot_pos, scale) 864 | circ_cen_n = self.cent.lerp(rot_neg, scale) 865 | align_p0 = self.pts[0] - circ_cen_p 866 | align_p1 = self.pts[1] - circ_cen_p 867 | self.ang_meas = align_p0.angle(align_p1, 0.0) 868 | if self.ang_meas == 0.0: 869 | self.bad_input = True 870 | return 871 | else: 872 | self.bad_input = False 873 | 874 | if radius > self.wid/2 and hgt > radius: 875 | self.ang_meas = 2 * pi - self.ang_meas 876 | 877 | dist_sn_to_pos = (rot_pos - snap).length 878 | dist_sn_to_neg = (rot_neg - snap).length 879 | 880 | if dist_sn_to_pos > dist_sn_to_neg: # closer to negative 881 | self.new_pts = self.pts[1], self.pts[0] 882 | self.circ_cen = circ_cen_p 883 | 884 | else: # dist_sn_to_pos < dist_sn_to_neg / closer to positive 885 | self.new_pts = self.pts[0], self.pts[1] 886 | self.circ_cen = circ_cen_n 887 | 888 | 889 | def click_handler(self, context): 890 | snap = self.snap.get_co(self.curr_ed_type) 891 | 892 | if self.paused: 893 | return 894 | 895 | elif self.stage == PLACE_1ST: 896 | add_pt(self, snap) 897 | self.prev_co = self.pts[-1].copy() 898 | self.stage += 1 899 | self.snap.grab(self.curr_ed_type) 900 | 901 | elif self.stage == PLACE_2ND: 902 | #draw_line 903 | if snap not in self.pts: 904 | add_pt(self, snap) 905 | self.stage += 1 906 | # move snap point to arch center before turning grab mode back on 907 | # as axis locks work from where an object was grabbed 908 | self.cent = self.pts[0].lerp(self.pts[1], 0.5) 909 | self.mov_aligned = self.pts[0] - self.cent 910 | self.wid = (self.pts[0] - self.pts[1]).length 911 | 912 | self.prev_co = self.cent.copy() 913 | self.snap.move(self.curr_ed_type, self.cent) 914 | cent2d = loc3d_to_reg2d(self.reg, self.rv3d, self.cent) 915 | warp_cursor(self, context, cent2d) 916 | self.snap.grab(self.curr_ed_type) 917 | 918 | elif self.stage == PLACE_3RD: 919 | # draw_arch 920 | if not self.bad_input: 921 | update_gui(self) 922 | add_pt(self, snap) 923 | self.stage += 1 924 | 925 | self.snap.move(self.curr_ed_type, self.circ_cen.copy()) 926 | bpy.ops.object.editmode_toggle() 927 | self.curr_ed_type = context.mode 928 | inv_mw = self.snap.point.matrix_world.inverted() 929 | piv_cent = inv_mw * self.circ_cen 930 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 931 | bm.verts.new(inv_mw * self.new_pts[0]) 932 | # Spin and deal with geometry on side 'a' 933 | edges_start_a = bm.edges[:] 934 | geom_start_a = bm.verts[:] + edges_start_a 935 | ret = bmesh.ops.spin( 936 | bm, 937 | geom=geom_start_a, 938 | angle=self.ang_meas, 939 | steps=self.segm_cnt, 940 | axis=self.piv_norm, 941 | cent=piv_cent) 942 | #edges_end_a = [ele for ele in ret["geom_last"] 943 | # if isinstance(ele, bmesh.types.BMEdge)] 944 | del ret 945 | if not self.extr_enabled: 946 | self.stage = EXIT 947 | else: 948 | bpy.context.scene.cursor_location = self.circ_cen 949 | bpy.context.tool_settings.snap_target = 'ACTIVE' 950 | bpy.context.space_data.pivot_point = 'CURSOR' 951 | bpy.context.space_data.transform_orientation = 'GLOBAL' 952 | bpy.ops.mesh.select_all(action='SELECT') 953 | bpy.ops.mesh.extrude_region_move() 954 | bpy.ops.transform.resize('INVOKE_DEFAULT', 955 | constraint_orientation='GLOBAL') 956 | 957 | self.stage = ARCH_EXTRUDE_1 958 | else: 959 | self.snap.grab(self.curr_ed_type) 960 | 961 | elif self.stage == ARCH_EXTRUDE_1: 962 | bpy.context.tool_settings.mesh_select_mode = False, False, True 963 | #bpy.context.space_data.transform_orientation = 'LOCAL' 964 | bpy.context.tool_settings.snap_target = 'CLOSEST' 965 | bpy.context.space_data.pivot_point = 'MEDIAN_POINT' 966 | update_gui(self) 967 | bpy.ops.mesh.select_all(action='SELECT') 968 | bpy.ops.view3d.edit_mesh_extrude_move_normal('INVOKE_DEFAULT') 969 | self.stage = ARCH_EXTRUDE_2 970 | 971 | elif self.stage == ARCH_EXTRUDE_2: 972 | self.stage = EXIT 973 | 974 | 975 | # To-Do : make something nicer than this for handling GUI changes... 976 | def update_gui(self): 977 | title_txt_sz = 24 978 | bar_txt_sz = 12 979 | #bar_txt_sz = 18 # tablet 980 | self.helpdisp.clear_str() 981 | if not self.paused: 982 | if self.stage < ARCH_EXTRUDE_1: 983 | self.helpdisp.add_str( 984 | "INS", 985 | "place 3 points to create arch", 986 | title_txt_sz, 987 | 'C') 988 | self.helpdisp.add_str( 989 | "TOP", 990 | "LMB - place point, CTRL - snap point, " 991 | "XYZ - add axis lock, C - clear axis lock", 992 | bar_txt_sz, 993 | 'L') 994 | self.helpdisp.add_str( 995 | "BOT", 996 | "SPACE - pause to navigate / change settings", 997 | bar_txt_sz, 998 | 'L') 999 | self.helpdisp.add_str( 1000 | "BOT", 1001 | "ESC, RMB - quit", 1002 | bar_txt_sz, 1003 | 'R') 1004 | elif self.stage == ARCH_EXTRUDE_1: 1005 | self.helpdisp.add_str( 1006 | "INS", 1007 | "set arch width / thickness", 1008 | title_txt_sz, 1009 | 'C') 1010 | self.helpdisp.add_str( 1011 | "TOP", 1012 | "LMB - confirm width", 1013 | bar_txt_sz, 1014 | 'L') 1015 | self.helpdisp.add_str( 1016 | "BOT", 1017 | "", 1018 | bar_txt_sz, 1019 | 'L') 1020 | self.helpdisp.add_str( 1021 | "BOT", 1022 | "ESC, RMB - quit", 1023 | bar_txt_sz, 1024 | 'R') 1025 | else: 1026 | self.helpdisp.add_str( 1027 | "INS", 1028 | "set arch length", 1029 | title_txt_sz, 1030 | 'C') 1031 | self.helpdisp.add_str( 1032 | "TOP", 1033 | "LMB - confirm length, CTRL - snap point", 1034 | bar_txt_sz, 1035 | 'L') 1036 | self.helpdisp.add_str( 1037 | "BOT", 1038 | "", 1039 | bar_txt_sz, 1040 | 'L') 1041 | self.helpdisp.add_str( 1042 | "BOT", 1043 | "ESC, RMB - quit", 1044 | bar_txt_sz, 1045 | 'R') 1046 | else: 1047 | if self.stage < ARCH_EXTRUDE_1: 1048 | self.helpdisp.add_str( 1049 | "INS", 1050 | "paused, navigate or change settings", 1051 | title_txt_sz, 1052 | 'C') 1053 | self.helpdisp.add_str( 1054 | "TOP", 1055 | "UP / MSWH_UP - increase segments, " 1056 | "DOWN / MSWH_DOWN - decrease segments", 1057 | bar_txt_sz, 1058 | 'L') 1059 | self.helpdisp.add_str( 1060 | "BOT", 1061 | "SPACE - resume point placement, R - reset point placement", 1062 | bar_txt_sz, 1063 | 'L') 1064 | self.helpdisp.add_str( 1065 | "BOT", 1066 | "ESC, RMB - quit", 1067 | bar_txt_sz, 1068 | 'R') 1069 | else: 1070 | self.helpdisp.add_str( 1071 | "INS", 1072 | "paused, navigate to better position", 1073 | title_txt_sz, 1074 | 'C') 1075 | self.helpdisp.add_str( 1076 | "TOP", 1077 | "", 1078 | bar_txt_sz, 1079 | 'L') 1080 | self.helpdisp.add_str( 1081 | "BOT", 1082 | "SPACE - resume extrude", 1083 | bar_txt_sz, 1084 | 'L') 1085 | self.helpdisp.add_str( 1086 | "BOT", 1087 | "ESC, RMB - quit", 1088 | bar_txt_sz, 1089 | 'R') 1090 | self.helpdisp.update() 1091 | 1092 | 1093 | def retreive_settings(arg): 1094 | settings_dict = {} 1095 | if arg == "csc_default_grey": 1096 | settings_dict.update( 1097 | col_font_np = (0.95, 0.95, 0.95, 1.0), 1098 | col_font_instruct_main = (0.67, 0.67, 0.67, 1.0), 1099 | col_font_instruct_shadow = (0.15, 0.15, 0.15, 1.0), 1100 | col_font_keys = (0.15, 0.15, 0.15, 1.0), 1101 | col_field_keys_aff = (0.51, 0.51, 0.51, 1.0), 1102 | col_field_keys_neg = (0.41, 0.41, 0.41, 1.0), 1103 | 1104 | col_line_main = (0.9, 0.9, 0.9, 1.0), 1105 | col_line_shadow = (0.1, 0.1, 0.1, 0.25), 1106 | col_num_main = (0.95, 0.95, 0.95, 1.0), 1107 | col_num_shadow = (0.0, 0.0, 0.0, 0.75), 1108 | 1109 | col_gw_line_cross = (0.25, 0.35, 0.4, 0.87), 1110 | col_gw_line_base_free = (1.0, 1.0, 1.0, 0.85), 1111 | col_gw_line_base_lock_x = (1.0, 0.0, 0.0, 1.0), 1112 | col_gw_line_base_lock_y = (0.5, 0.75, 0.0, 1.0), 1113 | col_gw_line_base_lock_z = (0.0, 0.2, 0.85, 1.0), 1114 | col_gw_line_base_lock_arb = (0.0, 0.0, 0.0, 0.5), 1115 | col_gw_line_all = (1.0, 1.0, 1.0, 0.85), 1116 | 1117 | col_gw_fill_base_x = (1.0, 0.0, 0.0, 0.2), 1118 | col_gw_fill_base_y = (0.0, 1.0, 0.0, 0.2), 1119 | col_gw_fill_base_z = (0.0, 0.2, 0.85, 0.2), 1120 | col_gw_fill_base_arb = (0.0, 0.0, 0.0, 0.15), 1121 | 1122 | col_bg_fill_main_run = (1.0, 0.5, 0.0, 1.0), 1123 | col_bg_fill_main_nav = (0.5, 0.75 ,0.0 ,1.0), 1124 | col_bg_fill_square = (0.0, 0.0, 0.0, 1.0), 1125 | col_bg_fill_aux = (0.4, 0.15, 0.75, 1.0), 1126 | 1127 | col_bg_line_symbol = (1.0, 1.0, 1.0, 1.0), 1128 | col_bg_font_main = (1.0, 1.0, 1.0, 1.0), 1129 | col_bg_font_aux = (1.0, 1.0, 1.0, 1.0) 1130 | ) 1131 | elif arg == "csc_school_marine": 1132 | settings_dict.update( 1133 | col_font_np = (0.25, 0.35, 0.4, 0.87), 1134 | col_font_instruct_main = (1.0, 1.0, 1.0, 1.0), 1135 | col_font_instruct_shadow = (0.25, 0.35, 0.4, 0.6), 1136 | col_font_keys = (1.0, 1.0, 1.0, 1.0), 1137 | col_field_keys_aff = (0.55, 0.6, 0.64, 1.0), 1138 | col_field_keys_neg = (0.67, 0.72, 0.76, 1.0), 1139 | 1140 | col_line_main = (1.0, 1.0, 1.0, 1.0), 1141 | col_line_shadow = (0.1, 0.1, 0.1, 1.0), 1142 | #col_line_shadow = (0.1, 0.1, 0.1, 0.25), 1143 | col_num_main = (0.25, 0.35, 0.4, 1.0), #(1.0, 0.5, 0.0, 1.0) 1144 | col_num_shadow = (1.0, 1.0, 1.0, 1.0), 1145 | 1146 | col_gw_line_cross = (0.25, 0.35, 0.4, 0.87), 1147 | col_gw_line_base_free = (1.0, 1.0, 1.0, 0.85), 1148 | col_gw_line_base_lock_x = (1.0, 0.0, 0.0, 1.0), 1149 | col_gw_line_base_lock_y = (0.5, 0.75, 0.0, 1.0), 1150 | col_gw_line_base_lock_z = (0.0, 0.2, 0.85, 1.0), 1151 | col_gw_line_base_lock_arb = (0.0, 0.0, 0.0, 0.5), 1152 | col_gw_line_all = (1.0, 1.0, 1.0, 0.85), 1153 | 1154 | col_gw_fill_base_x = (1.0, 0.0, 0.0, 0.2), 1155 | col_gw_fill_base_y = (0.0, 1.0, 0.0, 0.2), 1156 | col_gw_fill_base_z = (0.0, 0.2, 0.85, 0.2), 1157 | col_gw_fill_base_arb = (0.0, 0.0, 0.0, 0.15), 1158 | 1159 | col_bg_fill_main_run = (1.0, 0.5, 0.0, 1.0), 1160 | col_bg_fill_main_nav = (0.5, 0.75 ,0.0 ,1.0), 1161 | col_bg_fill_square = (0.0, 0.0, 0.0, 1.0), 1162 | col_bg_fill_aux = (0.4, 0.15, 0.75, 1.0), 1163 | #(0.4, 0.15, 0.75, 1.0) (0.2, 0.15, 0.55, 1.0) ? 1164 | col_bg_line_symbol = (1.0, 1.0, 1.0, 1.0), 1165 | col_bg_font_main = (1.0, 1.0, 1.0, 1.0), 1166 | col_bg_font_aux = (1.0, 1.0, 1.0, 1.0) 1167 | ) 1168 | elif arg == "def_blender_gray": 1169 | settings_dict.update( 1170 | # commented == not changed from csc_school_marine 1171 | #col_font_np = (0.25, 0.35, 0.4, 0.87), # logo 1172 | col_font_instruct_main = (0.9, 0.9, 0.9, 1.0), 1173 | col_font_instruct_shadow = (0.3, 0.3, 0.3, 1.0), 1174 | col_font_keys = (0.01, 0.01, 0.01, 1.0), 1175 | col_field_keys_aff = (0.55, 0.55, 0.55, 1.0), 1176 | col_field_keys_neg = (0.36, 0.36, 0.36, 1.0), 1177 | 1178 | col_line_main = (95.0, 95.0, 95.0, 1.0), 1179 | #col_line_shadow = (0.1, 0.1, 0.1, 1.0), 1180 | col_num_main = (0.85, 0.85, 0.85, 1.0), 1181 | col_num_shadow = (0.2, 0.2, 0.2, 1.0), 1182 | 1183 | #col_gw_line_cross = (0.25, 0.35, 0.4, 0.87), 1184 | #col_gw_line_base_free = (1.0, 1.0, 1.0, 0.85), 1185 | #col_gw_line_base_lock_x = (1.0, 0.0, 0.0, 1.0), 1186 | #col_gw_line_base_lock_y = (0.5, 0.75, 0.0, 1.0), 1187 | #col_gw_line_base_lock_z = (0.0, 0.2, 0.85, 1.0), 1188 | #col_gw_line_base_lock_arb = (0.0, 0.0, 0.0, 0.5), 1189 | #col_gw_line_all = (1.0, 1.0, 1.0, 0.85), 1190 | 1191 | #col_gw_fill_base_x = (1.0, 0.0, 0.0, 0.2), 1192 | #col_gw_fill_base_y = (0.0, 1.0, 0.0, 0.2), 1193 | #col_gw_fill_base_z = (0.0, 0.2, 0.85, 0.2), 1194 | #col_gw_fill_base_arb = (0.0, 0.0, 0.0, 0.15), 1195 | 1196 | #col_bg_fill_main_run = (1.0, 0.5, 0.0, 1.0), 1197 | #col_bg_fill_main_nav = (0.5, 0.75 ,0.0 ,1.0), 1198 | #col_bg_fill_square = (0.0, 0.0, 0.0, 1.0), 1199 | #col_bg_fill_aux = (0.4, 0.15, 0.75, 1.0), 1200 | 1201 | #col_bg_line_symbol = (1.0, 1.0, 1.0, 1.0), 1202 | #col_bg_font_main = (1.0, 1.0, 1.0, 1.0), 1203 | #col_bg_font_aux = (1.0, 1.0, 1.0, 1.0) 1204 | ) 1205 | return settings_dict 1206 | 1207 | 1208 | def draw_callback_px(self, context): 1209 | reg = bpy.context.region 1210 | rv3d = bpy.context.region_data 1211 | snap = self.snap.get_co(self.curr_ed_type) 1212 | pts2d = [] 1213 | line_pts = [] 1214 | 1215 | if self.stage == PLACE_1ST: 1216 | guide2d = loc3d_to_reg2d(reg, rv3d, snap) 1217 | 1218 | elif self.stage == PLACE_2ND: 1219 | guide2d = loc3d_to_reg2d(reg, rv3d, snap) 1220 | pts2d = [loc3d_to_reg2d(reg, rv3d, i) for i in self.pts] 1221 | line_pts = self.pts[0], snap 1222 | if not len(pts2d) > 0: 1223 | print("len(pts2d) == 0") 1224 | 1225 | elif self.stage == PLACE_3RD: 1226 | guide2d = loc3d_to_reg2d(reg, rv3d, snap) 1227 | pts2d = [loc3d_to_reg2d(reg, rv3d, i) for i in self.pts] 1228 | 1229 | # attempt to draw arch 1230 | update_arch(self, snap) 1231 | if not self.bad_input and self.circ_cen is not None: 1232 | arch_top = get_rotated_pt(self.circ_cen, self.new_pts[0], 1233 | self.ang_meas/2, self.piv_norm) 1234 | line_pts = self.cent, arch_top 1235 | 1236 | draw_circ_arch_3D(self.segm_cnt, self.new_pts, self.circ_cen, 1237 | self.ang_meas, self.piv_norm, Colr.green, reg, rv3d) 1238 | else: 1239 | if len(pts2d) > 1: 1240 | draw_line_2D(pts2d[0], pts2d[1], Colr.white) 1241 | 1242 | elif self.stage == ARCH_EXTRUDE_1: 1243 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 1244 | if hasattr(bm.verts, "ensure_lookup_table"): 1245 | bm.verts.ensure_lookup_table() 1246 | vts = bm.verts 1247 | vert_cnt = self.segm_cnt + 1 1248 | v_cent1_idx = vert_cnt // 2 1249 | v_cent2_idx = v_cent1_idx + vert_cnt 1250 | m_w = bpy.context.edit_object.matrix_world 1251 | v1 = m_w * vts[v_cent1_idx].co 1252 | v2 = m_w * vts[v_cent2_idx].co 1253 | line_pts = v1, v2 1254 | 1255 | pts_cust = self.pts[0], self.pts[1], v1 1256 | pts2d = [loc3d_to_reg2d(reg, rv3d, i) for i in pts_cust] 1257 | guide2d = loc3d_to_reg2d(reg, rv3d, v2) 1258 | 1259 | elif self.stage == ARCH_EXTRUDE_2: 1260 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 1261 | if hasattr(bm.verts, "ensure_lookup_table"): 1262 | bm.verts.ensure_lookup_table() 1263 | vts = bm.verts 1264 | vert_cnt = self.segm_cnt + 1 1265 | v_cent1_idx = vert_cnt // 2 1266 | v_cent2_idx = v_cent1_idx + (vert_cnt * 2) 1267 | m_w = bpy.context.edit_object.matrix_world 1268 | v1 = m_w * vts[v_cent1_idx].co 1269 | v2 = m_w * vts[v_cent2_idx].co 1270 | line_pts = v1, v2 1271 | pts2d = [loc3d_to_reg2d(reg, rv3d, v1)] 1272 | guide2d = loc3d_to_reg2d(reg, rv3d, v2) 1273 | 1274 | if line_pts != []: 1275 | self.mean_dist.draw(line_pts, self.meas_mult, self.meas_suff) 1276 | for i in pts2d: 1277 | draw_pt_2D(i, Colr.white) 1278 | draw_pt_2D(guide2d, Colr.green) 1279 | 1280 | # display number of segments 1281 | if self.paused and self.stage < ARCH_EXTRUDE_1: 1282 | if guide2d is not None: 1283 | self.segm_cntr.draw(self.segm_cnt, guide2d) 1284 | #self.segm_cntr.draw(self.segm_cnt, self.mouse_loc) 1285 | 1286 | self.helpdisp.draw() 1287 | 1288 | 1289 | # To-Do : move to DrawSegmCounter? 1290 | def segm_decrm(self): 1291 | if self.segm_cnt > 2: 1292 | self.segm_cnt -= 1 1293 | 1294 | 1295 | class TPARCH_OT_modal(bpy.types.Operator): 1296 | '''Launch the arch tool''' 1297 | bl_idname = "view3d.modal_arch_tool" 1298 | bl_label = "Three Point Arch Tool" 1299 | 1300 | # Only launch Add-On from OBJECT or EDIT modes 1301 | @classmethod 1302 | def poll(self, context): 1303 | return context.mode == 'OBJECT' or context.mode == 'EDIT_MESH' 1304 | 1305 | def modal(self, context, event): 1306 | context.area.tag_redraw() 1307 | self.curr_ed_type = context.mode 1308 | 1309 | if event.type in {'MIDDLEMOUSE', 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 1310 | 'NUMPAD_4', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}: 1311 | return {'PASS_THROUGH'} 1312 | 1313 | if event.type == 'MOUSEMOVE': 1314 | self.mouse_loc = Vector((event.mouse_region_x, event.mouse_region_y)) 1315 | 1316 | if event.type in {'RET', 'LEFTMOUSE'} and event.value == 'RELEASE': 1317 | click_handler(self, context) 1318 | 1319 | if event.type == 'SPACE' and event.value == 'RELEASE': 1320 | if not self.paused: 1321 | self.paused = True 1322 | update_gui(self) 1323 | else: 1324 | self.paused = False 1325 | update_gui(self) 1326 | if self.stage < ARCH_EXTRUDE_1: 1327 | self.snap.grab(self.curr_ed_type) 1328 | elif self.stage == ARCH_EXTRUDE_1: 1329 | bpy.ops.transform.resize('INVOKE_DEFAULT', 1330 | constraint_orientation = 'GLOBAL') 1331 | else: 1332 | bpy.ops.transform.translate('INVOKE_DEFAULT', 1333 | constraint_axis=(False, False, True), 1334 | constraint_orientation='NORMAL', 1335 | release_confirm=True) 1336 | 1337 | if self.paused: 1338 | if self.stage < ARCH_EXTRUDE_1: 1339 | if event.type == 'WHEELUPMOUSE': 1340 | self.segm_cnt += 1 1341 | 1342 | if event.type == 'WHEELDOWNMOUSE': 1343 | segm_decrm(self) 1344 | 1345 | if event.type == 'UP_ARROW' and event.value == 'RELEASE': 1346 | self.segm_cnt += 1 1347 | 1348 | if event.type == 'DOWN_ARROW' and event.value == 'RELEASE': 1349 | segm_decrm(self) 1350 | 1351 | if event.type == 'R' and event.value == 'RELEASE': 1352 | self.paused = False 1353 | update_gui(self) 1354 | if self.prev_co is not None: 1355 | last2d = loc3d_to_reg2d(self.reg, self.rv3d, self.prev_co) 1356 | self.snap.move(self.curr_ed_type, self.prev_co) 1357 | warp_cursor(self, context, last2d) 1358 | self.snap.grab(self.curr_ed_type) 1359 | else: 1360 | self.snap.mouse_grab(self.mouse_loc, self.curr_ed_type) 1361 | 1362 | # start debug console 1363 | ''' 1364 | if event.type == 'D' and event.value == 'RELEASE': 1365 | __import__('code').interact(local=dict(globals(), **locals())) 1366 | 1367 | if self.force_quit: 1368 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 1369 | exit_addon(self) 1370 | return {'CANCELLED'} 1371 | ''' 1372 | if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': 1373 | #print("pressed ESC or RIGHTMOUSE") # debug 1374 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 1375 | self.force_quit = True 1376 | exit_addon(self) 1377 | return {'CANCELLED'} 1378 | 1379 | if self.stage == EXIT: 1380 | if self.curr_ed_type == 'EDIT_MESH': 1381 | # recalc normals outside just in case they were inverted 1382 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 1383 | bmesh.ops.recalc_face_normals(bm, faces=bm.faces) 1384 | bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 1385 | exit_addon(self) 1386 | return {'FINISHED'} 1387 | 1388 | return {'RUNNING_MODAL'} 1389 | 1390 | def invoke(self, context, event): 1391 | if context.area.type == 'VIEW_3D': 1392 | args = (self, context) 1393 | 1394 | # Add the region OpenGL drawing callback 1395 | # draw in view space with 'POST_VIEW' and 'PRE_VIEW' 1396 | self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, 1397 | args, 'WINDOW', 'POST_PIXEL') 1398 | 1399 | if context.mode == 'EDIT_MESH': 1400 | bpy.ops.object.editmode_toggle() 1401 | bpy.ops.object.select_all(action='DESELECT') 1402 | 1403 | addon_prefs = context.user_preferences.addons[__name__].preferences 1404 | #sett_dict = retreive_settings(addon_prefs.np_col_scheme) 1405 | sett_dict = retreive_settings("def_blender_gray") 1406 | 1407 | self.helpdisp = HelpDisplay(context.region, sett_dict) 1408 | self.mean_dist = DrawMeanDistance(18, sett_dict) 1409 | self.segm_cntr = DrawSegmCounter(sett_dict) 1410 | self.curr_ed_type = context.mode # current Blender Editor Type 1411 | self.stage = PLACE_1ST 1412 | self.mouse_loc = Vector((event.mouse_region_x, event.mouse_region_y)) 1413 | self.reg = bpy.context.region 1414 | self.rv3d = bpy.context.region_data 1415 | self.piv_norm = None 1416 | self.segm_cnt = addon_prefs.segm_cnt # move to DrawSegmCounter? 1417 | self.meas_mult = addon_prefs.np_scale_dist 1418 | self.meas_suff = '' 1419 | self.pt_cnt = 0 1420 | self.pts = [] 1421 | self.new_pts = None 1422 | self.prev_co = None # previous coordinate 1423 | self.cent = None 1424 | self.ang_meas = None 1425 | self.circ_cen = None 1426 | self.wid = None 1427 | self.rad90 = radians(90) 1428 | self.mov_aligned = None 1429 | self.snap = SnapPoint() 1430 | self.settings_backup = backup_blender_settings() 1431 | self.sel_backup = None # place holder 1432 | self.bad_input = False 1433 | self.extr_enabled = addon_prefs.extr_enabled 1434 | #self.debug_flag = False 1435 | self.paused = False 1436 | self.force_quit = False 1437 | 1438 | tmp_suff = addon_prefs.np_suffix_dist 1439 | if tmp_suff != 'None': 1440 | self.meas_suff = tmp_suff 1441 | 1442 | context.window_manager.modal_handler_add(self) 1443 | 1444 | init_blender_settings() 1445 | update_gui(self) 1446 | self.snap.create(self.mouse_loc, self.curr_ed_type) 1447 | #print("Add-on started!") # debug 1448 | 1449 | return {'RUNNING_MODAL'} 1450 | else: 1451 | self.report({'WARNING'}, "View3D not found, cannot run operator") 1452 | return {'CANCELLED'} 1453 | 1454 | 1455 | class TPARCH_PT_panel(bpy.types.Panel): 1456 | # Creates a panel in the 3d view Toolshelf window 1457 | bl_label = 'Arch Panel' 1458 | bl_idname = 'TPArch_base_panel' 1459 | bl_space_type = 'VIEW_3D' 1460 | bl_region_type = 'TOOLS' 1461 | bl_context = 'objectmode' 1462 | bl_category = 'Tools' 1463 | 1464 | def draw(self, context): 1465 | #layout = self.layout 1466 | row = self.layout.row(align=True) 1467 | row.operator("view3d.modal_arch_tool", text="Create Arch", icon="SPHERECURVE") 1468 | 1469 | 1470 | def register(): 1471 | bpy.utils.register_class(TPARCH_prefs) 1472 | bpy.utils.register_class(TPARCH_OT_modal) 1473 | bpy.utils.register_class(TPARCH_PT_panel) 1474 | 1475 | def unregister(): 1476 | bpy.utils.unregister_class(TPARCH_PT_panel) 1477 | bpy.utils.unregister_class(TPARCH_OT_modal) 1478 | bpy.utils.unregister_class(TPARCH_prefs) 1479 | 1480 | if __name__ == "__main__": 1481 | register() 1482 | --------------------------------------------------------------------------------