Commit 5d637a90 authored by Guillaume Seguin's avatar Guillaume Seguin

Double clicking on a facet in plater will rebase object on facet #207

parent 0bba2c17
...@@ -63,7 +63,7 @@ class showstl(wx.Window): ...@@ -63,7 +63,7 @@ class showstl(wx.Window):
self.initpos = None self.initpos = None
self.prevsel = -1 self.prevsel = -1
def drawmodel(self, m, scale): def prepare_model(self, m, scale):
m.bitmap = wx.EmptyBitmap(800, 800, 32) m.bitmap = wx.EmptyBitmap(800, 800, 32)
dc = wx.MemoryDC() dc = wx.MemoryDC()
dc.SelectObject(m.bitmap) dc.SelectObject(m.bitmap)
...@@ -226,6 +226,43 @@ class StlPlater(Plater): ...@@ -226,6 +226,43 @@ class StlPlater(Plater):
self.simarrange_path = simarrange_path if simarrange_path else "./simarrange/sa" self.simarrange_path = simarrange_path if simarrange_path else "./simarrange/sa"
self.set_viewer(viewer) self.set_viewer(viewer)
def clickcb(self, event):
if not isinstance(self.s, stlview.StlViewPanel):
return
x, y = event.GetPosition()
ray_near, ray_far = self.s.mouse_to_ray(x, y, local_transform = True)
best_match = None
best_facet = None
best_dist = float("inf")
# TODO: speedup search by first checking if ray is in bounding box
# of the given model
for key, model in self.models.iteritems():
transformed = model
if any(model.centeroffset):
transformed = transformed.translate(model.centeroffset)
if model.rot:
transformed = transformed.rotate([0, 0, model.rot])
if any(model.offsets):
transformed = transformed.translate(model.offsets)
facet, facet_dist = transformed.intersect(ray_near, ray_far)
if facet is not None and facet_dist < best_dist:
best_match = key
best_facet = facet
best_dist = facet_dist
if best_match is not None:
model = self.models[best_match]
newmodel = model.rebase(best_facet)
newmodel.offsets = model.offsets
newmodel.rot = 0
newmodel.scale = model.scale
newmodel.filename = model.filename
newmodel.centeroffset = [-(newmodel.dims[1] + newmodel.dims[0]) / 2,
-(newmodel.dims[3] + newmodel.dims[2]) / 2,
0]
self.s.prepare_model(newmodel, 2)
self.models[best_match] = newmodel
wx.CallAfter(self.Refresh)
def done(self, event, cb): def done(self, event, cb):
try: try:
os.mkdir("tempstl") os.mkdir("tempstl")
...@@ -294,12 +331,12 @@ class StlPlater(Plater): ...@@ -294,12 +331,12 @@ class StlPlater(Plater):
newmodel.rot = model.rot newmodel.rot = model.rot
newmodel.scale = list(model.scale) newmodel.scale = list(model.scale)
self.add_model(name, newmodel) self.add_model(name, newmodel)
self.s.drawmodel(newmodel, 2) self.s.prepare_model(newmodel, 2)
break break
else: else:
# Filter out the path, just show the STL filename. # Filter out the path, just show the STL filename.
self.load_stl_into_model(name, name) self.load_stl_into_model(name, name)
self.Refresh() wx.CallAfter(self.Refresh)
def load_stl_into_model(self, path, name, offset = [0, 0, 0], rotation = 0, scale = [1.0, 1.0, 1.0]): def load_stl_into_model(self, path, name, offset = [0, 0, 0], rotation = 0, scale = [1.0, 1.0, 1.0]):
model = stltool.stl(path) model = stltool.stl(path)
...@@ -311,7 +348,7 @@ class StlPlater(Plater): ...@@ -311,7 +348,7 @@ class StlPlater(Plater):
model.centeroffset = [-(model.dims[1] + model.dims[0]) / 2, model.centeroffset = [-(model.dims[1] + model.dims[0]) / 2,
-(model.dims[3] + model.dims[2]) / 2, -(model.dims[3] + model.dims[2]) / 2,
0] 0]
self.s.drawmodel(model, 2) self.s.prepare_model(model, 2)
def export_to(self, name): def export_to(self, name):
with open(name.replace(".", "_") + ".scad", "w") as sf: with open(name.replace(".", "_") + ".scad", "w") as sf:
......
...@@ -20,6 +20,9 @@ import math ...@@ -20,6 +20,9 @@ import math
import numpy import numpy
import numpy.linalg import numpy.linalg
def normalize(v):
return v / numpy.linalg.norm(v)
def genfacet(v): def genfacet(v):
veca = v[1] - v[0] veca = v[1] - v[0]
vecb = v[2] - v[1] vecb = v[2] - v[1]
...@@ -154,6 +157,38 @@ class stl(object): ...@@ -154,6 +157,38 @@ class stl(object):
f.close() f.close()
return return
def intersect(self, ray_far, ray_near):
ray_near = numpy.array(ray_near)
ray_far = numpy.array(ray_far)
ray_dir = normalize(ray_far - ray_near)
best_facet = None
best_dist = float("inf")
eps = 0.000001
for facet_i, (normal, (v1, v2, v3)) in enumerate(self.facets):
edge1 = v2 - v1
edge2 = v3 - v1
pvec = numpy.cross(ray_dir, edge2)
det = edge1.dot(pvec)
if abs(det) < eps:
continue
inv_det = 1. / det
tvec = ray_near - v1
u = tvec.dot(pvec) * inv_det
if u < 0. or u > 1.:
continue
qvec = numpy.cross(tvec, edge1)
v = ray_dir.dot(qvec) * inv_det
if v < 0. or u + v > 1.:
continue
t = edge2.dot(qvec) * inv_det
if t < eps:
continue
if t < best_dist:
best_facet = facet_i
best_dist = t
return best_facet, best_dist
def rebase(self, facet_i): def rebase(self, facet_i):
normal, facet = self.facets[facet_i] normal, facet = self.facets[facet_i]
u1 = facet[1] - facet[0] u1 = facet[1] - facet[0]
...@@ -163,17 +198,21 @@ class stl(object): ...@@ -163,17 +198,21 @@ class stl(object):
u2 = v2 - u1 * v2.dot(u1) / n1 u2 = v2 - u1 * v2.dot(u1) / n1
e2 = u2 / numpy.linalg.norm(u2) e2 = u2 / numpy.linalg.norm(u2)
e3 = numpy.cross(e1, e2) e3 = numpy.cross(e1, e2)
# Ensure Z direction if opposed to the normal
if normal.dot(e3) > 0:
e2 = - e2
e3 = - e3
matrix = [[e1[0], e2[0], e3[0], 0], matrix = [[e1[0], e2[0], e3[0], 0],
[e1[1], e2[1], e3[1], 0], [e1[1], e2[1], e3[1], 0],
[e1[2], e2[2], e3[2], 0], [e1[2], e2[2], e3[2], 0],
[0, 0, 0, 1]] [0, 0, 0, 1]]
matrix = numpy.array(matrix) matrix = numpy.array(matrix)
# Inverse change of basis matrix
matrix = numpy.linalg.inv(matrix)
# Set first vertex of facet as origin
neworig = matrix.dot(homogeneous(facet[0]))
matrix[:3, 3] = -neworig[:3]
newmodel = self.transform(matrix) newmodel = self.transform(matrix)
# If object ends up being mostly below ground plane,
# rotate it around Y axis
avgz = (newmodel.dims[4] + newmodel.dims[5]) / 2
if avgz < 0:
newmodel = newmodel.rotate([0, 180, 0])
return newmodel return newmodel
def translate(self, v = [0, 0, 0]): def translate(self, v = [0, 0, 0]):
......
...@@ -253,12 +253,11 @@ class StlViewPanel(wxGLPanel): ...@@ -253,12 +253,11 @@ class StlViewPanel(wxGLPanel):
self.initialized = 1 self.initialized = 1
wx.CallAfter(self.Refresh) wx.CallAfter(self.Refresh)
def drawmodel(self, m, n): def prepare_model(self, m, scale):
batch = pyglet.graphics.Batch() batch = pyglet.graphics.Batch()
stlview(m.facets, batch = batch) stlview(m.facets, batch = batch)
m.batch = batch m.batch = batch
m.animoffset = 300 # m.animoffset = 300
# print m
# threading.Thread(target = self.anim, args = (m, )).start() # threading.Thread(target = self.anim, args = (m, )).start()
wx.CallAfter(self.Refresh) wx.CallAfter(self.Refresh)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment