Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Orient scale #1613

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 119 additions & 7 deletions bCNC/CNC.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import json
#import binascii

import random

from dxf import DXF
from bstl import Binary_STL_Writer
from bpath import eq,Path, Segment
Expand Down Expand Up @@ -482,6 +484,8 @@ def clear(self, item=None):
self.phi = 0.0
self.xo = 0.0
self.yo = 0.0
self.scalex = 1.0
self.scaley = 1.0
self.valid = False
self.saved = False

Expand Down Expand Up @@ -550,7 +554,7 @@ def __len__(self):
# 2. no aspect ratio
#
#-----------------------------------------------------------------------
def solve(self):
def solve_old(self):
self.valid = False
if len(self.markers)< 2: raise Exception("Too few markers")
A = []
Expand Down Expand Up @@ -582,6 +586,98 @@ def solve(self):
self.valid = True
return self.phi,self.xo,self.yo

def CalculateSSE(self, _xo, _yo, _phi, _scalex, _scaley):
_c = cos(_phi)
_s = sin(_phi)
SSE = 0.0
for i,(xm,ym,x,y) in enumerate(self.markers):
dx = _scalex * (_c*x - _s*y + _xo) - xm
dy = _scaley * (_s*x + _c*y + _yo) - ym
err = dx**2 + dy**2
SSE += err
return SSE

#-----------------------------------------------------------------------
# Return the rotation angle phi in radians, the offset (xo,yo)
# and the scales (scalex,scaley)
# Applies a primitive/naive evolutionary-like algorithm,
# not too sophisticated but does the job in (250-400) 400 iterations
# Provides better MSE values than the previous methods even without sx,sy
# It can be easily extended to handle skew (if necessary)
#-----------------------------------------------------------------------
def solve(self):
self.valid = False
if len(self.markers)< 3: raise Exception("Too few markers")
A = []
B = []
for xm,ym,x,y in self.markers:
A.append([x,y]); B.append([xm, ym])

print ("1")
rate = 0.1
SSE_0 = self.CalculateSSE(self.xo, self.yo, self.phi, self.scalex, self.scaley)
for rep in range(400):
dxo = random.random() - 0.5
dyo = random.random() - 0.5
dphi = rate * (random.random() - 0.5)
dsx = rate * (random.random() - 0.5)
dsy = rate * (random.random() - 0.5)

SSE = self.CalculateSSE(self.xo + dxo, self.yo, self.phi, self.scalex, self.scaley)
if SSE<=SSE_0:
self.xo += dxo
SSE_0 = SSE
else:
SSE = self.CalculateSSE(self.xo - dxo, self.yo, self.phi, self.scalex, self.scaley)
if SSE<=SSE_0:
self.xo -= dxo
SSE_0 = SSE

SSE = self.CalculateSSE(self.xo, self.yo + dyo, self.phi, self.scalex, self.scaley)
if SSE<=SSE_0:
self.yo += dyo
SSE_0 = SSE
else:
SSE = self.CalculateSSE(self.xo, self.yo - dyo, self.phi, self.scalex, self.scaley)
if SSE<=SSE_0:
self.yo -= dyo
SSE_0 = SSE

SSE = self.CalculateSSE(self.xo, self.yo, self.phi + dphi, self.scalex, self.scaley)
if SSE<=SSE_0:
self.phi += dphi
SSE_0 = SSE
else:
SSE = self.CalculateSSE(self.xo, self.yo, self.phi - dphi, self.scalex, self.scaley)
if SSE<=SSE_0:
self.phi -= dphi
SSE_0 = SSE

SSE = self.CalculateSSE(self.xo, self.yo, self.phi, self.scalex + dsx, self.scaley)
if SSE<=SSE_0:
self.scalex += dsx
SSE_0 = SSE
else:
SSE = self.CalculateSSE(self.xo, self.yo, self.phi, self.scalex - dsx, self.scaley)
if SSE<=SSE_0:
self.scalex -= dsx
SSE_0 = SSE

