diff --git a/release/scripts/modules/retopo.py b/release/scripts/modules/retopo.py new file mode 100644 index 00000000000..7d1c48a3f8f --- /dev/null +++ b/release/scripts/modules/retopo.py @@ -0,0 +1,291 @@ +# ##### 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +import bpy + +EPS = 0.001 +EPS_LINE_LINE = 0.02 +EPS_COLLAPSE = 0.05 +EPS_HUB = 0.05 + +def get_hub(co, _hubs): + + if 1: + for hub in _hubs.values(): + if (hub.co - co).length < EPS_HUB: + return hub + + key = co.toTuple(3) + hub = _hubs[key] = Hub(co, key, len(_hubs)) + return hub + else: + pass + + ''' + key = co.toTuple(3) + try: + return _hubs[key] + except: + hub = _hubs[key] = Hub(co, key, len(_hubs)) + return hub + ''' + + +class Hub: + def __init__(self, co, key, index): + self.co = co.copy() + self.key = key + self.index = index + self.links = [] + + def get_weight(self): + f = 0.0 + + for hub_other in self.links: + f += (self.co - hub_other.co).length + + def replace(self, other): + for hub in self.links: + try: + hub.links.remove(self) + except: + pass + if other not in hub.links: + hub.links.append(other) + + + def dist(self, other): + return (self.co - other.co).length + + def calc_faces(self, hub_ls): + faces = [] + # first tris + for l_a in self.links: + for l_b in l_a.links: + if l_b is not self and l_b in self.links: + # will give duplicates + faces.append((self.index, l_a.index, l_b.index)) + + # now quads, check which links share 2 different verts + # directly + def validate_quad(face): + if len(set(face)) != len(face): + return False + if hub_ls[face[0]] in hub_ls[face[2]].links: + return False + if hub_ls[face[2]] in hub_ls[face[0]].links: + return False + + if hub_ls[face[1]] in hub_ls[face[3]].links: + return False + if hub_ls[face[3]] in hub_ls[face[1]].links: + return False + + return True + + for i, l_a in enumerate(self.links): + links_a = set([l.index for l in l_a.links]) + for j in range(i): + l_b = self.links[j] + + links_b = set([l.index for l in l_b.links]) + + isect = links_a.intersection(links_b) + if len(isect) == 2: + isect = list(isect) + + # check there are no diagonal lines + face = (isect[0], l_a.index, isect[1], l_b.index) + if validate_quad(face): + + faces.append(face) + + return faces + + + +class Spline: + def __init__(self, points): + self.points = points + self.hubs = [] + + def link(self): + if len(self.hubs) < 2: + return + + edges = list(set([i for i, hub in self.hubs])) + edges.sort() + + edges_order = {} + for i in edges: + edges_order[i] = [] + + + # self.hubs.sort() + for i, hub in self.hubs: + edges_order[i].append(hub) + + hubs_order = [] + for i in edges: + ls = edges_order[i] + edge_start = self.points[i] + ls.sort(key=lambda hub: (hub.co - edge_start).length) + hubs_order.extend(ls) + + # Now we have the order, connect the hubs + hub_prev = hubs_order[0] + + for hub in hubs_order[1:]: + hub.links.append(hub_prev) + hub_prev.links.append(hub) + hub_prev = hub + + + +def get_points(spline): + points = spline.points + + if len(spline.bezier_points): + points = spline.bezier_points + + return [point.co.copy().resize3D() for point in points] + + +def get_splines(data): + return [Spline(get_points(spline)) for spline in data.splines] + +def xsect_spline(sp_a, sp_b, _hubs): + from Mathutils import LineIntersect + from Mathutils import MidpointVecs + from Geometry import ClosestPointOnLine + pt_a_prev = pt_b_prev = None + + pt_a_prev = sp_a.points[0] + for a, pt_a in enumerate(sp_a.points[1:]): + pt_b_prev = sp_b.points[0] + for b, pt_b in enumerate(sp_b.points[1:]): + + # Now we have 2 edges + # print(pt_a, pt_a_prev, pt_b, pt_b_prev) + xsect = LineIntersect(pt_a, pt_a_prev, pt_b, pt_b_prev) + if xsect is not None: + if (xsect[0]-xsect[1]).length <= EPS_LINE_LINE: + f = ClosestPointOnLine(xsect[1], pt_a, pt_a_prev)[1] + if f >= 0.0 and f <= 1.0: + f = ClosestPointOnLine(xsect[0], pt_b, pt_b_prev)[1] + if f >= 0.0 and f <= 1.0: + # This wont happen often + co = MidpointVecs(xsect[0], xsect[1]) + hub = get_hub(co, _hubs) + + sp_a.hubs.append((a, hub)) + sp_b.hubs.append((b, hub)) + + pt_b_prev = pt_b + + pt_a_prev = pt_a + + +def calculate(scene, obj): + data = obj.data + splines = get_splines(data) + _hubs = {} + + for i, sp in enumerate(splines): + for j, sp_other in enumerate(splines): + if j<=i: + continue + + xsect_spline(sp, sp_other, _hubs) + + for sp in splines: + sp.link() + + # remove these + hubs_ls = [hub for hub in _hubs.values() if hub.index != -1] + + _hubs.clear() + _hubs = None + + for i, hub in enumerate(hubs_ls): + hub.index = i + + # Now we have connected hubs, write all edges! + def order(i1, i2): + if i1 > i2: + return i2, i1 + return i1, i2 + + edges = {} + + for hub in hubs_ls: + i1 = hub.index + for hub_other in hub.links: + i2 = hub_other.index + edges[order(i1, i2)] = None + + verts = [] + edges = edges.keys() + faces = [] + + for hub in hubs_ls: + verts.append(hub.co) + faces.extend(hub.calc_faces(hubs_ls)) + + # remove double faces + faces = dict([(tuple(sorted(f)), f) for f in faces]).values() + + mesh = bpy.data.add_mesh("Retopo") + mesh.from_pydata(verts, [], faces) + + scene = bpy.context.scene + mesh.update() + obj_new = bpy.data.add_object('MESH', "Torus") + obj_new.data = mesh + scene.objects.link(obj_new) + + return obj_new + + +def main(): + # first convert gpencil + # *** evil! + scene = bpy.context.scene + + bpy.ops.gpencil.convert(type='PATH') + + + scene = bpy.context.scene + obj = bpy.context.object + if not obj: + raise Exception("no active object") + + obj_new = calculate(scene, obj) + + # obj.selected = False + scene.objects.unlink(obj) + + scene.objects.active = obj_new + obj_new.selected = True + + # nasty, recalc normals + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bpy.ops.mesh.normals_make_consistent(inside=False) + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) diff --git a/release/scripts/op/object.py b/release/scripts/op/object.py index 8823045e07d..89ba38860b3 100644 --- a/release/scripts/op/object.py +++ b/release/scripts/op/object.py @@ -95,5 +95,20 @@ class SubsurfSet(bpy.types.Operator): return ('FINISHED',) +class Retopo(bpy.types.Operator): + '''TODO - doc''' + + bl_idname = "object.retopology" + bl_label = "Retopology from Grease Pencil" + bl_register = True + bl_undo = True + + def execute(self, context): + import retopo + retopo.main() + return ('FINISHED',) + + bpy.ops.add(SelectPattern) bpy.ops.add(SubsurfSet) +bpy.ops.add(Retopo) diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index c2b9a1a4bb9..2f41d90b37c 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -531,7 +531,7 @@ static void gp_layer_to_curve (bContext *C, bGPdata *gpd, bGPDlayer *gpl, short } /* restore old active object */ - BASACT= base; + // BASACT= base; // removing since this is expected new objects are active. } /* --- */ diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 4229c66353c..dce49cc4845 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -151,6 +151,21 @@ static int gpencil_draw_poll (bContext *C) return (gpencil_data_get_pointers(C, NULL) != NULL); } +static int gpencil_project_check(tGPsdata *p) +{ + bGPdata *gpd= p->gpd; + + /* in 3d-space - pt->x/y/z are 3 side-by-side floats */ + if( (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && + (p->scene->toolsettings->snap_mode==SCE_SNAP_MODE_FACE) && + (p->scene->toolsettings->snap_flag & SCE_SNAP_PROJECT) ) + { + return 1; + } + + return 0; +} + /* ******************************************* */ /* Calculations/Conversions */ @@ -211,24 +226,29 @@ static void gp_stroke_convertcoords (tGPsdata *p, short mval[], float out[]) /* in 3d-space - pt->x/y/z are 3 side-by-side floats */ if (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) { - const short mx=mval[0], my=mval[1]; - float rvec[3], dvec[3]; - - /* Current method just converts each point in screen-coordinates to - * 3D-coordinates using the 3D-cursor as reference. In general, this - * works OK, but it could of course be improved. - * - * TODO: - * - investigate using nearest point(s) on a previous stroke as - * reference point instead or as offset, for easier stroke matching - * - investigate projection onto geometry (ala retopo) - */ - gp_get_3d_reference(p, rvec); - - /* method taken from editview.c - mouse_cursor() */ - project_short_noclip(p->ar, rvec, mval); - window_to_3d_delta(p->ar, dvec, mval[0]-mx, mval[1]-my); - sub_v3_v3v3(out, rvec, dvec); + if(gpencil_project_check(p) && (view_autodist_simple(p->ar, mval, out))) { + /* pass */ + } + else { + const short mx=mval[0], my=mval[1]; + float rvec[3], dvec[3]; + + /* Current method just converts each point in screen-coordinates to + * 3D-coordinates using the 3D-cursor as reference. In general, this + * works OK, but it could of course be improved. + * + * TODO: + * - investigate using nearest point(s) on a previous stroke as + * reference point instead or as offset, for easier stroke matching + */ + + gp_get_3d_reference(p, rvec); + + /* method taken from editview.c - mouse_cursor() */ + project_short_noclip(p->ar, rvec, mval); + window_to_3d_delta(p->ar, dvec, mval[0]-mx, mval[1]-my); + sub_v3_v3v3(out, rvec, dvec); + } } /* 2d - on 'canvas' (assume that p->v2d is set) */ @@ -1114,6 +1134,12 @@ static void gpencil_draw_exit (bContext *C, wmOperator *op) } /* cleanup */ + if(gpencil_project_check(p)) { + View3D *v3d= p->sa->spacedata.first; + view3d_operator_needs_opengl(C); + view_autodist_init(p->scene, p->ar, v3d); + } + gp_paint_cleanup(p); gp_session_cleanup(p); diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index ecd2d69b7fb..38a1d5ef9eb 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -117,8 +117,13 @@ unsigned int view3d_sample_backbuf_rect(struct ViewContext *vc, short mval[2], i void *handle, unsigned int (*indextest)(void *handle, unsigned int index)); unsigned int view3d_sample_backbuf(struct ViewContext *vc, int x, int y); +/* draws and does a 4x4 sample */ int view_autodist(struct Scene *scene, struct ARegion *ar, struct View3D *v3d, short *mval, float mouse_worldloc[3]); +/* only draw so view_autodist_simple can be called many times after */ +int view_autodist_init(struct Scene *scene, struct ARegion *ar, struct View3D *v3d); +int view_autodist_simple(struct ARegion *ar, short *mval, float mouse_worldloc[3]); + /* select */ #define MAXPICKBUF 10000 short view3d_opengl_select(struct ViewContext *vc, unsigned int *buffer, unsigned int bufsize, rcti *input); diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 2444c461bd0..9737e0cb167 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -2205,7 +2205,53 @@ int view_autodist(Scene *scene, ARegion *ar, View3D *v3d, short *mval, float mou return 1; } +int view_autodist_init(Scene *scene, ARegion *ar, View3D *v3d) //, float *autodist ) +{ + RegionView3D *rv3d= ar->regiondata; + /* Get Z Depths, needed for perspective, nice for ortho */ + draw_depth(scene, ar, v3d, NULL); + + /* force updating */ + if (rv3d->depths) { + rv3d->depths->damaged = 1; + } + + view3d_update_depths(ar, v3d); + return 1; +} + +// no 4x4 sampling, run view_autodist_init first +int view_autodist_simple(ARegion *ar, short *mval, float mouse_worldloc[3] ) //, float *autodist ) +{ + RegionView3D *rv3d= ar->regiondata; + bglMats mats; /* ZBuffer depth vars, could cache? */ + float depth; + double cent[2], p[3]; + + if (mval[0] < 0) return 0; + if (mval[1] < 0) return 0; + if (mval[0] >= rv3d->depths->w) return 0; + if (mval[1] >= rv3d->depths->h) return 0; + + /* Get Z Depths, needed for perspective, nice for ortho */ + bgl_get_mats(&mats); + depth= rv3d->depths->depths[mval[1]*rv3d->depths->w+mval[0]]; + + if (depth==MAXFLOAT) + return 0; + + cent[0] = (double)mval[0]; + cent[1] = (double)mval[1]; + + if (!gluUnProject(cent[0], cent[1], depth, mats.modelview, mats.projection, (GLint *)mats.viewport, &p[0], &p[1], &p[2])) + return 0; + + mouse_worldloc[0] = (float)p[0]; + mouse_worldloc[1] = (float)p[1]; + mouse_worldloc[2] = (float)p[2]; + return 1; +} /* ********************* NDOF ************************ */ /* note: this code is confusing and unclear... (ton) */