-- ************************************************** -- Intro -- ************************************************** ScriptName = "SS_MakeBones" -- ************************************************** -- Make Bones - Split, Clone & Reform -- version: 001.0 AS11-MH13.5 #510803/510828 -- by Sam Cogheil (SimplSam) -- ************************************************** --[[ ***** Licence & Warranty ***** This work is licensed under a GNU General Public License v3.0 license Please see: https://www.gnu.org/licenses/gpl-3.0.en.html You can: Usage - Use/Reuse Freely Adapt - remix, transform, and build upon the material for any purpose, even commercially Share - copy and redistribute the material in any medium or format Adapt / Share under the following terms: Attribution - You must give appropriate credit, provide a link to the GPL-3.0 license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. ShareAlike - If you remix, transform, or build upon the material, you must distribute your contributions under the same license as this original. Warranty: Your use of this software material is at your own risk. By accepting to use this material you acknowledge that Sam Cogheil / SimplSam ("The Developer") make no warranties whatsoever - expressed or implied for the merchantability or fitness for a particular purpose of the software provided. The Developer will not be liable for any direct, indirect or consequential loss of actual or anticipated - data, revenue, profits, business, trade or goodwill that is suffered as a result of the use of the software provided. --]] --[[ ***** SPECIAL THANKS to: * Stan (and team) @ MOHO Scripting -- http://mohoscripting.com * The friendly faces @ Lost Marble Moho forum -- https://www.lostmarble.com/forum ***** ]] SS_MakeBones = {} SS_MakeBones._MOHO_Version = -1 function SS_MakeBones:Name() return "Make Bones" end function SS_MakeBones:Version() return "1.0 #510828" end function SS_MakeBones:Description() return "Make Bones" end function SS_MakeBones:Creator() return "Sam Cogheil (SimplSam)" end function SS_MakeBones:UILabel() return "Make Bones" end function SS_MakeBones:IsEnabled(moho) if (not (moho.layer:IsBoneType())) or (moho.document:CurrentDocAction() ~= "") or (moho:CountSelectedBones(false) < 1) then return false end end function SS_MakeBones:IsRelevant(moho) return (moho:Skeleton() ~= nil) --and (moho.frame == 0) end function SS_MakeBones:ColorizeIcon() return true end -- ************************************************** -- Dialog -- ************************************************** SS_MakeBones.pieces = 2 SS_MakeBones.strength = false SS_MakeBones.parented = true SS_MakeBones.rescaled = true SS_MakeBones.weighted = false SS_MakeBones.angle = 0 SS_MakeBones.reangled = false SS_MakeBones.chained = true SS_MakeBones.shared = false function SS_MakeBones:LoadPrefs(prefs) self.pieces = prefs:GetInt("SS_MakeBonesDialog.pieces", 2) self.strength = prefs:GetBool("SS_MakeBonesDialog.strength", false) self.parented = prefs:GetBool("SS_MakeBonesDialog.parented", true) self.rescaled = prefs:GetBool("SS_MakeBonesDialog.rescaled", true) self.weighted = prefs:GetBool("SS_MakeBonesDialog.weighted", false) self.angle = prefs:GetFloat("SS_MakeBonesDialog.angle", 0) self.reangled = prefs:GetBool("SS_MakeBonesDialog.reangled", false) self.chained = prefs:GetBool("SS_MakeBonesDialog.chained", true) self.shared = prefs:GetBool("SS_MakeBonesDialog.shared", false) end function SS_MakeBones:SavePrefs(prefs) prefs:SetInt("SS_MakeBonesDialog.pieces", self.pieces) prefs:SetBool("SS_MakeBonesDialog.strength", self.strength) prefs:SetBool("SS_MakeBonesDialog.parented", self.parented) prefs:SetBool("SS_MakeBonesDialog.rescaled", self.rescaled) prefs:SetBool("SS_MakeBonesDialog.weighted", self.weighted) prefs:SetFloat("SS_MakeBonesDialog.angle", self.angle) prefs:SetBool("SS_MakeBonesDialog.reangled", self.reangled) prefs:SetBool("SS_MakeBonesDialog.chained", self.chained) prefs:SetBool("SS_MakeBonesDialog.shared", self.shared) end function SS_MakeBones:ResetPrefs() self.pieces = 2 self.strength = false self.parented = true self.rescaled = true self.weighted = false self.angle = 0 self.reangled = false self.chained = true self.shared = false end local SS_MakeBonesDialog = {} SS_MakeBonesDialog.MSG_BASE = MOHO.MSG_BASE +91612 --TODO MH13.5 BUG ??? MH12/13 OK using 0 SS_MakeBonesDialog.PIECES = SS_MakeBonesDialog.MSG_BASE SS_MakeBonesDialog.STRENGTH = SS_MakeBonesDialog.MSG_BASE + 1 SS_MakeBonesDialog.RESCALED = SS_MakeBonesDialog.MSG_BASE + 2 SS_MakeBonesDialog.WEIGHTED = SS_MakeBonesDialog.MSG_BASE + 3 SS_MakeBonesDialog.ANGLE = SS_MakeBonesDialog.MSG_BASE + 4 SS_MakeBonesDialog.PARENTED = SS_MakeBonesDialog.MSG_BASE + 5 SS_MakeBonesDialog.REANGLE = SS_MakeBonesDialog.MSG_BASE + 6 SS_MakeBonesDialog.RESET = SS_MakeBonesDialog.MSG_BASE + 7 SS_MakeBonesDialog.CHAINED = SS_MakeBonesDialog.MSG_BASE + 8 SS_MakeBonesDialog.GROUPED = SS_MakeBonesDialog.MSG_BASE + 9 function SS_MakeBonesDialog:new() local d = LM.GUI.SimpleDialog("Let's Make Some Bones ...", SS_MakeBonesDialog) local l = d:GetLayout() d.piecesInput = LM.GUI.TextControl(0, '999', d.PIECES, LM.GUI.FIELD_UINT, 'Number of Pieces:') l:AddChild(d.piecesInput, LM.GUI.ALIGN_LEFT, 0) l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) d.parentedCheckbox = LM.GUI.CheckBox('Parental Link', d.PARENTED) l:AddChild(d.parentedCheckbox, LM.GUI.ALIGN_LEFT, 0) l:PushH(LM.GUI.ALIGN_CENTER, 20) d.chainedRadio = LM.GUI.RadioButton('Chained', d.CHAINED) l:AddChild(d.chainedRadio, LM.GUI.ALIGN_LEFT, 0) d.sharedRadio = LM.GUI.RadioButton('Shared', d.GROUPED) l:AddChild(d.sharedRadio, LM.GUI.ALIGN_LEFT, 0) l:Pop() l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) d.rescaledCheckbox = LM.GUI.CheckBox('Rescale', d.RESCALED) l:AddChild(d.rescaledCheckbox, LM.GUI.ALIGN_LEFT, 0) d.weightedCheckbox = LM.GUI.CheckBox('Weighted', d.WEIGHTED) l:AddChild(d.weightedCheckbox, LM.GUI.ALIGN_LEFT, 20) d.strengthCheckbox = LM.GUI.CheckBox('Scale Strength', d.STRENGTH) l:AddChild(d.strengthCheckbox, LM.GUI.ALIGN_LEFT, 20) l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) d.angleText = LM.GUI.StaticText('Angle Offset') l:AddChild(d.angleText, LM.GUI.ALIGN_LEFT, 0) d.angleDial = ( (SS_MakeBones._MOHO_Version >= 12) and LM.GUI.AngleWidget(d.ANGLE, true) or LM.GUI.AngleWidget(d.ANGLE) ) l:AddChild(d.angleDial, LM.GUI.ALIGN_LEFT, 10) d.reangledCheckbox = LM.GUI.CheckBox('ReAngle First Bone', d.REANGLE) l:AddChild(d.reangledCheckbox, LM.GUI.ALIGN_LEFT, 20) -- l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) -- d.menu = LM.GUI.Menu("Menu") -- d.popup = LM.GUI.PopupMenu(100, false) -- d.popup:SetMenu(d.menu) -- l:AddChild(d.popup) -- d.menu:AddItem("Item 1", 0, 12345) l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL) l:PushH() d.resetText = LM.GUI.StaticText('Reset to defaults:') l:AddChild(d.resetText, LM.GUI.ALIGN_LEFT, 0) d.resetButton = LM.GUI.Button('Reset', d.RESET) l:AddChild(d.resetButton, LM.GUI.ALIGN_LEFT, 0) l:Pop() return d end function SS_MakeBonesDialog:UpdateWidgets() self.piecesInput:SetValue(SS_MakeBones.pieces) self.strengthCheckbox:SetValue(SS_MakeBones.strength) self.parentedCheckbox:SetValue(SS_MakeBones.parented) self.rescaledCheckbox:SetValue(SS_MakeBones.rescaled) self.weightedCheckbox:SetValue(SS_MakeBones.weighted) self.angleDial:SetValue(SS_MakeBones.angle) self.reangledCheckbox:SetValue(SS_MakeBones.reangled) self.chainedRadio:SetValue(SS_MakeBones.chained) self.sharedRadio:SetValue(SS_MakeBones.shared) self.weightedCheckbox:Enable(self.rescaledCheckbox:Value()) self.strengthCheckbox:Enable(self.rescaledCheckbox:Value()) self.reangledCheckbox:Enable(self.angleDial:Value() > 0) self.chainedRadio:Enable(self.parentedCheckbox:Value()) self.sharedRadio:Enable(self.parentedCheckbox:Value()) end function SS_MakeBonesDialog:OnOK(moho) SS_MakeBones.pieces = self.piecesInput:IntValue() SS_MakeBones.strength = self.strengthCheckbox:Value() SS_MakeBones.parented = self.parentedCheckbox:Value() SS_MakeBones.rescaled = self.rescaledCheckbox:Value() SS_MakeBones.weighted = self.weightedCheckbox:Value() SS_MakeBones.angle = self.angleDial:Value() SS_MakeBones.reangled = self.reangledCheckbox:Value() SS_MakeBones.chained = self.chainedRadio:Value() SS_MakeBones.shared = self.sharedRadio:Value() end function SS_MakeBonesDialog:HandleMessage(msg) if msg == self.RESET then SS_MakeBones:ResetPrefs() self:UpdateWidgets() elseif msg == self.PIECES then self.piecesInput:SetValue(LM.Clamp(self.piecesInput:IntValue(), 2, 999)) elseif msg == self.PARENTED then self.chainedRadio:Enable(self.parentedCheckbox:Value()) self.sharedRadio:Enable(self.parentedCheckbox:Value()) elseif msg == self.RESCALED then self.weightedCheckbox:Enable(self.rescaledCheckbox:Value()) self.strengthCheckbox:Enable(self.rescaledCheckbox:Value()) elseif msg == self.ANGLE then self.reangledCheckbox:Enable(self.angleDial:Value() > 0) end end -- ************************************************** -- The guts of this script -- ************************************************** function SS_MakeBones:Run(moho) SS_MakeBones._MOHO_Version = moho:AppVersion() and moho:AppVersion():match("(%d+%.?%d*)")+0 or -1 local boneLayer = moho:LayerAsBone(moho.layer) local skel = moho:Skeleton() local dlog = SS_MakeBonesDialog:new() if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then return end local function GetFiboWeights(_length, _splits) local function fibo(n) return (n>2) and (fibo(n-1) + fibo(n-2)) or 1 --< #Ref: inspired by http://progopedia.com/example/fibonacci/37/ end local hi = fibo(_splits +2) local lo = fibo(_splits +1) local dv = hi + lo -2 local len0 = _length / dv local len1, len2 = len0, len1 local _parts = {} for i = 1, _splits do table.insert(_parts, len0) len2 = len1 len1 = len0 len0 = len0 + len2 end for i=1, math.floor(#_parts / 2) do _parts[i], _parts[#_parts - i + 1] = _parts[#_parts - i + 1], _parts[i] end return(_parts) end moho.document:PrepUndo(moho.layer) moho.document:SetDirty() local frame0, curFrame = 0, moho.frame local selectedBones = {} for iBone =0, skel:CountBones(false) -1 do if (skel:Bone(iBone).fSelected) then table.insert(selectedBones, iBone) end end for iBone =1, #selectedBones do local boneID = selectedBones[iBone] local bone = skel:Bone(boneID) local fiboFactor, when local boneLen = bone.fLength local newBoneLen = boneLen local boneStrength = bone.fStrength local boneAngle = bone.fAngle local newAngle = boneAngle local childBones = {} local baseHasParent = bone.fParent ~= -1 if ((SS_MakeBones._MOHO_Version >= 12) and (not bone:IsZeroLength())) or (bone.fLength > 0.0001) then --no pins -- find children of the base bone (inc. animated) for _iBone =0, skel:CountBones() -1 do local childBone = skel:Bone(_iBone) for iKey = 0, childBone.fAnimParent:CountKeys() -1 do when = childBone.fAnimParent:GetKeyWhen(iKey) if (childBone.fAnimParent:GetValue(when) == boneID) then table.insert(childBones, _iBone) break end end end -- base bone if (SS_MakeBones.rescaled) then if (SS_MakeBones.weighted) then fiboFactor = GetFiboWeights(boneLen, SS_MakeBones.pieces) newBoneLen = fiboFactor[1] else newBoneLen = boneLen / SS_MakeBones.pieces end end bone.fLength = newBoneLen --todo @ 0? bone.fStrength = boneStrength * (SS_MakeBones.strength and (newBoneLen / boneLen) or 1) if (SS_MakeBones.reangled) then bone.fAngle = bone.fAngle + SS_MakeBones.angle bone.fAnimAngle:SetValue(curFrame, bone.fAngle) end -- new bones local lastBone, lastBoneID, lastBoneLen = bone, boneID, newBoneLen local lastBonePos, lastBoneTip = LM.Vector2:new_local(), LM.Vector2:new_local() local xd = lastBoneLen * math.cos(boneAngle) local yd = lastBoneLen * math.sin(boneAngle) if (baseHasParent) then lastBonePos:Set(lastBone.fPos) lastBoneTip:Set(lastBonePos.x + xd, lastBonePos.y + yd) if (SS_MakeBones.parented) then newAngle = boneAngle + SS_MakeBones.angle else local parentBone = skel:Bone(bone.fParent) parentBone.fMovedMatrix:Transform(lastBonePos) parentBone.fMovedMatrix:Transform(lastBoneTip) newAngle = math.atan2(lastBoneTip.y - lastBonePos.y, lastBoneTip.x - lastBonePos.x) + SS_MakeBones.angle end else lastBoneTip:Set(lastBone.fPos.x + xd, lastBone.fPos.y + yd) if (SS_MakeBones.parented and SS_MakeBones.chained) then newAngle = SS_MakeBones.angle else newAngle = boneAngle + SS_MakeBones.angle end end local newBone, newBoneID for _iBone =2, SS_MakeBones.pieces do newBone = skel:AddBone(frame0) newBoneID = skel:BoneID(newBone) newBone:SetName(bone:Name()) if (SS_MakeBones._MOHO_Version >= 12) then skel:MakeBoneNameUnique(newBoneID) end if (SS_MakeBones.parented) then if (SS_MakeBones.chained) then newBone.fParent = lastBoneID newBone.fAnimParent:SetValue(frame0, lastBoneID) lastBoneTip:Set(newBoneLen, 0) else -- shared if (baseHasParent) then newBone.fParent = bone.fParent newBone.fAnimParent:SetValue(frame0, bone.fParent) end end end newBone.fPos:Set(lastBoneTip) newBone.fAnimPos:SetValue(frame0, newBone.fPos) --todo @ 0? if (SS_MakeBones.rescaled and SS_MakeBones.weighted) then newBoneLen = fiboFactor[_iBone] end newBone.fLength = newBoneLen newBone.fAngle = (SS_MakeBones.parented and baseHasParent and (not SS_MakeBones.shared)) and SS_MakeBones.angle or newAngle newBone.fAnimAngle:SetValue(frame0, newBone.fAngle) newBone.fStrength = boneStrength * (SS_MakeBones.strength and (newBoneLen / boneLen) or 1) newBone:SetTags(bone:Tags()) -- color skel:UpdateBoneMatrix(newBoneID) -- next new bone lastBone, lastBoneID, lastBoneLen = newBone, newBoneID, newBoneLen if (SS_MakeBones.shared) or (not SS_MakeBones.parented) then if (baseHasParent and (SS_MakeBones.shared or (not SS_MakeBones.parented))) then xd = lastBoneLen * math.cos(lastBone.fAngle -SS_MakeBones.angle) --todo @ 0? yd = lastBoneLen * math.sin(lastBone.fAngle -SS_MakeBones.angle) else xd = lastBoneLen * math.cos(boneAngle) yd = lastBoneLen * math.sin(boneAngle) end lastBoneTip:Set(lastBone.fPos.x + xd, lastBone.fPos.y + yd) newAngle = lastBone.fAngle end end -- fixup original child bones if (#childBones > 0) then local xOffset = boneLen - newBoneLen for _, childBoneID in ipairs(childBones) do local childBone = skel:Bone(childBoneID) -- rePosition (inc. animated) if (childBone.fParent == boneID) then childBone.fPos:Set(childBone.fPos.x - xOffset, childBone.fPos.y) end for iKey = 0, childBone.fAnimPos:CountKeys() -1 do when = childBone.fAnimPos:GetKeyWhen(iKey) if (childBone.fAnimParent:GetValue(when) == boneID) then local xyPos = childBone.fAnimPos:GetValue(when) xyPos.x = xyPos.x - xOffset childBone.fAnimPos:SetValue(when, xyPos) end end -- reParent (inc. animated) if (childBone.fParent == boneID) then childBone.fParent = lastBoneID end for iKey = 0, childBone.fAnimParent:CountKeys() -1 do when = childBone.fAnimParent:GetKeyWhen(iKey) if (childBone.fAnimParent:GetValue(when) == boneID) then childBone.fAnimParent:SetValue(when, lastBoneID) end end skel:UpdateBoneMatrix(childBoneID) end end end end end
SS - Make Bones
Listed
Author: simplsam
View Script
Script type: Button/Menu
Uploaded: Aug 28 2021, 08:29
Script Version: 1.0 #510828
A tool to split, clone or reform Bones
Make Bones is a Moho tool to split, clone and reform Moho Bones. To use - Select one or more Bones (normally one), then Activate and configure the tool. By default the tool will divide the length of the selected Bone into a {number of pieces} of equal lengths, parent the new bones to each other in a chain and re-assign prior child bones to the last bone in the newly created set.
The tool is primarily intended to be used at rigging & design time, but should cope with some limited pre-existing animation (reparenting and repositioning) if those elements of the selected bone and/or children have already been keyframed.
Options & Features
- Set the No. of Pieces # that the selected bone should be broken into (or # clones)
- Use Parental Link to form a Parent-Child relationship with newly created bones
- Chained: New bones are parentally linked back to the base bone
- Shared: New bones share the same parent as the base bone (or none if base bone has none)
- Disable Parental Link to create the new bones with no parenting
- Rescale the selected Bone based on No. of Pieces, with a default simple Linear scale relation of 1/#
- Use Weighted Bones to gradually reduce the new Bone sizes based on Fibonacci sequencing - where the length of the Parent is equal to the length of the Child + Grandchild … i.e. (8, 5, 3) or (89, 55, 34, 21)
- Use Scale Strength to scale Bone strength in proportion to Bone size
- Disable Rescale to clone/duplicate the selected bone at full size # times
- Use Angle Offset to create curly wurly bones, or bones set at an angle (if not Chained in a Parental Link)
- Use ReAngle First Bone - if you also want the base bone to have an angular offset
- Use Reset to restore default settings. OK to Apply settings & changes. Cancel to Cancel
- The color of the base bone is copied to the new bones
- New bones are auto-named based the name of the base bone (MH12+)
- Multiple selected bones will be processed in order
- The last used settings are automatically saved
- Compatible with AS11+
- Optimised for MH12+
* ‘base bone’ is the originally selected bone, which may itself be altered in size & rotation by the Split/Clone process
--
In the animated example below the first set is just a big bone broken into a series of 16 identical linear chained bones. In the second set, the big bone was first divided into 6 weighted fibo bones, then each fibo bone was subdivided into 4 linear bones. All bones were then animated by rotating +45 degrees and back.
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: 1164
SS - Make Bones
Listed
Author: simplsam
View Script
Script type: Button/Menu
Uploaded: Aug 28 2021, 08:29
Script Version: 1.0 #510828
A tool to split, clone or reform Bones
Make Bones is a Moho tool to split, clone and reform Moho Bones. To use - Select one or more Bones (normally one), then Activate and configure the tool. By default the tool will divide the length of the selected Bone into a {number of pieces} of equal lengths, parent the new bones to each other in a chain and re-assign prior child bones to the last bone in the newly created set.
The tool is primarily intended to be used at rigging & design time, but should cope with some limited pre-existing animation (reparenting and repositioning) if those elements of the selected bone and/or children have already been keyframed.
Options & Features
- Set the No. of Pieces # that the selected bone should be broken into (or # clones)
- Use Parental Link to form a Parent-Child relationship with newly created bones
- Chained: New bones are parentally linked back to the base bone
- Shared: New bones share the same parent as the base bone (or none if base bone has none)
- Disable Parental Link to create the new bones with no parenting
- Rescale the selected Bone based on No. of Pieces, with a default simple Linear scale relation of 1/#
- Use Weighted Bones to gradually reduce the new Bone sizes based on Fibonacci sequencing - where the length of the Parent is equal to the length of the Child + Grandchild … i.e. (8, 5, 3) or (89, 55, 34, 21)
- Use Scale Strength to scale Bone strength in proportion to Bone size
- Disable Rescale to clone/duplicate the selected bone at full size # times
- Use Angle Offset to create curly wurly bones, or bones set at an angle (if not Chained in a Parental Link)
- Use ReAngle First Bone - if you also want the base bone to have an angular offset
- Use Reset to restore default settings. OK to Apply settings & changes. Cancel to Cancel
- The color of the base bone is copied to the new bones
- New bones are auto-named based the name of the base bone (MH12+)
- Multiple selected bones will be processed in order
- The last used settings are automatically saved
- Compatible with AS11+
- Optimised for MH12+
* ‘base bone’ is the originally selected bone, which may itself be altered in size & rotation by the Split/Clone process
--
In the animated example below the first set is just a big bone broken into a series of 16 identical linear chained bones. In the second set, the big bone was first divided into 6 weighted fibo bones, then each fibo bone was subdivided into 4 linear bones. All bones were then animated by rotating +45 degrees and back.
The tool is primarily intended to be used at rigging & design time, but should cope with some limited pre-existing animation (reparenting and repositioning) if those elements of the selected bone and/or children have already been keyframed.
Options & Features
- Set the No. of Pieces # that the selected bone should be broken into (or # clones)
- Use Parental Link to form a Parent-Child relationship with newly created bones
- Chained: New bones are parentally linked back to the base bone
- Shared: New bones share the same parent as the base bone (or none if base bone has none)
- Disable Parental Link to create the new bones with no parenting
- Rescale the selected Bone based on No. of Pieces, with a default simple Linear scale relation of 1/#
- Use Weighted Bones to gradually reduce the new Bone sizes based on Fibonacci sequencing - where the length of the Parent is equal to the length of the Child + Grandchild … i.e. (8, 5, 3) or (89, 55, 34, 21)
- Use Scale Strength to scale Bone strength in proportion to Bone size
- Disable Rescale to clone/duplicate the selected bone at full size # times
- Use Angle Offset to create curly wurly bones, or bones set at an angle (if not Chained in a Parental Link)
- Use ReAngle First Bone - if you also want the base bone to have an angular offset
- Use Reset to restore default settings. OK to Apply settings & changes. Cancel to Cancel
- The color of the base bone is copied to the new bones
- New bones are auto-named based the name of the base bone (MH12+)
- Multiple selected bones will be processed in order
- The last used settings are automatically saved
- Compatible with AS11+
- Optimised for MH12+
* ‘base bone’ is the originally selected bone, which may itself be altered in size & rotation by the Split/Clone process
--
In the animated example below the first set is just a big bone broken into a series of 16 identical linear chained bones. In the second set, the big bone was first divided into 6 weighted fibo bones, then each fibo bone was subdivided into 4 linear bones. All bones were then animated by rotating +45 degrees and back.
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: 1164