Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeffii <tetha.z@gmail.com>2016-08-01 00:23:44 +0300
committerzeffii <tetha.z@gmail.com>2016-08-01 00:23:44 +0300
commit3ce78656947b1b03125f5ca588a50878dc1a0e2a (patch)
treeb3521b7606997ed1c33e8bd84e81d748f7ef00c4 /mesh_tiny_cad
parent96a957faf31e15ed4922b9cb544fd154b01dee5e (diff)
move to release
Diffstat (limited to 'mesh_tiny_cad')
-rw-r--r--mesh_tiny_cad/BIX.py103
-rw-r--r--mesh_tiny_cad/CCEN.py167
-rw-r--r--mesh_tiny_cad/CFG.py84
-rw-r--r--mesh_tiny_cad/E2F.py95
-rw-r--r--mesh_tiny_cad/README.md91
-rw-r--r--mesh_tiny_cad/V2X.py72
-rw-r--r--mesh_tiny_cad/VTX.py183
-rw-r--r--mesh_tiny_cad/XALL.py189
-rw-r--r--mesh_tiny_cad/__init__.py75
-rw-r--r--mesh_tiny_cad/cad_module.py172
-rw-r--r--mesh_tiny_cad/icons/BIX.pngbin0 -> 3089 bytes
-rw-r--r--mesh_tiny_cad/icons/CCEN.pngbin0 -> 4434 bytes
-rw-r--r--mesh_tiny_cad/icons/E2F.pngbin0 -> 4867 bytes
-rw-r--r--mesh_tiny_cad/icons/V2X.pngbin0 -> 2583 bytes
-rw-r--r--mesh_tiny_cad/icons/VTX.pngbin0 -> 3263 bytes
-rw-r--r--mesh_tiny_cad/icons/XALL.pngbin0 -> 4177 bytes
16 files changed, 1231 insertions, 0 deletions
diff --git a/mesh_tiny_cad/BIX.py b/mesh_tiny_cad/BIX.py
new file mode 100644
index 00000000..4f021896
--- /dev/null
+++ b/mesh_tiny_cad/BIX.py
@@ -0,0 +1,103 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import bpy
+import bmesh
+from . import cad_module as cm
+
+
+def add_line_to_bisection(self):
+
+ obj = bpy.context.object
+ me = obj.data
+ bm = bmesh.from_edit_mesh(me)
+
+ if hasattr(bm.verts, "ensure_lookup_table"):
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+
+ edges = [e for e in bm.edges if e.select and not e.hide]
+
+ if not len(edges) == 2:
+ msg = "select two coplanar non parallel edges"
+ self.report({"WARNING"}, msg)
+ return
+
+ [[v1, v2], [v3, v4]] = [[v.co for v in e.verts] for e in edges]
+ print('vectors found:\n', v1, '\n', v2, '\n', v3, '\n', v4)
+
+ dist1 = (v1 - v2).length
+ dist2 = (v3 - v4).length
+ bdist = min([dist1, dist2])
+ edge1 = (v1, v2)
+ edge2 = (v3, v4)
+
+ if not cm.test_coplanar(edge1, edge2):
+ msg = "edges must be coplanar non parallel edges"
+ self.report({"WARNING"}, msg)
+ return
+
+ # get pt and pick fartest vertex from (projected) intersections
+ pt = cm.get_intersection(edge1, edge2)
+ far1 = v2 if (v1 - pt).length < (v2 - pt).length else v1
+ far2 = v4 if (v3 - pt).length < (v4 - pt).length else v3
+ # print('intersection: ', pt)
+
+ dex1 = far1 - pt
+ dex2 = far2 - pt
+ dex1 = dex1 * (bdist / dex1.length)
+ dex2 = dex2 * (bdist / dex2.length)
+ pt2 = pt + (dex1).lerp(dex2, 0.5)
+ # print('bisector point:', pt2)
+
+ pt3 = pt2.lerp(pt, 2.0)
+
+ vec1 = bm.verts.new(pt2)
+ vec2 = bm.verts.new(pt)
+ vec3 = bm.verts.new(pt3)
+ bm.edges.new((vec1, vec2))
+ bm.edges.new((vec2, vec3))
+ bmesh.update_edit_mesh(me)
+ # print("done")
+
+
+class TCLineOnBisection(bpy.types.Operator):
+ '''Generate the bisector of two selected edges'''
+ bl_idname = 'tinycad.linetobisect'
+ bl_label = 'BIX line to bisector'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return all([obj is not None, obj.type == 'MESH', obj.mode == 'EDIT'])
+
+ def execute(self, context):
+ add_line_to_bisection(self)
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/CCEN.py b/mesh_tiny_cad/CCEN.py
new file mode 100644
index 00000000..f625504b
--- /dev/null
+++ b/mesh_tiny_cad/CCEN.py
@@ -0,0 +1,167 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import math
+
+import bpy
+import bmesh
+import mathutils
+from mathutils import geometry
+from mathutils import Vector
+
+
+def generate_bmesh_repr(p1, v1, axis, num_verts):
+ '''
+ p1: center of circle (local coordinates)
+ v1: first vertex of circle in (local coordinates)
+ axis: orientation matrix
+ origin: obj.location
+ '''
+ props = bpy.context.scene.tinycad_props
+ rescale = props.rescale
+
+ # generate geometry up front
+ chain = []
+ gamma = 2 * math.pi / num_verts
+ for i in range(num_verts + 1):
+ theta = gamma * i
+ mat_rot = mathutils.Matrix.Rotation(theta, 4, axis)
+ local_point = (mat_rot * ((v1 - p1) * rescale))
+ chain.append(local_point + p1)
+
+ obj = bpy.context.edit_object
+ me = obj.data
+ bm = bmesh.from_edit_mesh(me)
+
+ # add verts
+ v_refs = []
+ for p in chain:
+ v = bm.verts.new(p)
+ v.select = False # this might be a default.. redundant?
+ v_refs.append(v)
+
+ # join verts, daisy chain
+ num_verts = len(v_refs)
+ for i in range(num_verts):
+ idx1 = i
+ idx2 = (i + 1) % num_verts
+ bm.edges.new([v_refs[idx1], v_refs[idx2]])
+
+ bmesh.update_edit_mesh(me, True)
+
+
+def generate_3PT(pts, obj, nv, mode=1):
+ mw = obj.matrix_world
+ V = Vector
+ nv = max(3, nv)
+
+ # construction
+ v1, v2, v3, v4 = V(pts[0]), V(pts[1]), V(pts[1]), V(pts[2])
+ edge1_mid = v1.lerp(v2, 0.5)
+ edge2_mid = v3.lerp(v4, 0.5)
+ axis = geometry.normal(v1, v2, v4)
+ mat_rot = mathutils.Matrix.Rotation(math.radians(90.0), 4, axis)
+
+ # triangle edges
+ v1_ = ((v1 - edge1_mid) * mat_rot) + edge1_mid
+ v2_ = ((v2 - edge1_mid) * mat_rot) + edge1_mid
+ v3_ = ((v3 - edge2_mid) * mat_rot) + edge2_mid
+ v4_ = ((v4 - edge2_mid) * mat_rot) + edge2_mid
+
+ r = geometry.intersect_line_line(v1_, v2_, v3_, v4_)
+ if r:
+ p1, _ = r
+ cp = mw * p1
+ bpy.context.scene.cursor_location = cp
+
+ if mode == 0:
+ pass
+
+ elif mode == 1:
+ generate_bmesh_repr(p1, v1, axis, nv)
+
+ else:
+ print('not on a circle')
+
+
+def get_three_verts_from_selection(obj):
+ me = obj.data
+ bm = bmesh.from_edit_mesh(me)
+
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+
+ return [v.co[:] for v in bm.verts if v.select]
+
+
+def dispatch(context, mode=0):
+ try:
+ obj = context.edit_object
+ pts = get_three_verts_from_selection(obj)
+ props = context.scene.tinycad_props
+ generate_3PT(pts, obj, props.num_verts, mode)
+ except:
+ print('dispatch failed', mode)
+
+
+class TCCallBackCCEN(bpy.types.Operator):
+ bl_idname = 'tinycad.reset_circlescale'
+ bl_label = 'CCEN circle reset'
+ bl_options = {'REGISTER'}
+
+ def execute(self, context):
+ context.scene.tinycad_props.rescale = 1
+ return {'FINISHED'}
+
+
+class TCCircleCenter(bpy.types.Operator):
+ '''Recreate a Circle from 3 selected verts, move 3dcursor its center'''
+
+ bl_idname = 'tinycad.circlecenter'
+ bl_label = 'CCEN circle center from selected'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def draw(self, context):
+ scn = context.scene
+ l = self.layout
+ col = l.column()
+
+ col.prop(scn.tinycad_props, 'num_verts', text='num verts')
+ row = col.row(align=True)
+ row.prop(scn.tinycad_props, 'rescale', text='rescale')
+ row.operator('tinycad.reset_circlescale', text="", icon="LINK")
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.edit_object
+ return obj is not None and obj.type == 'MESH'
+
+ def execute(self, context):
+ dispatch(context, mode=1)
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/CFG.py b/mesh_tiny_cad/CFG.py
new file mode 100644
index 00000000..ed703a2f
--- /dev/null
+++ b/mesh_tiny_cad/CFG.py
@@ -0,0 +1,84 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import os
+import bpy
+
+ICONS = 'BIX CCEN V2X VTX XALL E2F'.split(' ')
+icon_collection = {}
+
+
+class TinyCADProperties(bpy.types.PropertyGroup):
+
+ num_verts = bpy.props.IntProperty(
+ min=3, max=60, default=12)
+
+ rescale = bpy.props.FloatProperty(
+ default=1.0,
+ precision=4,
+ min=0.0001)
+
+
+class VIEW3D_MT_edit_mesh_tinycad(bpy.types.Menu):
+ bl_label = "TinyCAD"
+
+ @classmethod
+ def poll(cls, context):
+ return bool(context.object)
+
+ def draw(self, context):
+
+ pcoll = icon_collection["main"]
+
+ def cicon(name):
+ return pcoll[name].icon_id
+
+ op = self.layout.operator
+ op('tinycad.autovtx', text='VTX | AUTO', icon_value=cicon('VTX'))
+ op('tinycad.vertintersect', text='V2X | Vertex at intersection', icon_value=cicon('V2X'))
+ op('tinycad.intersectall', text='XALL | Intersect selected edges', icon_value=cicon('XALL'))
+ op('tinycad.linetobisect', text='BIX | Bisector of 2 planar edges', icon_value=cicon('BIX'))
+ op('tinycad.circlecenter', text='CCEN | Resurrect circle center', icon_value=cicon('CCEN'))
+ op('tinycad.edge_to_face', text='E2F | Extend Edge to Face', icon_value=cicon('E2F'))
+
+
+def register_icons():
+ import bpy.utils.previews
+ pcoll = bpy.utils.previews.new()
+ icons_dir = os.path.join(os.path.dirname(__file__), "icons")
+ for icon_name in ICONS:
+ pcoll.load(icon_name, os.path.join(icons_dir, icon_name + '.png'), 'IMAGE')
+
+ icon_collection["main"] = pcoll
+
+
+def unregister_icons():
+ for pcoll in icon_collection.values():
+ bpy.utils.previews.remove(pcoll)
+ icon_collection.clear()
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/E2F.py b/mesh_tiny_cad/E2F.py
new file mode 100644
index 00000000..984d5035
--- /dev/null
+++ b/mesh_tiny_cad/E2F.py
@@ -0,0 +1,95 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import bpy
+import bmesh
+from mathutils.geometry import intersect_line_plane
+
+
+def failure_message(self):
+ self.report({"WARNING"}, 'select 1 face and 1 detached edge')
+
+
+def extend_vertex(self):
+
+ obj = bpy.context.edit_object
+ me = obj.data
+ bm = bmesh.from_edit_mesh(me)
+ verts = bm.verts
+ faces = bm.faces
+
+ planes = [f for f in faces if f.select]
+ if (len(planes) > 1) or (len(planes) == 0):
+ failure_message(self)
+ return
+
+ plane = planes[0]
+ plane_vert_indices = [v for v in plane.verts[:]]
+ all_selected_vert_indices = [v for v in verts if v.select]
+
+ M = set(plane_vert_indices)
+ N = set(all_selected_vert_indices)
+ O = N.difference(M)
+ O = list(O)
+
+ if not len(O) == 2:
+ failure_message(self)
+ return
+
+ (v1_ref, v1), (v2_ref, v2) = [(i, i.co) for i in O]
+
+ plane_co = plane.calc_center_median()
+ plane_no = plane.normal
+
+ new_co = intersect_line_plane(v1, v2, plane_co, plane_no, False)
+ new_vertex = verts.new(new_co)
+
+ A_len = (v1 - new_co).length
+ B_len = (v2 - new_co).length
+
+ vertex_reference = v1_ref if (A_len < B_len) else v2_ref
+ bm.edges.new([vertex_reference, new_vertex])
+
+ bmesh.update_edit_mesh(me, True)
+
+
+class TCEdgeToFace(bpy.types.Operator):
+ '''Extend selected edge towards projected intersection with a selected face'''
+ bl_idname = 'tinycad.edge_to_face'
+ bl_label = 'E2F edge to face'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ return all([bool(ob), ob.type == 'MESH', ob.mode == 'EDIT'])
+
+ def execute(self, context):
+ extend_vertex(self)
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/README.md b/mesh_tiny_cad/README.md
new file mode 100644
index 00000000..ec37658e
--- /dev/null
+++ b/mesh_tiny_cad/README.md
@@ -0,0 +1,91 @@
+Blender CAD utils
+=================
+
+A tiny subset of unmissable CAD functions for Blender 3d.
+Addon [page on blender.org/wiki](http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/mesh_tinyCAD) (Which has most of the same info]
+
+### Installation
+
+Download the [`installable stable release zip` here](https://github.com/zeffii/mesh_tinyCAD/archive/v1_2_4.zip)
+
+
+__________________
+
+
+### OK, what's this all about?
+
+Dedicated CAD software speeds up drafting significantly with functions like: `Extend`, `Trim`, `Intersect`, `Fillet /w radius` and `Offset /w distance`. At the moment of this writing many of these functions aren't included by default in regular distributions on Blender.org, so i've coded scripts to perform a few of the main features that I missed most.
+
+My scripts have shortnames: `VTX, V2X, XALL, BIX, CCEN` and are described separately in sections below. `Fillet` and `Offset` are written by zmj100 and can be found [here](http://blenderartists.org/forum/showthread.php?179375).
+
+
+Since I started this repository: Vertex Fillet / Bevel was added to master. So no more need for a separate addon. (Ctrl+Shift+b)
+
+### VTX
+
+The VTX script has lived in contrib distributions of Blender since 2010, with relatively minor changes. The feedback from BlenderArtists has been [overwhelmingly positive](http://blenderartists.org/forum/showthread.php?204836-CAD-Addon-Edge-Tools-(blender-2-6x)). I'm not going to claim it's bug free, but finding any showstopping issues has proven difficult. It now performs V, T or X selection automatically.
+
+Expect full freedom of orientation, but stuff must really intersect within error margins (`1.5E-6` = tolerance). These kinds of functions are handy for drawing construction lines and fixing up geometry.
+
+ - V : extending two edges towards their _calculated_ intersection point.
+ ![V](http://i.imgur.com/zBSciFf.png)
+
+ - T : extending the path of one edge towards another edge.
+ ![T](http://i.imgur.com/CDH5oHm.png)
+
+ - X : two edges intersect, their intersection gets a weld vertex. You now have 4 edges and 5 vertices.
+ ![X](http://i.imgur.com/kqtX9OE.png)
+
+
+- Select two edges
+- hit `Spacebar` and type `vtx` ..select `autoVTX`
+- Bam. the rest is taken care of.
+
+
+### X ALL
+
+Intersect all, it programatically goes through all selected edges and slices them all using any found intersections, then welds them.
+
+ - XALL is fast!
+ ![Imgur](http://i.imgur.com/1I7totI.gif)
+ - Select as many edges as you want to intersect.
+ - hit `spacebar` and type `xa` ..select `XALL intersect all edges`
+
+### V2X (Vertex to Intersection)
+
+This might be a niche accessory, but sometimes all you want is a vertex positioned on the intersection of two edges. Nothing fancy.
+
+### BIX (generate Bisector)
+
+Creates a single edge which is the bisect of two edges.
+![Imgur](http://i.imgur.com/uzyv1Mv.gif)
+
+### CCEN (Circle Centers)
+
+Given either
+
+- two adjacent edges on the circumference of an incomplete circle
+- or three vertices (not required to be adjacent)
+
+this operator will places the 3d cursor at original center of that circle.
+
+![imgur](https://cloud.githubusercontent.com/assets/619340/5595657/2786f984-9279-11e4-9dff-9db5d5a52a52.gif)
+
+updated version may become a modal operator to generate a full set of circle vertices, with variable vertex count.
+
+![imgur demo](https://cloud.githubusercontent.com/assets/619340/5602194/ce613c96-933d-11e4-9879-d2cfc686cb69.gif)
+
+### E2F (Extend Edge to Selected Face, Edge 2 Face)
+
+Select a single Edge and a single Polygon (ngon, tri, quad) within the same Object. Execute `W > TinyCAD > E2F`
+
+![image](https://cloud.githubusercontent.com/assets/619340/12091278/2884820e-b2f6-11e5-9f1b-37ebfdf10cfc.png)
+
+
+### Why on github?
+
+The issue tracker, use it.
+
+- Let me know if these things are broken in new releases. Why? I don't update Blender as often as some so am oblivious to the slow evolution.
+- If you can make a valid argument for extra functionality and it seems like something I might use or be able to implement for fun, it's going to happen.
+- I'm always open to pull requests (just don't expect instant approval of something massive, we can talk..you can use your gift of persuasion and sharp objectivism)
diff --git a/mesh_tiny_cad/V2X.py b/mesh_tiny_cad/V2X.py
new file mode 100644
index 00000000..69eaeb8b
--- /dev/null
+++ b/mesh_tiny_cad/V2X.py
@@ -0,0 +1,72 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import bpy
+import bmesh
+from mathutils import geometry
+
+
+def add_vertex_to_intersection():
+
+ obj = bpy.context.object
+ me = obj.data
+ bm = bmesh.from_edit_mesh(me)
+
+ edges = [e for e in bm.edges if e.select]
+
+ if len(edges) == 2:
+ [[v1, v2], [v3, v4]] = [[v.co for v in e.verts] for e in edges]
+
+ iv = geometry.intersect_line_line(v1, v2, v3, v4)
+ if iv:
+ iv = (iv[0] + iv[1]) / 2
+ bm.verts.new(iv)
+
+ # precaution?
+ if hasattr(bm.verts, "ensure_lookup_table"):
+ bm.verts.ensure_lookup_table()
+
+ bm.verts[-1].select = True
+ bmesh.update_edit_mesh(me)
+
+
+class TCVert2Intersection(bpy.types.Operator):
+ '''Add a vertex at the intersection (projected or real) of two selected edges'''
+ bl_idname = 'tinycad.vertintersect'
+ bl_label = 'V2X vertex to intersection'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return obj is not None and obj.type == 'MESH' and obj.mode == 'EDIT'
+
+ def execute(self, context):
+ add_vertex_to_intersection()
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/VTX.py b/mesh_tiny_cad/VTX.py
new file mode 100644
index 00000000..8a8c4e8d
--- /dev/null
+++ b/mesh_tiny_cad/VTX.py
@@ -0,0 +1,183 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import bpy
+import bmesh
+import sys
+
+from . import cad_module as cm
+
+messages = {
+ 'SHARED_VERTEX': 'Shared Vertex, no intersection possible',
+ 'PARALLEL_EDGES': 'Edges Parallel, no intersection possible',
+ 'NON_PLANAR_EDGES': 'Non Planar Edges, no clean intersection point'
+}
+
+
+def add_edges(bm, pt, idxs, fdp):
+ '''
+ this function is a disaster --
+ index updates and ensure_lookup_table() are called before this function
+ and after, and i've tried doing this less verbose but results tend to be
+ less predictable. I'm obviously a terrible coder, but can only spend so
+ much time figuring out this stuff.
+ '''
+
+ v1 = bm.verts.new(pt)
+
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.verts.index_update()
+
+ try:
+ for e in idxs:
+ bm.edges.index_update()
+ v2 = bm.verts[e]
+ bm.edges.new((v1, v2))
+
+ bm.edges.index_update()
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+
+ except Exception as err:
+ print('some failure: details')
+ for l in fdp:
+ print(l)
+
+ sys.stderr.write('ERROR: %s\n' % str(err))
+ print(sys.exc_info()[-1].tb_frame.f_code)
+ print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno))
+
+
+def remove_earmarked_edges(bm, earmarked):
+ edges_select = [e for e in bm.edges if e.index in earmarked]
+ bmesh.ops.delete(bm, geom=edges_select, context=2)
+
+
+def perform_vtx(bm, pt, edges, pts, vertex_indices):
+ idx1, idx2 = edges[0].index, edges[1].index
+ fdp = pt, edges, pts, vertex_indices
+
+ # this list will hold those edges that pt lies on
+ edges_indices = cm.find_intersecting_edges(bm, pt, idx1, idx2)
+ mode = 'VTX'[len(edges_indices)]
+
+ if mode == 'V':
+ cl_vert1 = cm.closest_idx(pt, edges[0])
+ cl_vert2 = cm.closest_idx(pt, edges[1])
+ add_edges(bm, pt, [cl_vert1, cl_vert2], fdp)
+
+ elif mode == 'T':
+ to_edge_idx = edges_indices[0]
+ from_edge_idx = idx1 if to_edge_idx == idx2 else idx2
+
+ cl_vert = cm.closest_idx(pt, bm.edges[from_edge_idx])
+ to_vert1, to_vert2 = cm.vert_idxs_from_edge_idx(bm, to_edge_idx)
+ add_edges(bm, pt, [cl_vert, to_vert1, to_vert2], fdp)
+
+ elif mode == 'X':
+ add_edges(bm, pt, vertex_indices, fdp)
+
+ # final refresh before returning to user.
+ if edges_indices:
+ remove_earmarked_edges(bm, edges_indices)
+
+ bm.edges.index_update()
+ return bm
+
+
+def do_vtx_if_appropriate(bm, edges):
+ vertex_indices = cm.get_vert_indices_from_bmedges(edges)
+
+ # test 1, are there shared vers? if so return non-viable
+ if not len(set(vertex_indices)) == 4:
+ return {'SHARED_VERTEX'}
+
+ # test 2, is parallel?
+ p1, p2, p3, p4 = [bm.verts[i].co for i in vertex_indices]
+ point = cm.get_intersection([p1, p2], [p3, p4])
+ if not point:
+ return {'PARALLEL_EDGES'}
+
+ # test 3, coplanar edges?
+ coplanar = cm.test_coplanar([p1, p2], [p3, p4])
+ if not coplanar:
+ return {'NON_PLANAR_EDGES'}
+
+ # point must lie on an edge or the virtual extention of an edge
+ bm = perform_vtx(bm, point, edges, (p1, p2, p3, p4), vertex_indices)
+ return bm
+
+
+class TCAutoVTX(bpy.types.Operator):
+ '''Weld intersecting edges, project converging edges towards their intersection'''
+ bl_idname = 'tinycad.autovtx'
+ bl_label = 'VTX autoVTX'
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return bool(obj) and obj.type == 'MESH'
+
+ def cancel_message(self, msg):
+ print(msg)
+ self.report({"WARNING"}, msg)
+ return {'CANCELLED'}
+
+ def execute(self, context):
+
+ # final attempt to enter unfragmented bm/mesh
+ # ghastly, but what can I do? it works with these
+ # fails without.
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ obj = context.active_object
+ me = obj.data
+
+ bm = bmesh.from_edit_mesh(me)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+
+ edges = [e for e in bm.edges if e.select and not e.hide]
+
+ if len(edges) == 2:
+ message = do_vtx_if_appropriate(bm, edges)
+ if isinstance(message, set):
+ msg = messages.get(message.pop())
+ return self.cancel_message(msg)
+ bm = message
+ else:
+ return self.cancel_message('select two edges!')
+
+ bm.verts.index_update()
+ bm.edges.index_update()
+ bmesh.update_edit_mesh(me, True)
+
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/XALL.py b/mesh_tiny_cad/XALL.py
new file mode 100644
index 00000000..c1605393
--- /dev/null
+++ b/mesh_tiny_cad/XALL.py
@@ -0,0 +1,189 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import bpy
+import bmesh
+from mathutils.geometry import intersect_line_line as LineIntersect
+
+import itertools
+from collections import defaultdict
+from . import cad_module as cm
+
+
+def order_points(edge, point_list):
+ ''' order these edges from distance to v1, then
+ sandwich the sorted list with v1, v2 '''
+ v1, v2 = edge
+ def dist(co):
+ return (v1 - co).length
+ point_list = sorted(point_list, key=dist)
+ return [v1] + point_list + [v2]
+
+
+def remove_permutations_that_share_a_vertex(bm, permutations):
+ ''' Get useful Permutations '''
+ final_permutations = []
+ for edges in permutations:
+ raw_vert_indices = cm.vertex_indices_from_edges_tuple(bm, edges)
+ if cm.duplicates(raw_vert_indices):
+ continue
+
+ # reaches this point if they do not share.
+ final_permutations.append(edges)
+
+ return final_permutations
+
+
+def get_valid_permutations(bm, edge_indices):
+ raw_permutations = itertools.permutations(edge_indices, 2)
+ permutations = [r for r in raw_permutations if r[0] < r[1]]
+ return remove_permutations_that_share_a_vertex(bm, permutations)
+
+
+def can_skip(closest_points, vert_vectors):
+ '''this checks if the intersection lies on both edges, returns True
+ when criteria are not met, and thus this point can be skipped'''
+ if not closest_points:
+ return True
+ if not isinstance(closest_points[0].x, float):
+ return True
+ if cm.num_edges_point_lies_on(closest_points[0], vert_vectors) < 2:
+ return True
+
+ # if this distance is larger than than VTX_PRECISION, we can skip it.
+ cpa, cpb = closest_points
+ return (cpa - cpb).length > cm.CAD_prefs.VTX_PRECISION
+
+
+def get_intersection_dictionary(bm, edge_indices):
+
+ if hasattr(bm.verts, "ensure_lookup_table"):
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+
+ permutations = get_valid_permutations(bm, edge_indices)
+
+ k = defaultdict(list)
+ d = defaultdict(list)
+
+ for edges in permutations:
+ raw_vert_indices = cm.vertex_indices_from_edges_tuple(bm, edges)
+ vert_vectors = cm.vectors_from_indices(bm, raw_vert_indices)
+
+ points = LineIntersect(*vert_vectors)
+
+ # some can be skipped. (NaN, None, not on both edges)
+ if can_skip(points, vert_vectors):
+ continue
+
+ # reaches this point only when an intersection happens on both edges.
+ [k[edge].append(points[0]) for edge in edges]
+
+ # k will contain a dict of edge indices and points found on those edges.
+ for edge_idx, unordered_points in k.items():
+ tv1, tv2 = bm.edges[edge_idx].verts
+ v1 = bm.verts[tv1.index].co
+ v2 = bm.verts[tv2.index].co
+ ordered_points = order_points((v1, v2), unordered_points)
+ d[edge_idx].extend(ordered_points)
+
+ return d
+
+
+def update_mesh(obj, d):
+ ''' Make new geometry (delete old first) '''
+
+ bpy.ops.mesh.delete(type='EDGE')
+ bpy.ops.object.editmode_toggle()
+
+ oe = obj.data.edges
+ ov = obj.data.vertices
+ vert_count = len(ov)
+ edge_count = len(oe)
+
+ for old_edge, point_list in d.items():
+ num_points = len(point_list)
+ num_edges_to_add = num_points - 1
+
+ for i in range(num_edges_to_add):
+ oe.add(1)
+ ov.add(2)
+
+ ov[vert_count].co = point_list[i]
+ ov[vert_count + 1].co = point_list[i + 1]
+
+ oe[edge_count].vertices = [vert_count, vert_count + 1]
+ vert_count = len(ov)
+ edge_count = len(oe)
+
+ # set edit mode
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.remove_doubles(
+ threshold=cm.CAD_prefs.VTX_DOUBLES_THRSHLD,
+ use_unselected=False)
+
+
+def unselect_nonintersecting(bm, d_edges, edge_indices):
+ if len(edge_indices) > len(d_edges):
+ reserved_edges = set(edge_indices) - set(d_edges)
+ for edge in reserved_edges:
+ bm.edges[edge].select = False
+ print("unselected {}, non intersecting edges".format(reserved_edges))
+
+
+class TCIntersectAllEdges(bpy.types.Operator):
+ '''Adds a vertex at the intersections of all selected edges'''
+ bl_idname = 'tinycad.intersectall'
+ bl_label = 'XALL intersect all edges'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return obj is not None and obj.type == 'MESH' and obj.mode == 'EDIT'
+
+ def execute(self, context):
+ # must force edge selection mode here
+ bpy.context.tool_settings.mesh_select_mode = (False, True, False)
+
+ obj = context.active_object
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+
+ selected_edges = [edge for edge in bm.edges if edge.select]
+ edge_indices = [i.index for i in selected_edges]
+
+ d = get_intersection_dictionary(bm, edge_indices)
+
+ unselect_nonintersecting(bm, d.keys(), edge_indices)
+ update_mesh(obj, d)
+ else:
+ print('must be in edit mode')
+
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/mesh_tiny_cad/__init__.py b/mesh_tiny_cad/__init__.py
new file mode 100644
index 00000000..4c8e7100
--- /dev/null
+++ b/mesh_tiny_cad/__init__.py
@@ -0,0 +1,75 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+bl_info = {
+ "name": "tinyCAD Mesh tools",
+ "author": "zeffii (aka Dealga McArdle)",
+ "version": (1, 3, 0),
+ "blender": (2, 7, 7),
+ "category": "Mesh",
+ "location": "View3D > EditMode > (w) Specials",
+ "wiki_url": "http://zeffii.github.io/mesh_tiny_cad/",
+ "tracker_url": "https://github.com/zeffii/mesh_tiny_cad/issues"
+}
+
+
+if "bpy" in locals():
+ if 'VTX' in locals():
+
+ print('tinyCAD: detected reload event.')
+ import importlib
+
+ try:
+ modules = (CFG, VTX, V2X, XALL, BIX, CCEN, E2F)
+ for m in modules:
+ importlib.reload(m)
+ print("tinyCAD: reloaded modules, all systems operational")
+
+ except Exception as E:
+ print('reload failed with error:')
+ print(E)
+
+
+import bpy
+
+from .CFG import TinyCADProperties
+from .CFG import register_icons, unregister_icons
+from . import VTX, V2X, XALL, BIX, CCEN, E2F
+
+
+def menu_func(self, context):
+ self.layout.menu("VIEW3D_MT_edit_mesh_tinycad")
+ self.layout.separator()
+
+
+def register():
+ register_icons()
+ bpy.utils.register_module(__name__)
+ bpy.types.Scene.tinycad_props = bpy.props.PointerProperty(
+ name="TinyCAD props", type=TinyCADProperties)
+ bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func)
+
+
+def unregister():
+ bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
+ bpy.utils.unregister_module(__name__)
+ del bpy.types.Scene.tinycad_props
+ unregister_icons()
diff --git a/mesh_tiny_cad/cad_module.py b/mesh_tiny_cad/cad_module.py
new file mode 100644
index 00000000..575a6896
--- /dev/null
+++ b/mesh_tiny_cad/cad_module.py
@@ -0,0 +1,172 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+
+import bmesh
+
+from mathutils import Vector, geometry
+from mathutils.geometry import intersect_line_line as LineIntersect
+from mathutils.geometry import intersect_point_line as PtLineIntersect
+
+
+class CAD_prefs:
+ VTX_PRECISION = 1.0e-5
+ VTX_DOUBLES_THRSHLD = 0.0001
+
+
+def point_on_edge(p, edge):
+ '''
+ > p: vector
+ > edge: tuple of 2 vectors
+ < returns: True / False if a point happens to lie on an edge
+ '''
+ pt, _percent = PtLineIntersect(p, *edge)
+ on_line = (pt - p).length < CAD_prefs.VTX_PRECISION
+ return on_line and (0.0 <= _percent <= 1.0)
+
+
+def line_from_edge_intersect(edge1, edge2):
+ '''
+ > takes 2 tuples, each tuple contains 2 vectors
+ - prepares input for sending to intersect_line_line
+ < returns output of intersect_line_line
+ '''
+ [p1, p2], [p3, p4] = edge1, edge2
+ return LineIntersect(p1, p2, p3, p4)
+
+
+def get_intersection(edge1, edge2):
+ '''
+ > takes 2 tuples, each tuple contains 2 vectors
+ < returns the point halfway on line. See intersect_line_line
+ '''
+ line = line_from_edge_intersect(edge1, edge2)
+ if line:
+ return (line[0] + line[1]) / 2
+
+
+def test_coplanar(edge1, edge2):
+ '''
+ the line that describes the shortest line between the two edges
+ would be short if the lines intersect mathematically. If this
+ line is longer than the VTX_PRECISION then they are either
+ coplanar or parallel.
+ '''
+ line = line_from_edge_intersect(edge1, edge2)
+ if line:
+ return (line[0] - line[1]).length < CAD_prefs.VTX_PRECISION
+
+
+def closest_idx(pt, e):
+ '''
+ > pt: vector
+ > e: bmesh edge
+ < returns: returns index of vertex closest to pt.
+
+ if both points in e are equally far from pt, then v1 is returned.
+ '''
+ if isinstance(e, bmesh.types.BMEdge):
+ ev = e.verts
+ v1 = ev[0].co
+ v2 = ev[1].co
+ distance_test = (v1 - pt).length <= (v2 - pt).length
+ return ev[0].index if distance_test else ev[1].index
+
+ print("received {0}, check expected input in docstring ".format(e))
+
+
+def closest_vector(pt, e):
+ '''
+ > pt: vector
+ > e: 2 vector tuple
+ < returns:
+ pt, 2 vector tuple: returns closest vector to pt
+
+ if both points in e are equally far from pt, then v1 is returned.
+ '''
+ if isinstance(e, tuple) and all([isinstance(co, Vector) for co in e]):
+ v1, v2 = e
+ distance_test = (v1 - pt).length <= (v2 - pt).length
+ return v1 if distance_test else v2
+
+ print("received {0}, check expected input in docstring ".format(e))
+
+
+def coords_tuple_from_edge_idx(bm, idx):
+ ''' bm is a bmesh representation '''
+ return tuple(v.co for v in bm.edges[idx].verts)
+
+
+def vectors_from_indices(bm, raw_vert_indices):
+ ''' bm is a bmesh representation '''
+ return [bm.verts[i].co for i in raw_vert_indices]
+
+
+def vertex_indices_from_edges_tuple(bm, edge_tuple):
+ '''
+ > bm: is a bmesh representation
+ > edge_tuple: contains two edge indices.
+ < returns the vertex indices of edge_tuple
+ '''
+ def k(v, w):
+ return bm.edges[edge_tuple[v]].verts[w].index
+
+ return [k(i >> 1, i % 2) for i in range(4)]
+
+
+def get_vert_indices_from_bmedges(edges):
+ '''
+ > bmedges: a list of two bm edges
+ < returns the vertex indices of edge_tuple as a flat list.
+ '''
+ temp_edges = []
+ print(edges)
+ for e in edges:
+ for v in e.verts:
+ temp_edges.append(v.index)
+ return temp_edges
+
+
+def num_edges_point_lies_on(pt, edges):
+ ''' returns the number of edges that a point lies on. '''
+ res = [point_on_edge(pt, edge) for edge in [edges[:2], edges[2:]]]
+ return len([i for i in res if i])
+
+
+def find_intersecting_edges(bm, pt, idx1, idx2):
+ '''
+ > pt: Vector
+ > idx1, ix2: edge indices
+ < returns the list of edge indices where pt is on those edges
+ '''
+ if not pt:
+ return []
+ idxs = [idx1, idx2]
+ edges = [coords_tuple_from_edge_idx(bm, idx) for idx in idxs]
+ return [idx for edge, idx in zip(edges, idxs) if point_on_edge(pt, edge)]
+
+
+def duplicates(indices):
+ return len(set(indices)) < 4
+
+
+def vert_idxs_from_edge_idx(bm, idx):
+ edge = bm.edges[idx]
+ return edge.verts[0].index, edge.verts[1].index
diff --git a/mesh_tiny_cad/icons/BIX.png b/mesh_tiny_cad/icons/BIX.png
new file mode 100644
index 00000000..73bcfcd8
--- /dev/null
+++ b/mesh_tiny_cad/icons/BIX.png
Binary files differ
diff --git a/mesh_tiny_cad/icons/CCEN.png b/mesh_tiny_cad/icons/CCEN.png
new file mode 100644
index 00000000..51d426e4
--- /dev/null
+++ b/mesh_tiny_cad/icons/CCEN.png
Binary files differ
diff --git a/mesh_tiny_cad/icons/E2F.png b/mesh_tiny_cad/icons/E2F.png
new file mode 100644
index 00000000..e4006f8e
--- /dev/null
+++ b/mesh_tiny_cad/icons/E2F.png
Binary files differ
diff --git a/mesh_tiny_cad/icons/V2X.png b/mesh_tiny_cad/icons/V2X.png
new file mode 100644
index 00000000..21d11679
--- /dev/null
+++ b/mesh_tiny_cad/icons/V2X.png
Binary files differ
diff --git a/mesh_tiny_cad/icons/VTX.png b/mesh_tiny_cad/icons/VTX.png
new file mode 100644
index 00000000..6e08247e
--- /dev/null
+++ b/mesh_tiny_cad/icons/VTX.png
Binary files differ
diff --git a/mesh_tiny_cad/icons/XALL.png b/mesh_tiny_cad/icons/XALL.png
new file mode 100644
index 00000000..534a977e
--- /dev/null
+++ b/mesh_tiny_cad/icons/XALL.png
Binary files differ