SSE = self.CalculateSSE(self.xo, self.yo, self.phi, self.scalex, self.scaley + dsy)
if SSE<=SSE_0:
self.scaley += dsy
SSE_0 = SSE
else:
SSE = self.CalculateSSE(self.xo, self.yo, self.phi, self.scalex, self.scaley - dsy)
if SSE<=SSE_0:
self.scaley -= dsy
SSE_0 = SSE

# if abs(self.phi)<TOLERANCE: self.phi = 0.0 # rotation

self.valid = True
return self.phi,self.xo,self.yo,self.scalex,self.scaley

#-----------------------------------------------------------------------
# @return minimum, average and maximum error
#-----------------------------------------------------------------------
Expand All @@ -597,8 +693,8 @@ def error(self):
del self.errors[:]

for i,(xm,ym,x,y) in enumerate(self.markers):
dx = c*x - s*y + self.xo - xm
dy = s*x + c*y + self.yo - ym
dx = self.scalex * (c*x - s*y + self.xo) - xm
dy = self.scaley * (s*x + c*y + self.yo) - ym
err = sqrt(dx**2 + dy**2)
self.errors.append(err)

Expand All @@ -614,15 +710,17 @@ def error(self):
def gcode2machine(self, x, y):
c = cos(self.phi)
s = sin(self.phi)
return c*x - s*y + self.xo, \
s*x + c*y + self.yo
return self.scalex * (c*x - s*y + self.xo), \
self.scaley * (s*x + c*y + self.yo)

#-----------------------------------------------------------------------
# Convert machine to gcode coordinates
#-----------------------------------------------------------------------
def machine2gcode(self, x, y):
c = cos(self.phi)
s = sin(self.phi)
x /= self.scalex
y /= self.scaley
x -= self.xo
y -= self.yo
return c*x + s*y, \
Expand Down Expand Up @@ -4422,7 +4520,20 @@ def transformFunc(self, new, old, relative, c, s, xo, yo):
new['I'] = c*i - s*j
new['J'] = s*i + c*j
return True

def transformScaleFunc(self, new, old, relative, c, s, xo, yo, scalex, scaley):
if 'X' not in new and 'Y' not in new: return False
x = getValue('X',new,old)
y = getValue('Y',new,old)
new['X'] = scalex * (c*x - s*y + xo)
new['Y'] = scaley * (s*x + c*y + yo)

if 'I' in new or 'J' in new:
i = getValue('I',new,old)
j = getValue('J',new,old)
new['I'] = scalex * (c*i - s*j)
new['J'] = scaley * (s*i + c*j)
return True
#----------------------------------------------------------------------
# Rotate items around optional center (on XY plane)
# ang in degrees (counter-clockwise)
Expand All @@ -4443,8 +4554,9 @@ def orientLines(self, items):
if not self.orient.valid: return "ERROR: Orientation information is not valid"
c = math.cos(self.orient.phi)
s = math.sin(self.orient.phi)
return self.modify(items, self.transformFunc, None, c, s,
self.orient.xo, self.orient.yo)
return self.modify(items, self.transformScaleFunc, None, c, s,
self.orient.xo, self.orient.yo,
self.orient.scalex, self.orient.scaley)

#----------------------------------------------------------------------
# Mirror Horizontal
Expand Down
6 changes: 3 additions & 3 deletions bCNC/ProbePage.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,10 +817,10 @@ def probeCenter(self, event=None):
#-----------------------------------------------------------------------
def orientSolve(self, event=None):
try:
phi, xo, yo = self.app.gcode.orient.solve()
phi, xo, yo, scalex, scaley = self.app.gcode.orient.solve()
self.angle_orient["text"]="%*f"%(CNC.digits, math.degrees(phi))
self.xo_orient["text"]="%*f"%(CNC.digits, xo)
self.yo_orient["text"]="%*f"%(CNC.digits, yo)
self.xo_orient["text"]="%*f (Scale: x%*f)"%(CNC.digits, xo, CNC.digits, scalex)
self.yo_orient["text"]="%*f (Scale: x%*f)"%(CNC.digits, yo, CNC.digits, scaley)

minerr, meanerr, maxerr = self.app.gcode.orient.error()
self.err_orient["text"] = "Avg:%*f Max:%*f Min:%*f"%\
Expand Down