Functioning 3-point arc. Progress on 2pt + center arc.

This commit is contained in:
jpcaram 2014-12-22 21:31:57 -05:00
parent 97a1e17b0d
commit 360127e6ad
2 changed files with 252 additions and 14 deletions

View File

@ -1,5 +1,6 @@
from PyQt4 import QtGui, QtCore, Qt from PyQt4 import QtGui, QtCore, Qt
import FlatCAMApp import FlatCAMApp
from camlib import *
from shapely.geometry import Polygon, LineString, Point, LinearRing from shapely.geometry import Polygon, LineString, Point, LinearRing
from shapely.geometry import MultiPoint, MultiPolygon from shapely.geometry import MultiPoint, MultiPolygon
@ -10,7 +11,8 @@ from shapely.wkt import loads as sloads
from shapely.wkt import dumps as sdumps from shapely.wkt import dumps as sdumps
from shapely.geometry.base import BaseGeometry from shapely.geometry.base import BaseGeometry
from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos, sign, dot
from numpy.linalg import solve
from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea
@ -32,8 +34,14 @@ class DrawTool(object):
self.geometry = None self.geometry = None
def click(self, point): def click(self, point):
"""
:param point: [x, y] Coordinate pair.
"""
return "" return ""
def on_key(self, key):
return None
def utility_geometry(self, data=None): def utility_geometry(self, data=None):
return None return None
@ -79,11 +87,183 @@ class FCCircle(FCShapeTool):
def make(self): def make(self):
p1 = self.points[0] p1 = self.points[0]
p2 = self.points[1] p2 = self.points[1]
radius = sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) radius = distance(p1, p2)
self.geometry = Point(p1).buffer(radius) self.geometry = Point(p1).buffer(radius)
self.complete = True self.complete = True
class FCArc(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.start_msg = "Click on CENTER ..."
# Direction of rotation between point 1 and 2.
# 'cw' or 'ccw'. Switch direction by hitting the
# 'o' key.
self.direction = "cw"
# Mode
# C12 = Center, p1, p2
# 12C = p1, p2, Center
# 132 = p1, p3, p2
self.mode = "c12" # Center, p1, p2
self.steps_per_circ = 55
def click(self, point):
self.points.append(point)
if len(self.points) == 1:
return "Click on 1st point ..."
if len(self.points) == 2:
return "Click on 2nd point to complete ..."
if len(self.points) == 3:
self.make()
return "Done."
return ""
def on_key(self, key):
if key == 'o':
self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
return 'Direction: ' + self.direction.upper()
if key == 'p':
if self.mode == 'c12':
self.mode = '12c'
elif self.mode == '12c':
self.mode = '132'
else:
self.mode = 'c12'
return 'Mode: ' + self.mode
def utility_geometry(self, data=None):
if len(self.points) == 1: # Show the radius
center = self.points[0]
p1 = data
return LineString([center, p1])
if len(self.points) == 2: # Show the arc
if self.mode == 'c12':
center = self.points[0]
p1 = self.points[1]
p2 = data
radius = sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
elif self.mode == '132':
p1 = array(self.points[0])
p3 = array(self.points[1])
p2 = array(data)
center, radius, t = three_point_circle(p1, p2, p3)
direction = 'cw' if sign(t) > 0 else 'ccw'
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
return [LineString(arc(center, radius, startangle, stopangle,
direction, self.steps_per_circ)),
Point(center), Point(p1), Point(p3)]
else: # '12c'
p1 = array(self.points[0])
p2 = array(self.points[1])
# Midpoint
a = (p1 + p2) / 2.0
# Parallel vector
c = p2 - p1
# Perpendicular vector
b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
b /= norm(b)
# Distance
t = distance(data, a)
# Which side? Cross product with c.
side = data[0] * c[1] - data[1] * c[0]
t *= sign(side)
# Center = a + bt
center = a + b * t
radius = norm(center - p1)
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
return [LineString(arc(center, radius, startangle, stopangle,
self.direction, self.steps_per_circ)),
Point(center)]
return None
def make(self):
if self.mode == 'c12':
center = self.points[0]
p1 = self.points[1]
p2 = self.points[2]
radius = distance(center, p1)
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
self.geometry = LineString(arc(center, radius, startangle, stopangle,
self.direction, self.steps_per_circ))
elif self.mode == '132':
p1 = array(self.points[0])
p3 = array(self.points[1])
p2 = array(self.points[2])
center, radius, t = three_point_circle(p1, p2, p3)
direction = 'cw' if sign(t) > 0 else 'ccw'
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
self.geometry = LineString(arc(center, radius, startangle, stopangle,
direction, self.steps_per_circ))
else: # self.mode == '12c'
p1 = array(self.points[0])
p2 = array(self.points[1])
# Midpoint
a = (p1 + p2) / 2.0
# Parallel vector
c = p2 - p1
# Perpendicular vector
b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
# Distance
t = distance(self.points[2], p1)
# Which side? Cross product with c.
side = self.points[2][0] * c[1] - self.points[2][1] * c[0]
t *= sign(side)
# Center = a + bt
center = a + b * t
radius = norm(center - p1)
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
self.geometry = LineString(arc(center, radius, startangle, stopangle,
self.direction, self.steps_per_circ))
self.complete = True
class FCRectangle(FCShapeTool): class FCRectangle(FCShapeTool):
""" """
Resulting type: Polygon Resulting type: Polygon
@ -277,6 +457,7 @@ class FlatCAMDraw(QtCore.QObject):
self.app.ui.addToolBar(self.drawing_toolbar) self.app.ui.addToolBar(self.drawing_toolbar)
self.select_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), 'Select') self.select_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), 'Select')
self.add_circle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/circle32.png'), 'Add Circle') self.add_circle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/circle32.png'), 'Add Circle')
self.add_arc_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/arc32.png'), 'Add Arc')
self.add_rectangle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/rectangle32.png'), 'Add Rectangle') self.add_rectangle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/rectangle32.png'), 'Add Rectangle')
self.add_polygon_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), 'Add Polygon') self.add_polygon_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), 'Add Polygon')
self.add_path_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/path32.png'), 'Add Path') self.add_path_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/path32.png'), 'Add Path')
@ -320,6 +501,8 @@ class FlatCAMDraw(QtCore.QObject):
"constructor": FCSelect}, "constructor": FCSelect},
"circle": {"button": self.add_circle_btn, "circle": {"button": self.add_circle_btn,
"constructor": FCCircle}, "constructor": FCCircle},
"arc": {"button": self.add_arc_btn,
"constructor": FCArc},
"rectangle": {"button": self.add_rectangle_btn, "rectangle": {"button": self.add_rectangle_btn,
"constructor": FCRectangle}, "constructor": FCRectangle},
"polygon": {"button": self.add_polygon_btn, "polygon": {"button": self.add_polygon_btn,
@ -620,6 +803,11 @@ class FlatCAMDraw(QtCore.QObject):
if event.key == 'k': if event.key == 'k':
self.corner_snap_btn.trigger() self.corner_snap_btn.trigger()
### Propagate to tool
response = self.active_tool.on_key(event.key)
if response is not None:
self.app.info(response)
def on_canvas_key_release(self, event): def on_canvas_key_release(self, event):
self.key = None self.key = None
@ -671,6 +859,12 @@ class FlatCAMDraw(QtCore.QObject):
plot_elements.append(element) plot_elements.append(element)
continue continue
if type(geo) == Point:
x, y = geo.coords.xy
element, = self.axes.plot(x, y, 'bo', linewidth=linewidth, animated=animated)
plot_elements.append(element)
continue
return plot_elements return plot_elements
# self.canvas.auto_adjust_axes() # self.canvas.auto_adjust_axes()
@ -823,4 +1017,8 @@ class FlatCAMDraw(QtCore.QObject):
def distance(pt1, pt2): def distance(pt1, pt2):
return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2) return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
def mag(vec):
return sqrt(vec[0] ** 2 + vec[1] ** 2)

View File

@ -8,7 +8,9 @@
#from __future__ import division #from __future__ import division
import traceback import traceback
from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos, dot, float32, \
transpose
from numpy.linalg import solve, norm
from matplotlib.figure import Figure from matplotlib.figure import Figure
import re import re
@ -2738,26 +2740,33 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
da_sign = {"cw": -1.0, "ccw": 1.0} da_sign = {"cw": -1.0, "ccw": 1.0}
points = [] points = []
if direction == "ccw" and stop <= start: if direction == "ccw" and stop <= start:
stop += 2*pi stop += 2 * pi
if direction == "cw" and stop >= start: if direction == "cw" and stop >= start:
stop -= 2*pi stop -= 2 * pi
angle = abs(stop - start) angle = abs(stop - start)
#angle = stop-start #angle = stop-start
steps = max([int(ceil(angle/(2*pi)*steps_per_circ)), 2]) steps = max([int(ceil(angle / (2 * pi) * steps_per_circ)), 2])
delta_angle = da_sign[direction]*angle*1.0/steps delta_angle = da_sign[direction] * angle * 1.0 / steps
for i in range(steps+1): for i in range(steps + 1):
theta = start + delta_angle*i theta = start + delta_angle * i
points.append((center[0]+radius*cos(theta), center[1]+radius*sin(theta))) points.append((center[0] + radius * cos(theta), center[1] + radius * sin(theta)))
return points return points
def arc2(p1, p2, center, direction, steps_per_circ):
r = sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
start = arctan2(p1[1] - center[1], p1[0] - center[0])
stop = arctan2(p2[1] - center[1], p2[0] - center[0])
return arc(center, r, start, stop, direction, steps_per_circ)
def arc_angle(start, stop, direction): def arc_angle(start, stop, direction):
if direction == "ccw" and stop <= start: if direction == "ccw" and stop <= start:
stop += 2*pi stop += 2 * pi
if direction == "cw" and stop >= start: if direction == "cw" and stop >= start:
stop -= 2*pi stop -= 2 * pi
angle = abs(stop - start) angle = abs(stop - start)
return angle return angle
@ -3108,4 +3117,35 @@ def autolist(obj):
_ = iter(obj) _ = iter(obj)
return obj return obj
except TypeError: except TypeError:
return [obj] return [obj]
def three_point_circle(p1, p2, p3):
"""
Computes the center and radius of a circle from
3 points on its circumference.
:param p1: Point 1
:param p2: Point 2
:param p3: Point 3
:return: center, radius
"""
# Midpoints
a1 = (p1 + p2) / 2.0
a2 = (p2 + p3) / 2.0
# Normals
b1 = dot((p2 - p1), array([[0, -1], [1, 0]], dtype=float32))
b2 = dot((p3 - p2), array([[0, 1], [-1, 0]], dtype=float32))
# Params
T = solve(transpose(array([-b1, b2])), a1 - a2)
print T
# Center
center = a1 + b1 * T[0]
# Radius
radius = norm(center - p1)
return center, radius, T[0]