| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import FreeCAD |
| | import Path |
| | import Path.Base.Language as PathLanguage |
| | import math |
| |
|
| | |
| |
|
| | PI = math.pi |
| |
|
| |
|
| | class Kink(object): |
| | """A Kink represents the angle at which two moves connect. |
| | A positive kink angle represents a move to the left, and a negative angle represents a move to the right. |
| | """ |
| |
|
| | def __init__(self, m0, m1): |
| | if m1 is None: |
| | m1 = m0[1] |
| | m0 = m0[0] |
| | self.m0 = m0 |
| | self.m1 = m1 |
| | self.t0 = m0.anglesOfTangents()[1] |
| | self.t1 = m1.anglesOfTangents()[0] |
| | if Path.Geom.isRoughly(self.t0, self.t1): |
| | self.defl = 0 |
| | else: |
| | self.defl = Path.Geom.normalizeAngle(self.t1 - self.t0) |
| |
|
| | def isKink(self): |
| | return self.defl != 0 |
| |
|
| | def goesLeft(self): |
| | return self.defl > 0 |
| |
|
| | def goesRight(self): |
| | return self.defl < 0 |
| |
|
| | def deflection(self): |
| | """deflection() ... returns the tangential difference of the two edges at their intersection""" |
| | return self.defl |
| |
|
| | def normAngle(self): |
| | """normAngle() ... returns the angle opposite between the two tangents""" |
| |
|
| | |
| | |
| | |
| | if self.t0 > self.t1: |
| | return Path.Geom.normalizeAngle((self.t0 + self.t1 + math.pi) / 2) |
| | return Path.Geom.normalizeAngle((self.t0 + self.t1 - math.pi) / 2) |
| |
|
| | def position(self): |
| | """position() ... position of the edge's intersection""" |
| | return self.m0.positionEnd() |
| |
|
| | def x(self): |
| | return self.position().x |
| |
|
| | def y(self): |
| | return self.position().y |
| |
|
| | def __repr__(self): |
| | return f"({self.x():.4f}, {self.y():.4f})[t0={180*self.t0/math.pi:.2f}, t1={180*self.t1/math.pi:.2f}, deflection={180*self.defl/math.pi:.2f}, normAngle={180*self.normAngle()/math.pi:.2f}]" |
| |
|
| |
|
| | class Bone(object): |
| | """A Bone holds all the information of a bone and the kink it is attached to""" |
| |
|
| | def __init__(self, kink, angle, length, instr=None): |
| | self.kink = kink |
| | self.angle = angle |
| | self.length = length |
| | self.instr = [] if instr is None else instr |
| |
|
| | def addInstruction(self, instr): |
| | self.instr.append(instr) |
| |
|
| | def position(self): |
| | """pos() ... return the position of the bone""" |
| | return self.kink.position() |
| |
|
| | def tip(self): |
| | """tip() ... return the tip of the bone.""" |
| | dx = abs(self.length) * math.cos(self.angle) |
| | dy = abs(self.length) * math.sin(self.angle) |
| | return self.position() + FreeCAD.Vector(dx, dy, 0) |
| |
|
| |
|
| | def kink_to_path(kink, g0=False): |
| | return Path.Path([PathLanguage.instruction_to_command(instr) for instr in [kink.m0, kink.m1]]) |
| |
|
| |
|
| | def bone_to_path(bone, g0=False): |
| | kink = bone.kink |
| | cmds = [] |
| | if g0 and not Path.Geom.pointsCoincide(kink.m0.positionBegin(), FreeCAD.Vector(0, 0, 0)): |
| | pos = kink.m0.positionBegin() |
| | param = {} |
| | if not Path.Geom.isRoughly(pos.x, 0): |
| | param["X"] = pos.x |
| | if not Path.Geom.isRoughly(pos.y, 0): |
| | param["Y"] = pos.y |
| | cmds.append(Path.Command("G0", param)) |
| | for instr in [kink.m0, bone.instr[0], bone.instr[1], kink.m1]: |
| | cmds.append(PathLanguage.instruction_to_command(instr)) |
| | return Path.Path(cmds) |
| |
|
| |
|
| | def generate_bone(kink, length, angle): |
| | dx = length * math.cos(angle) |
| | dy = length * math.sin(angle) |
| | p0 = kink.position() |
| |
|
| | if Path.Geom.isRoughly(0, dx): |
| | |
| | moveIn = PathLanguage.MoveStraight(kink.position(), "G1", {"Y": p0.y + dy}) |
| | moveOut = PathLanguage.MoveStraight(moveIn.positionEnd(), "G1", {"Y": p0.y}) |
| | elif Path.Geom.isRoughly(0, dy): |
| | |
| | moveIn = PathLanguage.MoveStraight(kink.position(), "G1", {"X": p0.x + dx}) |
| | moveOut = PathLanguage.MoveStraight(moveIn.positionEnd(), "G1", {"X": p0.x}) |
| | else: |
| | moveIn = PathLanguage.MoveStraight(kink.position(), "G1", {"X": p0.x + dx, "Y": p0.y + dy}) |
| | moveOut = PathLanguage.MoveStraight(moveIn.positionEnd(), "G1", {"X": p0.x, "Y": p0.y}) |
| |
|
| | return Bone(kink, angle, length, [moveIn, moveOut]) |
| |
|
| |
|
| | class Generator(object): |
| | def __init__(self, calc_length, nominal_length, custom_length): |
| | self.calc_length = calc_length |
| | self.nominal_length = nominal_length |
| | self.custom_length = custom_length |
| |
|
| | def length(self, kink, angle): |
| | return self.calc_length(kink, angle, self.nominal_length, self.custom_length) |
| |
|
| | def generate_func(self): |
| | return generate_bone |
| |
|
| | def generate(self, kink): |
| | angle = self.angle(kink) |
| | return self.generate_func()(kink, self.length(kink, angle), angle) |
| |
|
| |
|
| | class GeneratorTBoneHorizontal(Generator): |
| | def angle(self, kink): |
| | if abs(kink.normAngle()) > (PI / 2): |
| | return -PI |
| | else: |
| | return 0 |
| |
|
| |
|
| | class GeneratorTBoneVertical(Generator): |
| | def angle(self, kink): |
| | if kink.normAngle() > 0: |
| | return PI / 2 |
| | else: |
| | return -PI / 2 |
| |
|
| |
|
| | class GeneratorTBoneOnShort(Generator): |
| | def angle(self, kink): |
| | rot = PI / 2 if kink.goesRight() else -PI / 2 |
| |
|
| | if kink.m0.pathLength() < kink.m1.pathLength(): |
| | return Path.Geom.normalizeAngle(kink.t0 + rot) |
| | else: |
| | return Path.Geom.normalizeAngle(kink.t1 + rot) |
| |
|
| |
|
| | class GeneratorTBoneOnLong(Generator): |
| | def angle(self, kink): |
| | rot = PI / 2 if kink.goesRight() else -PI / 2 |
| |
|
| | if kink.m0.pathLength() > kink.m1.pathLength(): |
| | return Path.Geom.normalizeAngle(kink.t0 + rot) |
| | else: |
| | return Path.Geom.normalizeAngle(kink.t1 + rot) |
| |
|
| |
|
| | class GeneratorDogbone(Generator): |
| | def angle(self, kink): |
| | return kink.normAngle() |
| |
|
| |
|
| | def generate(kink, generator, calc_length, nominal_length, custom_length=None): |
| | if custom_length is None: |
| | custom_length = nominal_length |
| | gen = generator(calc_length, nominal_length, custom_length) |
| | return gen.generate(kink) |
| |
|