1 # ##### BEGIN GPL LICENSE BLOCK #####
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 3
6 # of the License, or (at your option) any later version.
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.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 # ##### END GPL LICENSE BLOCK #####
18 # Contact for more information about the Addon:
19 # Email: germano.costa@ig.com.br
20 # Twitter: wii_mano @mano_wii
24 "name": "Snap Utilities Line",
25 "author": "Germano Cavalcante",
27 "blender": (2, 75, 0),
28 "location": "View3D > TOOLS > Snap Utilities > snap utilities",
29 "description": "Extends Blender Snap controls",
30 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/Snap_Utils_Line",
36 from mathutils
import Vector
37 from mathutils
.geometry
import (
43 from bpy
.types
import (
48 from bpy
.props
import (
57 ## from .snap_framebuffer_debug import screenTexture
58 ## from .snap_context import mesh_drawing
61 def get_units_info(scale
, unit_system
, separate_units
):
62 if unit_system
== 'METRIC':
63 scale_steps
= ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
64 (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
65 elif unit_system
== 'IMPERIAL':
66 scale_steps
= ((5280, 'mi'), (1, '\''),
67 (1 / 12, '"'), (1 / 12000, 'thou'))
68 scale
/= 0.3048 # BU to feet
70 scale_steps
= ((1, ' BU'),)
71 separate_units
= False
73 return (scale
, scale_steps
, separate_units
)
76 def convert_distance(val
, units_info
, precision
=5):
77 scale
, scale_steps
, separate_units
= units_info
80 while idx
< len(scale_steps
) - 1:
81 if sval
>= scale_steps
[idx
][0]:
84 factor
, suffix
= scale_steps
[idx
]
86 if not separate_units
or idx
== len(scale_steps
) - 1:
87 dval
= str(round(sval
, precision
)) + suffix
90 dval
= str(round(ival
, precision
)) + suffix
93 while idx
< len(scale_steps
):
94 fval
*= scale_steps
[idx
- 1][0] / scale_steps
[idx
][0]
105 def location_3d_to_region_2d(region
, rv3d
, coord
):
106 prj
= rv3d
.perspective_matrix
* Vector((coord
[0], coord
[1], coord
[2], 1.0))
107 width_half
= region
.width
/ 2.0
108 height_half
= region
.height
/ 2.0
109 return Vector((width_half
+ width_half
* (prj
.x
/ prj
.w
),
110 height_half
+ height_half
* (prj
.y
/ prj
.w
),
115 def out_Location(rv3d
, region
, orig
, vector
):
116 view_matrix
= rv3d
.view_matrix
117 v1
= Vector((int(view_matrix
[0][0] * 1.5), int(view_matrix
[0][1] * 1.5), int(view_matrix
[0][2] * 1.5)))
118 v2
= Vector((int(view_matrix
[1][0] * 1.5), int(view_matrix
[1][1] * 1.5), int(view_matrix
[1][2] * 1.5)))
120 hit
= intersect_ray_tri(Vector((1, 0, 0)), Vector((0, 1, 0)), Vector(), (vector
), (orig
), False)
122 hit
= intersect_ray_tri(v1
, v2
, Vector(), (vector
), (orig
), False)
124 hit
= intersect_ray_tri(v1
, v2
, Vector(), (-vector
), (orig
), False)
130 def get_closest_edge(bm
, point
, dist
):
132 for edge
in bm
.edges
:
133 v1
= edge
.verts
[0].co
134 v2
= edge
.verts
[1].co
135 # Test the BVH (AABB) first
138 isect
= v1
[i
] - dist
<= point
[i
] <= v2
[i
] + dist
140 isect
= v2
[i
] - dist
<= point
[i
] <= v1
[i
] + dist
145 ret
= intersect_point_line(point
, v1
, v2
)
154 new_dist
= (point
- tmp
).length
176 bm_geom_selected
= None
181 cache
, context
, obj_matrix_world
,
184 previous_vert
= None,
187 rv3d
= context
.region_data
188 region
= context
.region
189 scene
= context
.scene
196 if cache
.bm_geom_selected
:
198 cache
.bm_geom_selected
.select
= False
199 except ReferenceError as e
:
202 snp_obj
, loc
, elem
= sctx
.snap_get(mcursor
)
203 view_vector
, orig
= sctx
.last_ray
208 end
= orig
+ view_vector
209 t_loc
= intersect_line_line(constrain
[0], constrain
[1], orig
, end
)
215 r_loc
= out_Location(rv3d
, region
, orig
, view_vector
)
217 elif snp_obj
.data
[0] != obj
: #OUT
222 r_loc
= intersect_point_line(r_loc
, constrain
[0], constrain
[1])[0]
224 r_loc
= out_Location(rv3d
, region
, orig
, view_vector
)
237 bm_geom
= bm
.verts
[elem
[0]]
239 if cache
.bvert
!= bm_geom
:
240 cache
.bvert
= bm_geom
242 #cache.v2d = location_3d_to_region_2d(region, rv3d, cache.vco)
245 r_loc
= intersect_point_line(cache
.vco
, constrain
[0], constrain
[1])[0]
250 v1
= bm
.verts
[elem
[0]]
251 v2
= bm
.verts
[elem
[1]]
252 bm_geom
= bm
.edges
.get([v1
, v2
])
254 if cache
.bedge
!= bm_geom
:
255 cache
.bedge
= bm_geom
256 cache
.v0
= obj_matrix_world
* v1
.co
257 cache
.v1
= obj_matrix_world
* v2
.co
258 cache
.vmid
= 0.5 * (cache
.v0
+ cache
.v1
)
259 cache
.v2d0
= location_3d_to_region_2d(region
, rv3d
, cache
.v0
)
260 cache
.v2d1
= location_3d_to_region_2d(region
, rv3d
, cache
.v1
)
261 cache
.v2dmid
= location_3d_to_region_2d(region
, rv3d
, cache
.vmid
)
263 if previous_vert
and previous_vert
not in {v1
, v2
}:
264 pvert_co
= obj_matrix_world
* previous_vert
.co
265 perp_point
= intersect_point_line(pvert_co
, cache
.v0
, cache
.v1
)
266 cache
.vperp
= perp_point
[0]
267 #factor = point_perpendicular[1]
268 cache
.v2dperp
= location_3d_to_region_2d(region
, rv3d
, perp_point
[0])
270 #else: cache.v2dperp = None
273 t_loc
= intersect_line_line(constrain
[0], constrain
[1], cache
.v0
, cache
.v1
)
276 end
= orig
+ view_vector
277 t_loc
= intersect_line_line(constrain
[0], constrain
[1], orig
, end
)
280 elif cache
.v2dperp
and\
281 abs(cache
.v2dperp
[0] - mcursor
[0]) < 10 and abs(cache
.v2dperp
[1] - mcursor
[1]) < 10:
282 r_type
= 'PERPENDICULAR'
285 elif abs(cache
.v2dmid
[0] - mcursor
[0]) < 10 and abs(cache
.v2dmid
[1] - mcursor
[1]) < 10:
290 if increment
and previous_vert
in cache
.bedge
.verts
:
305 faces
= set(tri
[0].link_faces
).intersection(tri
[1].link_faces
, tri
[2].link_faces
)
307 bm_geom
= faces
.pop()
311 while not edge
and i
!= 1:
312 edge
= bm
.edges
.get([tri
[i
], tri
[i
+ 1]])
315 for l
in edge
.link_loops
:
316 if l
.link_loop_next
.vert
== tri
[i
] or l
.link_loop_prev
.vert
== tri
[i
- 2]:
319 else: # This should never happen!!!!
321 bm_geom
= faces
.pop()
327 r_loc
= intersect_point_line(r_loc
, constrain
[0], constrain
[1])[0]
330 pv_co
= obj_matrix_world
* previous_vert
.co
332 if is_increment
and increment
:
333 r_len
= round((1 / increment
) * vec
.length
) * increment
334 r_loc
= r_len
* vec
.normalized() + pv_co
339 bm_geom
.select
= True
341 cache
.bm_geom_selected
= bm_geom
343 return r_loc
, r_type
, bm_geom
, r_len
346 def get_loose_linked_edges(bmvert
):
347 linked
= [e
for e
in bmvert
.link_edges
if not e
.link_faces
]
349 linked
+= [le
for v
in e
.verts
if not v
.link_faces
for le
in v
.link_edges
if le
not in linked
]
353 def draw_line(self
, obj
, bm
, bm_geom
, location
):
356 drawing_is_dirt
= False
357 update_edit_mesh
= False
361 vert
= bm
.verts
.new(location
)
362 self
.list_verts
.append(vert
)
364 elif isinstance(bm_geom
, bmesh
.types
.BMVert
):
365 if (bm_geom
.co
- location
).length_squared
< .001:
366 if self
.list_verts
== [] or self
.list_verts
[-1] != bm_geom
:
367 self
.list_verts
.append(bm_geom
)
369 vert
= bm
.verts
.new(location
)
370 self
.list_verts
.append(vert
)
371 drawing_is_dirt
= True
373 elif isinstance(bm_geom
, bmesh
.types
.BMEdge
):
374 self
.list_edges
.append(bm_geom
)
375 ret
= intersect_point_line(location
, bm_geom
.verts
[0].co
, bm_geom
.verts
[1].co
)
377 if (ret
[0] - location
).length_squared
< .001:
379 vert
= bm_geom
.verts
[0]
381 vert
= bm_geom
.verts
[1]
383 edge
, vert
= bmesh
.utils
.edge_split(bm_geom
, bm_geom
.verts
[0], ret
[1])
384 drawing_is_dirt
= True
385 self
.list_verts
.append(vert
)
386 # self.list_edges.append(edge)
388 else: # constrain point is near
389 vert
= bm
.verts
.new(location
)
390 self
.list_verts
.append(vert
)
391 drawing_is_dirt
= True
393 elif isinstance(bm_geom
, bmesh
.types
.BMFace
):
394 split_faces
.add(bm_geom
)
395 vert
= bm
.verts
.new(location
)
396 self
.list_verts
.append(vert
)
397 drawing_is_dirt
= True
399 # draw, split and create face
400 if len(self
.list_verts
) >= 2:
401 v1
, v2
= self
.list_verts
[-2:]
402 # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2]
403 edge
= bm
.edges
.get([v1
, v2
])
405 self
.list_edges
.append(edge
)
407 else: # if v1 not in v2_link_verts:
408 if not v2
.link_edges
:
409 edge
= bm
.edges
.new([v1
, v2
])
410 self
.list_edges
.append(edge
)
411 drawing_is_dirt
= True
413 v1_link_faces
= v1
.link_faces
414 v2_link_faces
= v2
.link_faces
415 if v1_link_faces
and v2_link_faces
:
416 split_faces
.update(set(v1_link_faces
).intersection(v2_link_faces
))
420 faces
= v1_link_faces
423 faces
= v2_link_faces
427 if bmesh
.geometry
.intersect_face_point(face
, co2
):
428 co
= co2
- face
.calc_center_median()
429 if co
.dot(face
.normal
) < 0.001:
430 split_faces
.add(face
)
433 edge
= bm
.edges
.new([v1
, v2
])
434 self
.list_edges
.append(edge
)
435 ed_list
= get_loose_linked_edges(v2
)
436 for face
in split_faces
:
437 facesp
= bmesh
.utils
.face_split_edgenet(face
, ed_list
)
439 update_edit_mesh
= True
443 facesp
= bmesh
.ops
.connect_vert_pair(bm
, verts
=[v1
, v2
], verts_exclude
=bm
.verts
)
445 if not self
.intersect
or not facesp
['edges']:
446 edge
= bm
.edges
.new([v1
, v2
])
447 self
.list_edges
.append(edge
)
448 drawing_is_dirt
= True
450 for edge
in facesp
['edges']:
451 self
.list_edges
.append(edge
)
452 update_edit_mesh
= True
457 ed_list
= set(self
.list_edges
)
458 for edge
in v2
.link_edges
:
459 for vert
in edge
.verts
:
460 if vert
!= v2
and vert
in self
.list_verts
:
465 # Inner loop had a break, break the outer
468 ed_list
.update(get_loose_linked_edges(v2
))
470 bmesh
.ops
.edgenet_fill(bm
, edges
=list(ed_list
))
471 update_edit_mesh
= True
473 # print('face created')
475 bmesh
.update_edit_mesh(obj
.data
, tessface
= tessface
)
476 self
.sctx
.update_drawn_snap_object(self
.snap_obj
)
477 #bm.verts.index_update()
478 elif drawing_is_dirt
:
479 self
.obj
.update_from_editmode()
480 self
.sctx
.update_drawn_snap_object(self
.snap_obj
)
482 return [obj
.matrix_world
* v
.co
for v
in self
.list_verts
]
485 class NavigationKeys
:
486 def __init__(self
, context
):
488 # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
492 for key
in context
.window_manager
.keyconfigs
.user
.keymaps
['3D View'].keymap_items
:
493 if key
.idname
== 'view3d.rotate':
494 #self.keys_rotate[key.id]={'Alt': key.alt, 'Ctrl': key.ctrl, 'Shift':key.shift, 'Type':key.type, 'Value':key.value}
495 self
._rotate
.add((key
.alt
, key
.ctrl
, key
.shift
, key
.type, key
.value
))
496 if key
.idname
== 'view3d.move':
497 self
._move
.add((key
.alt
, key
.ctrl
, key
.shift
, key
.type, key
.value
))
498 if key
.idname
== 'view3d.zoom':
499 if key
.type == 'WHEELINMOUSE':
500 self
._zoom
.add((key
.alt
, key
.ctrl
, key
.shift
, 'WHEELUPMOUSE', key
.value
, key
.properties
.delta
))
501 elif key
.type == 'WHEELOUTMOUSE':
502 self
._zoom
.add((key
.alt
, key
.ctrl
, key
.shift
, 'WHEELDOWNMOUSE', key
.value
, key
.properties
.delta
))
504 self
._zoom
.add((key
.alt
, key
.ctrl
, key
.shift
, key
.type, key
.value
, key
.properties
.delta
))
509 ".", ",", "-", "+", "1", "2", "3",
510 "4", "5", "6", "7", "8", "9", "0",
511 "c", "m", "d", "k", "h", "a",
512 " ", "/", "*", "'", "\""
517 'LEFT_ARROW', 'RIGHT_ARROW'
521 def modal(self
, context
, event
):
526 self
.length_entered
= self
.length_entered
[:self
.line_pos
] + c
+ self
.length_entered
[self
.line_pos
:]
528 if self
.length_entered
:
529 if event
.type == 'BACK_SPACE':
530 self
.length_entered
= self
.length_entered
[:self
.line_pos
- 1] + self
.length_entered
[self
.line_pos
:]
533 elif event
.type == 'DEL':
534 self
.length_entered
= self
.length_entered
[:self
.line_pos
] + self
.length_entered
[self
.line_pos
+ 1:]
536 elif event
.type == 'LEFT_ARROW':
537 self
.line_pos
= (self
.line_pos
- 1) % (len(self
.length_entered
) + 1)
539 elif event
.type == 'RIGHT_ARROW':
540 self
.line_pos
= (self
.line_pos
+ 1) % (len(self
.length_entered
) + 1)
543 class SnapUtilitiesLine(Operator
):
544 bl_idname
= "mesh.snap_utilities_line"
545 bl_label
= "Line Tool"
546 bl_description
= "Draw edges. Connect them to split faces"
547 bl_options
= {'REGISTER', 'UNDO'}
550 'X': Vector((1, 0, 0)),
551 'Y': Vector((0, 1, 0)),
552 'Z': Vector((0, 0, 1)),
553 'RIGHT_SHIFT': 'shift',
554 'LEFT_SHIFT': 'shift',
558 def poll(cls
, context
):
559 preferences
= context
.user_preferences
.addons
[__name__
].preferences
560 return (context
.mode
in {'EDIT_MESH', 'OBJECT'} and
561 preferences
.create_new_obj
or
562 (context
.object is not None and
563 context
.object.type == 'MESH'))
566 def draw_callback_px(self
, context
):
567 # draw 3d point OpenGL in the 3D View
568 bgl
.glEnable(bgl
.GL_BLEND
)
569 bgl
.glDisable(bgl
.GL_DEPTH_TEST
)
571 # bgl.glMultMatrixf(self.obj_glmatrix)
574 ## mesh_drawing._store_current_shader_state(mesh_drawing.PreviousGLState)
575 ## self.screen.Draw(self.sctx._texture)
576 ## mesh_drawing._restore_shader_state(mesh_drawing.PreviousGLState)
578 if self
.vector_constrain
:
579 vc
= self
.vector_constrain
580 if hasattr(self
, 'preloc') and self
.type in {'VERT', 'FACE'}:
581 bgl
.glColor4f(1.0, 1.0, 1.0, 0.5)
583 bgl
.glBegin(bgl
.GL_POINTS
)
584 bgl
.glVertex3f(*self
.preloc
)
587 Color4f
= (self
.axis_x_color
+ (1.0,))
589 Color4f
= (self
.axis_y_color
+ (1.0,))
591 Color4f
= (self
.axis_z_color
+ (1.0,))
593 Color4f
= self
.constrain_shift_color
595 if self
.type == 'OUT':
596 Color4f
= self
.out_color
597 elif self
.type == 'FACE':
598 Color4f
= self
.face_color
599 elif self
.type == 'EDGE':
600 Color4f
= self
.edge_color
601 elif self
.type == 'VERT':
602 Color4f
= self
.vert_color
603 elif self
.type == 'CENTER':
604 Color4f
= self
.center_color
605 elif self
.type == 'PERPENDICULAR':
606 Color4f
= self
.perpendicular_color
607 else: # self.type == None
608 Color4f
= self
.out_color
610 bgl
.glColor4f(*Color4f
)
612 bgl
.glBegin(bgl
.GL_POINTS
)
613 bgl
.glVertex3f(*self
.location
)
616 # draw 3d line OpenGL in the 3D View
617 bgl
.glEnable(bgl
.GL_DEPTH_TEST
)
618 bgl
.glDepthRange(0, 0.9999)
619 bgl
.glColor4f(1.0, 0.8, 0.0, 1.0)
621 bgl
.glEnable(bgl
.GL_LINE_STIPPLE
)
622 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
623 for vert_co
in self
.list_verts_co
:
624 bgl
.glVertex3f(*vert_co
)
625 bgl
.glVertex3f(*self
.location
)
628 # restore opengl defaults
630 bgl
.glDepthRange(0, 1)
633 bgl
.glDisable(bgl
.GL_BLEND
)
634 bgl
.glDisable(bgl
.GL_LINE_STIPPLE
)
635 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
638 def modal_navigation(self
, context
, event
):
639 evkey
= (event
.alt
, event
.ctrl
, event
.shift
, event
.type, event
.value
)
640 if evkey
in self
.navigation_keys
._rotate
:
641 bpy
.ops
.view3d
.rotate('INVOKE_DEFAULT')
643 elif evkey
in self
.navigation_keys
._move
:
644 if event
.shift
and self
.vector_constrain
and \
645 self
.vector_constrain
[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
646 self
.vector_constrain
= None
647 bpy
.ops
.view3d
.move('INVOKE_DEFAULT')
650 for key
in self
.navigation_keys
._zoom
:
651 if evkey
== key
[0:5]:
652 if True: # TODO: Use Zoom to mouse position
653 v3d
= context
.space_data
654 dist_range
= (v3d
.clip_start
, v3d
.clip_end
)
655 rv3d
= context
.region_data
656 if (key
[5] < 0 and rv3d
.view_distance
< dist_range
[1]) or\
657 (key
[5] > 0 and rv3d
.view_distance
> dist_range
[0]):
658 rv3d
.view_location
+= key
[5] * (self
.location
- rv3d
.view_location
) / 6
659 rv3d
.view_distance
-= key
[5] * rv3d
.view_distance
/ 6
661 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT', delta
= key
[5])
668 def modal(self
, context
, event
):
669 if self
.modal_navigation(context
, event
):
670 return {'RUNNING_MODAL'}
672 context
.area
.tag_redraw()
674 if event
.ctrl
and event
.type == 'Z' and event
.value
== 'PRESS':
676 self
.vector_constrain
= None
677 self
.list_verts_co
= []
680 self
.obj
= bpy
.context
.active_object
681 self
.obj_matrix
= self
.obj
.matrix_world
.copy()
682 self
.bm
= bmesh
.from_edit_mesh(self
.obj
.data
)
683 self
.sctx
.update_drawn_snap_object(self
.snap_obj
)
684 return {'RUNNING_MODAL'}
686 if event
.type == 'MOUSEMOVE' or self
.bool_update
:
687 if self
.rv3d
.view_matrix
!= self
.rotMat
:
688 self
.rotMat
= self
.rv3d
.view_matrix
.copy()
689 self
.bool_update
= True
690 self
.cache
.bedge
= None
692 self
.bool_update
= False
694 mval
= Vector((event
.mouse_region_x
, event
.mouse_region_y
))
696 self
.location
, self
.type, self
.geom
, self
.len = snap_utilities(
697 self
.sctx
, self
.obj
, self
.cache
, context
, self
.obj_matrix
,
699 constrain
= self
.vector_constrain
,
700 previous_vert
= (self
.list_verts
[-1] if self
.list_verts
else None),
701 increment
= self
.incremental
703 if self
.snap_to_grid
and self
.type == 'OUT':
704 loc
= self
.location
/ self
.rd
705 self
.location
= Vector((round(loc
.x
),
707 round(loc
.z
))) * self
.rd
709 if self
.keyf8
and self
.list_verts_co
:
710 lloc
= self
.list_verts_co
[-1]
711 view_vec
, orig
= self
.sctx
.last_ray
712 location
= intersect_point_line(lloc
, orig
, (orig
+ view_vec
))
713 vec
= (location
[0] - lloc
)
714 ax
, ay
, az
= abs(vec
.x
), abs(vec
.y
), abs(vec
.z
)
715 vec
.x
= ax
> ay
> az
or ax
> az
> ay
716 vec
.y
= ay
> ax
> az
or ay
> az
> ax
717 vec
.z
= az
> ay
> ax
or az
> ax
> ay
719 self
.vector_constrain
= None
723 if vc
!= self
.vector_constrain
[1]:
724 type = 'X' if vec
.x
else 'Y' if vec
.y
else 'Z' if vec
.z
else 'shift'
725 self
.vector_constrain
= [lloc
, vc
, type]
727 type = 'X' if vec
.x
else 'Y' if vec
.y
else 'Z' if vec
.z
else 'shift'
728 self
.vector_constrain
= [lloc
, vc
, type]
730 if event
.value
== 'PRESS':
731 if self
.list_verts_co
and (event
.ascii
in CharMap
.ascii
or event
.type in CharMap
.type):
732 CharMap
.modal(self
, context
, event
)
734 elif event
.type in self
.constrain_keys
:
735 self
.bool_update
= True
736 if self
.vector_constrain
and self
.vector_constrain
[2] == event
.type:
737 self
.vector_constrain
= ()
741 if isinstance(self
.geom
, bmesh
.types
.BMEdge
):
743 loc
= self
.list_verts_co
[-1]
744 self
.vector_constrain
= (loc
, loc
+ self
.geom
.verts
[1].co
-
745 self
.geom
.verts
[0].co
, event
.type)
747 self
.vector_constrain
= [self
.obj_matrix
* v
.co
for
748 v
in self
.geom
.verts
] + [event
.type]
751 loc
= self
.list_verts_co
[-1]
754 self
.vector_constrain
= [loc
, loc
+ self
.constrain_keys
[event
.type]] + [event
.type]
756 elif event
.type == 'LEFTMOUSE':
757 point
= self
.obj_matinv
* self
.location
758 # with constraint the intersection can be in a different element of the selected one
759 if self
.vector_constrain
and self
.geom
:
760 geom2
= get_closest_edge(self
.bm
, point
, 0.001)
764 self
.vector_constrain
= None
765 self
.list_verts_co
= draw_line(self
, self
.obj
, self
.bm
, geom2
, point
)
766 bpy
.ops
.ed
.undo_push(message
="Undo draw line*")
768 elif event
.type == 'TAB':
769 self
.keytab
= self
.keytab
is False
771 self
.sctx
.set_snap_mode(False, False, True)
772 context
.tool_settings
.mesh_select_mode
= (False, False, True)
774 self
.sctx
.set_snap_mode(True, True, True)
775 context
.tool_settings
.mesh_select_mode
= (True, True, True)
777 elif event
.type == 'F8':
778 self
.vector_constrain
= None
779 self
.keyf8
= self
.keyf8
is False
781 elif event
.value
== 'RELEASE':
782 if event
.type in {'RET', 'NUMPAD_ENTER'}:
783 if self
.length_entered
!= "" and self
.list_verts_co
:
785 text_value
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', self
.length_entered
)
786 vector
= (self
.location
- self
.list_verts_co
[-1]).normalized()
787 location
= (self
.list_verts_co
[-1] + (vector
* text_value
))
788 G_location
= self
.obj_matinv
* location
789 self
.list_verts_co
= draw_line(self
, self
.obj
, self
.bm
, self
.geom
, G_location
)
790 self
.length_entered
= ""
791 self
.vector_constrain
= None
793 except: # ValueError:
794 self
.report({'INFO'}, "Operation not supported yet")
796 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
797 if self
.list_verts_co
== [] or event
.type == 'ESC':
801 del self
.list_verts_co
803 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
804 context
.area
.header_text_set()
807 #restore initial state
808 context
.user_preferences
.view
.use_rotate_around_active
= self
.use_rotate_around_active
809 context
.tool_settings
.mesh_select_mode
= self
.select_mode
810 if not self
.is_editmode
:
811 bpy
.ops
.object.editmode_toggle()
815 self
.vector_constrain
= None
818 self
.list_verts_co
= []
821 if self
.list_verts_co
:
822 if self
.length_entered
:
824 a
= 'length: ' + self
.length_entered
[:pos
] + '|' + self
.length_entered
[pos
:]
827 length
= convert_distance(length
, self
.uinfo
)
828 a
= 'length: ' + length
830 context
.area
.header_text_set(
831 "hit: %.3f %.3f %.3f %s" % (self
.location
[0],
832 self
.location
[1], self
.location
[2], a
)
835 return {'RUNNING_MODAL'}
837 def invoke(self
, context
, event
):
838 if context
.space_data
.type == 'VIEW_3D':
839 # print('name', __name__, __package__)
840 preferences
= context
.user_preferences
.addons
[__name__
].preferences
842 #Store the preferences that will be used in modal
843 self
.intersect
= preferences
.intersect
844 self
.create_face
= preferences
.create_face
845 self
.outer_verts
= preferences
.outer_verts
846 self
.snap_to_grid
= preferences
.increments_grid
848 self
.out_color
= preferences
.out_color
849 self
.face_color
= preferences
.face_color
850 self
.edge_color
= preferences
.edge_color
851 self
.vert_color
= preferences
.vert_color
852 self
.center_color
= preferences
.center_color
853 self
.perpendicular_color
= preferences
.perpendicular_color
854 self
.constrain_shift_color
= preferences
.constrain_shift_color
856 self
.axis_x_color
= tuple(context
.user_preferences
.themes
[0].user_interface
.axis_x
)
857 self
.axis_y_color
= tuple(context
.user_preferences
.themes
[0].user_interface
.axis_y
)
858 self
.axis_z_color
= tuple(context
.user_preferences
.themes
[0].user_interface
.axis_z
)
860 if context
.mode
== 'OBJECT' and \
861 (preferences
.create_new_obj
or context
.object is None or context
.object.type != 'MESH'):
863 mesh
= bpy
.data
.meshes
.new("")
864 obj
= bpy
.data
.objects
.new("", mesh
)
865 context
.scene
.objects
.link(obj
)
866 context
.scene
.objects
.active
= obj
869 self
.is_editmode
= context
.object.data
.is_editmode
870 self
.use_rotate_around_active
= context
.user_preferences
.view
.use_rotate_around_active
871 self
.select_mode
= context
.tool_settings
.mesh_select_mode
[:]
873 #Modify the current state
874 bpy
.ops
.object.mode_set(mode
='EDIT')
875 bpy
.ops
.mesh
.select_all(action
='DESELECT')
876 context
.user_preferences
.view
.use_rotate_around_active
= True
877 context
.tool_settings
.mesh_select_mode
= (True, True, True)
878 context
.space_data
.use_occlude_geometry
= True
880 #Configure the unit of measure
881 scale
= context
.scene
.unit_settings
.scale_length
882 self
.unit_system
= context
.scene
.unit_settings
.system
883 separate_units
= context
.scene
.unit_settings
.use_separate
884 self
.uinfo
= get_units_info(scale
, self
.unit_system
, separate_units
)
886 scale
/= context
.space_data
.grid_scale
* preferences
.relative_scale
887 self
.rd
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', str(1 / scale
))
889 self
.incremental
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', str(preferences
.incremental
))
891 #Store values from 3d view context
892 self
.rv3d
= context
.region_data
893 self
.rotMat
= self
.rv3d
.view_matrix
.copy()
894 self
.obj
= bpy
.context
.active_object
895 self
.obj_matrix
= self
.obj
.matrix_world
.copy()
896 self
.obj_matinv
= self
.obj_matrix
.inverted()
897 # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
898 self
.bm
= bmesh
.from_edit_mesh(self
.obj
.data
) #remove at end
899 self
.cache
= SnapCache()
901 #init these variables to avoid errors
902 self
.prevloc
= Vector()
903 self
.location
= Vector()
906 self
.list_verts_co
= []
907 self
.bool_update
= False
908 self
.vector_constrain
= ()
909 self
.navigation_keys
= NavigationKeys(context
)
912 self
.length_entered
= ""
914 self
.bm_geom_selected
= None
916 #Init event variables
921 from snap_context
import SnapContext
923 self
.sctx
= SnapContext(context
.region
, context
.space_data
)
924 self
.sctx
.set_pixel_dist(12)
925 self
.sctx
.use_clip_planes(True)
927 act_base
= context
.active_base
930 for base
in context
.visible_bases
:
932 self
.sctx
.add_obj(base
.object, base
.object.matrix_world
)
934 self
.snap_obj
= self
.sctx
.add_obj(act_base
.object, act_base
.object.matrix_world
)
936 self
.snap_face
= context
.space_data
.viewport_shade
not in {'BOUNDBOX', 'WIREFRAME'}
937 self
.sctx
.set_snap_mode(True, True, self
.snap_face
)
940 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(self
.draw_callback_px
, (context
,), 'WINDOW', 'POST_VIEW')
941 context
.window_manager
.modal_handler_add(self
)
943 return {'RUNNING_MODAL'}
945 self
.report({'WARNING'}, "Active space must be a View3d")
949 class PanelSnapUtilities(Panel
):
950 bl_space_type
= "VIEW_3D"
951 bl_region_type
= "TOOLS"
952 bl_category
= "Snap Utilities"
953 bl_label
= "Snap Utilities"
956 def poll(cls
, context
):
957 preferences
= context
.user_preferences
.addons
[__name__
].preferences
958 return (context
.mode
in {'EDIT_MESH', 'OBJECT'} and
959 preferences
.create_new_obj
or
960 (context
.object is not None and
961 context
.object.type == 'MESH'))
963 def draw(self
, context
):
965 TheCol
= layout
.column(align
=True)
966 TheCol
.operator("mesh.snap_utilities_line", text
="Line", icon
="GREASEPENCIL")
968 addon_prefs
= context
.user_preferences
.addons
[__name__
].preferences
969 expand
= addon_prefs
.expand_snap_settings
970 icon
= "TRIA_DOWN" if expand
else "TRIA_RIGHT"
973 box
.prop(addon_prefs
, "expand_snap_settings", icon
=icon
,
974 text
="Settings:", emboss
=False)
976 box
.prop(addon_prefs
, "outer_verts")
977 box
.prop(addon_prefs
, "incremental")
978 box
.prop(addon_prefs
, "increments_grid")
979 if addon_prefs
.increments_grid
:
980 box
.prop(addon_prefs
, "relative_scale")
981 box
.label(text
="Line Tool:")
982 box
.prop(addon_prefs
, "intersect")
983 box
.prop(addon_prefs
, "create_face")
984 box
.prop(addon_prefs
, "create_new_obj")
987 # Add-ons Preferences Update Panel
989 # Define Panel classes for updating
995 def update_panel(self
, context
):
996 message
= "Snap Utilities Line: Updating Panel locations has failed"
997 addon_prefs
= context
.user_preferences
.addons
[__name__
].preferences
1000 if addon_prefs
.category
!= panel
.bl_category
:
1001 if "bl_rna" in panel
.__dict
__:
1002 bpy
.utils
.unregister_class(panel
)
1004 panel
.bl_category
= addon_prefs
.category
1005 bpy
.utils
.register_class(panel
)
1007 except Exception as e
:
1008 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1012 class SnapAddonPreferences(AddonPreferences
):
1013 # this must match the addon name, use '__package__'
1014 # when defining this in a submodule of a python package.
1015 bl_idname
= __name__
1017 intersect
= BoolProperty(
1019 description
="Intersects created line with the existing edges, "
1020 "even if the lines do not intersect",
1023 create_new_obj
= BoolProperty(
1024 name
="Create a new object",
1025 description
="If have not a active object, or the active object "
1026 "is not in edit mode, it creates a new object",
1029 create_face
= BoolProperty(
1030 name
="Create faces",
1031 description
="Create faces defined by enclosed edges",
1034 outer_verts
= BoolProperty(
1035 name
="Snap to outer vertices",
1036 description
="The vertices of the objects are not activated also snapped",
1039 expand_snap_settings
= BoolProperty(
1041 description
="Expand, to display the settings",
1044 expand_color_settings
= BoolProperty(
1045 name
="Color Settings",
1046 description
="Expand, to display the color settings",
1049 increments_grid
= BoolProperty(
1050 name
="Increments of Grid",
1051 description
="Snap to increments of grid",
1054 category
= StringProperty(
1056 description
="Choose a name for the category of the panel",
1057 default
="Snap Utilities",
1060 incremental
= FloatProperty(
1062 description
="Snap in defined increments",
1068 relative_scale
= FloatProperty(
1069 name
="Relative Scale",
1070 description
="Value that divides the global scale",
1076 out_color
= FloatVectorProperty(
1078 default
=(0.0, 0.0, 0.0, 0.5),
1083 face_color
= FloatVectorProperty(
1085 default
=(1.0, 0.8, 0.0, 1.0),
1090 edge_color
= FloatVectorProperty(
1092 default
=(0.0, 0.8, 1.0, 1.0),
1097 vert_color
= FloatVectorProperty(
1099 default
=(1.0, 0.5, 0.0, 1.0),
1104 center_color
= FloatVectorProperty(
1106 default
=(1.0, 0.0, 1.0, 1.0),
1111 perpendicular_color
= FloatVectorProperty(
1112 name
="PERPENDICULAR",
1113 default
=(0.1, 0.5, 0.5, 1.0),
1118 constrain_shift_color
= FloatVectorProperty(
1119 name
="SHIFT CONSTRAIN",
1120 default
=(0.8, 0.5, 0.4, 1.0),
1126 def draw(self
, context
):
1127 layout
= self
.layout
1128 icon
= "TRIA_DOWN" if self
.expand_color_settings
else "TRIA_RIGHT"
1131 box
.prop(self
, "expand_color_settings", icon
=icon
, toggle
=True, emboss
=False)
1132 if self
.expand_color_settings
:
1135 col
= split
.column()
1136 col
.prop(self
, "out_color")
1137 col
.prop(self
, "constrain_shift_color")
1138 col
= split
.column()
1139 col
.prop(self
, "face_color")
1140 col
.prop(self
, "center_color")
1141 col
= split
.column()
1142 col
.prop(self
, "edge_color")
1143 col
.prop(self
, "perpendicular_color")
1144 col
= split
.column()
1145 col
.prop(self
, "vert_color")
1149 col
= row
.column(align
=True)
1151 box
.label(text
="Snap Items:")
1152 box
.prop(self
, "incremental")
1153 box
.prop(self
, "outer_verts")
1154 box
.prop(self
, "increments_grid")
1155 if self
.increments_grid
:
1156 box
.prop(self
, "relative_scale")
1161 col
= row
.column(align
=True)
1163 box
.label(text
="Line Tool:")
1164 box
.prop(self
, "intersect")
1165 box
.prop(self
, "create_face")
1166 box
.prop(self
, "create_new_obj")
1172 col
.label(text
="Tab Category:")
1173 col
.prop(self
, "category", text
="")
1177 bpy
.utils
.register_class(SnapAddonPreferences
)
1178 bpy
.utils
.register_class(SnapUtilitiesLine
)
1179 bpy
.utils
.register_class(PanelSnapUtilities
)
1180 update_panel(None, bpy
.context
)
1184 bpy
.utils
.unregister_class(PanelSnapUtilities
)
1185 bpy
.utils
.unregister_class(SnapUtilitiesLine
)
1186 bpy
.utils
.unregister_class(SnapAddonPreferences
)
1189 if __name__
== "__main__":
1190 __name__
= "mesh_snap_utilities_line"