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
):
354 if not hasattr(self
, 'list_verts'):
357 if not hasattr(self
, 'list_edges'):
362 drawing_is_dirt
= False
363 update_edit_mesh
= False
367 vert
= bm
.verts
.new(location
)
368 self
.list_verts
.append(vert
)
370 elif isinstance(bm_geom
, bmesh
.types
.BMVert
):
371 if (bm_geom
.co
- location
).length_squared
< .001:
372 if self
.list_verts
== [] or self
.list_verts
[-1] != bm_geom
:
373 self
.list_verts
.append(bm_geom
)
375 vert
= bm
.verts
.new(location
)
376 self
.list_verts
.append(vert
)
377 drawing_is_dirt
= True
379 elif isinstance(bm_geom
, bmesh
.types
.BMEdge
):
380 self
.list_edges
.append(bm_geom
)
381 ret
= intersect_point_line(location
, bm_geom
.verts
[0].co
, bm_geom
.verts
[1].co
)
383 if (ret
[0] - location
).length_squared
< .001:
385 vert
= bm_geom
.verts
[0]
387 vert
= bm_geom
.verts
[1]
389 edge
, vert
= bmesh
.utils
.edge_split(bm_geom
, bm_geom
.verts
[0], ret
[1])
390 drawing_is_dirt
= True
391 self
.list_verts
.append(vert
)
392 # self.list_edges.append(edge)
394 else: # constrain point is near
395 vert
= bm
.verts
.new(location
)
396 self
.list_verts
.append(vert
)
397 drawing_is_dirt
= True
399 elif isinstance(bm_geom
, bmesh
.types
.BMFace
):
400 split_faces
.add(bm_geom
)
401 vert
= bm
.verts
.new(location
)
402 self
.list_verts
.append(vert
)
403 drawing_is_dirt
= True
405 # draw, split and create face
406 if len(self
.list_verts
) >= 2:
407 v1
, v2
= self
.list_verts
[-2:]
408 # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2]
409 edge
= bm
.edges
.get([v1
, v2
])
411 self
.list_edges
.append(edge
)
413 else: # if v1 not in v2_link_verts:
414 if not v2
.link_edges
:
415 edge
= bm
.edges
.new([v1
, v2
])
416 self
.list_edges
.append(edge
)
417 drawing_is_dirt
= True
419 v1_link_faces
= v1
.link_faces
420 v2_link_faces
= v2
.link_faces
421 if v1_link_faces
and v2_link_faces
:
422 split_faces
.update(set(v1_link_faces
).intersection(v2_link_faces
))
426 faces
= v1_link_faces
429 faces
= v2_link_faces
433 if bmesh
.geometry
.intersect_face_point(face
, co2
):
434 co
= co2
- face
.calc_center_median()
435 if co
.dot(face
.normal
) < 0.001:
436 split_faces
.add(face
)
439 edge
= bm
.edges
.new([v1
, v2
])
440 self
.list_edges
.append(edge
)
441 ed_list
= get_loose_linked_edges(v2
)
442 for face
in split_faces
:
443 facesp
= bmesh
.utils
.face_split_edgenet(face
, ed_list
)
445 update_edit_mesh
= True
449 facesp
= bmesh
.ops
.connect_vert_pair(bm
, verts
=[v1
, v2
], verts_exclude
=bm
.verts
)
451 if not self
.intersect
or not facesp
['edges']:
452 edge
= bm
.edges
.new([v1
, v2
])
453 self
.list_edges
.append(edge
)
454 drawing_is_dirt
= True
456 for edge
in facesp
['edges']:
457 self
.list_edges
.append(edge
)
458 update_edit_mesh
= True
463 ed_list
= set(self
.list_edges
)
464 for edge
in v2
.link_edges
:
465 for vert
in edge
.verts
:
466 if vert
!= v2
and vert
in self
.list_verts
:
471 # Inner loop had a break, break the outer
474 ed_list
.update(get_loose_linked_edges(v2
))
476 bmesh
.ops
.edgenet_fill(bm
, edges
=list(ed_list
))
477 update_edit_mesh
= True
479 # print('face created')
481 bmesh
.update_edit_mesh(obj
.data
, tessface
= tessface
)
482 self
.sctx
.update_drawn_snap_object(self
.snap_obj
)
483 #bm.verts.index_update()
484 elif drawing_is_dirt
:
485 self
.obj
.update_from_editmode()
486 self
.sctx
.update_drawn_snap_object(self
.snap_obj
)
488 return [obj
.matrix_world
* v
.co
for v
in self
.list_verts
]
491 class NavigationKeys
:
492 def __init__(self
, context
):
494 # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
498 for key
in context
.window_manager
.keyconfigs
.user
.keymaps
['3D View'].keymap_items
:
499 if key
.idname
== 'view3d.rotate':
500 #self.keys_rotate[key.id]={'Alt': key.alt, 'Ctrl': key.ctrl, 'Shift':key.shift, 'Type':key.type, 'Value':key.value}
501 self
._rotate
.add((key
.alt
, key
.ctrl
, key
.shift
, key
.type, key
.value
))
502 if key
.idname
== 'view3d.move':
503 self
._move
.add((key
.alt
, key
.ctrl
, key
.shift
, key
.type, key
.value
))
504 if key
.idname
== 'view3d.zoom':
505 if key
.type == 'WHEELINMOUSE':
506 self
._zoom
.add((key
.alt
, key
.ctrl
, key
.shift
, 'WHEELUPMOUSE', key
.value
, key
.properties
.delta
))
507 elif key
.type == 'WHEELOUTMOUSE':
508 self
._zoom
.add((key
.alt
, key
.ctrl
, key
.shift
, 'WHEELDOWNMOUSE', key
.value
, key
.properties
.delta
))
510 self
._zoom
.add((key
.alt
, key
.ctrl
, key
.shift
, key
.type, key
.value
, key
.properties
.delta
))
515 ".", ",", "-", "+", "1", "2", "3",
516 "4", "5", "6", "7", "8", "9", "0",
517 "c", "m", "d", "k", "h", "a",
518 " ", "/", "*", "'", "\""
523 'LEFT_ARROW', 'RIGHT_ARROW'
527 def modal(self
, context
, event
):
532 self
.length_entered
= self
.length_entered
[:self
.line_pos
] + c
+ self
.length_entered
[self
.line_pos
:]
534 if self
.length_entered
:
535 if event
.type == 'BACK_SPACE':
536 self
.length_entered
= self
.length_entered
[:self
.line_pos
- 1] + self
.length_entered
[self
.line_pos
:]
539 elif event
.type == 'DEL':
540 self
.length_entered
= self
.length_entered
[:self
.line_pos
] + self
.length_entered
[self
.line_pos
+ 1:]
542 elif event
.type == 'LEFT_ARROW':
543 self
.line_pos
= (self
.line_pos
- 1) % (len(self
.length_entered
) + 1)
545 elif event
.type == 'RIGHT_ARROW':
546 self
.line_pos
= (self
.line_pos
+ 1) % (len(self
.length_entered
) + 1)
549 class SnapUtilitiesLine(Operator
):
550 bl_idname
= "mesh.snap_utilities_line"
551 bl_label
= "Line Tool"
552 bl_description
= "Draw edges. Connect them to split faces"
553 bl_options
= {'REGISTER', 'UNDO'}
556 'X': Vector((1, 0, 0)),
557 'Y': Vector((0, 1, 0)),
558 'Z': Vector((0, 0, 1)),
559 'RIGHT_SHIFT': 'shift',
560 'LEFT_SHIFT': 'shift',
564 def poll(cls
, context
):
565 preferences
= context
.user_preferences
.addons
[__name__
].preferences
566 return (context
.mode
in {'EDIT_MESH', 'OBJECT'} and
567 preferences
.create_new_obj
or
568 (context
.object is not None and
569 context
.object.type == 'MESH'))
572 def draw_callback_px(self
, context
):
573 # draw 3d point OpenGL in the 3D View
574 bgl
.glEnable(bgl
.GL_BLEND
)
575 bgl
.glDisable(bgl
.GL_DEPTH_TEST
)
577 # bgl.glMultMatrixf(self.obj_glmatrix)
580 ## mesh_drawing._store_current_shader_state(mesh_drawing.PreviousGLState)
581 ## self.screen.Draw(self.sctx._texture)
582 ## mesh_drawing._restore_shader_state(mesh_drawing.PreviousGLState)
584 if self
.vector_constrain
:
585 vc
= self
.vector_constrain
586 if hasattr(self
, 'preloc') and self
.type in {'VERT', 'FACE'}:
587 bgl
.glColor4f(1.0, 1.0, 1.0, 0.5)
589 bgl
.glBegin(bgl
.GL_POINTS
)
590 bgl
.glVertex3f(*self
.preloc
)
593 Color4f
= (self
.axis_x_color
+ (1.0,))
595 Color4f
= (self
.axis_y_color
+ (1.0,))
597 Color4f
= (self
.axis_z_color
+ (1.0,))
599 Color4f
= self
.constrain_shift_color
601 if self
.type == 'OUT':
602 Color4f
= self
.out_color
603 elif self
.type == 'FACE':
604 Color4f
= self
.face_color
605 elif self
.type == 'EDGE':
606 Color4f
= self
.edge_color
607 elif self
.type == 'VERT':
608 Color4f
= self
.vert_color
609 elif self
.type == 'CENTER':
610 Color4f
= self
.center_color
611 elif self
.type == 'PERPENDICULAR':
612 Color4f
= self
.perpendicular_color
613 else: # self.type == None
614 Color4f
= self
.out_color
616 bgl
.glColor4f(*Color4f
)
618 bgl
.glBegin(bgl
.GL_POINTS
)
619 bgl
.glVertex3f(*self
.location
)
622 # draw 3d line OpenGL in the 3D View
623 bgl
.glEnable(bgl
.GL_DEPTH_TEST
)
624 bgl
.glDepthRange(0, 0.9999)
625 bgl
.glColor4f(1.0, 0.8, 0.0, 1.0)
627 bgl
.glEnable(bgl
.GL_LINE_STIPPLE
)
628 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
629 for vert_co
in self
.list_verts_co
:
630 bgl
.glVertex3f(*vert_co
)
631 bgl
.glVertex3f(*self
.location
)
634 # restore opengl defaults
636 bgl
.glDepthRange(0, 1)
639 bgl
.glDisable(bgl
.GL_BLEND
)
640 bgl
.glDisable(bgl
.GL_LINE_STIPPLE
)
641 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
644 def modal_navigation(self
, context
, event
):
645 evkey
= (event
.alt
, event
.ctrl
, event
.shift
, event
.type, event
.value
)
646 if evkey
in self
.navigation_keys
._rotate
:
647 bpy
.ops
.view3d
.rotate('INVOKE_DEFAULT')
649 elif evkey
in self
.navigation_keys
._move
:
650 if event
.shift
and self
.vector_constrain
and \
651 self
.vector_constrain
[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
652 self
.vector_constrain
= None
653 bpy
.ops
.view3d
.move('INVOKE_DEFAULT')
656 for key
in self
.navigation_keys
._zoom
:
657 if evkey
== key
[0:5]:
658 if True: # TODO: Use Zoom to mouse position
659 v3d
= context
.space_data
660 dist_range
= (v3d
.clip_start
, v3d
.clip_end
)
661 rv3d
= context
.region_data
662 if (key
[5] < 0 and rv3d
.view_distance
< dist_range
[1]) or\
663 (key
[5] > 0 and rv3d
.view_distance
> dist_range
[0]):
664 rv3d
.view_location
+= key
[5] * (self
.location
- rv3d
.view_location
) / 6
665 rv3d
.view_distance
-= key
[5] * rv3d
.view_distance
/ 6
667 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT', delta
= key
[5])
674 def modal(self
, context
, event
):
675 if self
.modal_navigation(context
, event
):
676 return {'RUNNING_MODAL'}
678 context
.area
.tag_redraw()
680 if event
.ctrl
and event
.type == 'Z' and event
.value
== 'PRESS':
682 self
.vector_constrain
= None
683 self
.list_verts_co
= []
687 self
.obj
= bpy
.context
.active_object
688 self
.obj_matrix
= self
.obj
.matrix_world
.copy()
689 self
.bm
= bmesh
.from_edit_mesh(self
.obj
.data
)
690 self
.sctx
.update_drawn_snap_object(self
.snap_obj
)
691 return {'RUNNING_MODAL'}
693 if event
.type == 'MOUSEMOVE' or self
.bool_update
:
694 if self
.rv3d
.view_matrix
!= self
.rotMat
:
695 self
.rotMat
= self
.rv3d
.view_matrix
.copy()
696 self
.bool_update
= True
697 self
.cache
.bedge
= None
699 self
.bool_update
= False
701 mval
= Vector((event
.mouse_region_x
, event
.mouse_region_y
))
703 self
.location
, self
.type, self
.geom
, self
.len = snap_utilities(
704 self
.sctx
, self
.obj
, self
.cache
, context
, self
.obj_matrix
,
706 constrain
= self
.vector_constrain
,
707 previous_vert
= (self
.list_verts
[-1] if self
.list_verts
else None),
708 increment
= self
.incremental
710 if self
.snap_to_grid
and self
.type == 'OUT':
711 loc
= self
.location
/ self
.rd
712 self
.location
= Vector((round(loc
.x
),
714 round(loc
.z
))) * self
.rd
716 if self
.keyf8
and self
.list_verts_co
:
717 lloc
= self
.list_verts_co
[-1]
718 view_vec
, orig
= self
.sctx
.last_ray
719 location
= intersect_point_line(lloc
, orig
, (orig
+ view_vec
))
720 vec
= (location
[0] - lloc
)
721 ax
, ay
, az
= abs(vec
.x
), abs(vec
.y
), abs(vec
.z
)
722 vec
.x
= ax
> ay
> az
or ax
> az
> ay
723 vec
.y
= ay
> ax
> az
or ay
> az
> ax
724 vec
.z
= az
> ay
> ax
or az
> ax
> ay
726 self
.vector_constrain
= None
730 if vc
!= self
.vector_constrain
[1]:
731 type = 'X' if vec
.x
else 'Y' if vec
.y
else 'Z' if vec
.z
else 'shift'
732 self
.vector_constrain
= [lloc
, vc
, type]
734 type = 'X' if vec
.x
else 'Y' if vec
.y
else 'Z' if vec
.z
else 'shift'
735 self
.vector_constrain
= [lloc
, vc
, type]
737 if event
.value
== 'PRESS':
738 if self
.list_verts_co
and (event
.ascii
in CharMap
.ascii
or event
.type in CharMap
.type):
739 CharMap
.modal(self
, context
, event
)
741 elif event
.type in self
.constrain_keys
:
742 self
.bool_update
= True
743 if self
.vector_constrain
and self
.vector_constrain
[2] == event
.type:
744 self
.vector_constrain
= ()
748 if isinstance(self
.geom
, bmesh
.types
.BMEdge
):
750 loc
= self
.list_verts_co
[-1]
751 self
.vector_constrain
= (loc
, loc
+ self
.geom
.verts
[1].co
-
752 self
.geom
.verts
[0].co
, event
.type)
754 self
.vector_constrain
= [self
.obj_matrix
* v
.co
for
755 v
in self
.geom
.verts
] + [event
.type]
758 loc
= self
.list_verts_co
[-1]
761 self
.vector_constrain
= [loc
, loc
+ self
.constrain_keys
[event
.type]] + [event
.type]
763 elif event
.type == 'LEFTMOUSE':
764 point
= self
.obj_matinv
* self
.location
765 # with constraint the intersection can be in a different element of the selected one
766 if self
.vector_constrain
and self
.geom
:
767 geom2
= get_closest_edge(self
.bm
, point
, 0.001)
771 self
.vector_constrain
= None
772 self
.list_verts_co
= draw_line(self
, self
.obj
, self
.bm
, geom2
, point
)
773 bpy
.ops
.ed
.undo_push(message
="Undo draw line*")
775 elif event
.type == 'TAB':
776 self
.keytab
= self
.keytab
is False
778 self
.sctx
.set_snap_mode(False, False, True)
779 context
.tool_settings
.mesh_select_mode
= (False, False, True)
781 self
.sctx
.set_snap_mode(True, True, True)
782 context
.tool_settings
.mesh_select_mode
= (True, True, True)
784 elif event
.type == 'F8':
785 self
.vector_constrain
= None
786 self
.keyf8
= self
.keyf8
is False
788 elif event
.value
== 'RELEASE':
789 if event
.type in {'RET', 'NUMPAD_ENTER'}:
790 if self
.length_entered
!= "" and self
.list_verts_co
:
792 text_value
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', self
.length_entered
)
793 vector
= (self
.location
- self
.list_verts_co
[-1]).normalized()
794 location
= (self
.list_verts_co
[-1] + (vector
* text_value
))
795 G_location
= self
.obj_matinv
* location
796 self
.list_verts_co
= draw_line(self
, self
.obj
, self
.bm
, self
.geom
, G_location
)
797 self
.length_entered
= ""
798 self
.vector_constrain
= None
800 except: # ValueError:
801 self
.report({'INFO'}, "Operation not supported yet")
803 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
804 if self
.list_verts_co
== [] or event
.type == 'ESC':
805 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
806 context
.tool_settings
.mesh_select_mode
= self
.select_mode
807 context
.area
.header_text_set()
808 context
.user_preferences
.view
.use_rotate_around_active
= self
.use_rotate_around_active
810 if not self
.is_editmode
:
811 bpy
.ops
.object.editmode_toggle()
814 self
.vector_constrain
= None
816 self
.list_verts_co
= []
820 if self
.list_verts_co
:
821 if self
.length_entered
:
823 a
= 'length: ' + self
.length_entered
[:pos
] + '|' + self
.length_entered
[pos
:]
826 length
= convert_distance(length
, self
.uinfo
)
827 a
= 'length: ' + length
829 context
.area
.header_text_set(
830 "hit: %.3f %.3f %.3f %s" % (self
.location
[0],
831 self
.location
[1], self
.location
[2], a
)
834 return {'RUNNING_MODAL'}
836 def invoke(self
, context
, event
):
837 if context
.space_data
.type == 'VIEW_3D':
838 # print('name', __name__, __package__)
839 preferences
= context
.user_preferences
.addons
[__name__
].preferences
840 create_new_obj
= preferences
.create_new_obj
841 if context
.mode
== 'OBJECT' and \
842 (create_new_obj
or context
.object is None or context
.object.type != 'MESH'):
844 mesh
= bpy
.data
.meshes
.new("")
845 obj
= bpy
.data
.objects
.new("", mesh
)
846 context
.scene
.objects
.link(obj
)
847 context
.scene
.objects
.active
= obj
849 # bgl.glEnable(bgl.GL_POINT_SMOOTH)
850 self
.is_editmode
= bpy
.context
.object.data
.is_editmode
851 bpy
.ops
.object.mode_set(mode
='EDIT')
852 context
.space_data
.use_occlude_geometry
= True
854 self
.scale
= context
.scene
.unit_settings
.scale_length
855 self
.unit_system
= context
.scene
.unit_settings
.system
856 self
.separate_units
= context
.scene
.unit_settings
.use_separate
857 self
.uinfo
= get_units_info(self
.scale
, self
.unit_system
, self
.separate_units
)
859 grid
= context
.scene
.unit_settings
.scale_length
/ context
.space_data
.grid_scale
860 relative_scale
= preferences
.relative_scale
861 self
.scale
= grid
/ relative_scale
862 self
.rd
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', str(1 / self
.scale
))
864 incremental
= preferences
.incremental
865 self
.incremental
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', str(incremental
))
867 self
.use_rotate_around_active
= context
.user_preferences
.view
.use_rotate_around_active
868 context
.user_preferences
.view
.use_rotate_around_active
= True
870 self
.select_mode
= context
.tool_settings
.mesh_select_mode
[:]
871 context
.tool_settings
.mesh_select_mode
= (True, True, True)
873 self
.region
= context
.region
874 self
.rv3d
= context
.region_data
875 self
.rotMat
= self
.rv3d
.view_matrix
.copy()
876 self
.obj
= bpy
.context
.active_object
877 self
.obj_matrix
= self
.obj
.matrix_world
.copy()
878 self
.obj_matinv
= self
.obj_matrix
.inverted()
879 # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
880 self
.bm
= bmesh
.from_edit_mesh(self
.obj
.data
)
881 self
.cache
= SnapCache()
883 self
.location
= Vector()
885 self
.list_verts_co
= []
886 self
.bool_update
= False
887 self
.vector_constrain
= ()
888 self
.navigation_keys
= NavigationKeys(context
)
893 self
.length_entered
= ""
896 self
.out_color
= preferences
.out_color
897 self
.face_color
= preferences
.face_color
898 self
.edge_color
= preferences
.edge_color
899 self
.vert_color
= preferences
.vert_color
900 self
.center_color
= preferences
.center_color
901 self
.perpendicular_color
= preferences
.perpendicular_color
902 self
.constrain_shift_color
= preferences
.constrain_shift_color
904 self
.axis_x_color
= tuple(context
.user_preferences
.themes
[0].user_interface
.axis_x
)
905 self
.axis_y_color
= tuple(context
.user_preferences
.themes
[0].user_interface
.axis_y
)
906 self
.axis_z_color
= tuple(context
.user_preferences
.themes
[0].user_interface
.axis_z
)
908 self
.intersect
= preferences
.intersect
909 self
.create_face
= preferences
.create_face
910 self
.outer_verts
= preferences
.outer_verts
911 self
.snap_to_grid
= preferences
.increments_grid
913 from snap_context
import SnapContext
915 self
.sctx
= SnapContext(context
.region
, context
.space_data
)
916 self
.sctx
.set_pixel_dist(12)
917 self
.sctx
.use_clip_planes(True)
919 act_base
= context
.active_base
920 self
.snap_obj
= self
.sctx
.add_obj(act_base
.object, act_base
.object.matrix_world
)
923 for base
in context
.visible_bases
:
925 self
.sctx
.add_obj(base
.object, base
.object.matrix_world
)
928 ## self.screen = screenTexture()
930 bpy
.ops
.mesh
.select_all(action
='DESELECT')
932 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(
933 self
.draw_callback_px
,
935 'WINDOW', 'POST_VIEW'
937 context
.window_manager
.modal_handler_add(self
)
938 return {'RUNNING_MODAL'}
940 self
.report({'WARNING'}, "Active space must be a View3d")
944 class PanelSnapUtilities(Panel
):
945 bl_space_type
= "VIEW_3D"
946 bl_region_type
= "TOOLS"
947 bl_category
= "Snap Utilities"
948 bl_label
= "Snap Utilities"
951 def poll(cls
, context
):
952 preferences
= context
.user_preferences
.addons
[__name__
].preferences
953 return (context
.mode
in {'EDIT_MESH', 'OBJECT'} and
954 preferences
.create_new_obj
or
955 (context
.object is not None and
956 context
.object.type == 'MESH'))
958 def draw(self
, context
):
960 TheCol
= layout
.column(align
=True)
961 TheCol
.operator("mesh.snap_utilities_line", text
="Line", icon
="GREASEPENCIL")
963 addon_prefs
= context
.user_preferences
.addons
[__name__
].preferences
964 expand
= addon_prefs
.expand_snap_settings
965 icon
= "TRIA_DOWN" if expand
else "TRIA_RIGHT"
968 box
.prop(addon_prefs
, "expand_snap_settings", icon
=icon
,
969 text
="Settings:", emboss
=False)
971 box
.prop(addon_prefs
, "outer_verts")
972 box
.prop(addon_prefs
, "incremental")
973 box
.prop(addon_prefs
, "increments_grid")
974 if addon_prefs
.increments_grid
:
975 box
.prop(addon_prefs
, "relative_scale")
976 box
.label(text
="Line Tool:")
977 box
.prop(addon_prefs
, "intersect")
978 box
.prop(addon_prefs
, "create_face")
979 box
.prop(addon_prefs
, "create_new_obj")
982 # Add-ons Preferences Update Panel
984 # Define Panel classes for updating
990 def update_panel(self
, context
):
991 message
= "Snap Utilities Line: Updating Panel locations has failed"
992 addon_prefs
= context
.user_preferences
.addons
[__name__
].preferences
995 if addon_prefs
.category
!= panel
.bl_category
:
996 if "bl_rna" in panel
.__dict
__:
997 bpy
.utils
.unregister_class(panel
)
999 panel
.bl_category
= addon_prefs
.category
1000 bpy
.utils
.register_class(panel
)
1002 except Exception as e
:
1003 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1007 class SnapAddonPreferences(AddonPreferences
):
1008 # this must match the addon name, use '__package__'
1009 # when defining this in a submodule of a python package.
1010 bl_idname
= __name__
1012 intersect
= BoolProperty(
1014 description
="Intersects created line with the existing edges, "
1015 "even if the lines do not intersect",
1018 create_new_obj
= BoolProperty(
1019 name
="Create a new object",
1020 description
="If have not a active object, or the active object "
1021 "is not in edit mode, it creates a new object",
1024 create_face
= BoolProperty(
1025 name
="Create faces",
1026 description
="Create faces defined by enclosed edges",
1029 outer_verts
= BoolProperty(
1030 name
="Snap to outer vertices",
1031 description
="The vertices of the objects are not activated also snapped",
1034 expand_snap_settings
= BoolProperty(
1036 description
="Expand, to display the settings",
1039 expand_color_settings
= BoolProperty(
1040 name
="Color Settings",
1041 description
="Expand, to display the color settings",
1044 increments_grid
= BoolProperty(
1045 name
="Increments of Grid",
1046 description
="Snap to increments of grid",
1049 category
= StringProperty(
1051 description
="Choose a name for the category of the panel",
1052 default
="Snap Utilities",
1055 incremental
= FloatProperty(
1057 description
="Snap in defined increments",
1063 relative_scale
= FloatProperty(
1064 name
="Relative Scale",
1065 description
="Value that divides the global scale",
1071 out_color
= FloatVectorProperty(
1073 default
=(0.0, 0.0, 0.0, 0.5),
1078 face_color
= FloatVectorProperty(
1080 default
=(1.0, 0.8, 0.0, 1.0),
1085 edge_color
= FloatVectorProperty(
1087 default
=(0.0, 0.8, 1.0, 1.0),
1092 vert_color
= FloatVectorProperty(
1094 default
=(1.0, 0.5, 0.0, 1.0),
1099 center_color
= FloatVectorProperty(
1101 default
=(1.0, 0.0, 1.0, 1.0),
1106 perpendicular_color
= FloatVectorProperty(
1107 name
="PERPENDICULAR",
1108 default
=(0.1, 0.5, 0.5, 1.0),
1113 constrain_shift_color
= FloatVectorProperty(
1114 name
="SHIFT CONSTRAIN",
1115 default
=(0.8, 0.5, 0.4, 1.0),
1121 def draw(self
, context
):
1122 layout
= self
.layout
1123 icon
= "TRIA_DOWN" if self
.expand_color_settings
else "TRIA_RIGHT"
1126 box
.prop(self
, "expand_color_settings", icon
=icon
, toggle
=True, emboss
=False)
1127 if self
.expand_color_settings
:
1130 col
= split
.column()
1131 col
.prop(self
, "out_color")
1132 col
.prop(self
, "constrain_shift_color")
1133 col
= split
.column()
1134 col
.prop(self
, "face_color")
1135 col
.prop(self
, "center_color")
1136 col
= split
.column()
1137 col
.prop(self
, "edge_color")
1138 col
.prop(self
, "perpendicular_color")
1139 col
= split
.column()
1140 col
.prop(self
, "vert_color")
1144 col
= row
.column(align
=True)
1146 box
.label(text
="Snap Items:")
1147 box
.prop(self
, "incremental")
1148 box
.prop(self
, "outer_verts")
1149 box
.prop(self
, "increments_grid")
1150 if self
.increments_grid
:
1151 box
.prop(self
, "relative_scale")
1156 col
= row
.column(align
=True)
1158 box
.label(text
="Line Tool:")
1159 box
.prop(self
, "intersect")
1160 box
.prop(self
, "create_face")
1161 box
.prop(self
, "create_new_obj")
1167 col
.label(text
="Tab Category:")
1168 col
.prop(self
, "category", text
="")
1172 bpy
.utils
.register_class(SnapAddonPreferences
)
1173 bpy
.utils
.register_class(SnapUtilitiesLine
)
1174 bpy
.utils
.register_class(PanelSnapUtilities
)
1175 update_panel(None, bpy
.context
)
1179 bpy
.utils
.unregister_class(PanelSnapUtilities
)
1180 bpy
.utils
.unregister_class(SnapUtilitiesLine
)
1181 bpy
.utils
.unregister_class(SnapAddonPreferences
)
1184 if __name__
== "__main__":
1185 __name__
= "mesh_snap_utilities_line"