Source code for cam.curvecamequation

"""BlenderCAM 'curvecamequation.py' © 2021, 2022 Alain Pelletier

Operators to create a number of geometric shapes with curves.
"""

from math import pi, sin, cos, sqrt

import numpy as np

import bpy
from bpy.props import (
    EnumProperty,
    FloatProperty,
    IntProperty,
    StringProperty,
)
from bpy.types import Operator

from . import parametric


[docs] class CamSineCurve(Operator): """Object Sine """ # by Alain Pelletier april 2021
[docs] bl_idname = "object.sine"
[docs] bl_label = "Periodic Wave"
[docs] bl_options = {'REGISTER', 'UNDO', 'PRESET'}
# zstring: StringProperty(name="Z equation", description="Equation for z=F(u,v)", default="0.05*sin(2*pi*4*t)" )
[docs] axis: EnumProperty( name="Displacement Axis", items=( ('XY', 'Y to displace X axis', 'Y constant; X sine displacement'), ('YX', 'X to displace Y axis', 'X constant; Y sine displacement'), ('ZX', 'X to displace Z axis', 'X constant; Y sine displacement'), ('ZY', 'Y to displace Z axis', 'X constant; Y sine displacement') ), default='ZX', )
[docs] wave: EnumProperty( name="Wave", items=( ('sine', 'Sine Wave', 'Sine Wave'), ('triangle', 'Triangle Wave', 'triangle wave'), ('cycloid', 'Cycloid', 'Sine wave rectification'), ('invcycloid', 'Inverse Cycloid', 'Sine wave rectification') ), default='sine', )
[docs] amplitude: FloatProperty( name="Amplitude", default=.01, min=0, max=10, precision=4, unit="LENGTH", )
[docs] period: FloatProperty( name="Period", default=.5, min=0.001, max=100, precision=4, unit="LENGTH", )
[docs] beatperiod: FloatProperty( name="Beat Period Offset", default=0.0, min=0.0, max=100, precision=4, unit="LENGTH", )
[docs] shift: FloatProperty( name="Phase Shift", default=0, min=-360, max=360, precision=4, unit="ROTATION", )
[docs] offset: FloatProperty( name="Offset", default=0, min=- 1.0, max=1, precision=4, unit="LENGTH", )
[docs] iteration: IntProperty( name="Iteration", default=100, min=50, max=2000, )
[docs] maxt: FloatProperty( name="Wave Ends at X", default=0.5, min=-3.0, max=3, precision=4, unit="LENGTH", )
[docs] mint: FloatProperty( name="Wave Starts at X", default=0, min=-3.0, max=3, precision=4, unit="LENGTH", )
[docs] wave_distance: FloatProperty( name="Distance Between Multiple Waves", default=0.0, min=0.0, max=100, precision=4, unit="LENGTH", )
[docs] wave_angle_offset: FloatProperty( name="Angle Offset for Multiple Waves", default=pi/2, min=-200*pi, max=200*pi, precision=4, unit="ROTATION", )
[docs] wave_amount: IntProperty( name="Amount of Multiple Waves", default=1, min=1, max=2000, )
[docs] def execute(self, context): amp = self.amplitude period = self.period beatperiod = self.beatperiod offset = self.offset shift = self.shift # z=Asin(B(x+C))+D if self.wave == 'sine': zstring = ssine(amp, period, dc_offset=offset, phase_shift=shift) if self.beatperiod != 0: zstring += f"+ {ssine(amp, period+beatperiod, dc_offset=offset, phase_shift=shift)}" # build triangle wave from fourier series elif self.wave == 'triangle': zstring = f"{round(offset, 6) + triangle(80, period, amp)}" if self.beatperiod != 0: zstring += f"+ {triangle(80, period+beatperiod, amp)}" elif self.wave == 'cycloid': zstring = f"abs({ssine(amp, period, dc_offset=offset, phase_shift=shift)})" elif self.wave == 'invcycloid': zstring = f"-1 * abs({ssine(amp, period, dc_offset=offset, phase_shift=shift)})" print(zstring) # make equation from string def e(t): return eval(zstring) # build function to be passed to create parametric curve () def f(t, offset: float = 0.0, angle_offset: float = 0.0): if self.axis == "XY": c = (e(t + angle_offset) + offset, t, 0) elif self.axis == "YX": c = (t, e(t + angle_offset) + offset, 0) elif self.axis == "ZX": c = (t, offset, e(t + angle_offset)) elif self.axis == "ZY": c = (offset, t, e(t + angle_offset)) return c for i in range(self.wave_amount): angle_off = self.wave_angle_offset * period * i / (2 * pi) parametric.create_parametric_curve( f, offset=self.wave_distance * i, min=self.mint, max=self.maxt, use_cubic=True, iterations=self.iteration, angle_offset=angle_off ) return {'FINISHED'}
[docs] class CamLissajousCurve(Operator): """Lissajous """ # by Alain Pelletier april 2021
[docs] bl_idname = "object.lissajous"
[docs] bl_label = "Lissajous Figure"
[docs] bl_options = {'REGISTER', 'UNDO', 'PRESET'}
[docs] amplitude_A: FloatProperty( name="Amplitude A", default=.1, min=0, max=100, precision=4, unit="LENGTH", )
[docs] waveA: EnumProperty( name="Wave X", items=( ('sine', 'Sine Wave', 'Sine Wave'), ('triangle', 'Triangle Wave', 'triangle wave') ), default='sine', )
[docs] amplitude_B: FloatProperty( name="Amplitude B", default=.1, min=0, max=100, precision=4, unit="LENGTH", )
[docs] waveB: EnumProperty( name="Wave Y", items=( ('sine', 'Sine Wave', 'Sine Wave'), ('triangle', 'Triangle Wave', 'triangle wave') ), default='sine', )
[docs] period_A: FloatProperty( name="Period A", default=1.1, min=0.001, max=100, precision=4, unit="LENGTH", )
[docs] period_B: FloatProperty( name="Period B", default=1.0, min=0.001, max=100, precision=4, unit="LENGTH", )
[docs] period_Z: FloatProperty( name="Period Z", default=1.0, min=0.001, max=100, precision=4, unit="LENGTH", )
[docs] amplitude_Z: FloatProperty( name="Amplitude Z", default=0.0, min=0, max=100, precision=4, unit="LENGTH", )
[docs] shift: FloatProperty( name="Phase Shift", default=0, min=-360, max=360, precision=4, unit="ROTATION", )
[docs] iteration: IntProperty( name="Iteration", default=500, min=50, max=10000, )
[docs] maxt: FloatProperty( name="Wave Ends at X", default=11, min=-3.0, max=1000000, precision=4, unit="LENGTH", )
[docs] mint: FloatProperty( name="Wave Starts at X", default=0, min=-10.0, max=3, precision=4, unit="LENGTH", )
[docs] def execute(self, context): # x=Asin(at+delta ),y=Bsin(bt) if self.waveA == 'sine': xstring = ssine(self.amplitude_A, self.period_A, phase_shift=self.shift) elif self.waveA == 'triangle': xstring = f"{triangle(100, self.period_A, self.amplitude_A)}" if self.waveB == 'sine': ystring = ssine(self.amplitude_B, self.period_B) elif self.waveB == 'triangle': ystring = f"{triangle(100, self.period_B, self.amplitude_B)}" zstring = ssine(self.amplitude_Z, self.period_Z) # make equation from string def x(t): return eval(xstring) def y(t): return eval(ystring) def z(t): return eval(zstring) print(f"x= {xstring}") print(f"y= {ystring}") # build function to be passed to create parametric curve () def f(t, offset: float = 0.0): c = (x(t), y(t), z(t)) return c parametric.create_parametric_curve( f, offset=0.0, min=self.mint, max=self.maxt, use_cubic=True, iterations=self.iteration ) return {'FINISHED'}
[docs] class CamHypotrochoidCurve(Operator): """Hypotrochoid """ # by Alain Pelletier april 2021
[docs] bl_idname = "object.hypotrochoid"
[docs] bl_label = "Spirograph Type Figure"
[docs] bl_options = {'REGISTER', 'UNDO', 'PRESET'}
[docs] typecurve: EnumProperty( name="Type of Curve", items=( ('hypo', 'Hypotrochoid', 'Inside ring'), ('epi', 'Epicycloid', 'Outside inner ring') ), )
[docs] R: FloatProperty( name="Big Circle Radius", default=0.25, min=0.001, max=100, precision=4, unit="LENGTH", )
[docs] r: FloatProperty( name="Small Circle Radius", default=0.18, min=0.0001, max=100, precision=4, unit="LENGTH", )
[docs] d: FloatProperty( name="Distance from Center of Interior Circle", default=0.050, min=0, max=100, precision=4, unit="LENGTH", )
[docs] dip: FloatProperty( name="Variable Depth from Center", default=0.00, min=-100, max=100, precision=4, )
[docs] def execute(self, context): r = round(self.r, 6) R = round(self.R, 6) d = round(self.d, 6) Rmr = round(R - r, 6) # R-r Rpr = round(R + r, 6) # R +r Rpror = round(Rpr / r, 6) # (R+r)/r Rmror = round(Rmr / r, 6) # (R-r)/r maxangle = 2 * pi * ((np.lcm(round(self.R * 1000), round(self.r * 1000)) / (R * 1000))) if self.typecurve == "hypo": xstring = f"{Rmr} * cos(t) + {d} * cos({Rmror} * t)" ystring = f"{Rmr} * sin(t) - {d} * sin({Rmror} * t)" else: xstring = f"{Rpr} * cos(t) - {d} * cos({Rpror} * t)" ystring = f"{Rpr} * sin(t) - {d} * sin({Rpror} * t)" zstring = f"({round(self.dip, 6)} * (sqrt((({xstring})**2) + (({ystring})**2))))" # make equation from string def x(t): return eval(xstring) def y(t): return eval(ystring) def z(t): return eval(zstring) print(f"x= {xstring}") print(f"y= {ystring}") print(f"z= {zstring}") print(f"maxangle {maxangle}") # build function to be passed to create parametric curve () def f(t, offset: float = 0.0): c = (x(t), y(t), z(t)) return c iter = int(maxangle * 10) if iter > 10000: # do not calculate more than 10000 points print("limiting calculations to 10000 points") iter = 10000 parametric.create_parametric_curve( f, offset=0.0, min=0, max=maxangle, use_cubic=True, iterations=iter ) return {'FINISHED'}
[docs] class CamCustomCurve(Operator): """Object Custom Curve """ # by Alain Pelletier april 2021
[docs] bl_idname = "object.customcurve"
[docs] bl_label = "Custom Curve"
[docs] bl_options = {'REGISTER', 'UNDO', 'PRESET'}
[docs] xstring: StringProperty( name="X Equation", description="Equation x=F(t)", default="t", )
[docs] ystring: StringProperty( name="Y Equation", description="Equation y=F(t)", default="0", )
[docs] zstring: StringProperty( name="Z Equation", description="Equation z=F(t)", default="0.05*sin(2*pi*4*t)", )
[docs] iteration: IntProperty( name="Iteration", default=100, min=50, max=2000, )
[docs] maxt: FloatProperty( name="Wave Ends at X", default=0.5, min=-3.0, max=10, precision=4, unit="LENGTH", )
[docs] mint: FloatProperty( name="Wave Starts at X", default=0, min=-3.0, max=3, precision=4, unit="LENGTH", )
[docs] def execute(self, context): print("x= " + self.xstring) print("y= " + self.ystring) print("z= " + self.zstring) # make equation from string def ex(t): return eval(self.xstring) def ey(t): return eval(self.ystring) def ez(t): return eval(self.zstring) # build function to be passed to create parametric curve () def f(t, offset: float = 0.0): c = (ex(t), ey(t), ez(t)) return c parametric.create_parametric_curve( f, offset=0.0, min=self.mint, max=self.maxt, use_cubic=True, iterations=self.iteration ) return {'FINISHED'}
[docs] def triangle(i, T, A): s = f"{A * 8 / (pi**2)} * (" for n in range(i): if n % 2 != 0: e = (n-1)/2 a = round(((-1)**e)/(n**2), 8) b = round(n*pi/(T/2), 8) if n > 1: s += '+' s += f"{a} * sin({b} * t)" s += ')' return s
[docs] def ssine(A, T, dc_offset=0, phase_shift=0): args = [ dc_offset, phase_shift, A, T ] for arg in args: arg = round(arg, 6) return f"{dc_offset} + {A} * sin((2 * pi / {T}) * (t + {phase_shift}))"