-- ************************************************** -- Provide Moho with the name of this script object -- ************************************************** ScriptName = "HS_Hypotrochoid" -- ************************************************** -- General information about this script -- ************************************************** HS_Hypotrochoid = {} function HS_Hypotrochoid:Name() return "Spirograph" end function HS_Hypotrochoid:Version() return "1.00" end function HS_Hypotrochoid:IsBeginnerScript() return false end function HS_Hypotrochoid:Description() return "Hypotrochoids (Spirograph)" end function HS_Hypotrochoid:Creator() return "Hayasidist" end function HS_Hypotrochoid:UILabel() return HS_Hypotrochoid:Name() .. " Vers: " .. HS_Hypotrochoid:Version() end function HS_Hypotrochoid:ColorizeIcon() return true end --****************************** -- local utility subroutines -- ***************************** -- ************************************************************ -- Globals for use in the UI -- the values here are irrelevant -- -- ************************************************************ HS_Hypotrochoid.OuterCircle = 0 HS_Hypotrochoid.InnerCircle = 0 HS_Hypotrochoid.Offset = 0 HS_Hypotrochoid.Cusps = 1 --****************************** -- prefs -- ***************************** function HS_Hypotrochoid:LoadPrefs(prefs) self.OuterCircle = prefs:GetFloat("HS_Hypotrochoid.OuterCircle", 1) self.InnerCircle = prefs:GetFloat("HS_Hypotrochoid.InnerCircle", .5) self.Offset = prefs:GetFloat("HS_Hypotrochoid.Offset", .5) self.Cusps = prefs:GetFloat("HS_Hypotrochoid.Cusps", 1) end function HS_Hypotrochoid:SavePrefs(prefs) prefs:SetFloat("HS_Hypotrochoid.OuterCircle", self.OuterCircle) prefs:SetFloat("HS_Hypotrochoid.InnerCircle", self.InnerCircle) prefs:SetFloat("HS_Hypotrochoid.Offset", self.Offset) prefs:SetFloat("HS_Hypotrochoid.Cusps", self.Cusps) end function HS_Hypotrochoid:ResetPrefs() self.OuterCircle = 1 self.InnerCircle = .5 self.Offset = .5 self.Cusps = 1 end -- ************************************************** -- Recurring values -- -- ************************************************** local thisUUT = "B" -- ************************************************** -- Drawing controls for the current shape -- -- ************************************************** HS_Hypotrochoid.startPoint = 0 HS_Hypotrochoid.drawnPoints = 0 HS_Hypotrochoid.startVec = LM.Vector2:new_local() HS_Hypotrochoid.endVec = LM.Vector2:new_local() HS_Hypotrochoid.activeLayer = nil -- which layer did we last write the points on? --****************************** -- global utility subroutines -- ***************************** -- ************************************************** -- The guts of this script -- ************************************************** -- -- relevant / enabled?? -- function HS_Hypotrochoid:IsEnabled(moho) local mesh = moho:DrawingMesh() if (mesh == nil) then return false end if (moho.drawingLayer:CurrentAction() ~= "") then return false -- creating new objects in the middle of an action can lead to unexpected results end return true end function HS_Hypotrochoid:IsRelevant(moho) local rtx = false local vectorLayer = moho:LayerAsVector(moho.drawingLayer) if type (MOHO.ScriptInterface.AppVersion) == "function" then MohoVers = tonumber((string.gsub(moho:AppVersion(), "^(%d+)(%.%d+)(%..+)", "%1%2"))) else MohoVers = 0 end if MohoVers < 12 then rtx = false elseif (moho:DisableDrawingTools()) then rtx = false elseif (vectorLayer) then rtx = true end return rtx end local function makeLoop (moho, points) local mesh = moho:DrawingMesh() local n = mesh:CountPoints() mesh:SelectNone() local vec = LM.Vector2:new_local() local curv = .30 vec:Set(0,0) mesh:AddLonePoint(vec, 0) for i = 0, points-1 do mesh:AppendPoint(vec, 0) end mesh:WeldPoints(n + points, n, 0) for i = 0, points-1 do mesh:Point(n+i):SetCurvature(curv, 0) end mesh:SelectConnected() local shapeID = moho:CreateShape(true, false, 0) if (shapeID >= 0) then -- activeShape = shapeID mesh:Shape(shapeID).fHasOutline = true end HS_Hypotrochoid.startPoint = n end function HS_Hypotrochoid:Draw(moho) if self.activeLayer ~= moho.drawingLayer then return false end local mesh = moho:DrawingMesh() if (self.startPoint + self.drawnPoints) < mesh:CountPoints() then -- bale out if something else has deleted stuff (if they've added stuf ... well...) return end local i, j, s, y moho.document:PrepUndo(moho.drawingLayer) local drawingFrame = moho.drawingFrame local ptX = {} local ptY = {} local theta = 0 local step = 2*math.pi/self.drawnPoints self.InnerCircle = self.OuterCircle / self.Cusps -- (a-b)*COS(I60)+h*(COS(((a-b)/b)*I60)) -- (a-b)*SIN(I60)-h*(SIN(((a-b)/b)*I60)) for i = 0, self.drawnPoints - 1 do ptX[i] = (self.OuterCircle-self.InnerCircle)*math.cos(theta) + self.Offset*(math.cos(((self.OuterCircle-self.InnerCircle)/self.InnerCircle)*theta)) ptY[i] = (self.OuterCircle-self.InnerCircle)*math.sin(theta) - self.Offset*(math.sin(((self.OuterCircle-self.InnerCircle)/self.InnerCircle)*theta)) theta = theta + step end local xLow = math.min(table.unpack(ptX)) local xHi = math.max(table.unpack(ptX)) local yLow = math.min(table.unpack(ptY)) local yHi = math.max(table.unpack(ptY)) local Pt local xScale = (self.endVec.x-self.startVec.x)/ (xHi - xLow) local xOffset = (self.endVec.x+self.startVec.x)/2 local yScale = (self.endVec.y-self.startVec.y) / (yLow - yHi) local yOffset = self.endVec.y - (yScale * yLow) for i = 0, self.drawnPoints - 1 do local pt = mesh:Point(self.startPoint + i) pt.fPos.x = xOffset + (xScale * ptX[i]) pt.fPos.y = yOffset + (yScale * ptY[i]) end mesh:SelectConnected() moho:AddPointKeyframe(drawingFrame) moho:UpdateSelectedChannels() end --***************************** -- mouse actions --***************************** function HS_Hypotrochoid:OnMouseDown(moho, mouseEvent) local mesh = moho:DrawingMesh() if (mesh == nil) then return end self.activeLayer = moho.drawingLayer moho.document:PrepUndo(moho.drawingLayer) moho.document:SetDirty() self.drawnPoints = math.min(360, 7*HS_Hypotrochoid.Cusps) makeLoop (moho, self.drawnPoints) mouseEvent.view:DrawMe() end function HS_Hypotrochoid:OnMouseMoved(moho, mouseEvent) local mesh = moho:DrawingMesh() if (mesh == nil) then return end self.startVec:Set(mouseEvent.drawingStartVec) self.endVec:Set(mouseEvent.drawingVec) if (moho.gridOn) then moho:SnapToGrid(self.startVec) moho:SnapToGrid(self.endVec) end if mouseEvent.shiftKey then -- constrain width = height if (self.endVec.x > self.startVec.x) then if (self.endVec.y > self.startVec.y) then self.endVec.y = self.startVec.y + (self.endVec.x - self.startVec.x) else self.endVec.y = self.startVec.y - (self.endVec.x - self.startVec.x) end else if (self.endVec.y > self.startVec.y) then self.endVec.y = self.startVec.y - (self.endVec.x - self.startVec.x) else self.endVec.y = self.startVec.y + (self.endVec.x - self.startVec.x) end end end if mouseEvent.altKey then -- draw from center point self.startVec.x = self.startVec.x - (self.endVec.x - self.startVec.x) self.startVec.y = self.startVec.y - (self.endVec.y - self.startVec.y) end self:Draw(moho) mouseEvent.view:DrawMe() end function HS_Hypotrochoid:OnMouseUp(moho, mouseEvent) moho:UpdateUI() end local function OnKeyDownNew (moho, keyEvent) local mesh = moho:DrawingMesh() if (mesh == nil) then return false end -- activeShape = nil HS_Hypotrochoid.activeLayer = nil -- inactive until mouse click for draw mesh:SelectNone() return true end local function OnKeyDownDelete (moho, keyEvent) local mesh = moho:DrawingMesh() if (mesh == nil) then return false end local deleted = false if moho:CountSelectedPoints() > 0 then moho.document:PrepUndo(moho.drawingLayer) moho.document:SetDirty() mesh:DeleteSelectedPoints() HS_Hypotrochoid.activeLayer = nil -- inactive until mouse click for draw deleted = true end return deleted end function HS_Hypotrochoid:OnKeyDown(moho, keyEvent) local keyDispatch = { {LM.GUI.KEY_DELETE, OnKeyDownDelete}, {LM.GUI.KEY_BACKSPACE, OnKeyDownDelete}, {LM.GUI.KEY_ESCAPE, OnKeyDownNew}, {27, OnKeyDownNew}, -- the escape key {LM.GUI.KEY_BIND, OnKeyDownNew} } local updateView = false if keyEvent.ctrlKey then LM_SelectPoints:OnKeyDown(moho, keyEvent) return end local i for i = 1, #keyDispatch do if keyEvent.keyCode == keyDispatch[i][1] then updateView = keyDispatch[i][2] (moho, keyEvent) break end end if updateView then moho:UpdateSelectedChannels() keyEvent.view:DrawMe() end end -- ************************************************** -- ************************************************** -- ************************************************** -- *************************************************************************************************************************************** UI -- ************************************************** -- ************************************************** -- Tool options - data values -- ************************************************** local presetBase = MOHO.MSG_BASE + 20 HS_Hypotrochoid.PRESET = presetBase + 1 HS_Hypotrochoid.ALT_PRESET = presetBase - 1 HS_Hypotrochoid.OUTER = MOHO.MSG_BASE + 110 HS_Hypotrochoid.INNER = MOHO.MSG_BASE + 120 HS_Hypotrochoid.OFFSET = MOHO.MSG_BASE + 130 HS_Hypotrochoid.CUSPS = MOHO.MSG_BASE + 140 local presets = { {name="hypocycloid", outer=6, cusps=3, offset=2}, {name="ellipse", outer=4, cusps=2, offset=.5}, {name="rose (12)", outer=4, cusps=12, offset=(4 - 1/3)}, {name="data entry", outer=nil, cusps=nil, offset=nil} } local presetIx = #presets-1 -- counts from 0 local entered_Outer = HS_Hypotrochoid.OuterCircle local entered_Inner = HS_Hypotrochoid.InnerCircle local entered_Offset = HS_Hypotrochoid.Offset local entered_Cusps = HS_Hypotrochoid.Cusps -- ************************************************** -- Create and respond to tool's UI -- ************************************************** function HS_Hypotrochoid:DoLayout(moho, layout) local i self.butt = LM.GUI.Button(presets[presetIx+1].name, self.PRESET) self.butt:SetAlternateMessage(self.ALT_PRESET) layout:AddChild(self.butt) self.OuterCircleIn = LM.GUI.TextControl (0, "0.0000", self.OUTER, LM.GUI.FIELD_UFLOAT, "Outer Circle") layout:AddChild(self.OuterCircleIn, LM.GUI.ALIGN_RIGHT) self.CuspsIn = LM.GUI.TextControl (0, "00", self.CUSPS, LM.GUI.FIELD_UINT, "Cusps (loops)") layout:AddChild(self.CuspsIn, LM.GUI.ALIGN_RIGHT) self.CuspsIn:SetWheelInc(1) self.OffsetIn = LM.GUI.TextControl (0, "0.0000", self.OFFSET, LM.GUI.FIELD_FLOAT, "Offset") layout:AddChild(self.OffsetIn, LM.GUI.ALIGN_RIGHT) end function HS_Hypotrochoid:UpdateWidgets(moho) self.OuterCircleIn:SetValue(self.OuterCircle) self.OffsetIn:SetValue(self.Offset) self.CuspsIn:SetValue(self.Cusps) local function setWheel (x) x = math.abs(x) local i = math.floor(math.log (x, 10)) if i < -1 then i = -2 else i = i - 1 end return 10^i end self.OuterCircleIn:SetWheelInc(setWheel(self.OuterCircle)) self.OffsetIn:SetWheelInc(setWheel(self.Offset)) self.butt:SetLabel(presets[presetIx+1].name, false) self.butt:Redraw() HS_Hypotrochoid.OuterCircleIn:Redraw() HS_Hypotrochoid.OffsetIn:Redraw() HS_Hypotrochoid.CuspsIn:Redraw() end function HS_Hypotrochoid:HandleMessage(moho, view, msg) if (msg == self.OUTER) then self.OuterCircle = self.OuterCircleIn:FloatValue() elseif (msg == self.OFFSET) then self.Offset = self.OffsetIn:FloatValue() elseif (msg == self.CUSPS) then self.Cusps = self.CuspsIn:IntValue() if self.Cusps < 2 then self.Cusps = 2 end elseif (msg == self.PRESET) or (msg == self.ALT_PRESET) then presetIx = math.fmod(presetIx + #presets + (msg-presetBase), #presets) if presets[presetIx+1].outer then -- if it's data entry then restore the entered values and put in the presets entered_Outer = self.OuterCircle entered_Inner = self.InnerCircle entered_Offset = self.Offset entered_Cusps = self.Cusps self.OuterCircle = presets[presetIx+1].outer -- self.InnerCircle (calculated in mainline) self.Offset = presets[presetIx+1].offset self.Cusps = presets[presetIx+1].cusps else self.OuterCircle = entered_Outer self.InnerCircle = entered_Inner self.Offset = entered_Offset self.Cusps = entered_Cusps end end self:UpdateWidgets(moho) HS_Hypotrochoid:Draw(moho) end
Spirograph
Listed
Author: hayasidist
View Script
Script type: Tool
Uploaded: Oct 23 2022, 14:54
Draws a path similarly to drawing with a spirograph.
A hypotrochoid is generated by a fixed point on a circle rolling inside a fixed circle. This script allows the user to select the radius of the outer (fixed) circle, the number of nodes (i.e. how many times the inner circle will roll round the outer circle) and how far off centre the "pen" is.
The shape is created on frame 0. It can be modified after it is created either on frame 0 or in the timeline by changing the input parameters.
This is a preliminary version. The pace of updates will depend on feedback on its usefulness!
Here's a video that uses the script to generate many of the shapes.
This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 170

Spirograph
Listed
Author: hayasidist
View Script
Script type: Tool
Uploaded: Oct 23 2022, 14:54
Draws a path similarly to drawing with a spirograph.
A hypotrochoid is generated by a fixed point on a circle rolling inside a fixed circle. This script allows the user to select the radius of the outer (fixed) circle, the number of nodes (i.e. how many times the inner circle will roll round the outer circle) and how far off centre the "pen" is.
The shape is created on frame 0. It can be modified after it is created either on frame 0 or in the timeline by changing the input parameters.
This is a preliminary version. The pace of updates will depend on feedback on its usefulness!
Here's a video that uses the script to generate many of the shapes.
The shape is created on frame 0. It can be modified after it is created either on frame 0 or in the timeline by changing the input parameters.
This is a preliminary version. The pace of updates will depend on feedback on its usefulness!
Here's a video that uses the script to generate many of the shapes.
This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 170