Image
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName = "HS_Shape"

-- **************************************************
-- General information about this script
-- **************************************************

HS_Shape = {}

-- HS_Shape.BASE_STR ---- No localisation is this tool (yet?!)


-->> Live run **** vvv
local HS_Test_harness = {}
HS_Test_harness.tracePrint = function () end
HS_Test_harness.diagnosePrint = function () end
HS_Test_harness.diagnoseModifierKeys = function () end
--<< ***** ^^^^^^
--[[]]


function HS_Shape:Name()
	return "Draw shapes and curves"
end

function HS_Shape:Version()
	return "10.01"

end

function HS_Shape:IsBeginnerScript()
	return false
end

function HS_Shape:Description()
	return "Draw a shape (hold <shift> to constrain, <alt> and <ctrl> for other options)"
end


function HS_Shape:Creator()
	return "Hayasidist"
end

function HS_Shape:UILabel()
	return HS_Shape:Name() .. " Vers: " .. HS_Shape:Version()
end


function HS_Shape:ColorizeIcon()
	return true
end


--******************************
-- local utility subroutines
-- *****************************
local thisUUT = "B"					-- for the load customised tools portion ... 


local function getFunction(t, a) 			-- look for function t.a in the global table
--	HS_Test_harness:diagnosePrint (thisUUT, t, a, type(_G[t][a]))
	return type(_G[t][a]) == "function"
end


local function getArg (n, ...)
	local arg = {...}
	return arg[n]
end


local HS_MaxPt = 3000 	-- this is the maximum number of points that a subtool can draw
			-- in practice, much more than 1000 gives serious performance issues
local function validatePtCt (n)
	local rtx = false
	if (type (n) == "number")
	 		and (n >= 2) and (n <= HS_MaxPt)
	 		and math.ceil (n) == n then
		rtx = true
	end
	return (rtx)
end

local function validateInt (val)
	if type(val) == "number" then
		val = math.floor (val)
	else
		val = 0
	end
	return val
end

local function validateFloat (val)
	if type(val) ~= "number" then
		val = 0
	end
	return val
end

local function validateBool (val)
	if type(val) ~= "boolean" then
		val = false
	end
	return val
end

local function validateString (val)
	if type(val) == "string" then
		val:gsub ("%C", "") -- strip out control characters
	else
		val = false
	end
	return val
end




local shapeFncs = {
		{"load", "LoadPrefs"},
		{"save", "SavePrefs"},
		{"reset", "ResetPrefs"},
		{"mdn", "OnMouseDown"},
		{"mmov", "OnMouseMoved"},

--	from here down, optional routine names are allowed

		{"about", "About"},
		{"alt", {"Alt", "Alternative"}},
		{"onSel", {"OnSelection", "OnStart"}},
		{"onExit", {"OnMouseUp", "OnExit"}},
		{"LabelUI", "Labels"},
		{"bigN", {"BigN", "bigN", "Int1", "Int"}},
		{"lilN", {"LittleN", "littleN", "Int2"}},
		{"rat", {"Scale", "Float1", "Float"}},
		{"but", {"Button", "Butt"}},
		{"box", {"Box", "CheckBox"}}
		}


--[=[
a fully populated template ident table
	local t =
		{ident="Ident",		self=HS_ShapeIdent,	icon="hs_filename",	gKey="HS_ShapeIdent",
		mdn=self.OnMouseDown,	mmov=self.OnMouseMoved,

		tip="any string",	about=self.About,	alt=self.Alt,		onSel=self.OnSelection,	onExit=self.OnExit,
		load=self.LoadPrefs, 	save=self.SavePrefs,	reset=self.ResetPrefs,
		LabelUI=self.Labels, 	bigN=self.BigN,		lilN=self.LittleN,	rat=self.Scale,		but=self.Button,	box=self.CheckBox}

]=]


local function populateFunctions (t)

--
-- **	this takes a partial Identify table and fills in the functions from within the designated "self"
--

	for i = 1, #shapeFncs do
		t[shapeFncs[i][1]] = nil 			-- assume we won't find it

		if type(shapeFncs[i][2]) == "table" then
			for j = 1, #shapeFncs[i][2] do
				t[shapeFncs[i][1]] = _G[t.gKey][shapeFncs[i][2][j]]
				if t[shapeFncs[i][1]] and type(t[shapeFncs[i][1]]) == "function"   then
--					 -->> possible that a later option is actually the required function?!?!?
					break
				end
			end
		else
			t[shapeFncs[i][1]] = _G[t.gKey][shapeFncs[i][2]]
		end
	end

	return t

end



local function buildIdentifyTable (name)

--
-- **	this uses name:Shape() function to populate an Identify table
--

	local t = 
	{ident=nil, self=_G[name], icon=nil, gKey=name,
		mdn=nil, mmov=nil,

-- ** 	from here down, the functions are optional

		tip=nil, about=nil, alt= nil, onSel=nil,
		load=nil, save=nil, reset=nil,
		LabelUI=nil, bigN=nil, lilN=nil, rat=nil, but=nil, box=nil}

	local i, j

	local param = _G[name]:Shape()

	if type(param) == "table" then
		t.ident = param[1]
		t.tip = param[2]
		t.icon = param[3]
	else					-- we have separate basic items
		t.ident = getArg(1, _G[name]:Shape())
		t.tip = getArg(2, _G[name]:Shape())
		t.icon = getArg(3, _G[name]:Shape())
	end

	t = populateFunctions (t)

	return (t)
end


local function validateTool (t)
--
-- **	check the contents of an Identify table for correct data types and self-consistency as far as possible
--

	local i, j

	local val, msg				-- for validation / status messages

	local valMsg = {}
	local fatal = false
	local scan = true 	-- scan for functions needed

	local doInfoMsg = true -- for "info / warning" messages about a subtool (if false the only errors are reported -- useful for testing)

-- **	ident
	val = validateString (t.ident) -- is the identity a string?
	if val then
		if #val < 3 then
			val = false
		end
	end

	if not val then
		HS_Test_harness:diagnosePrint (thisUUT, "bad ident", type(t.ident), t.ident)
		msg = "FATAL -- Bad identifier. Found data type " .. type(t.ident) .. " with value " .. tostring(t.ident)
		table.insert(valMsg, msg)
		fatal = true
		t.ident = ""

	else
		t.ident = val
	end

-- **	tool tip
	local val = validateString (t.tip) -- and tooltip
	if not val then
		HS_Test_harness:diagnosePrint (thisUUT, "bad tip", type(t.tip), t.tip)
		if doInfoMsg then
			msg = "WARNING -- Tip is not of type " .. [["string". Type is ]] .. type(t.tip) .. " with value " .. tostring(t.tip)
			table.insert(valMsg, msg)
		end
		t.tip = ""
	else
		t.tip = val
	end

-- ** 	icon file name
	local val = validateString (t.icon)
	if val then 
		local invalFile = [==[[%\%/%:%?%"%<%>%|%.]]==]
		val = 	val:gsub ("^%s+", "") -- strip leading spaces
		if val:find (invalFile) then
			val = false
		end
	end

	if not val then
		HS_Test_harness:diagnosePrint (thisUUT, "bad icon", t.icon)
		msg = "FATAL -- Bad icon filename. Found data type " .. type(t.icon) .. " with value " .. tostring(t.icon)
		table.insert(valMsg, msg)
		t.icon = nil
		fatal = true
	else
		t.icon = val
	end

-- **	global table entry
	local val = validateString (t.gKey) -- the string that is "self"
	if not val then
		HS_Test_harness:diagnosePrint (thisUUT, "bad tool table name", type(t.gKey), t.gKey)
		msg = "FATAL -- Bad tool table name. Found data type " .. type(t.gKey) .. " with value " .. tostring(t.gKey)
		table.insert(valMsg, msg)
		t.gKey = "Bad Tool Id"
		fatal = true
	else
		if t.self ~= _G[val] then
			HS_Test_harness:diagnosePrint (thisUUT, "tool table name inconsistent with self", t.gKey)
			msg = "FATAL -- Textual tool table name inconsistemt with internal name " .. t.gKey
			table.insert(valMsg, msg)
			fatal = true
		end
		t.gKey = val
	end


-- **	functions are actually functions
	for i = 1, #shapeFncs do
		if t[shapeFncs[i][1]] and type(t[shapeFncs[i][1]]) ~= "function" then
			HS_Test_harness:diagnosePrint (thisUUT, "Function expected, got", type(t[shapeFncs[i][1]]), "for", t[shapeFncs[i][1]])
			msg = "ERROR -- " .. t.gKey .. "." .. t[shapeFncs[i][1]] .. " is of type " .. type(t[shapeFncs[i][1]]) .. ". Function expected."
			table.insert(valMsg, msg)
			t[shapeFncs[i][1]] = nil
		end
	end


-- **	consistent set of prefs
	local prefFncs = {"reset", "save", "load"}
	local prefCt = 0
	local fnc = ""

	msg = "; but not "

	for i = 1, #prefFncs do
		fnc = "*error*"
		for j = 1, #shapeFncs do
			if prefFncs[i] == shapeFncs[j][1] then
				fnc = shapeFncs[j][2]
				break
			end
		end

		if t[prefFncs[i]] then
			prefCt = prefCt + 1
			msg = fnc .. " " .. msg
		else
			msg = msg .. " " .. fnc
		end
	end

	if not (prefCt == 0 or prefCt == #prefFncs) then
		msg = "WARNING -- " ..  t.ident .. " Preferences has " .. msg .. "."
		HS_Test_harness:diagnosePrint (thisUUT, msg)
		if doInfoMsg then
			table.insert(valMsg, msg)
		end
	end


-- **	consistent set of mouse
	local mouseFncs = {"mmov", "mdn"}
	local mouseCt = 0

	msg = " but not "

	for i = 1, #mouseFncs do

		fnc = "*error*"
		for j = 1, #shapeFncs do
			if mouseFncs[i] == shapeFncs[j][1] then
				fnc = shapeFncs[j][2]
				break
			end
		end


		if t[mouseFncs[i]] then
			mouseCt = mouseCt + 1
			msg = fnc .. " " .. msg
		else
			msg = msg .. " " .. fnc
		end
	end

	if not (mouseCt == 0 or mouseCt == #mouseFncs) then
		msg = "FATAL -- " .. t.ident .. " Has " .. msg .. "."
		table.insert(valMsg, msg)
		fatal = true
	end

-- **	help / about?
	if not t.about then
		msg = "Information. " .. t.ident .. " Has no " .. [["About"]] .. " function."
		HS_Test_harness:diagnosePrint (thisUUT, msg)
		t.about = HS_Shape.About_Void

		if doInfoMsg then
			table.insert(valMsg, msg)
		end
	end


--[=[
-- -- vvvv DIAGNOSTIC ===========

	HS_Test_harness:tracePrint (thisUUT, "Validation finished. Fatal?", fatal, "Messages", #valMsg)

	for i = 1, #valMsg do	
		HS_Test_harness:diagnosePrint (thisUUT, valMsg[i])
	end

-- -- ^^^ ==========================
]=]

	if #valMsg > 0 then
		msg = ""
		for i = 1, #valMsg do	
			msg = msg .. valMsg[i] .. "\n"
		end
		HS_Shape:ToolError (t.gKey, "Validation error(s)", msg)
	end

	if fatal then
		return nil
	else
		return t
	end
end



--******************************
-- prefs and load custom subtools
-- *****************************


function HS_Shape:LoadPrefs(prefs)

	local i, j
	local key, value, name, goodTool

	if HS_Test_harness then
		HS_Test_harness:tracePrint (thisUUT, self:Name(), self:Version(), "Load prefs")
	end

--
-- ** 	check the internal tools
--
	for i = 1, #HS_Shape.subToolDispatch - 1 do

		goodTool = validateTool (populateFunctions (HS_Shape.subToolDispatch[i].self:Identify()))

		if not goodTool then
			HS_Test_harness:diagnosePrint (thisUUT, name, "validation errors")
			HS_Shape.subToolDispatch[i] = HS_Shape.subToolDispatch[#HS_Shape.subToolDispatch] -- Void
		else
			HS_Shape.subToolDispatch[i] = goodTool
		end

	end


--
-- **	look for inserts.
--
--	A user-shape unit is structured similarly to a tool script but must also have the script:Shape() function to provide basic info
--


--	local key, value, name, goodTool
--	name = nil
	for key,value in pairs(_G) do
		if type(_G[key]) == "table" then
			if getFunction(key, "Shape") then
--				HS_Test_harness:diagnosePrint (thisUUT, key, "candidate plug-in")
				goodTool = validateTool (buildIdentifyTable (key), key)
				if goodTool then
					table.insert (HS_Shape.subToolDispatch, #HS_Shape.subToolDispatch, goodTool)
				end
			end
		end
	end




--
-- ** Actually load preferences
--

-- ** System
	self.autoFill = prefs:GetBool("HS_Shape.autoFill", true)
	self.autoOutline = prefs:GetBool("HS_Shape.autoOutline", true)
	self.drawOn0 = prefs:GetBool("HS_Shape.drawOn0", true)
--	self.extenderOpt = prefs:GetInt("HS_Shape.extenderOpt", 0)
	self.extenderOpt = 0 -- always start with basic options

	self.shapeType = prefs:GetString("HS_Shape.shapeType", "") -- will default to "first" (see below) because "" won't be found...
	self.toolSeqA = prefs:GetString("HS_Shape.toolSeqA", "") -- default is "" which wil lead to "dispatch table order"
--	HS_Test_harness:diagnosePrint (thisUUT, "toolSeqA is", self.toolSeqA, #self.toolSeqA) 


	if #self.toolSeqA < 3 then
--
--	if no stored SeqA set up default mapping of tools in the toolbar = order in the dispatch table (but not the "void") and set SeqA accordingly
--

--		HS_Test_harness:diagnosePrint (thisUUT, "subTool dispatch entries", #self.subToolDispatch)

		local s = ""
		for i = 1, #self.subToolDispatch - 1 do
			HS_Shape.toolSeq[i] = i
			s = s .. "[" .. self.subToolDispatch[i].ident .. "], "
		end

		self.toolSeqA = "{" .. s:sub(1, #s-2) .. "}"
--		HS_Test_harness:diagnosePrint (thisUUT, "toolSeqA default is", self.toolSeqA)
	else
--
--	map the tools to the list in seqA
--
		local sTrim, key, ix, toolNum, found, dup
		sTrim = self.toolSeqA:match("%b{}")

		local toolIgnored = 	-1
		local toolUsed =	1
		local toolNew =		0

		usedTool = {} -- keep check on what we've used
		for i = 1, #self.subToolDispatch - 1 do
			usedTool[i] = toolNew   	-- assume they're new
		end

		toolNum = 0
		for key in sTrim:gmatch("%b[]") do
			key = key:sub(2, #key-1)
			found = false
			dup = false
--			HS_Test_harness:diagnosePrint (thisUUT, "seeking", key)
			for j = 1, #self.subToolDispatch - 1 do -- ignore the "Void" end
				if self.subToolDispatch[j].ident == key then
					if usedTool[j] == toolUsed then
--						HS_Test_harness:diagnosePrint (thisUUT, key, "(tool number".. j ..") alreday in use")
						dup = true
					else
						toolNum = toolNum + 1
						self.toolSeq[toolNum] = j
						usedTool[j] = toolUsed
						found = true
--						HS_Test_harness:diagnosePrint (thisUUT, key, "is tool number", j)
					end
					break
				end
			end
			if not found and not dup then
--				HS_Test_harness:diagnosePrint (thisUUT, key, "is not a known tool") -- and remove from seqA
				HS_Shape:ToolChangeAlert(key, "no longer installed.")
			elseif dup then
--				HS_Test_harness:diagnosePrint (thisUUT, key, "duplicated") -- and remove from seqA
				HS_Shape:ToolChangeAlert(key, "duplicated in tool list", "Duplicate removed.")
			end
		end
--		HS_Test_harness:diagnosePrint (thisUUT, "Found", toolNum, "tools", "max is", #self.subToolDispatch - 1)

--
--	now look for new tools -->> if tool not in SeqA but in Dispatch  >> will happen with newly recognised custom subtool?!
--

		sTrim = self.toolSeqA:match("%b{}", #sTrim) -- look for a list of ignored tools
		if not sTrim then
			sTrim = "{}"
		end
--		HS_Test_harness:diagnosePrint (thisUUT, "unused tools", sTrim)

		for key in sTrim:gmatch("%b[]") do
			key = key:sub(2, #key-1)
			found = false
			dup = false -->>>what about this?????
--			HS_Test_harness:diagnosePrint (thisUUT, "seeking", key, "unused")
			for j = 1, #self.subToolDispatch - 1 do -- ignore the "Void" end
				if self.subToolDispatch[j].ident == key then
					found = true
--					HS_Test_harness:diagnosePrint (thisUUT, key, "is tool number", j, "and is in the ignored list")
					usedTool[j] = toolIgnored
					break
				end
			end
		end

--
--	here we've found everything in the "used" and "ignored" lists, so anything still unused is new
--

		for j = 1, #usedTool do
			if usedTool[j] == toolNew then
				usedTool[j] = toolUsed
				toolNum = toolNum + 1
				self.toolSeq[toolNum] = j
--				HS_Test_harness:diagnosePrint (thisUUT, self.subToolDispatch[j].ident, "is new tool") -- add to UI
				HS_Shape:ToolChangeAlert(self.subToolDispatch[j].ident, "installed as a new subtool.")
			end
		end


--		HS_Test_harness:diagnosePrint (thisUUT, "Now found", toolNum, "tools", "max is", #self.subToolDispatch - 1)


--
--	rebuild SeqA
--
-- 	installed tools

		local s = ""
		for i = 1, #HS_Shape.toolSeq do 
			s = s .. "[" .. self.subToolDispatch[HS_Shape.toolSeq[i]].ident .. "], "
		end
		self.toolSeqA = "{" .. s:sub(1, #s-2) .. "}" -- used tools

-- 	ignored tools
		s = ""
		for i = 1, #usedTool do
			if usedTool[i] == toolIgnored then 
				s = s .. "[" .. self.subToolDispatch[i].ident .. "], "
			end
		end
		if #s < 3 then
			s = ", {}"
		else
			s = ", {" .. s:sub(1, #s-2) .. "}"
		end

--	concatenate
		self.toolSeqA = self.toolSeqA  ..  s 

--		HS_Test_harness:diagnosePrint (thisUUT, "toolSeqA reset to", self.toolSeqA)

	end




-- ** sub tools -- load all prefs irrespective of the button order or presence in the UI.

	for i = 1, #self.subToolDispatch do
		if self.subToolDispatch[i].load then
			self.subToolDispatch[i].load(self.subToolDispatch[i].self, prefs)
		end
	end

-- find "last used tool" in tool seq and reset 
	self.buttonNum = self.First -- default if not found
	for i = 1, #HS_Shape.toolSeq do
		if self.subToolDispatch[HS_Shape.toolSeq[i]].ident == self.shapeType then
			self.buttonNum = i + self.First - 1
--			HS_Test_harness:diagnosePrint (thisUUT, "Last used was", self.shapeType, "now button number", i, "message number", self.buttonNum)
			break
		end
	end
end




--
-- **	Save preferences
--

function HS_Shape:SavePrefs(prefs)

-- ** System
	prefs:SetBool("HS_Shape.autoFill", self.autoFill)
	prefs:SetBool("HS_Shape.autoOutline", self.autoOutline)
	prefs:SetBool("HS_Shape.drawOn0", self.drawOn0)
--	prefs:SetInt("HS_Shape.extenderOpt", self.extenderOpt)

	prefs:SetString("HS_Shape.shapeType", self.shapeType)
	prefs:SetString("HS_Shape.toolSeqA", self.toolSeqA)


-- ** sub tools -- save all irrespective ...

	for i = 1, #self.subToolDispatch do
		if self.subToolDispatch[i].save then
			self.subToolDispatch[i].save(self.subToolDispatch[i].self, prefs)
		end
	end
end


--
-- **	 Reset prefs
--

function HS_Shape:ResetPrefs()

--	HS_Test_harness:tracePrint (thisUUT, "Reset Prefs")

-- ** System
	self.autoFill = true
	self.autoOutline = true
	self.drawOn0 = true
	self.extenderOpt = 0
	self.shapeType = self.subToolDispatch[1].ident
	self.toolSeqA =  ""

-- ** sub tools -- reset all irrespetive ...

	for i = 1, #self.subToolDispatch do
		if self.subToolDispatch[i].reset then
			self.subToolDispatch[i].reset(self.subToolDispatch[i].self)
		end
	end


end


-- **************************************************
-- Recurring values
--
-- **************************************************


local HS_curvC = 4/(3*math.sqrt(2)) 	-- factor to calc curvatures
local HS_roundcorner = 0.15
local HS_circleCorner = 0.390524 -- recalculated (for a 4-point circle) -- was local HS_circleCorner = 0.391379
local HS_maxCurv = 0.666667
local HS_segment = math.rad(180) -- semicircle


local MohoVers = 0

-- for mouseup shape creation
local shapePoints = {}

-- for diagnosis
local thisUUT = "D"

-- for when (timeline) a shape is created
local drawingFrame = 0
-- local keyingFrame = 0

-- for UI data input / validation
local bigNmax = 72 			-- any number bigger than min!!
local wheelMax = 10			-- maximum that a scroll wheel can add


local gridMax = 48			-- limiter rows and cols in grid
local bigNmed = 24			-- limiter for sides on polygon and concentrics

local bigNmin = 4 			-- can increase but not decrease from 4 for ovals...

local bigNText = ""
local littleNText = ""
local floatText = ""
local buttText = ""
local checkText = ""

local bigN, littleN, rat, box 		-- from the UI

HS_Shape.toolSeq = {} 			-- mappping of buttons to tool dispatch List -- if this changes, set active subtool to 1 (unless it's in the new list??)

HS_Shape.toolSeqA = "{}"		-- the alpha string of button sequence in the tool bar


-- ************************************************************
-- Glaobals for use in the UI -- the values here are irrelevant
--
-- ************************************************************


HS_Shape.autoFill = true
HS_Shape.autoOutline = true
HS_Shape.extenderOpt = 0
HS_Shape.shapeType = ""
HS_Shape.buttonNum = HS_Shape.First
HS_Shape.drawOn0 = true




-- **************************************************
-- Drawing controls for the current shape
--
-- **************************************************


HS_Shape.startVec = LM.Vector2:new_local()
HS_Shape.endVec = LM.Vector2:new_local()
HS_Shape.drawnPoints = 0



local FakeMouse = {drawingVec = LM.Vector2:new_local(), drawingStartVec = LM.Vector2:new_local(), ctrlKey = false, shiftKey = false, altKey = false}

-- for Edit mode checks:
HS_Shape.lastItem = ""
HS_Shape.lastUUID = ""
HS_Shape.pointCt = 0

HS_Shape.editMode = false

--******************************
-- global utility subroutines
-- *****************************

--
-- ** 	default "no help"
--
function HS_Shape:About_Void()

	return [==[
No "help" / "about" function for this tool.

]==]

end



--
-- **	disable all UI data fields
--

function HS_Shape:Labels_Void()

	HS_Shape.bigNIn:Enable(false)		--  "big N" input ...
	HS_Shape.bigNIn:SetValue(nil)
	HS_Shape.bigNIn:SetWheelInc(1)
	bigNText = ""

	HS_Shape.littleNIn:Enable(false)	-- "little N" input ...
	HS_Shape.littleNIn:SetValue(nil)
	HS_Shape.littleNIn:SetWheelInc(1)
	littleNText = ""

	HS_Shape.scaleIn:Enable(false)		-- "scale factor" input ...
	HS_Shape.scaleIn:SetValue(nil)
	HS_Shape.scaleIn:SetWheelInc(0.01)
	floatText = ""

	HS_Shape.optionButton:Enable(false)	--  "options" input ...
	buttText = ""

	HS_Shape.checkIn:Enable(false)		--  checkbox input ...
	HS_Shape.checkIn:SetValue(nil)
	checkText = ""

end

--
-- **	enable data inputs
--

function HS_Shape:SetInt1 (label, value, inc) 
	bigNText = validateString(label) or "Integer 1"
	value = validateInt(value)
	HS_Shape.bigNIn:SetValue(value)
	if inc then
		inc = LM.Clamp(validateInt(inc), 1, wheelMax)
	else
		inc = 1
	end
	HS_Shape.bigNIn:SetWheelInc(inc)
	HS_Shape.bigNIn:Enable(true)
end

function HS_Shape:SetInt2 (label, value, inc)
	littleNText = validateString(label) or "Integer 2"
	value = validateInt(value)
	HS_Shape.littleNIn:SetValue(value)
	if inc then
		inc = LM.Clamp(validateInt(inc), 1, wheelMax)
	else
		inc = 1
	end
	HS_Shape.littleNIn:SetWheelInc(inc)
	HS_Shape.littleNIn:Enable(true)
end

function HS_Shape:SetFloat (label, value, inc)
	floatText = validateString(label) or "Float 1"
	if inc then
		inc = LM.Clamp(validateFloat(inc), 0.01, wheelMax)
	else
		inc = 0.01
	end
	value = validateFloat(value)
--?? make multiple of inc?
	HS_Shape.scaleIn:SetValue(value)
	HS_Shape.scaleIn:SetWheelInc(inc)
	HS_Shape.scaleIn:Enable(true)
end

function HS_Shape:SetButton (label)
	buttText = validateString(label) or "Button"
	HS_Shape.optionButton:Enable(true)
end

function HS_Shape:SetCheckBox (label, value)
	checkText = validateString(label) or "CheckBox"
	value = validateBool(value)
	HS_Shape.checkIn:SetValue(value)
	HS_Shape.checkIn:Enable(true)
end


--
--**	Set the drawing vector to snap to grid or not
--
function HS_Shape:DrawingPos (moho, mouseEvent)
	vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end
	return vec
end

--
--**	Set the drawing bounds
--
function HS_Shape:DrawingBounds (moho, mouseEvent, shift, alt) -- shift / alt = false or true to override the mouseEvent values
	vec1 = LM.Vector2:new_local()
	vec1:Set(mouseEvent.drawingStartVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end
	vec2 = LM.Vector2:new_local()
	vec2:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec2)
	end


	local doSquare
	if shift == nil then
		doSquare = mouseEvent.shiftKey
	else
		doSquare = shift
	end

	if doSquare then
		-- constrain width = height
		if (vec2.x > vec1.x) then
			if (vec2.y > vec1.y) then
				vec2.y = vec1.y + (vec2.x - vec1.x)
			else
				vec2.y = vec1.y - (vec2.x - vec1.x)
			end
		else
			if (vec2.y > vec1.y) then
				vec2.y = vec1.y - (vec2.x - vec1.x)
			else
				vec2.y = vec1.y + (vec2.x - vec1.x)
			end
		end
	end


	local doCentre
	if alt == nil then
		doCentre = mouseEvent.altKey
	else
		doCentre = alt
	end


	if doCentre then
		-- draw from center point
		vec1.x = vec1.x - (vec2.x - vec1.x)
		vec1.y = vec1.y - (vec2.y - vec1.y)
	end

	return vec1, vec2
end



--
-- **	make a closed path of points
--
function HS_Shape:MakeLoop (moho, vec, drawingFrame, points, curv, doShape)
	local shapeID = -1
	local shape
	local mesh =  moho:DrawingMesh()
	local n = mesh:CountPoints()
	local i

	mesh:AddLonePoint(vec, drawingFrame)
	for i = 0, points-1 do
		mesh:AppendPoint(vec, drawingFrame)
	end

--	add the points for the curve plus "one more" because welding loses the last point
	mesh:WeldPoints(n + points, n, drawingFrame)

	for i = 0, points-1 do
		mesh:Point(n+i):SetCurvature(curv, drawingFrame)
	end

	if doShape == nil or doShape then
		mesh:SelectConnected()
		if (HS_Shape.autoFill) then
			shapeID = moho:CreateShape(true, false, drawingFrame)
			if (shapeID >= 0) then
				if (not HS_Shape.autoOutline) then
					mesh:Shape(shapeID).fHasOutline = false
				end
			end
		elseif (HS_Shape.autoOutline) then
			shapeID = moho:CreateShape(false, false, drawingFrame)
		end
	end

--	shape = mesh:Shape(shapeID)

	if shapeID > -1 then
		shape = mesh:Shape(shapeID)
	else
		shape = nil
	end

	if shape and doShape then
		shape.fSelected = true
	end

	return n, shape

end


--
-- **	make an open path of points
--

--	startPt = HS_Shape:MakeLine(moho, startVec, drawingFrame, self.Points, curv)
--	startPt = HS_Shape:MakeLine (moho, vec, drawingFrame, drawnPoints, HS_circleCorner) -->> improve curvature in MouseMoved



function HS_Shape:MakeLine (moho, vec, drawingFrame, points, curv, doShape)

	local mesh =  moho:DrawingMesh()
	local n = mesh:CountPoints()
	local shapeID = -1

	mesh:AddLonePoint(vec, drawingFrame)
	for i = 0, points - 2 do
		mesh:AppendPoint(vec, drawingFrame)
	end

	for i = 0, points - 1 do
		mesh:Point(n + i):SetCurvature(curv, drawingFrame)
	end


	if doShape == nil or doShape then
		mesh:SelectConnected()
		if (HS_Shape.autoOutline) then
			shapeID = moho:CreateShape(false, false, drawingFrame)
		end
	end

	if shapeID > -1 then
		shape = mesh:Shape(shapeID)
	else
		shape = nil
	end

	if shape and doShape then
		shape.fSelected = true
	end

	return n, shape
end

--
-- **	Alerts
--

function HS_Shape:BadToolAlert(name, toolId, text, data)

-- >> extend to have the desired data type? (this is only for bad numbers)

	local line1 = "Subtool " .. name .. "\nCritical error."
	local t = type(data)
	local subText = ""
	if t == "nil" then
		subText = "Expected data, got nil"
	elseif t == "number" then
		subText = "Invalid number " .. tostring(data)
	else
		subText = "Expected number, got type " .. t .. "  (" .. tostring(data) .. ")"
	end

--	HS_Test_harness:tracePrint (thisUUT, "Alert", name, text, subText)

	subText = "\n" .. subText

	LM.GUI.Alert(LM.GUI.ALERT_WARNING,
		line1,
		text,
		subText,
		"OK")

	self.shapeButtons[toolId]:Enable(false)

end

function HS_Shape:ToolChangeAlert(name, text, sub)

	local line1 = "Subtool " .. name .. " " .. text
	local subText = sub or ""
--	HS_Test_harness:tracePrint (thisUUT, "Alert", name, text, subText)

	LM.GUI.Alert(LM.GUI.ALERT_INFO,
		line1,
		subText,
		"",
		"OK")
end


function HS_Shape:AboutTool (name, text)

	text = text or HS_Shape:About_Void()

	local line1 = "About " .. name .. string.rep ("    ", 10)

-- lose all \n at the front
	text = text:gsub ("^%c+", "")

	LM.GUI.Alert(LM.GUI.ALERT_INFO,
		line1,
		text,
		"",
		"OK", nil, nil)

end

function HS_Shape:ToolError (name, reason, text)

	local line1 = "Subtool " .. name .. string.rep ("    ", 10) ..  "\n" .. reason
	LM.GUI.Alert(LM.GUI.ALERT_WARNING,
		line1,
		text,
		"",
		"OK")

end

-- **************************************************
-- The guts of this script
-- **************************************************

--
--  	relevant / enabled??
--

function HS_Shape: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_Shape: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



--*********************************************************************************************************  MOUSE:  MAIN SWITCH
-- 	mouse actions
--*****************************


function HS_Shape:OnMouseDown(moho, mouseEvent)
	local mesh =  moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	moho.document:PrepUndo(moho.drawingLayer)
	moho.document:SetDirty()

--
--	clear the mouse up shape table
--
	local i, j, k
	for j in pairs (shapePoints) do
		shapePoints[j] = nil
	end

--
--	check if edit mode (when skip to "mouse move") or create mode (when dispatch)
--

--	HS_Test_harness:diagnosePrint (thisUUT, "Last UUID / now", self.lastUUID,  moho.drawingLayer:UUID())
--	HS_Test_harness:diagnosePrint (thisUUT, "point counts then / now", self.pointCt, mesh:CountPoints())

	if mouseEvent.ctrlKey 
		and (self.drawnPoints > 0) 
		and (self.pointCt == mesh:CountPoints())
		and (moho:CountSelectedPoints() == self.drawnPoints) 
		and (self.startVec.x ~= nil)
		and (self.lastItem == self.shapeType)
		and (self.lastUUID == moho.drawingLayer:UUID())

	then
--
--	if control key and the right number of points etc, then edit the active selection
--
		self.editMode = true

	else
--
--	if not control on initial mouse down then draw new
--
		self.editMode = false


		if self.drawOn0 then
			drawingFrame = 0
--			keyingFrame = 0
		else
			drawingFrame = moho.drawingLayerFrame
--			keyingFrame = moho.drawingFrame
		end

		mesh:SelectNone()



		self.drawnPoints = 0
		i = 0
		if (self.buttonNum >= self.First) and (self.buttonNum <= self.Last) then
			i = self.buttonNum - self.First + 1
			if self.subToolDispatch[HS_Shape.toolSeq[i]].mdn then
--				HS_Test_harness:tracePrint (thisUUT, "Mouse down with shape type", i, type(self.subToolDispatch[HS_Shape.toolSeq[i]].mdn))

				j = self.subToolDispatch[HS_Shape.toolSeq[i]].mdn(self.subToolDispatch[HS_Shape.toolSeq[i]].self, moho, mouseEvent, mesh, drawingFrame)

				if validatePtCt (j) then
--					save stuff for edit / mouse moved validation
					self.lastUUID = moho.drawingLayer:UUID() -- this layer drawn on ...
					self.pointCt = mesh:CountPoints() -- how many points now
					self.drawnPoints = j	-- and how many of those we just drew

					for k = 1, self.drawnPoints do
						mesh:Point(self.pointCt - k).fSelected = true -- select everything we just drew
					end

--				elseif j == nil then -- not using mouse to draw?!

				else
--					HS_Test_harness:diagnosePrint (thisUUT, self.subToolDispatch[HS_Shape.toolSeq[i]].ident, "not supplied a valid drawn point count")
					self:BadToolAlert(self.subToolDispatch[HS_Shape.toolSeq[i]].ident, i, "Bad drawn point count", j)
				end

			end
		end
		mouseEvent.view:DrawMe()

	end
end

function HS_Shape:OnMouseMoved(moho, mouseEvent)
	local mesh =  moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if self.editMode then
		mouseEvent.drawingStartVec:Set(FakeMouse.drawingStartVec) -- keep the original initial mouse down vec

--[[
-- >> 	is it possible to determine what the mouse start vec should be if the selection has been moved / transformed????
		local min = LM.Vector2:new_local()
		local max = LM.Vector2:new_local()
		mesh:SelectedBounds(min, max)
]]

		mouseEvent.ctrlKey = FakeMouse.ctrlKey -- and the last modifer keys
		mouseEvent.shiftKey = FakeMouse.shiftKey
		mouseEvent.altKey = FakeMouse.altKey
	end

	local i = 0
	if (self.buttonNum >= self.First) and (self.buttonNum <= self.Last) then
		i = self.buttonNum - self.First + 1

		if self.subToolDispatch[HS_Shape.toolSeq[i]].mmov then
--			HS_Test_harness:tracePrint (thisUUT, "Mouse moved with shape type", i, type(self.subToolDispatch[HS_Shape.toolSeq[i]].mmov))
			self.subToolDispatch[HS_Shape.toolSeq[i]].mmov(self.subToolDispatch[HS_Shape.toolSeq[i]].self, moho, mouseEvent, mesh, drawingFrame, self.drawnPoints)
		end
	end

-- save the modifier keys for edit
	FakeMouse.ctrlKey = mouseEvent.ctrlKey
	FakeMouse.shiftKey = mouseEvent.shiftKey
	FakeMouse.altKey = mouseEvent.altKey




	if self.drawOn0 then
		moho:AddPointKeyframe(drawingFrame)
	else
		moho:AddPointKeyframe(moho.drawingFrame)
	end

	mouseEvent.view:DrawMe()


end


function HS_Shape:OnMouseUp(moho, mouseEvent)
	local mesh =  moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

--
--	create shapes if required - routines can place lists of point numbers in a table ...
--
	local i, j, k

	for j in pairs (shapePoints) do

		mesh:SelectNone()
		for k = 1, #shapePoints[j] do
			mesh:Point(shapePoints[j][k]).fSelected = true
		end


		if (self.autoFill) then
			local shapeID = moho:CreateShape(true, false, drawingFrame)
			if (shapeID >= 0) then
				if (not self.autoOutline) then
					mesh:Shape(shapeID).fHasOutline = false
				end
			end
		elseif (self.autoOutline) then
			moho:CreateShape(false, false, drawingFrame)
		end

	end
	
--	mesh:SelectConnected()				-- << may need to review this and save/restore selections if non-connected shapes are to be created
							-- << ?? concentric circs are not connected but edit works???
	mesh:SelectNone()
	for i = 1, self.drawnPoints do
		mesh:Point(self.pointCt - i).fSelected = true -- select everything we just drew
	end

--	HS_Test_harness:diagnosePrint (thisUUT, "Mouse up -- Draw on 0", self.drawOn0, "Drawing frame", drawingFrame, "Moho DF", moho.drawingFrame)



	local i = 0
	if (self.buttonNum >= self.First) and (self.buttonNum <= self.Last) then
		i = self.buttonNum - self.First + 1

		if self.subToolDispatch[HS_Shape.toolSeq[i]].onExit then
--			HS_Test_harness:tracePrint (thisUUT, "Mouse up with shape type", i, type(self.subToolDispatch[HS_Shape.toolSeq[i]].onExit))
			self.subToolDispatch[HS_Shape.toolSeq[i]].onExit(self.subToolDispatch[HS_Shape.toolSeq[i]].self, moho, mouseEvent, mesh, drawingFrame, self.drawnPoints)
		end
	end



	if self.drawOn0 then
		moho:AddPointKeyframe(drawingFrame)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:NewKeyframe(CHANNEL_CURVE)
	else
		moho:AddPointKeyframe(moho.drawingFrame)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:NewKeyframe(CHANNEL_CURVE)
	end

	moho:UpdateSelectedChannels()
	mouseEvent.view:DrawMe()
	moho:UpdateUI()



--
-- **	set up for possible edit
--

--	self.drawnPoints is set in mouse down
	self.startVec, self.endVec = HS_Shape:DrawingBounds (moho, mouseEvent, false, false)
	self.lastItem = self.shapeType

	FakeMouse.drawingVec:Set(self.endVec)
	FakeMouse.drawingStartVec:Set(self.startVec)


--	HS_Test_harness:diagnosePrint (thisUUT, "Mouse up - fake mouse", {"start vec", FakeMouse.drawingStartVec}, {"end vec", FakeMouse.drawingVec})
--	HS_Test_harness:diagnosePrint (thisUUT,HS_Test_harness:diagnoseModifierKeys (FakeMouse.ctrlKey, FakeMouse.shiftKey, FakeMouse.altKey))

end


local function OnKeyDownDelete (moho, keyEvent)

	local mesh =  moho:DrawingMesh()
	if (mesh == nil) then
		return false
	end

	local deleted = false

	if  (HS_Shape.drawnPoints > 0) -- make as sure as we can, it's one of ours...
		and (HS_Shape.pointCt == mesh:CountPoints())
		and (HS_Shape.drawnPoints == moho:CountSelectedPoints()) 
		and (HS_Shape.lastUUID == moho.drawingLayer:UUID())

	then
--		HS_Test_harness:diagnosePrint (thisUUT, "Delete accepted")
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
		mesh:DeleteSelectedPoints()
		deleted = true
	end

	return deleted
end


function HS_Shape:OnKeyDown(moho, keyEvent)
	local keyDispatch = {
		{LM.GUI.KEY_DELETE, OnKeyDownDelete},
		{LM.GUI.KEY_BACKSPACE, OnKeyDownDelete}
	}

	local updateView = false

	if keyEvent.ctrlKey then
--		HS_Test_harness:diagnosePrint (thisUUT, "using factory point tool")
		LM_SelectPoints:OnKeyDown(moho, keyEvent)
		return
	end

	local i
	for i = 1, #keyDispatch do
		if keyEvent.keyCode == keyDispatch[i][1] then
--			HS_Test_harness:diagnosePrint (thisUUT, "found key entry", i)
			updateView = keyDispatch[i][2] (moho, keyEvent)
			break
		end
	end

	if updateView then
--		HS_Test_harness:diagnosePrint (thisUUT, "updating view")
		moho:UpdateSelectedChannels()
		keyEvent.view:DrawMe()
	end

end




-- **************************************************
-- ****************************************************************************************************************************  SECTOR
-- **************************************************
HS_ShapeSector = {}

HS_ShapeSector.Points = 21
local startVec = LM.Vector2:new_local()
local startPt


function HS_ShapeSector:Identify()

	local t =
		{ident="Sector",
		tip="Sector: Ctrl: make semi-circle or less. Shift: make exact quadrant or semi-circle. Alt: start horizontal.",
		icon="hs_sector",
		self=HS_ShapeSector,
		gKey="HS_ShapeSector"}


	return t
end

function HS_ShapeSector:About()

	local body = [==[
This tool creates a sector of up to (but excluding) a full circle.
Ctrl constrains to a semi-circle. Shift constrains the sector to quadrant or semi-circle; Alt moves the initial radius from vertical to horizontal.
One data entry field is enabled:  this is the number of points and is constrained to be between 4 and 72 inclusive.

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeSector:LoadPrefs(prefs)
	self.Points = prefs:GetInt("HS_ShapeSector.Points", 21)
end

function HS_ShapeSector:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeSector.Points", self.Points)
end

function HS_ShapeSector:ResetPrefs()
	self.Points = 21
end

function HS_ShapeSector:Labels ()
	HS_Shape:SetInt1 ("Points in Sector", self.Points) 
end


function HS_ShapeSector:BigN (moho, view, value)
	self.Points = LM.Clamp(value, bigNmin, bigNmax)
	return (self.Points)
end

-- ===

function HS_ShapeSector:OnMouseDown (moho, mouseEvent, mesh, drawingFrame)

	local points = HS_ShapeSector.Points
	local curv = HS_circleCorner

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, points, curv)

	mesh:Point(startPt):SetCurvature(MOHO.PEAKED, drawingFrame)
	mesh:Point(startPt + 1):SetCurvature(MOHO.PEAKED, drawingFrame)
	mesh:Point(startPt + points-1):SetCurvature(MOHO.PEAKED, drawingFrame)

	return points 
end

function HS_ShapeSector:OnMouseMoved (moho, mouseEvent, mesh, drawingFrame, drawnPoints)


	local vec = LM.Vector2:new_local()

	vec:Set(HS_Shape:DrawingPos(moho, mouseEvent))

	local x1 = startVec.x
	local x2 = vec.x
	local y1 = startVec.y
	local y2 = vec.y



-- which quadrant are we in?
	local q1 = (x2>=x1) 	and (y2>=y1)
	local q2 = (x2>=x1) 	and (y2<y1)
	local q3 = (x2<x1) 	and (y2<y1)
	local q4 = (x2<x1) 	and (y2>=y1)


	local n = mesh:CountPoints()
	local centre = n - drawnPoints  	-- in edit mode, self.Points may have changed


	local pt = mesh:Point(centre)
	pt.fPos.x = x1
	pt.fPos.y = y1

	local radius = (vec - startVec):Mag()

	local maxAngle = math.atan2(x2-x1, y2-y1) -- is between -180 0 +180

-- allow greater than semi circ unless Ctrl is down

	if (not mouseEvent.ctrlKey) then
		if q3 or q4 then     -- maxAngle will be negative so ...
			maxAngle = math.rad(360) + maxAngle 
		end


	end

	
-- semicircle if Shift is down and >90 ; quadrant if <90

	if (mouseEvent.shiftKey) then

		maxAngle = HS_segment
		if q3 or q4 then
			maxAngle = - maxAngle
		end
		if q1 or q4 then
			maxAngle = maxAngle / 2
		end

	end


-->>>	consider resetting curvatures to give better roundness



-- rotate through 90 if Alt

	local offsetAngle = math.rad(0)	
	if mouseEvent.altKey then
		offsetAngle = math.rad(90)
		maxAngle = maxAngle + math.rad(90)
	end



	local segAngle =  (maxAngle - offsetAngle) / (self.Points - 2)
	local circPoints= 0

	for circPoints = 0, drawnPoints - 2 do
		local pt = mesh:Point(centre + circPoints+1)
		pt.fPos.x = x1 + radius * math.sin(offsetAngle + segAngle*circPoints)
		pt.fPos.y = y1 + radius * math.cos(offsetAngle + segAngle*circPoints)
	end

end






-- **************************************************
-- ********************************************************************************************************************************** OVAL
-- **************************************************

HS_ShapeOval = {}

HS_ShapeOval.Points = 4
local startVec = LM.Vector2:new_local()
local startPt

function HS_ShapeOval:Identify()

	local t =
		{ident="Oval",
		tip="Oval: Ctrl: all corners rounded. Shift: height=width. Ctrl+Shift: Circle. Alt: draw from centre.",
		icon="hs_oval",	
		self=HS_ShapeOval,					
		gKey="HS_ShapeOval"}

	return t
end

function HS_ShapeOval:About()

	local body = [==[
This tool creates an ellipse.
Point curvature the curvature is set to give the correct ellipsoidal shape – from a straight line where one dimension is very small,
to near circular where the dimensions are similar.
Ctrl forces the point curvatures to “circular”.
Shift distributes the points equidistant from the centre.
Ctrl-Shift together create a circle. 
One data entry field is enabled:  this is the number of points and is constrained to be between 4 and 72 inclusive.
If the number of points chosen is not divisible by 4 the input is reduced to the nearest lower multiple of 4.
]==]
	return body .. "\n" ..  HS_Shape:UILabel()

end

function HS_ShapeOval:LoadPrefs(prefs)
	self.Points = prefs:GetInt("HS_ShapeOval.Points", 4)
end

function HS_ShapeOval:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeOval.Points", self.Points)
end

function HS_ShapeOval:ResetPrefs()
	self.Points = 4
end

function HS_ShapeOval:Labels ()
	HS_Shape:SetInt1 ("Points on Oval", HS_ShapeOval.Points, 4) 

end


function HS_ShapeOval:BigN (moho, view, value)
	self.Points = LM.Clamp(value, bigNmin, bigNmax)
	self.Points = 4 * math.floor(self.Points/4) -- make sure we're divisble by 4 as we need pts at 0,90,180,270
	return (self.Points)
end



-- ====================

function HS_ShapeOval:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)
	local points = HS_ShapeOval.Points
	local curv = HS_roundcorner

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, points, curv)

	return points 
end

function HS_ShapeOval:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent)


	local radFloor = 1e-6					-- the smallest radius we can handle
	local eccCeil = 1e3					-- the biggest ratio of radii

	local xRad = math.max(math.abs(vec2.x-vec1.x)/2, radFloor)
	local yRad = math.max(math.abs(vec2.y-vec1.y)/2, radFloor)

	local points = drawnPoints

	local i, j, ratio
	local n = mesh:CountPoints()


	local ptStart = n - points

	local angleStart = math.rad(0)
	local angleStep = math.rad(360) / points

	local radius, theta, offsetX, offsetY,  firstStep, incrStep, XisBiggest
	local step = {}
	local eccentric 	-- how round is it??



-- generate the angle table
	if xRad > yRad then
-- 		cluster at 90 / 270
		XisBiggest = true
		eccentric = xRad/yRad
		incrStep = -1
		firstStep = points/4

	else
--		cluster at 0 / 180
		XisBiggest = false
		eccentric = yRad/xRad
		firstStep = 1
		incrStep = 1
	end

-- spread out the steps 

--	ratio = math.min(eccentric, 10)
	ratio = 1 + 0.5*math.log(eccentric)
	step[1] = 1
	j = 1
	for i = 1, points/4 - 1 do		-- so if we have 40 points we'll have 10 table entries or 8 pts just 2
		if (step[i] / ratio) > radFloor then
			step[i+1] = step[i] / ratio
		else
			step[i+1] = 0		-- if it's very tiny then treat as zero to cluster points at the peak
		end
		j = j + step[i+1]		 -- cumulative sum of steps to make 90 degrees
	end
	angleStep = math.rad(90) / j		-- radians per unit step 

	theta = angleStart


-- set the shape

	for i = 0, points - 1 do

-- 		calc the angle
		theta = theta + step[firstStep] * angleStep

		radius = 2 * (xRad * yRad) / 
			math.sqrt (xRad*math.sin(theta) * xRad*math.sin(theta) + yRad*math.cos(theta) * yRad*math.cos(theta))
		offsetY = radius * math.sin(theta)
		offsetX = radius * math.cos(theta)
		local pt = mesh:Point(ptStart+i)
		pt.fPos.x = vec1.x + offsetX
		pt.fPos.y = vec1.y + offsetY

--		check if we need to switch round in the table
		firstStep = firstStep + incrStep

		if firstStep > points / 4 or firstStep < 1 then -- hit the end going up or down - switch direction ...
			incrStep = - incrStep
			firstStep = firstStep + incrStep
		end
		
	end



	local curvature = HS_roundcorner
	local curvatureStartPt
	local curvatureMidPt
		
-- if ctrl then circular
	if mouseEvent.ctrlKey then
		
		curvature = HS_circleCorner
		for i = 0, points - 1 do
			mesh:Point(ptStart+i):SetCurvature(curvature, drawingFrame)
		end

	else
		eccentric = math.min(eccentric, eccCeil)
		if XisBiggest then
--	ecc is X/Y								
			if yRad < radFloor*10 then

				curvatureStartPt = MOHO.PEAKED
				curvatureMidPt = MOHO.PEAKED
				curvature = MOHO.PEAKED
			else
				curvatureStartPt = LM.Clamp(eccentric, 0, HS_maxCurv / HS_roundcorner) * HS_roundcorner
				curvatureMidPt = LM.Clamp(1/eccentric, 0, HS_maxCurv / HS_roundcorner) * HS_roundcorner
				curvature = LM.Slerp ( 1/eccentric, curvatureMidPt,curvatureStartPt)
			end

		else
--	ecc is Y/X
			if xRad < radFloor*10  then
				curvatureStartPt = MOHO.PEAKED
				curvatureMidPt = MOHO.PEAKED
				curvature = MOHO.PEAKED
			else
				curvatureStartPt = LM.Clamp(1/eccentric, 0, HS_maxCurv / HS_roundcorner) * HS_roundcorner
				curvatureMidPt = LM.Clamp(eccentric, 0, HS_maxCurv / HS_roundcorner) * HS_roundcorner
				curvature = LM.Slerp ( 1/eccentric,curvatureStartPt, curvatureMidPt)
 			end


		end

		local limit = points/4		
		mesh:Point(ptStart):SetCurvature(curvatureStartPt, drawingFrame)
		mesh:Point(ptStart+2*limit):SetCurvature(curvatureStartPt, drawingFrame)
		mesh:Point(ptStart+limit):SetCurvature(curvatureMidPt, drawingFrame)
		mesh:Point(ptStart+3*limit):SetCurvature(curvatureMidPt, drawingFrame)

				
		for i = 1,  limit - 1 do
			mesh:Point(ptStart+i):SetCurvature(curvature , drawingFrame)
			mesh:Point(ptStart+2*limit-i):SetCurvature(curvature , drawingFrame)
			mesh:Point(ptStart+2*limit+i):SetCurvature(curvature , drawingFrame)
			mesh:Point(ptStart+4*limit-i):SetCurvature(curvature , drawingFrame)
		end
	end

end






-- **************************************************
-- ****************************************************************************************************************************** ROUND CORNER RECTANGLE
-- **************************************************

HS_ShapeRectangle = {}

local startVec = LM.Vector2:new_local()
local startPt

HS_ShapeRectangle.Roundness = 0.2		-- corner roundness
local cvMax = 0.4 -- absolute max is .5 which makes it "bone" with coincident points
local cvMin = 0.1

local points

function HS_ShapeRectangle:Identify()

	local t =
		{ident="Rectangle",
		tip="Round Cornered Rectangle: Shift: height=width. Alt: draw from centre.",
		icon="hs_rc_rect",	
		self=HS_ShapeRectangle,					
		gKey="HS_ShapeRectangle"}

	return t
end

function HS_ShapeRectangle:About()

	local body =  [==[
This tool creates a round-cornered rectangle.
One data entry field is enabled: the radius of the semicircle that forms the corner as a ratio of the length of the smallest side.
It is constrained to be between 0.01 and 0.4.

There is also a checkbox for "smoother corners". If checked, additional points are created at each corner.

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end


function HS_ShapeRectangle:LoadPrefs(prefs)
	self.Roundness = prefs:GetFloat("HS_ShapeRectangle.Roundness", 0.2)	-- round corner radius as fraction of shortest side
	self.Smoother = prefs:GetBool("HS_ShapeRectangle.Smoother", false)
end

function HS_ShapeRectangle:SavePrefs(prefs)
	prefs:SetFloat("HS_ShapeRectangle.Roundness", self.Roundness)
	prefs:SetBool("HS_ShapeRectangle.Smoother", self.Smoother)
end

function HS_ShapeRectangle:ResetPrefs()
	self.Roundness = 0.2
	self.Smoother = false
end


function HS_ShapeRectangle:Labels ()
	HS_Shape:SetFloat ("corner roundness", HS_ShapeRectangle.Roundness)
	HS_Shape:SetCheckBox ("Smoother corners?", HS_ShapeRectangle.Smoother)
end

function HS_ShapeRectangle:Scale (moho, view, value)
	self.Roundness = LM.Clamp(value, cvMin, cvMax)
	return (self.Roundness)
end

function HS_ShapeRectangle:CheckBox (moho, view, value)
	self.Smoother = value
	return self.Smoother
end



-- ======================================

function HS_ShapeRectangle:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	if self.Smoother then 
		points = 20
	else
		points = 12
	end

	local curv = MOHO.PEAKED

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, points, curv)

	local i, j

-- 	four C shapes with 5 points each (smoother) or 3
-- 	points S+0 (peak); S+1,2,3 (Curve) S+4 (peak) for "smoother"
--	S+0 peak, S+1 Corve, S+2 Peak
	for i = 0, 3 do
		if self.Smoother then
			for j = 1, 3 do -- 3 points to curve
				mesh:Point((startPt + i*5)+j):SetCurvature(HS_circleCorner, drawingFrame)
			end
		else
			mesh:Point(startPt + 1 + (i*3)):SetCurvature(HS_circleCorner, drawingFrame)
		end
	end

	return points 
end

function HS_ShapeRectangle:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent)



--	local n = mesh:CountPoints()

	local pt

	local xLen = math.abs(vec1.x-vec2.x)
	local yLen = math.abs(vec1.y-vec2.y)

	local radius = self.Roundness * math.min(xLen, yLen)

	local xDir
	local yDir
	local smallDeg

	if (vec2.x>vec1.x) then xDir = -1 else xDir = 1 end
	if (vec2.y>vec1.y) then yDir = -1 else yDir = 1 end

	smallDeg = .3

	local sin45 = math.sqrt(2)/2
	local Deg01 = math.rad (smallDeg)
	local Deg89 = math.rad (90-smallDeg)
	local rDelta01 = radius * (1-math.sin(Deg01)) 	-- also = cos(90-small)
	local rDelta45 = radius * (1-sin45) 		-- also = cos45
	local rDelta89 = radius * (1-math.sin(Deg89)) 	-- also = cos(small)

--[[
Interesting "other" shape
--	smallDeg = 0.1
--	local rDelta01 = radius * (1-smallDeg) 	-- also = cos(90-small) -- this produces a "scolloped" edge!
--	local rDelta89 = radius * (smallDeg) 	-- also = cos(small)
]]

	local p = {{x=0, y=radius}, {x=rDelta89, y=rDelta01}, {x=rDelta45, y=rDelta45}, {x=rDelta01, y=rDelta89}, {x=radius, y=0}}

	local c = {{x=vec1.x, y=vec1.y}, {x=vec2.x, y=vec1.y}, {x=vec2.x, y=vec2.y}, {x=vec1.x, y=vec2.y}}

	local m = {{x=-1, y=-1}, {x=1, y=-1}, {x=1, y=1}, {x=-1, y=1}}

	local jStep, jPts
	local i, j

	if self.Smoother then
		jStep = 1
		jPts = 5
	else
		jStep = 2
		jPts = 3
	end



	for i = 0, 3, 2 do -- four C shapes -- do the L-R ones
		for j = 0, jPts-1 do -- 5 points use all j's; 3 points use 1,3,5
			pt = mesh:Point((startPt + i*jPts)+j)
			pt.fPos.x = c[i+1].x + (m[i+1].x * xDir * (p[(j*jStep)+1].x))
			pt.fPos.y = c[i+1].y + (m[i+1].y * yDir * (p[(j*jStep)+1].y))
		end
	end

	for i = 1, 3, 2 do -- four C shapes -- do the R-L ones
		for j = 0, jPts-1 do -- 3 or 5 points
			pt = mesh:Point((startPt + i*jPts)+j)
			pt.fPos.x = c[i+1].x + (m[i+1].x * xDir * (p[5-(j*jStep)].x))
			pt.fPos.y = c[i+1].y + (m[i+1].y * yDir * (p[5-(j*jStep)].y))
		end
	end
end








-- **************************************************
-- ****************************************************************************************************************************** ROUND END RECTANGLE (BONE INFLUENCE)
-- **************************************************

HS_ShapeBone = {}

local BonePoints = 10
local startPoint = 0
local startVec = LM.Vector2:new_local()

HS_ShapeBone.Opt = doVector
HS_ShapeBone.Wide = 0.1		-- half width (radius of curved end) of the bone shape


-- for bone lozenge shape
local doBoundingBox = 0
local doVector = 1
local ctBoneOpts = 2


function HS_ShapeBone:Identify()

	local t =
		{ident="Bone",
		tip="Bone: Use Options Button to select draw by Bounding box or Vector.",
		icon="hs_bone",	
		self=HS_ShapeBone,					
		gKey="HS_ShapeBone"}

	return t
end

function HS_ShapeBone:About()

	local body = [==[

This tool creates a "bone influence" shape. It has two parallel sides with semicircles for the shorter edges.
The style of creation can be selected by a button which, when pressed, cycles through the options:
>> use a bounding box created by the mouse click-drag to define the shape.
   The shape will be aligned horizontally or vertically and the dimensions will be set from the bounding box.
   Alt draws from the centre, but shift is not used.
>> use the mouse drag to define the vector along which the shape is created.
  In this case the “radius of the end semicircle” needs to be manually entered.

]==]
	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeBone:LoadPrefs(prefs)
	self.Opt = prefs:GetInt("HS_ShapeBone.Opt", 0) 
	self.Wide = prefs:GetFloat("HS_ShapeBone.Wide", 0.1)	-- half width (radius of curved end) of the bone shape
end

function HS_ShapeBone:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeBone.Opt", self.Opt)
	prefs:SetFloat("HS_ShapeBone.Wide", self.Wide)
end

function HS_ShapeBone:ResetPrefs()
	self.Opt = 0
	self.Wide = 0.1
end


function HS_ShapeBone:Labels ()
	local buttText
	if HS_ShapeBone.Opt == doVector then
		HS_Shape:SetFloat ("radius of end", HS_ShapeBone.Wide)
		buttText = "Vector"
	elseif HS_ShapeBone.Opt == doBoundingBox then
		buttText = "B/Box"
	end
	HS_Shape:SetButton (buttText)
end


function HS_ShapeBone:Scale (moho, view, value)
	self.Wide = LM.Clamp(value, .001, 1)
	return (self.Wide)
end

function HS_ShapeBone:Button (moho, view, value)
	self.Opt = math.fmod(self.Opt + value + ctBoneOpts, ctBoneOpts)
end


-- ======================================

function HS_ShapeBone:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)


	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPoint = HS_Shape:MakeLoop(moho, startVec, drawingFrame, BonePoints, HS_circleCorner)

	local corners = {2,3,7,8}
	local i, v
	for i, v in ipairs(corners) do
		mesh:Point(startPoint+v):SetCurvature(MOHO.PEAKED, drawingFrame)
	end

	return BonePoints

end


function HS_ShapeBone:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	if self.Opt == doBoundingBox then
		self:OnMouseMoved_BB(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	elseif self.Opt == doVector then
		self:OnMouseMoved_Vec(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	end
end



function HS_ShapeBone:OnMouseMoved_BB(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent, false) -- no "shift"

	local x1 = vec1.x
	local x2 = vec2.x
	local y1 = vec1.y
	local y2 = vec2.y



	local xLen = math.abs(x1-x2)
	local yLen = math.abs(y1-y2)

	local radius = math.min(xLen, yLen) / 2
	local sin45 = math.sqrt(2)/2

	local rDelta = radius * (1-sin45)

	local xDir
	local yDir

	if (x2>x1) then xDir = -1 else xDir = 1 end
	if (y2>y1) then yDir = -1 else yDir = 1 end

	local pt

	if xLen > yLen then

		pt = mesh:Point(startPoint+0)
		pt.fPos.x = x1
		pt.fPos.y = y1 - (radius * yDir)

		pt = mesh:Point(startPoint+1)
		pt.fPos.x = x1 - (rDelta * xDir)
		pt.fPos.y = y1 - (rDelta * yDir)

		pt = mesh:Point(startPoint+2)
		pt.fPos.x = x1 - (radius * xDir)
		pt.fPos.y = y1

		pt = mesh:Point(startPoint+3)
		pt.fPos.x = x2 + (radius * xDir)
		pt.fPos.y = y1

		pt = mesh:Point(startPoint+4)
		pt.fPos.x = x2 + (rDelta * xDir)
		pt.fPos.y = y1 - (rDelta * yDir)

		pt = mesh:Point(startPoint+5)
		pt.fPos.x = x2
		pt.fPos.y = y1 - (radius * yDir) 

		pt = mesh:Point(startPoint+6)
		pt.fPos.x = x2 + (rDelta * xDir)
		pt.fPos.y = y2 + (rDelta * yDir)

		pt = mesh:Point(startPoint+7)
		pt.fPos.x = x2 + (radius * xDir)
		pt.fPos.y = y2

		pt = mesh:Point(startPoint+8)
		pt.fPos.x = x1 - (radius * xDir)
		pt.fPos.y = y2

		pt = mesh:Point(startPoint+9)
		pt.fPos.x = x1 - (rDelta * xDir)
		pt.fPos.y = y2 + (rDelta * yDir)

	else

		pt = mesh:Point(startPoint+0)
		pt.fPos.x = x1 - (radius * xDir)
		pt.fPos.y = y1

		pt = mesh:Point(startPoint+1)
		pt.fPos.x = x1 - (rDelta * xDir)
		pt.fPos.y = y1 - (rDelta * yDir)

		pt = mesh:Point(startPoint+2)
		pt.fPos.x = x1
		pt.fPos.y = y1 - (radius * yDir)

		pt = mesh:Point(startPoint+3)
		pt.fPos.x = x1
		pt.fPos.y = y2 + (radius * yDir)

		pt = mesh:Point(startPoint+4)
		pt.fPos.x = x1 - (rDelta * xDir)
		pt.fPos.y = y2 + (rDelta * yDir)

		pt = mesh:Point(startPoint+5)
		pt.fPos.x = x1 - (radius * xDir)
		pt.fPos.y = y2 

		pt = mesh:Point(startPoint+6)
		pt.fPos.x = x2 + (rDelta * xDir)
		pt.fPos.y = y2 + (rDelta * yDir)

		pt = mesh:Point(startPoint+7)
		pt.fPos.x = x2
		pt.fPos.y = y2 + (radius * yDir)

		pt = mesh:Point(startPoint+8)
		pt.fPos.x = x2
		pt.fPos.y = y1 - (radius * yDir)

		pt = mesh:Point(startPoint+9)
		pt.fPos.x = x2 + (rDelta * xDir)
		pt.fPos.y = y1 - (rDelta * yDir)

	end

end


function HS_ShapeBone:OnMouseMoved_Vec(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent, false, false) -- do not process shift / alt

	local x1 = vec1.x
	local x2 = vec2.x
	local y1 = vec1.y
	local y2 = vec2.y

	local xLen = x2-x1
	local yLen = y2-y1
	local theta = math.atan2 (yLen, xLen)
	local oLen = math.sqrt (xLen*xLen + yLen*yLen)

	local radius = self.Wide
	local sin45 = math.sqrt(2)/2
	local rDelta = radius * sin45


	local pt

	pt = mesh:Point(startPoint+0)
	pt.fTempPos.x = -radius
	pt.fTempPos.y = 0

	pt = mesh:Point(startPoint+1)
	pt.fTempPos.x = -rDelta
	pt.fTempPos.y = rDelta

	pt = mesh:Point(startPoint+2)
	pt.fTempPos.x = 0
	pt.fTempPos.y = radius

	pt = mesh:Point(startPoint+3)
	pt.fTempPos.x = oLen
	pt.fTempPos.y = radius

	pt = mesh:Point(startPoint+4)
	pt.fTempPos.x = oLen + rDelta
	pt.fTempPos.y = rDelta

	pt = mesh:Point(startPoint+5)
	pt.fTempPos.x = oLen + radius
	pt.fTempPos.y = 0 

	pt = mesh:Point(startPoint+6)
	pt.fTempPos.x = oLen + rDelta
	pt.fTempPos.y = -rDelta

	pt = mesh:Point(startPoint+7)
	pt.fTempPos.x = oLen
	pt.fTempPos.y = -radius

	pt = mesh:Point(startPoint+8)
	pt.fTempPos.x = 0
	pt.fTempPos.y = -radius

	pt = mesh:Point(startPoint+9)
	pt.fTempPos.x = -rDelta
	pt.fTempPos.y = -rDelta

	for i = 0, 9 do
		pt = mesh:Point(startPoint + i)
		pt.fTempPos:Rotate(theta)
		pt.fTempPos.x = pt.fTempPos.x + x1
		pt.fTempPos.y = pt.fTempPos.y + y1
		pt.fPos:Set(pt.fTempPos)
	end

end







-- **************************************************
-- ******************************************************************************************************************************** ANNULUS
-- **************************************************

HS_ShapeAnnulus = {}
HS_ShapeAnnulus.Points = 8
HS_ShapeAnnulus.startVec = LM.Vector2:new_local()

HS_ShapeAnnulus.scale = 0.85		--  annulus inner diameter as a fraction of outer


function HS_ShapeAnnulus:Identify()

	local t =
		{ident="Annulus",
		tip="Annulus: Shift: height=width. Alt: draw from centre. (Ctrl not used).",
		icon="hs_annulus",	
		self=HS_ShapeAnnulus,					
		gKey="HS_ShapeAnnulus"}

	return t
end


function HS_ShapeAnnulus:About()

	local body = [==[

This tool creates a single shape composed of two concentric ovals. Shift creates concentric circles.
One data entry field is enabled:  the ratio of the width of the centre to outer radius; it is constrained to be between 0.001 and 0.9950 inclusive
(A large number creates a large hole in the centre)
]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeAnnulus:LoadPrefs(prefs)
	self.scale = prefs:GetFloat("HS_ShapeAnnulus.scale", 0.85)	-- annulus inner as a fraction of outer
end
 
function HS_ShapeAnnulus:SavePrefs(prefs)
	prefs:SetFloat("HS_ShapeAnnulus.scale", self.scale)
end


function HS_ShapeAnnulus:ResetPrefs()
	self.scale = 0.85
end


function HS_ShapeAnnulus:Labels ()
	HS_Shape:SetFloat ("inner ring ratio", self.scale)

end


function HS_ShapeAnnulus:Scale (moho, view, value)
	self.scale = LM.Clamp(value, .001, .995)
	return (self.scale)
end



-- ======================================

function HS_ShapeAnnulus:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints()

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

--
-- ring 1
--
	mesh:AddLonePoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)

	mesh:WeldPoints(n + 4, n, drawingFrame)

	-- set all their curvatures appropriately (0.391379 is the magic number for a 4 point circle)
	mesh:Point(n):SetCurvature(HS_circleCorner, drawingFrame)
	mesh:Point(n + 1):SetCurvature(HS_circleCorner, drawingFrame)
	mesh:Point(n + 2):SetCurvature(HS_circleCorner, drawingFrame)
	mesh:Point(n + 3):SetCurvature(HS_circleCorner, drawingFrame)
--
-- ring 2
--

	mesh:AddLonePoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)
	mesh:AppendPoint(self.startVec, drawingFrame)

	mesh:WeldPoints(n + 8, n + 4, drawingFrame)

	mesh:Point(n + 4):SetCurvature(HS_circleCorner, drawingFrame)
	mesh:Point(n + 5):SetCurvature(HS_circleCorner, drawingFrame)
	mesh:Point(n + 6):SetCurvature(HS_circleCorner, drawingFrame)
	mesh:Point(n + 7):SetCurvature(HS_circleCorner, drawingFrame)

	mesh:SelectNone()

	mesh:Point(n).fSelected = true
	mesh:Point(n + 4).fSelected = true
	mesh:SelectConnected()

	if (HS_Shape.autoFill) then
		local shapeID = moho:CreateShape(true, false, drawingFrame)
		if (shapeID >= 0) then
			if (not HS_Shape.autoOutline) then
				mesh:Shape(shapeID).fHasOutline = false
			end
		end
	elseif (HS_Shape.autoOutline) then
		moho:CreateShape(false, false, drawingFrame)
	end

	return 8
end



function HS_ShapeAnnulus:OnMouseMoved (moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
	if (mouseEvent.altKey) then
		-- draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
	end

	local n = mesh:CountPoints()

--
--  	radius lengths and of "oval" origin
-- 
	local yLen = y2 - y1   -- can be -ve
	local xLen = x2 - x1

	local x0 = (x1 + x2) / 2
	local y0 = (y1 + y2) / 2

--
-- ours are the last 8 -- do in 2 batches of 4: last 4 are "outside"
--

	local pt = mesh:Point(n - 4)
	pt.fPos.x = x0
	pt.fPos.y = y1
	local pt = mesh:Point(n - 3)
	pt.fPos.x = x2
	pt.fPos.y = y0
	local pt = mesh:Point(n - 2)
	pt.fPos.x = x0
	pt.fPos.y = y2
	local pt = mesh:Point(n - 1)
	pt.fPos.x = x1
	pt.fPos.y = y0

	-- now set the proper curvature
	local f = math.abs(x2 - x1) - math.abs(y2 - y1)
	if (math.abs(f) > 1e-6) then

		if (math.abs(y2 - y1) > 1e-6) then
			f = (x2 - x1) / (y2 - y1)
		else
			f = 1000
		end
		f = math.abs(f)
		f = LM.Clamp(f, 0, HS_maxCurv / HS_circleCorner) * HS_circleCorner
		mesh:Point(n - 4):SetCurvature(f, drawingFrame)
		mesh:Point(n - 2):SetCurvature(f, drawingFrame)

		if (math.abs(x2 - x1) > 1e-6) then
			f = (y2 - y1) / (x2 - x1)
		else
			f = 1000
		end
		f = math.abs(f)
		f = LM.Clamp(f, 0, HS_maxCurv / HS_circleCorner) * HS_circleCorner
		mesh:Point(n - 3):SetCurvature(f, drawingFrame)
		mesh:Point(n - 1):SetCurvature(f, drawingFrame)
	else
		mesh:Point(n - 4):SetCurvature(HS_circleCorner, drawingFrame)
		mesh:Point(n - 3):SetCurvature(HS_circleCorner, drawingFrame)
		mesh:Point(n - 2):SetCurvature(HS_circleCorner, drawingFrame)
		mesh:Point(n - 1):SetCurvature(HS_circleCorner, drawingFrame)
	end

--
--	first 4 are inside
--
	n = n-4 -- skip back ....

	local dx = (1 - self.scale) * xLen / 2
	local dy = (1 - self.scale) * yLen / 2

	local pt = mesh:Point(n - 4)
	pt.fPos.x = x0
	pt.fPos.y = y1 + dy
	local pt = mesh:Point(n - 3)
	pt.fPos.x = x2 - dx
	pt.fPos.y = y0
	local pt = mesh:Point(n - 2)
	pt.fPos.x = x0
	pt.fPos.y = y2 - dy
	local pt = mesh:Point(n - 1)
	pt.fPos.x = x1 + dx
	pt.fPos.y = y0


	-- now set the proper curvature
	local f = math.abs(x2 - x1) - math.abs(y2 - y1)
	if (math.abs(f) > 1e-6) then

		if (math.abs(y2 - y1) > 1e-6) then
			f = (x2 - x1) / (y2 - y1)
		else
			f = 1000
		end
		f = math.abs(f)
		f = LM.Clamp(f, 0, HS_maxCurv / HS_circleCorner) * HS_circleCorner
		mesh:Point(n - 4):SetCurvature(f, drawingFrame)
		mesh:Point(n - 2):SetCurvature(f, drawingFrame)

		if (math.abs(x2 - x1) > 1e-6) then
			f = (y2 - y1) / (x2 - x1)
		else
			f = 1000
		end
		f = math.abs(f)
		f = LM.Clamp(f, 0, HS_maxCurv / HS_circleCorner) * HS_circleCorner
		mesh:Point(n - 3):SetCurvature(f, drawingFrame)
		mesh:Point(n - 1):SetCurvature(f, drawingFrame)
	else
		mesh:Point(n - 4):SetCurvature(HS_circleCorner, drawingFrame)
		mesh:Point(n - 3):SetCurvature(HS_circleCorner, drawingFrame)
		mesh:Point(n - 2):SetCurvature(HS_circleCorner, drawingFrame)
		mesh:Point(n - 1):SetCurvature(HS_circleCorner, drawingFrame)
	end


end







-- **************************************************
-- *************************************************************************************************************************** RT TRIANGLE 
-- **************************************************

HS_ShapeRtTriangle = {}

local startVec = LM.Vector2:new_local()
local startPt


function HS_ShapeRtTriangle:Identify()

	local t =
		{ident="RtTriangle",
		tip="Right Triangle: Shift: height=width. Alt: draw from centre",
		icon="hs_rt_tri",	
		self=HS_ShapeRtTriangle,					
		gKey="HS_ShapeRtTriangle"}
	return t
end

function HS_ShapeRtTriangle:About()

	local body = [==[

This tool creates a right angled triangle. No data entry fields are enabled.
]==]
	return body .. "\n" ..  HS_Shape:UILabel()


end


-- no prefs

-- no data



-- ==============

function HS_ShapeRtTriangle:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	local curv = MOHO.PEAKED
	local drawPts = 3

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, drawPts, curv)

	return drawPts

end

function HS_ShapeRtTriangle:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)


	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now
	local pt

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent)


	pt = mesh:Point(startPt + 0)
	pt.fPos.x = vec1.x
	pt.fPos.y = vec1.y
	pt = mesh:Point(startPt + 1)
	pt.fPos.x = vec1.x
	pt.fPos.y = vec2.y
	pt = mesh:Point(startPt + 2)
	pt.fPos.x = vec2.x
	pt.fPos.y = vec1.y

end







-- **************************************************
-- ************************************************************************************************************************************************ GRID
-- **************************************************
HS_ShapeGrid = {}

HS_ShapeGrid.startVec = LM.Vector2:new_local()

local doOneFill = 0
local doSepShapes = 1
local ctQuadOpts = 2

HS_ShapeGrid.Opt = doOneFill 	-- options for grid
HS_ShapeGrid.Wide = 6 		-- Columns in a quad grid
HS_ShapeGrid.Deep = 6		-- Rows


function HS_ShapeGrid:Identify()

	local t =
		{ident="Grid",
		tip="Rectangular Grid: Use Options Button to create one fill and one stroke or individual shapes for each cell. Shift: height=width. Alt: draw from centre",
		icon="hs_grid",	
		self=HS_ShapeGrid,					
		gKey="HS_ShapeGrid"}

	return t
end


function HS_ShapeGrid:About()

	local body = [==[

This tool creates a grid of the chosen number of rows and columns.
Two data entry fields are enabled:  these are the number of rows and columns. Both are constrained to be between 1 and 48 inclusive.
There is an options button to select the type of fill. Clicking on the button cycles through the options.
There are two options:
>> create just one shape for the grid lines and one for the background fill (if the relevant autostroke / autofill options are set)
>> create separate shapes for each cell

Note that where the choice of input values will result in a large number of shapes being created (e.g., a square grid with 48*48 cells)
the elapsed time taken to create all these shapes will be high.
(Where the potential for creating a large numbers of individual shapes exists, shapes are created on mouse up and not on mouse down.)

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeGrid:LoadPrefs(prefs)
	self.Opt = prefs:GetInt("HS_ShapeGrid.Opt", 0)
	self.Deep = prefs:GetInt("HS_ShapeGrid.Deep", 6)
	self.Wide = prefs:GetInt("HS_ShapeGrid.Wide", 6)
end


function HS_ShapeGrid:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeGrid.Deep", self.Deep)
	prefs:SetInt("HS_ShapeGrid.Wide", self.Wide)
	prefs:SetInt("HS_ShapeGrid.Opt", self.quadGridOpt)
end


function HS_ShapeGrid:ResetPrefs()
	self.Opt = 0
	self.Deep = 6
	self.Wide = 6
end


function HS_ShapeGrid:Labels ()
	HS_Shape:SetInt1 ("Grid Rows", HS_ShapeGrid.Deep) 
	HS_Shape:SetInt2 ("Columns", HS_ShapeGrid.Wide)

	local buttText
	if HS_ShapeGrid.Opt == doOneFill then
		buttText = "One Shape"
	elseif HS_ShapeGrid.Opt == doSepShapes then
		buttText = "Cell Shapes"
	end
	HS_Shape:SetButton (buttText)
end


function HS_ShapeGrid:BigN (moho, view, value)
	self.Deep = LM.Clamp(value, 1, gridMax)
	return self.Deep
end

function HS_ShapeGrid:LittleN (moho, view, value)
	self.Wide = LM.Clamp(value, 1, gridMax)
	return self.Wide
end

function HS_ShapeGrid:Button (moho, view, value)
	self.Opt = math.fmod(self.Opt + value + ctQuadOpts, ctQuadOpts)
end


-- ======================================

function HS_ShapeGrid:OnMouseDown (moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints()

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

	local i, j, m

-- set up the dimensions as total Size
	
	local gridWide = self.Wide +1				-- points not lines
	local gridDeep = self.Deep +1				-- the UI gives rows not rows of pts

	local points = {}

	local gridSize = gridWide*gridDeep

--
--	first create the outside box and fill as necessary
--

	j= 2*gridWide + 2*gridDeep - 4

	mesh:AddLonePoint(self.startVec, drawingFrame)
	for i=0, j-1 do
		mesh:AppendPoint(self.startVec, drawingFrame)
	end

-- last one (n+j) moves to weld
	mesh:WeldPoints(n+j, n, drawingFrame)
	

--
--	fill only (if selected) for the outer box
--
	mesh:SelectConnected()

	if (HS_Shape.autoFill) and (self.Opt == doOneFill) then
		local shapeID = moho:CreateShape(true, false, drawingFrame)

		if (shapeID >= 0) then

			mesh:Shape(shapeID).fHasOutline = false

		end
	end


-- 	create the remaining "Deep" lines of "Wide" points if there are any (i.e. not a 1*1 grid)

	if gridSize > 4 then
		local topLeftNext =  n + 2*gridWide + 2*gridDeep - 5	-- point number for weld
		local topRightNext = n + gridWide
	
		local leftPtNew =  topLeftNext + 1 			-- pt number in the new lines

		local leftPt = {}					-- keep a list of the left most points for subsequent bindings

		leftPt[1] = 1 + n					-- top row
		leftPt[gridDeep] =  n + 2*gridWide + gridDeep-4  	-- bottom row 



		for j=0, gridDeep-3 do
			mesh:AddLonePoint(self.startVec, drawingFrame)
			for i=0, gridWide-2 do
				mesh:AppendPoint(self.startVec, drawingFrame)
			end
			mesh:WeldPoints(leftPtNew, topLeftNext, drawingFrame)
			mesh:WeldPoints(leftPtNew + gridWide-2, topRightNext, drawingFrame)
			leftPt[j+2] = leftPtNew
			topLeftNext =  topLeftNext - 1
			topRightNext = topRightNext + 1
			leftPtNew = leftPtNew + gridWide - 2
		end


-- now add pairs of points to link and weld
-- as you weld points so they disappear, so the next to weld is always the same reference


--	bottom row bottom pts are "special"

		for j=0, gridDeep-3 do
			for i= 0, gridWide-3 do
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)
				mesh:WeldPoints(n+gridSize, leftPt[j+1]+i, drawingFrame)
				mesh:WeldPoints(n+gridSize, leftPt[j+2]+i, drawingFrame)
			end
		end


		j =  gridDeep-2 
		for i= 0, gridWide-3 do
			mesh:AddLonePoint(self.startVec, drawingFrame)
			mesh:AppendPoint(self.startVec, drawingFrame)
			mesh:WeldPoints(n+gridSize, leftPt[j+1]+i, drawingFrame)
			mesh:WeldPoints(n+gridSize, leftPt[j+2]-i, drawingFrame)
		end


	end


-- now create the grid shape

	mesh:SelectConnected()
	if (HS_Shape.autoOutline) and (self.Opt == doOneFill)
	then
		local shapeID = moho:CreateShape(false, false, drawingFrame)
	end

-- and finally set peaked points 
	for i = n, n + gridSize-1 do
		mesh:Point(i):SetCurvature(MOHO.PEAKED, drawingFrame)
	end


-- if we're deferring shape creation until mouse up ...
	local row, col = 0, 0
	if self.Opt == doSepShapes then -- populate points table
		-- start with the outer ring
		for i = 0, gridWide - 1 do --top row
			points[i+1] = n + i
		end
		for i = 1, gridDeep-1 do -- right col
			points[gridWide*(i+1)] = n + gridWide + i - 1
		end
		for i = 1, gridWide - 1 do -- bottom row
			points[(gridWide * (gridDeep-1)) + i] = n + (gridWide*2) + (gridDeep-2) - i
		end
		for i = 1, gridDeep-2 do -- left col
			points[(gridWide*i)+1] = n + 2*gridWide + 2*gridDeep - 4 - i
		end

		-- now the middle bit ...

		m = n + 2*gridWide + 2*gridDeep - 4

		for i = 1, gridDeep-2 do 
			for j = 1, gridWide-2 do
				points[(gridWide*i) + (j+1)] = m
				m = m + 1
			end
		end

		-- finally, populate the shapes to make table

		local p = {}

		for i = 0, gridWide-2 do
			for j = 0, gridDeep - 2 do
				p[1] = points[gridWide*j + i + 1]
				p[2] = points[gridWide*j + i + 2]
				p[3] = points[gridWide*(j+1) + i + 1]
				p[4] = points[gridWide*(j+1) + i + 2]
				table.insert (shapePoints, {p[1], p[2], p[3], p[4]})
			end
		end


	end

	return gridSize
end



function HS_ShapeGrid:OnMouseMoved (moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
	if (mouseEvent.altKey) then
		-- draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
	end

	local n = mesh:CountPoints()
	local gridWide = self.Wide +1 -- points not lines
	local gridDeep = self.Deep +1
	local gridSize = gridWide*gridDeep -- the last gridsize points are ours

	local deltaX = (x2-x1) / (gridWide-1)
	local deltaY = (y2-y1) / (gridDeep-1)


	local i
	local j


-- move the outside
-- pts +0 to +Wide are top row; the next d-2 are right side; the next w are bottom in rev order the next d-2 are lft side rev

-- base points number for the perimter = n - gridSize
	local basePt = n-gridSize

	j=0
	for i=0, gridWide-1 do
		local pt = mesh:Point(basePt + j)
		pt.fPos.x = deltaX*i + x1
		pt.fPos.y = y1
		j=j+1
	end

	for i=1, gridDeep-2 do
		local pt = mesh:Point(n-gridSize + j)
		pt.fPos.x = deltaX*(gridWide-1) + x1
		pt.fPos.y = deltaY*i + y1
		j=j+1
	end

	for i=0, gridWide-1 do
		local pt = mesh:Point(n-gridSize + j)
		pt.fPos.x = deltaX*(gridWide-i-1) + x1
		pt.fPos.y = deltaY*(gridDeep-1) + y1
		j=j+1
	end

	for i=1, gridDeep-2 do
		local pt = mesh:Point(n-gridSize + j)
		pt.fPos.x = x1
		pt.fPos.y = deltaY*(gridDeep-i-1) + y1
		j=j+1
	end

-- now the remaining points are inside.. we've done the edges so just the insides..

-- base points number for the insides
	local basePt = basePt + j
	 
	for j=1, gridDeep-2 do
		for i= 1, gridWide-2 do
			local pt = mesh:Point(basePt + i-1 + (gridWide-2)*(j-1))
			pt.fPos.x = deltaX*i + x1
			pt.fPos.y = deltaY*j + y1
		end
	end


end






-- **************************************************
-- *********************************************************************************************************************************** TRI GRID COMMON / DISPATCH
-- **************************************************


HS_ShapeTri_Grid = {}

HS_ShapeTri_Grid.startVec = LM.Vector2:new_local()

-- for triGridOpt 
local doHex = 2 		-- do hexagons - ragged edge
local doSqAlt = 0		-- do squares with alternate diagonals
local doSqHex = 1		-- do squares with diagnoals all in the same orientation
local ctGridOpts = 3		-- how many options?

HS_ShapeTri_Grid.Opt = doSqHex		-- options for grid
HS_ShapeTri_Grid.hexWide = 6 		-- Columns of triangles in a hexagonal patterned grid
HS_ShapeTri_Grid.hexDeep = 4		-- Rows
HS_ShapeTri_Grid.triWide = 5 		-- Columns of squares in an alternating diagonals triangular grid
HS_ShapeTri_Grid.triDeep = 5		-- Rows
HS_ShapeTri_Grid.sqhWide = 6 		-- Columns of squares in a square hex-based triangular grid
HS_ShapeTri_Grid.sqhDeep = 6		-- Rows






function HS_ShapeTri_Grid:Identify()

	local t =
		{ident="TriangularGrid",
		tip="Triangulated Grid: Use Options Button for style of triangulation. Shift: height=width. Alt: draw from centre.",
		icon="hs_tri_grid",	
		self=HS_ShapeTri_Grid,					
		gKey="HS_ShapeTri_Grid"}

	return t
end



function HS_ShapeTri_Grid:About()

	local body = [==[


The options button is used to select the style of the grid of triangles. Clicking on the button cycles through the options.
There are three grid variants:
>> a grid based on hexagons ("Hexagonal")
Two data entry fields are enabled:  these are the number of rows and the number of triangles per row.
The number of rows is constrained to be between 1 and 48 inclusive; the number of triangles per row is constrained to be between 2 and 48 inclusive.
This produces a “ragged edge” grid of similar triangles.

>> a grid based on squares with alternate diagonals ("Squares/alt")
>> a grid based on squares with the same diagonals ("Hex Square")
In these last two options two data entry fields are enabled:  these are the number of rows and the number of squares per row. Both are constrained to be between 1 and 48 inclusive.
 
Note that where the choice of input values will result in a large number of shapes being created (e.g., a triangular grid with 48*96 cells)
the elapsed time taken to create all these shapes will be high.
(Where the potential for creating a large numbers of individual shapes exists, shapes are created on mouse up and not on mouse down.)

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeTri_Grid:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeTri_Grid.Opt", self.Opt)
	prefs:SetInt("HS_ShapeTri_Grid.hexDeep", self.hexDeep)
	prefs:SetInt("HS_ShapeTri_Grid.hexWide", self.hexWide)
	prefs:SetInt("HS_ShapeTri_Grid.triDeep", self.triDeep)
	prefs:SetInt("HS_ShapeTri_Grid.triWide", self.triWide)
	prefs:SetInt("HS_ShapeTri_Grid.sqhDeep", self.sqhDeep)
	prefs:SetInt("HS_ShapeTri_Grid.sqhWide", self.sqhWide)
end

function HS_ShapeTri_Grid:LoadPrefs(prefs)
	self.Opt = prefs:GetInt("HS_ShapeTri_Grid.Opt", 0) 
	self.hexDeep = prefs:GetInt("HS_ShapeTri_Grid.hexDeep", 4)
	self.hexWide = prefs:GetInt("HS_ShapeTri_Grid.hexWide", 6)
	self.triDeep = prefs:GetInt("HS_ShapeTri_Grid.triDeep", 5)
	self.triWide = prefs:GetInt("HS_ShapeTri_Grid.triWide", 5)
	self.sqhDeep = prefs:GetInt("HS_ShapeTri_Grid.sqhDeep", 6)
	self.sqhWide = prefs:GetInt("HS_ShapeTri_Grid.sqhWide", 6)
end


function HS_ShapeTri_Grid:ResetPrefs()
	self.Opt = 0
	self.hexDeep = 4
	self.hexWide = 6
	self.triDeep = 5
	self.triWide = 5
	self.sqhDeep = 6
	self.sqhWide = 6
end


function HS_ShapeTri_Grid:Labels ()

	local bigNText, bigNVal

	local LittleNText, LittleNVal

	local buttText


	if self.Opt == doHex then

		bigNText = "Rows"
		bigNVal = self.hexDeep

		littleNText = "Triangles per row"
		LittleNVal = self.hexWide

		buttText = "Hexagonal"

	elseif self.Opt == doSqAlt then

		bigNText = "Rows"
		bigNVal = self.triDeep

		littleNText = "Columns of squares"
		LittleNVal = self.triWide

		buttText = "Squares/alt"

	elseif self.Opt == doSqHex then

		bigNText = "Rows"
		bigNVal = self.sqhDeep

		littleNText = "Columns of squares"
		LittleNVal = self.sqhWide

		buttText = "Hex Square"

	end

	HS_Shape:SetInt1 (bigNText, bigNVal) 
	HS_Shape:SetInt2 (littleNText, LittleNVal)
	HS_Shape:SetButton (buttText)

end


function HS_ShapeTri_Grid:BigN (moho, view, value)
	local rtx
	if self.Opt == doHex then
		self.hexDeep = LM.Clamp(value, 1, gridMax)
		rtx = self.hexDeep
	elseif self.Opt == doSqAlt then
		self.triDeep = LM.Clamp(value, 1, gridMax)
		rtx = self.triDeep
	elseif self.Opt == doSqHex then
		self.sqhDeep = LM.Clamp(value, 1, gridMax)
		rtx = self.sqhDeep
	end
	return rtx
end


function HS_ShapeTri_Grid:LittleN (moho, view, value)
	local rtx
	if self.Opt == doHex then
		self.hexWide = LM.Clamp(value, 2, gridMax)
		rtx = self.hexWide
	elseif self.Opt == doSqAlt then
		self.triWide = LM.Clamp(value, 1, gridMax)
		rtx = self.triWide
	elseif self.Opt == doSqHex then
		self.sqhWide = LM.Clamp(value, 1, gridMax)
		rtx = self.sqhWide
	end
	return rtx
end


function HS_ShapeTri_Grid:Button (moho, view, value)
	self.Opt = math.fmod(self.Opt + value + ctGridOpts, ctGridOpts)
end


-- ======================================

function HS_ShapeTri_Grid:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)
	local j
	if self.Opt == doHex then
		j = self:OnMouseDown_HexGrid(moho, mouseEvent, mesh, drawingFrame)
	elseif self.Opt == doSqAlt then
		j = self:OnMouseDown_Rt_TriGrid(moho, mouseEvent, mesh, drawingFrame)
	elseif self.Opt == doSqHex then
		j = self:OnMouseDown_Sq_HexGrid(moho, mouseEvent, mesh, drawingFrame)
 	end

	return j
end

function HS_ShapeTri_Grid:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	if self.Opt == doHex then
		self:OnMouseMoved_HexGrid(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	elseif self.Opt == doSqAlt then
		self:OnMouseMoved_Rt_TriGrid(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	elseif self.Opt == doSqHex then
		self:OnMouseMoved_Sq_HexGrid(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	end
end


-- **************************************************
-- *********************************************************************************************************************************** HEX TRIANGLE GRID
-- **************************************************

function HS_ShapeTri_Grid:OnMouseDown_HexGrid(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints()

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

	local i, j, k

-- set up the dimensions as total Size
	
	local gridDeep = self.hexDeep + 1				-- the UI gives rows not rows of pts - so this many lines

	local gridSize = 0

--
--	first create the lines
--
	local rows = {}
	local rowPts

	for i = 1, gridDeep do
		rows[i] = gridSize
		rowPts = math.ceil ((self.hexWide-math.fmod(i+1,2))/2)+1
		gridSize = gridSize + rowPts

		mesh:AddLonePoint(self.startVec, drawingFrame)
		for j= 2, rowPts do
			mesh:AppendPoint(self.startVec, drawingFrame) -- >>>logical, but what if only one colums - IOW 1 point?
		end

	end
	rows[gridDeep+1] = gridSize



-- now add pairs of points to link and weld
-- as you weld points so they disappear, so the next to weld is always the same reference


	for i = 1, gridDeep-1 do

		local thisPts = rows[i+1] - rows[i] 
		local nextPts = rows[i+2] - rows[i+1]
		local oddRow = math.fmod(i,2)



		local limit = math.min(thisPts, nextPts)

		for j = 0, limit-1 do
			mesh:AddLonePoint(self.startVec, drawingFrame)
			mesh:AppendPoint(self.startVec, drawingFrame)

			mesh:WeldPoints(n+gridSize, n+j+rows[i], drawingFrame)
			mesh:WeldPoints(n+gridSize, n+j+rows[i+1], drawingFrame)

		end



		if thisPts < nextPts then 		-- if fewer in "top" row then weld all top points n:n+1
			for j = 0, thisPts-1 do
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, n+j+rows[i], drawingFrame)
				mesh:WeldPoints(n+gridSize, n+j+1+rows[i+1], drawingFrame)
			end

		elseif  thisPts == nextPts and oddRow == 0 then  	-- if same number in top row and an eve row then weld n:n+1 from 1st
			for j = 0, thisPts-2 do
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, n+j+rows[i], drawingFrame)
				mesh:WeldPoints(n+gridSize, n+j+1+rows[i+1], drawingFrame)
			end

		else 		-- if more in top row then weld n:n-1 from 2nd or 
				-- if same number in top row and an odd row then weld n:n-1 from 2nd 
			for j = 1, thisPts-1 do
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, n+j+rows[i], drawingFrame)
				mesh:WeldPoints(n+gridSize, n+j-1+rows[i+1], drawingFrame)
			end

		end

	end




-- create shapes

	for i = 1, self.hexDeep do
		local ttb
		local nextTop, nextBtm = n, n
		local p = {}

		ttb = (math.fmod(i,2) == 1) 	-- i.e.if it is rows 1, 3... start with a top-top-bottom

		for j = 0, self.hexWide-1 do

			if ttb then
				p[1] = rows[i]+nextTop
				p[2] = rows[i]+nextTop+1
				p[3] = rows[i+1]+nextBtm
				nextTop = nextTop + 1
			else
				p[1] = rows[i]+nextTop
				p[2] = rows[i+1]+nextBtm
				p[3] = rows[i+1]+nextBtm+1
				nextBtm = nextBtm + 1
			end
			ttb = not ttb 	--flip

			table.insert (shapePoints, {p[1], p[2], p[3]})

		end
	end



-- and finally set peaked points 
	for i = n, n + gridSize-1 do
		mesh:Point(i):SetCurvature(MOHO.PEAKED, drawingFrame)
	end
	mesh:SelectConnected()
	return gridSize

end





function HS_ShapeTri_Grid:OnMouseMoved_HexGrid(moho, mouseEvent, mesh, drawingFrame, drawnPoints)


	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
	if (mouseEvent.altKey) then
		-- draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
	end

	local n = mesh:CountPoints()

	local gridDeep = self.hexDeep + 1				-- the UI gives rows not rows of pts - so this many lines
	local gridWide = 1+math.floor (self.hexWide/2)			-- calculate base number of points for the # of triangles
	local gridSize = 0

	local rows = {}
	local rowPts

	local deltaX = (x2-x1) / gridWide
	local deltaY = (y2-y1) / (gridDeep-1)



	local i
	local j


	for i = 1, gridDeep+1 do
		rows[i] = gridSize
		rowPts = math.ceil ((self.hexWide-math.fmod(i+1,2))/2)+1
		gridSize = gridSize + rowPts
	end


	local basePt = n-drawnPoints

	 
	for i=1, gridDeep do
		local rem = math.fmod (i+1, 2) -- are we an even # row -- if so shift x by half an increment

		rowPts = rows[i+1] - rows[i]


		for j = 0, rowPts-1 do
			local pt = mesh:Point(basePt + rows[i] + j)

			pt.fPos.x = deltaX*(j+rem/2) + x1
			pt.fPos.y = deltaY*(i-1) + y1
		end
	end


end







-- **************************************************
-- *********************************************************************************************************************************** SQUARE TRIANGLE GRID
-- **************************************************

function HS_ShapeTri_Grid:OnMouseDown_Rt_TriGrid(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints()

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

	local i, j, k

-- set up the dimensions as total Size
	
	local gridDeep = self.triDeep + 1				-- the UI gives rows not rows of pts - so this many lines
	local gridWide = self.triWide + 1

	local gridSize = gridDeep * gridWide

--
--	first create the lines
--

	for i = 1, gridDeep do
		mesh:AddLonePoint(self.startVec, drawingFrame)
		for j = 2, gridWide do
			mesh:AppendPoint(self.startVec, drawingFrame)
		end
	end




-- now add pairs of points to link and weld
-- as you weld points so they disappear, so the next to weld is always the same reference


-- 	first make the squares
	for i = 1, gridDeep-1 do

		local thisPts = n + (i-1) * gridWide
		local nextPts = thisPts + gridWide

		for j = 0, gridWide-1 do
			mesh:AddLonePoint(self.startVec, drawingFrame)
			mesh:AppendPoint(self.startVec, drawingFrame)

			mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
			mesh:WeldPoints(n+gridSize, j+nextPts, drawingFrame)

		end
	end

--	now the diagonals for odd numbers

	for i = 1, gridDeep-1, 2 do

		local thisPts = n + (i-1) * gridWide
		local nextPts = thisPts + gridWide

		for j = 0, gridWide-1, 2 do
			if j-1 >= 0 then
			
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
				mesh:WeldPoints(n+gridSize, j+nextPts-1, drawingFrame)
			end

			if j+1 < gridWide then
			
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
				mesh:WeldPoints(n+gridSize, j+nextPts+1, drawingFrame)
			end

		end
	end

-- 	and diagonals for even numbered rows
	for i = 2, gridDeep-1, 2 do

		local thisPts = n + (i-1) * gridWide
		local nextPts = thisPts + gridWide

		for j = 1, gridWide-1, 2 do
			if j-1 >= 0 then
			
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
				mesh:WeldPoints(n+gridSize, j+nextPts-1, drawingFrame)
			end

			if j+1 < gridWide then
			
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
				mesh:WeldPoints(n+gridSize, j+nextPts+1, drawingFrame)
			end

		end
	end





-- create shapes


	for i = 1, self.triDeep do
			
		local p = {}
		local thisPt = n
		local whichShape = 2*math.fmod(i,2)  	-- if it is rows 1, 3... start with a type 2; rows 2, 4 ... start with type 0

		for j = 1, self.triWide*2 do -- there are 2 triangles per square and 4 shapes of triangle ...


			if whichShape == 0 then -- top, top+1, btm
				p[1] = gridWide * (i-1) + thisPt 
				p[2] = gridWide * (i-1) + thisPt + 1
				p[3] = gridWide * (i) + thisPt
				whichShape = 1

			elseif whichShape == 1 then -- top+1, btm, btm+1
				p[1] = gridWide * (i-1) + thisPt + 1
				p[2] = gridWide * (i) + thisPt
				p[3] = gridWide * (i) + thisPt + 1
				whichShape = 2
				thisPt = thisPt + 1


			elseif whichShape == 2 then -- top, btm, btm +1
				p[1] = gridWide * (i-1) + thisPt 
				p[2] = gridWide * (i) + thisPt
				p[3] = gridWide * (i) + thisPt + 1
				whichShape = 3


			elseif whichShape == 3 then -- top top+1 btm+1
				p[1] = gridWide * (i-1) + thisPt 
				p[2] = gridWide * (i-1) + thisPt + 1
				p[3] = gridWide * (i) + thisPt + 1
				whichShape = 0
				thisPt = thisPt + 1

			end

			table.insert (shapePoints, {p[1], p[2], p[3]}) -- insert (t,p) failed!! scpoing issues??

		end
	end


-- and finally set peaked points 
	for i = n, n + gridSize-1 do
		mesh:Point(i):SetCurvature(MOHO.PEAKED, drawingFrame)
	end
	mesh:SelectConnected()
	return gridSize

end





function HS_ShapeTri_Grid:OnMouseMoved_Rt_TriGrid(moho, mouseEvent, mesh, drawingFrame, drawnPoints)


	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
	if (mouseEvent.altKey) then
		-- draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
	end

	local n = mesh:CountPoints()

	local gridDeep = self.triDeep + 1				-- the UI gives rows not rows of pts - so this many lines
	local gridWide = self.triWide + 1 


	local deltaX = (x2-x1) / (gridWide-1)
	local deltaY = (y2-y1) / (gridDeep-1)



	local i
	local j


	local basePt = n-drawnPoints

	 
	for i=0, gridDeep-1 do

		for j = 0, gridWide-1 do
			local pt = mesh:Point(basePt + j + gridWide*i)

			pt.fPos.x = deltaX*j + x1
			pt.fPos.y = deltaY*i + y1
		end
	end


end






-- **************************************************
-- *********************************************************************************************************************************** SQUARE HEX GRID
-- **************************************************

function HS_ShapeTri_Grid:OnMouseDown_Sq_HexGrid(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints()

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

	local i, j, k

-- set up the dimensions as total Size
	
	local gridDeep = self.sqhDeep + 1				-- the UI gives rows not rows of pts - so this many lines
	local gridWide = self.sqhWide + 1

	local gridSize = gridDeep * gridWide

--
--	first create the lines
--

	for i = 1, gridDeep do
		mesh:AddLonePoint(self.startVec, drawingFrame)
		for j = 2, gridWide do
			mesh:AppendPoint(self.startVec, drawingFrame)
		end
	end




-- now add pairs of points to link and weld
-- as you weld points so they disappear, so the next to weld is always the same reference


-- 	first make the squares
	for i = 1, gridDeep-1 do

		local thisPts = n + (i-1) * gridWide
		local nextPts = thisPts + gridWide

		for j = 0, gridWide-1 do
			mesh:AddLonePoint(self.startVec, drawingFrame)
			mesh:AppendPoint(self.startVec, drawingFrame)

			mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
			mesh:WeldPoints(n+gridSize, j+nextPts, drawingFrame)

		end
	end


--	now the diagonals top to one right

	for i = 1, gridDeep-1 do

		local thisPts = n + (i-1) * gridWide
		local nextPts = thisPts + gridWide

		for j = 0, gridWide-2 do
			
				mesh:AddLonePoint(self.startVec, drawingFrame)
				mesh:AppendPoint(self.startVec, drawingFrame)

				mesh:WeldPoints(n+gridSize, j+thisPts, drawingFrame)
				mesh:WeldPoints(n+gridSize, j+1+nextPts, drawingFrame)


		end
	end


-- create shapes


	for i = 1, self.sqhDeep do
			
		local p = {}
		local thisPt = n
		local L_Shape = true -- start with an L

		for j = 1, self.sqhWide*2 do -- there are 2 triangles per square and 2 shapes of triangle ...

			if L_Shape then   -- top, btm, btm +1
				p[1] = gridWide * (i-1) + thisPt 
				p[2] = gridWide * (i) + thisPt
				p[3] = gridWide * (i) + thisPt + 1
				L_Shape = not L_Shape


			else  -- top top+1 btm+1
				p[1] = gridWide * (i-1) + thisPt 
				p[2] = gridWide * (i-1) + thisPt + 1
				p[3] = gridWide * (i) + thisPt + 1
				L_Shape = not L_Shape
				thisPt = thisPt + 1

			end

--?? only do mouse up creation if > threshold shapes???
			table.insert (shapePoints, {p[1], p[2], p[3]})

		end
	end


-- and finally set peaked points 
	for i = n, n + gridSize-1 do
		mesh:Point(i):SetCurvature(MOHO.PEAKED, drawingFrame)
	end
	mesh:SelectConnected()
	return gridSize

end





function HS_ShapeTri_Grid:OnMouseMoved_Sq_HexGrid(moho, mouseEvent, mesh, drawingFrame, drawnPoints)


	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
	if (mouseEvent.altKey) then
		-- draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
	end

	local n = mesh:CountPoints()

	local gridDeep = self.sqhDeep + 1				-- the UI gives rows not rows of pts - so this many lines
	local gridWide = self.sqhWide + 1 


	local deltaX = (x2-x1) / (gridWide-1)
	local deltaY = (y2-y1) / (gridDeep-1)



	local i
	local j


	local basePt = n-drawnPoints

	 
	for i=0, gridDeep-1 do

		for j = 0, gridWide-1 do
			local pt = mesh:Point(basePt + j + gridWide*i)

			pt.fPos.x = deltaX*j + x1
			pt.fPos.y = deltaY*i + y1
		end
	end


end







-- **************************************************
-- ****************************************************************************************************************************** CROSS
-- **************************************************

HS_ShapeCross = {}


HS_ShapeCross.scale = 1/3		-- scaling factor - swiss cross
local startVec = LM.Vector2:new_local()
local startPt

function HS_ShapeCross:Identify()

	local t =
		{ident="SwissCross",
		tip="Swiss Cross: Ctrl: all corners rounded. Shift: height=width. Alt: draw from centre.",
		icon="hs_cross",						
		self=HS_ShapeCross,
		gKey="HS_ShapeCross"}

	return t
end

function HS_ShapeCross:About()

	local body = [==[

This tool creates a Swiss Cross. Ctrl makes the points rounded (otherwise they are right angles).
One data entry field is enabled: width of the arm of the cross as a ratio of the shortest side. This can be between 0.01 and 0.99 inclusive.

]==]
	return body .. "\n" ..  HS_Shape:UILabel()


end



function HS_ShapeCross:LoadPrefs(prefs)
	self.scale = prefs:GetFloat("HS_ShapeCross.scale", 0.333) 	-- swiss cross as fraction of shortest side
end

function HS_ShapeCross:SavePrefs(prefs)
	prefs:SetFloat("HS_ShapeCross.scale", self.scale)
end

function HS_ShapeCross:ResetPrefs()
	self.scale = 1/3
end


function HS_ShapeCross:Labels ()
	HS_Shape:SetFloat ("Arm width", self.scale)
end


function HS_ShapeCross:Scale (moho, view, value)
	self.scale = LM.Clamp(value, .01, .80)
	return self.scale
end


-- ======================================

function HS_ShapeCross:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	local drawPts = 12
	local curv

	if (mouseEvent.ctrlKey) then
		curv = HS_roundcorner -- >>>  better curvature....
	else
		curv = MOHO.PEAKED
	end

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, drawPts, curv)

	return drawPts

end

function HS_ShapeCross:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent)

	local x1 = vec1.x
	local x2 = vec2.x
	local y1 = vec1.y
	local y2 = vec2.y

		
	local i

	local sides = LM.Vector2:new_local()
	local pointA = LM.Vector2:new_local()
	local pointB = LM.Vector2:new_local()
	local crossW
	local pt

	sides.x = x2 - x1
	sides.y = y2 - y1

	if math.abs(sides.x) > math.abs(sides.y) then
		crossW = sides.y * self.scale
	else
		crossW = sides.x * self.scale
	end


	if sides.x * crossW > 0 then
		pointA.x = x1 + (sides.x - crossW) / 2
		pointB.x = x1 + (sides.x + crossW) / 2
	else
		pointB.x = x1 + (sides.x - crossW) / 2
		pointA.x = x1 + (sides.x + crossW) / 2
	end


	if sides.y * crossW < 0  then
		pointB.y = y1 + (sides.y - crossW) / 2
		pointA.y = y1 + (sides.y + crossW) / 2
	else
		pointA.y = y1 + (sides.y - crossW) / 2
		pointB.y = y1 + (sides.y + crossW) / 2
	end

		
	pt = mesh:Point(startPt+0)
	pt.fPos.x = x1
	pt.fPos.y = pointA.y

	pt = mesh:Point(startPt+1)
	pt.fPos.x = pointA.x
	pt.fPos.y = pointA.y

	pt = mesh:Point(startPt+2)
	pt.fPos.x = pointA.x
	pt.fPos.y = y1

	pt = mesh:Point(startPt+3)
	pt.fPos.x = pointB.x
	pt.fPos.y = y1

	pt = mesh:Point(startPt+4)
	pt.fPos.x = pointB.x
	pt.fPos.y = pointA.y

	pt = mesh:Point(startPt+5)
	pt.fPos.x = x2
	pt.fPos.y = pointA.y

	pt = mesh:Point(startPt+6)
	pt.fPos.x = x2
	pt.fPos.y = pointB.y

	pt = mesh:Point(startPt+7)
	pt.fPos.x = pointB.x
	pt.fPos.y = pointB.y

	pt = mesh:Point(startPt+8)
	pt.fPos.x = pointB.x
	pt.fPos.y = y2

	pt = mesh:Point(startPt+9)
	pt.fPos.x = pointA.x
	pt.fPos.y = y2

	pt = mesh:Point(startPt+10)
	pt.fPos.x = pointA.x
	pt.fPos.y = pointB.y

	pt = mesh:Point(startPt+11)
	pt.fPos.x = x1
	pt.fPos.y = pointB.y


	if (mouseEvent.ctrlKey) then
		curv = HS_roundcorner -- >>>  better curvature....
	else
		curv = MOHO.PEAKED
	end

	for i = startPt, startPt+11 do
		mesh:Point(i):SetCurvature(curv, drawingFrame)
	end

end








-- **************************************************
-- ******************************************************************************************************************************************* STAR
-- **************************************************


HS_ShapeStar = {}
HS_ShapeStar.Points = 6			-- points on a star
HS_ShapeStar.scale = 0.382		-- scaling factor - star

local startVec = LM.Vector2:new_local()
local startPt


function HS_ShapeStar:Identify()

	local t =
		{ident="Star",
		tip="Star: Ctrl: all corners rounded. Shift: height=width. Alt: draw from centre.",
		icon="hs_star",						
		self=HS_ShapeStar,
		gKey="HS_ShapeStar"}

	return t
end

function HS_ShapeStar:About()

	local body = [==[

This tool creates a  Star with the chosen number of arms. Ctrl makes the points rounded (otherwise they are sharp).
Two data entry fields are enabled:  these are the number of arms on the star and the ratio of the width of the centre to total radius of the star.
The star can have between 3 and 72 arms inclusive; the centre ratio can be between 0.001 and 0.9950 inclusive.

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeStar:LoadPrefs(prefs)
	self.Points = prefs:GetInt("HS_ShapeStar.Points", 6)
	self.scale = prefs:GetFloat("HS_ShapeStar.scale", 0.382) 	-- star inner as a fraction of outer
end

function HS_ShapeStar:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeStar.Points", self.Points)
	prefs:SetFloat("HS_ShapeStar.scale", self.scale)
end


function HS_ShapeStar:ResetPrefs()
	self.Points = 6
	self.scale = 0.382
end


function HS_ShapeStar:Labels ()
	HS_Shape:SetInt1 ("Arms on Star", self.Points) 
	HS_Shape:SetFloat ("Centre size ratio", self.scale)

end


function HS_ShapeStar:BigN (moho, view, value)
	self.Points = LM.Clamp(value, 3, bigNmax)
	return self.Points
end

function HS_ShapeStar:Scale (moho, view, value)
	self.scale = LM.Clamp(value, .001, .995)
	return self.scale
end




-- ======================================

function HS_ShapeStar:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	local curv
	local drawPts = 2 * self.Points

	if (mouseEvent.ctrlKey) then
		curv = HS_roundcorner -- >>>  better curvature....
	else
		curv = MOHO.PEAKED
	end

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, drawPts, curv)

	return drawPts
end

function HS_ShapeStar:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent)

	local x1 = vec1.x
	local x2 = vec2.x
	local y1 = vec1.y
	local y2 = vec2.y


	if (mouseEvent.ctrlKey) then
		curv = HS_roundcorner -- >>>  better curvature....
	else
		curv = MOHO.PEAKED
	end



	local numPoints = drawnPoints / 2
	local ptID = startPt
	local angle = math.pi / 2
	local dAngle = math.pi / numPoints
	local bigR = 1.0 
	local initRatio = self.scale
	local littleR = bigR*initRatio 		
	local scale = LM.Vector2:new_local()
	local offset = LM.Vector2:new_local()
	local pt


	scale.x = math.abs(x2 - x1) / 2
	scale.y = math.abs(y2 - y1) / 2
	offset.x = (x1 + x2) / 2
	offset.y = (y1 + y2) / 2

	for i = 1, numPoints do
		
		pt = mesh:Point(ptID)
		ptID = ptID + 1
		pt.fPos.x = (bigR * math.cos(angle)) * scale.x + offset.x
		pt.fPos.y = (bigR * math.sin(angle)) * scale.y + offset.y
		pt:SetCurvature(curv, drawingFrame)
		angle = angle + dAngle

		pt = mesh:Point(ptID)
		ptID = ptID + 1
		pt.fPos.x = (littleR * math.cos(angle)) * scale.x + offset.x
		pt.fPos.y = (littleR * math.sin(angle)) * scale.y + offset.y
		pt:SetCurvature(curv, drawingFrame)
		angle = angle + dAngle
	end
end







-- **************************************************
-- ********************************************************************************************************************************** POLYGON
-- **************************************************


HS_ShapePolygon = {}

HS_ShapePolygon.Sides = 6	-- sides on a polygon

local startVec = LM.Vector2:new_local()
local startPt



function HS_ShapePolygon:Identify()

	local t =
		{ident="Polygon",
		tip="Polygon Ctrl: all corners rounded. Shift: height=width. Alt: draw from centre.",
		icon="hs_polygon",	
		self=HS_ShapePolygon,					
		gKey="HS_ShapePolygon"}

	return t
end

function HS_ShapePolygon:About()

	local body = [==[

This tool creates a polygon. One data entry field is enabled: the number of sides which can be between 3 and 24 inclusive.
Ctrl rounds the points (they are peaked by default). Shift makes the height and width the same. Alt draws form the centre.

]==]
	return body .. "\n" ..  HS_Shape:UILabel()


end



function HS_ShapePolygon:LoadPrefs(prefs)
	self.Sides = prefs:GetInt("HS_ShapePolygon.Sides", 6)
end

function HS_ShapePolygon:SavePrefs(prefs)
	prefs:SetInt("HS_ShapePolygon.Sides", self.Sides)
end

function HS_ShapePolygon:ResetPrefs()
	self.Sides = 6
end

function HS_ShapePolygon:Labels ()
	HS_Shape:SetInt1 ("Sides on Polygon", self.Sides) 
end

function HS_ShapePolygon:BigN (moho, view, value)
	self.Sides = LM.Clamp(value, 3, bigNmed)
	return self.Sides
end



-- ======================================

function HS_ShapePolygon:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	local curv
	local drawPts = self.Sides

	if (mouseEvent.ctrlKey) then
		curv = HS_roundcorner -- >>>  better curvature....
	else
		curv = MOHO.PEAKED
	end

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLoop(moho, startVec, drawingFrame, drawPts, curv)

	return drawPts

end

function HS_ShapePolygon:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec1 = LM.Vector2:new_local() -- start
	local vec2 = LM.Vector2:new_local() -- mouse now

	vec1, vec2 = HS_Shape:DrawingBounds (moho, mouseEvent)

	local x1 = vec1.x
	local x2 = vec2.x
	local y1 = vec1.y
	local y2 = vec2.y

	local pt 
	
	local Curvature = MOHO.PEAKED

	if (mouseEvent.ctrlKey)
	then
		Curvature = HS_roundcorner
	end

	local dAngle = math.rad(360) / drawnPoints 	-- angle between apex in regular polygon
	local angle = (math.rad(180) - dAngle)/2	-- starting position (in effect the "point" on an odd-numbered sided polygon)

	if (y2>y1) then 
		angle = angle + math.rad(180)		-- flip if mouse higher on the page...
	end



	local scale = LM.Vector2:new_local()
	local offset = LM.Vector2:new_local()


	scale.x = math.abs(x2 - x1) / math.sqrt(2)
	scale.y = math.abs(y2 - y1) / math.sqrt(2)		
	offset.x = (x1 + x2) / 2
	offset.y = (y1 + y2) / 2


	for i = 0, drawnPoints - 1 do
		pt = mesh:Point(startPt + i)

		pt.fPos.x = (scale.x * math.cos(angle)) + offset.x
		pt.fPos.y = (scale.y * math.sin(angle)) + offset.y
		pt:SetCurvature(Curvature, drawingFrame)
		angle = angle + dAngle
	end
end






-- **************************************************
-- ********************************************************************************************************************************** CONCENTRIC POLYGONS
-- **************************************************


HS_ShapeConcentrics = {}

HS_ShapeConcentrics.Concs = 2	-- how many concentric polygons
HS_ShapeConcentrics.Sides = 6	-- sides on concentric polygons

HS_ShapeConcentrics.startVec = LM.Vector2:new_local()



function HS_ShapeConcentrics:Identify()

	local t =
		{ident="Concentrics",
		tip="Concentric Polygons: Ctrl: all corners rounded. Shift: height=width. Ctrl+Shift: Circles. Alt: draw from centre.",
		icon="hs_concentrics",	
		self=HS_ShapeConcentrics,					
		gKey="HS_ShapeConcentrics"}

	return t
end


function HS_ShapeConcentrics:About()

	local body = [==[

This tool creates concentric polygons: each is a separate shape with the largest at the bottom of the stack. 
Two data entry fields are enabled:
the number of sides on each polygon which can be between 3 and 24 inclusive; 
the number of concentric shapes which can be between 1 and 24 inclusive.
Ctrl rounds the points (they are peaked by default). Shift makes the height and width the same. Alt draws form the centre.

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end

function HS_ShapeConcentrics:LoadPrefs(prefs)
	self.Concs = prefs:GetInt("HS_ShapeConcentrics.Concs", 2)
	self.Sides = prefs:GetInt("HS_ShapeConcentrics.Sides", 6)
end

function HS_ShapeConcentrics:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeConcentrics.Concs", self.Concs)
	prefs:SetInt("HS_ShapeConcentrics.Sides", self.Sides)
end

function HS_ShapeConcentrics:ResetPrefs()
	self.Concs = 2
	self.Sides = 6
end


function HS_ShapeConcentrics:Labels ()
	HS_Shape:SetInt1 ("Sides on Polygon", self.Sides) 
	HS_Shape:SetInt2 ("Concentric shapes", self.Concs)

end


function HS_ShapeConcentrics:BigN (moho, view, value)
	self.Sides = LM.Clamp(value, 3, bigNmed)
	return self.Sides
end


function HS_ShapeConcentrics:LittleN (moho, view, value)
	self.Concs = LM.Clamp(value, 1, bigNmed)
	return self.Concs
end


-- ======================================



function HS_ShapeConcentrics:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints() -- how many points before we started??

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

-- we add start points
	local drawPts = self.Sides

	local Curvature = MOHO.PEAKED

	if (mouseEvent.ctrlKey)
	then
		Curvature = HS_curvC * math.tan(math.pi/(2*self.Sides))
	end

	local i, j


	for j = 0, self.Concs-1 do

		mesh:SelectNone()

		mesh:AddLonePoint(self.startVec, drawingFrame)
		for i = 0, self.Sides-1 do 
			mesh:AppendPoint(self.startVec, drawingFrame)
		end

		local startPt = n + j*drawPts

		mesh:WeldPoints(startPt + drawPts, startPt, drawingFrame)

		for i = startPt, startPt+drawPts-1 do
			mesh:Point(i):SetCurvature(Curvature, drawingFrame)
		end


		mesh:SelectConnected()
		if (HS_Shape.autoFill) then
			local shapeID = moho:CreateShape(true, false, drawingFrame)
			if (shapeID >= 0) then
				if (not HS_Shape.autoOutline) then
					mesh:Shape(shapeID).fHasOutline = false
				end
			end
		elseif (HS_Shape.autoOutline) then
			moho:CreateShape(false, false, drawingFrame)
		end
	end

	for i  = 0, (self.Sides * self.Concs)-1 do
		mesh:Point(i+n).fSelected = true
	end

	return self.Sides * self.Concs
end

function HS_ShapeConcentrics:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local i, j

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
	if (mouseEvent.altKey) then
		-- draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
	end

		
	local Curvature = MOHO.PEAKED

	if (mouseEvent.ctrlKey)
	then
		Curvature = HS_curvC * math.tan(math.pi/(2*self.Sides))
	end



	local n = mesh:CountPoints()
	local ptID = n - (drawnPoints)			-- the first one we drew (the numbers in the UI might have changed!)


	local dAngle = math.rad(360) / self.Sides 		-- angle between apex in regular polygon
	local angle = (math.rad(180) - dAngle)/2		-- starting position (in effect the "point" on an odd-numbered sided polygon)

	if (y2>y1) then 
		angle = angle + math.rad(180)		-- flip if mouse higher on the page...
	end

	local baseAngle = angle

	local scale = LM.Vector2:new_local()
	local offset = LM.Vector2:new_local()


	scale.x = math.abs(x2 - x1) / (math.sqrt(2) * self.Concs)
	scale.y = math.abs(y2 - y1) / (math.sqrt(2) * self.Concs)
	offset.x = (x1 + x2) / 2
	offset.y = (y1 + y2) / 2

		


	for j = self.Concs, 1, -1 do

		for i = 1, self.Sides do
		
			local pt = mesh:Point(ptID)
			ptID = ptID + 1
			pt.fPos.x = (scale.x * j * math.cos(angle)) + offset.x
			pt.fPos.y = (scale.y * j * math.sin(angle)) + offset.y
			pt:SetCurvature(Curvature, drawingFrame)
			angle = angle + dAngle
		end
		angle = baseAngle
	end

end






-- **************************************************
-- ***************************************************************************************************************************************** LINE
-- **************************************************

HS_ShapeLine = {}

-- for line
local doParallel = 0
local doRadial = 1
local ctLineOpts = 2


HS_ShapeLine.startVec = LM.Vector2:new_local()

HS_ShapeLine.Points = 12
HS_ShapeLine.Parallels = 1	-- how many lines
HS_ShapeLine.Sep = 0.1		-- how far apart (moho units)
HS_ShapeLine.WidthDef = false	-- use stroke width?
HS_ShapeLine.scale = 0.382
HS_ShapeLine.Opt = doParallel

local oldLineSep 			-- to save the line separation if we overwrite with default stroke width


function HS_ShapeLine:Identify()

	local t =
		{ident="Line",
		tip="Straight Line(s): Ctrl: points are not Peaked. If more than one line: use Options Button to create Parallel or Radial. Shift and Alt depend on option.",
		icon="hs_line",	
		self=HS_ShapeLine,					
		gKey="HS_ShapeLine"}

	return t
end


function HS_ShapeLine:About()

	local body = [==[

The Line tool has two main modes of operation:
>> Generate one straight line with points evenly spaced along it.
>> Generate several lines.

Shift key makes the line horizontal or, with the Alt key also pressed, Vertical. Control makes the points rounded otherwise they are peaked.

Two data entry fields are always enabled:
>> the number of points on each line – this constrained to be between 2 and 72 inclusive.
>> how many lines to draw

If more than one line is to be drawn, there is a button that cycles through the options of which there are two:
>> create multiple parallel lines – in which case the spacing may be set manually or, by checking the “base on stroke width” box,
the spacing is set automatically to bring the lines close together so that a standard brush will fill the gap.
This automatic setting may be further manually adjusted.
>> create radial lines – in which case one data entry field is enabled – this defines the distance from the origin to the innermost points
as a ratio of the line length. This is constrained to be between 0 and .990.
Note that if this option is set to be very small, there will be multiple points created at the same (or very close) location
and it may prove difficult to select the origin point for a specific line.

]==]

	return body .. "\n" ..  HS_Shape:UILabel()

end


function HS_ShapeLine:LoadPrefs(prefs)
	self.Points = prefs:GetInt("HS_ShapeLine.Points", 12)
	self.Parallels = prefs:GetInt("HS_ShapeLine.Parallels", 1)
	self.scale = prefs:GetFloat("HS_ShapeLine.scale", 0.382)
	self.Opt = prefs:GetInt("HS_ShapeLine.eOpt", 0)
	self.WidthDef = prefs:GetBool("HS_ShapeLine.WidthDef", false)
	self.Sep = prefs:GetFloat("HS_ShapeLine.Sep", .1)
end


function HS_ShapeLine:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeLine.Points", self.Points)
	prefs:SetInt("HS_ShapeLine.Parallels", self.Parallels)
	prefs:SetInt("HS_ShapeLine.Opt", self.Opt)
	prefs:SetFloat("HS_ShapeLine.Sep", self.Sep)
	prefs:SetFloat("HS_ShapeLine.scale", self.scale)
	prefs:SetBool("HS_ShapeLine.WidthDef", self.WidthDef)
end

function HS_ShapeLine:ResetPrefs()
	self.Points = 12
	self.Parallels = 1
	self.Opt = 0
	self.Sep = 0.1
	self.WidthDef = false
	self.scale = 0.382

end


function HS_ShapeLine:Labels ()
	HS_Shape:SetInt1 ("Points on Line", self.Points) 
	HS_Shape:SetInt2 ("Number of lines", self.Parallels)

	local buttText, floatText, floatVal

	if self.Parallels > 1 then
		if self.Opt == doParallel then
			buttText = "Parallel"
			floatText = "Spacing"
			floatVal = self.Sep
			HS_Shape:SetCheckBox ("Base on Stroke Width", self.WidthDef)

		elseif self.Opt == doRadial then
			buttText = "Radial"
			floatText = "Inner Radius Ratio"
			floatVal = self.scale

		end

		HS_Shape:SetFloat (floatText, floatVal)
		HS_Shape:SetButton (buttText)

	end
end

function HS_ShapeLine:BigN (moho, view, value)
	self.Points = LM.Clamp(value, 2, bigNmax)
	return self.Points
end

function HS_ShapeLine:LittleN (moho, view, value)
	self.Parallels = LM.Clamp(value, 1, gridMax)
	return self.Parallels
end


function HS_ShapeLine:Scale (moho, view, value)
	local rtx
	if self.Opt == doParallel then
		self.Sep = LM.Clamp(value, .001, 1)
		rtx = self.Sep
	elseif self.Opt == doRadial then
		self.scale = LM.Clamp(value, 0, .99)
		rtx = self.scale
	end
	return rtx
end

function HS_ShapeLine:Button (moho, view, value)
	self.Opt = math.fmod(self.Opt + 1, ctLineOpts)
end

function HS_ShapeLine:CheckBox (moho, view, value)
	self.WidthDef = value
	if self.WidthDef then
		oldLineSep = self.Sep
		self.Sep = moho.NewShapeLineWidth() * 2 -- factor actually depends on brush depth
	else
		self.Sep = oldLineSep
	end
end




-- ======================================

function HS_ShapeLine:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	local i, j
	local vec = LM.Vector2:new_local()


	self.basePt = mesh:CountPoints()

	if (moho.gridOn) then
		moho:SnapToGrid(mouseEvent.drawingVec)
	end

	self.startVec:Set(mouseEvent.drawingVec)
	vec:Set(mouseEvent.drawingVec)

	for j = 0, self.Parallels - 1 do
		mesh:AddLonePoint(vec, drawingFrame)
		for i = 0, self.Points - 2 do
			mesh:AppendPoint(self.startVec, drawingFrame)
		end
	end


	mesh:SelectNone()

	for i  = 0, (self.Points * self.Parallels)-1 do
		mesh:Point(i+self.basePt).fSelected = true
	end


	if (HS_Shape.autoOutline) then
		local shapeID = moho:CreateShape(false, false, drawingFrame)
	end
	
	return self.Points * self.Parallels
end	


function HS_ShapeLine:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	if drawnPoints ~= self.Points * self.Parallels then
		return false -- break out if the UI numbers have changed -- can happen in edit mode
	end

	if self.Opt == doParallel then
		self:OnMouseMovedP(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	elseif self.Opt == doRadial then
		self:OnMouseMovedR(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	end
end


function HS_ShapeLine:OnMouseMovedP(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	if (moho.gridOn) then
		moho:SnapToGrid(mouseEvent.drawingStartVec)
		moho:SnapToGrid(mouseEvent.drawingVec)
	end

	local vec = LM.Vector2:new_local()
	local baseVec = LM.Vector2:new_local()

	baseVec:Set(mouseEvent.drawingVec)

	vec:Set(baseVec)

	local offset = LM.Vector2:new_local()
	local endShift = LM.Vector2:new_local()
	local orth = LM.Vector2:new_local()

	offset:Set(self.startVec)

	offset = vec - offset
	orth = offset:GetOrthogonal()
	local theta = math.atan2(orth.y, orth.x)

	local i, k
	local j = self.Points
	local n = mesh:CountPoints()

	offset:Set(self.startVec)

	for k = self.Parallels-1, 0, -1 do

		for i = 1, j do
			local percent = (i - 1) / (j - 1)

			local deltaX = percent * (vec.x-offset.x)

			local deltaY = percent * (vec.y-offset.y)

			local xShift = deltaX
			local yShift = deltaY
		
			local pt = mesh:Point(n - (i + k * j))


			if (mouseEvent.altKey) then

				if (mouseEvent.shiftKey) then			
					xShift  = 0
					if theta < 0 then
						theta = - math.pi
					else
						theta = 0
					end
				end
			else	
				if (mouseEvent.shiftKey) then			
					yShift  = 0
					if theta < 0 then
						theta = - math.pi/2
					else
						theta = math.pi/2
					end

				end
			end

			endShift.x =  k*self.Sep*math.cos(theta)
			endShift.y =  k*self.Sep*math.sin(theta)

			pt.fPos.y = offset.y + yShift + endShift.y
			pt.fPos.x = offset.x + xShift + endShift.x
	
--	line is Straight (spiky) unless CTRL

			local Curvature = MOHO.PEAKED

			if (mouseEvent.ctrlKey)
			then
				Curvature = HS_roundcorner
			end
			pt:SetCurvature(Curvature, drawingFrame)
		end

	end

	
end


function HS_ShapeLine:OnMouseMovedR(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
--	if not (mouseEvent.altKey) then
		--  always draw from center point 
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
--	end


	local i, j, k


	local n = mesh:CountPoints() - drawnPoints 		-- the first point we drew
	local dAngle = math.rad(360) / self.Parallels 		-- angle between apex in regular polygon
	local angle = (math.rad(180) - dAngle)/2		-- starting position (in effect the "point" on an odd-numbered sided polygon)

	if (y2>y1) then 
		angle = angle + math.rad(180)		-- flip if mouse higher on the page...
	end

	local scale = LM.Vector2:new_local()
	local offset = LM.Vector2:new_local()
	local ratio = 1


	scale.x = math.abs(x2 - x1) / math.sqrt(2)
	scale.y = math.abs(y2 - y1) / math.sqrt(2)		
	offset.x = (x1 + x2) / 2
	offset.y = (y1 + y2) / 2
	for i = 0, self.Points-1 do -- for each ring...
		ratio = 1 - i *((1 - self.scale) /(self.Points - 1))
		for j = 0, self.Parallels - 1 do -- for each line
			k = j*self.Points + i
			local pt = mesh:Point(n + k)
			pt.fPos.x = ratio * (scale.x * math.cos(angle)) + offset.x
			pt.fPos.y = ratio * (scale.y * math.sin(angle)) + offset.y
			local Curvature = MOHO.PEAKED --	line is (peaked points) unless CTRL
			if (mouseEvent.ctrlKey)
			then
				Curvature = HS_roundcorner
			end
			pt:SetCurvature(Curvature, drawingFrame)
			angle = angle + dAngle
		end
	end
end







-- **************************************************
-- ************************************************************************************************************************************* SINE
-- **************************************************


HS_ShapeSine = {}


HS_ShapeSine.Points = 40 
HS_ShapeSine.Cycles = 2 		-- how many 360 degree cycles we want
HS_ShapeSine.scale = 0.4		-- relative amplitude - sine
local maxCycles = 12
local ptsPerCycle = 6			-- we need 6 points per cycle or more

local startVec = LM.Vector2:new_local()
local startPt

function HS_ShapeSine:Identify()

	local t =
		{ident="Sine",
		tip="Sine Curve: Ctrl: separate points by 15 degrees. Shift: Horizontal axis. Alt: First cycle is downwards.",
		icon="hs_sine",						
		self=HS_ShapeSine,
		gKey="HS_ShapeSine"}

	return t
end

function HS_ShapeSine:About()
	local body = [==[

This tool creates a Sine wave. Dragging the “end” point extends the curve from zero to the selected number of cycles until the whole curve has been generated and then it stretches the curve.

The Control key constrains the curve so that fewer cycles are drawn if there are insufficient points to keep the curve properly shaped (12 points for a half-cycle).
The Alt key inverts the curve.
The Shift key keeps the line of zero amplitude horizontal.

Three data entry fields are enabled:
the number of points per full cycle (6 to 60 inclusive),
the maximum number of cycles (1 to 12) 
the peak-to-peak size of the curve (amplitude) relative to the default frame size (0.001 to 10 inclusive).

]==]
	return body .. "\n" ..  HS_Shape:UILabel()

end

function HS_ShapeSine:LoadPrefs(prefs)
	self.Points = prefs:GetInt("HS_ShapeSine.Points", 40)
	self.Cycles = prefs:GetInt("HS_ShapeSine.Cycles", 2)
	self.scale = prefs:GetFloat("HS_ShapeSine.scale", 0.4) 	-- sine amplitude wrt the render area
end


function HS_ShapeSine:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeSine.Points", self.Points)
	prefs:SetInt("HS_ShapeSine.Cycles", self.Cycles)
	prefs:SetFloat("HS_ShapeSine.scale", self.scale)
end

function HS_ShapeSine:ResetPrefs()
--	self.Points = 40 
	self.Cycles = 2 
	self.Points = self.Cycles * 10 
	self.scale = 0.3
end


function HS_ShapeSine:Labels ()
	HS_Shape:SetInt1 ("Points Per Cycle", self.Points) 
	HS_Shape:SetInt2 ("Cycles of Sine", self.Cycles)
	HS_Shape:SetFloat ("Amplitude", self.scale)

end

function HS_ShapeSine:BigN (moho, view, value)
	self.Points = LM.Clamp(value, ptsPerCycle, 60)
	return self.Points
end

function HS_ShapeSine:LittleN (moho, view, value)
	self.Cycles = LM.Clamp(value, 1, maxCycles)
	return self.Cycles
end

function HS_ShapeSine:Scale (moho, view, value)
	self.scale = LM.Clamp(value, .001, 10)
	return self.scale
end




--============


function HS_ShapeSine:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)


	local drawnPoints = self.Points * self.Cycles
	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLine (moho, vec, drawingFrame, drawnPoints, HS_circleCorner) -->> improve curvature in MouseMoved
	mesh:Point(startPt):SetCurvature(MOHO.PEAKED, drawingFrame)
	mesh:Point(startPt + drawnPoints - 1):SetCurvature(MOHO.PEAKED, drawingFrame)

	return drawnPoints
end

function HS_ShapeSine:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local maxCurveAngle = math.rad(maxCycles*360)  

	local maxAngle = LM.Clamp (self.Cycles * math.rad(360), 0, maxCurveAngle) 

	if (mouseEvent.ctrlKey) then
		maxAngle = drawnPoints * math.rad(15) -- if control then each point is at 15 degrees (24 per cycle)
	end

	local radius = (vec - startVec):Mag()
	local totalAngle = radius * self.Cycles * math.rad(360)

	totalAngle = LM.Clamp (totalAngle, 0, maxAngle)  -- we draw fewer cycles than demanded until radius exceeeds a threshold


	if (mouseEvent.altKey) then
		totalAngle = -totalAngle
	end

-- ?? better spacing along x?? i.e. by y
	local n = mesh:CountPoints()
	for i = 1, drawnPoints do
		local percent = (i - 1) / (drawnPoints - 1)

		local deltaX = percent * (vec.x-startVec.x)
		local deltaTrig = percent * totalAngle
		
		local pt = mesh:Point(n - i)
		pt.fPos.x = startVec.x + deltaX


		local deltaTrigY = math.sin(deltaTrig) -- range -1:1
		local yScale = self.scale
		local yShift

		if (mouseEvent.shiftKey) then			
			yShift  = 0
		else
			yShift  = percent * (vec.y-startVec.y)
		end
		pt.fPos.y = startVec.y + yShift + (deltaTrigY*yScale)
	end
end






-- **************************************************
-- ************************************************************************************************************************************* PARABOLA
-- **************************************************

HS_ShapeParabola = {}

HS_ShapeParabola.Points = 16 		-- points on a quadratic curve
HS_ShapeParabola.scale = 2		-- relative amplitude - parabola

local startVec = LM.Vector2:new_local()
local startPt


function HS_ShapeParabola:Identify()

	local t =
		{ident="Parabola",
		tip="Parabola: Ctrl: points are Peaked. Shift: Horizontal axis. Alt: Apex is inverted.",
		icon="hs_quad",	
		self=HS_ShapeParabola,					
		gKey="HS_ShapeParabola"}

	return t
end

function HS_ShapeParabola:About()

	local body = [==[

This tool creates a parabolic line with points evenly spaced along its x axis.
Shift key aligns the end points of the line horizontally.
The apex of the curve is, by default, in the same quadrant as the end point, but with the Alt key pressed the apex of the curve is inverted.
Control makes the points peaked otherwise they are rounded.

Two data entry fields are enabled:
the number of points, which is constrained to be between 3 and 72 inclusive
the steepness of the curve (0.001 to 10 inclusive) – a smaller number makes a flatter curve.

]==]

	return body .. "\n" ..  HS_Shape:UILabel()

end

function HS_ShapeParabola:LoadPrefs(prefs)
	self.Points = prefs:GetInt("HS_ShapeParabola.Points", 6)
	self.scale = prefs:GetFloat("HS_ShapeParabola.scale", 2) 	-- parabola amplitude wrt the render area
end


function HS_ShapeParabola:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeParabola.Points", self.Points)
	prefs:SetFloat("HS_ShapeParabola.scale", self.scale)
end


function HS_ShapeParabola:ResetPrefs()
	self.Points = 16
 	self.scale = 2
end


function HS_ShapeParabola:Labels ()
	HS_Shape:SetInt1 ("Points on Parabola", self.Points) 
	HS_Shape:SetFloat ("Steepness", self.scale)

end

function HS_ShapeParabola:BigN (moho, view, value)
	self.Points = LM.Clamp(value, 3, bigNmax)
	return self.Points
end

function HS_ShapeParabola:Scale (moho, view, value)
	self.scale = LM.Clamp(value, .001, 10)
	return self.scale
end


-- ======================================


function HS_ShapeParabola:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)

	local curv

	if (mouseEvent.ctrlKey) then
		curv = HS_roundcorner -- >>>  better curvature....
	else
		curv = MOHO.PEAKED
	end

	startVec:Set(HS_Shape:DrawingPos(moho, mouseEvent))
	startPt = HS_Shape:MakeLine(moho, startVec, drawingFrame, self.Points, curv)


	return self.Points

end

function HS_ShapeParabola:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local offset = LM.Vector2:new_local()
	local vec = LM.Vector2:new_local()

	offset, vec = HS_Shape:DrawingBounds (moho, mouseEvent, false, false)



--
-- **	calculate quadratic coeffs for the end points we have. rebase to be realtive to start; then at "p2" y=ax^2+bx+c so calc b
--

	local relX = (vec.x-offset.x)
	local relY = (vec.y-offset.y)

	local dirX = 1
	local dirY = 1
	local altDir = 1

	if (mouseEvent.altKey) then
		altDir = -1
	else
		altDir = 1
	end

	if (mouseEvent.shiftKey) then			
		relY  = 0
	end

	if relY < 0 then 
		dirY = 1 	-- curve apex is down
	else 
		dirY = -1 	-- curve apex is up
	end
--[[
	if relX < 0 then 
		dirX = 1
	else 
		dirX = -1
	end
]]

	local dir = dirX*dirY*altDir

	local quadB

	if math.abs(relX) < 1e-3 then
		quadB = 0
	else
		quadB = (relY-(relX*relX*dir))/relX
	end


	local pt
	local j = drawnPoints
	local n = startPt + j
	for i = 1, j do
		local percent = (i - 1) / (j - 1)

		local deltaX = percent * relX
		local deltaY = percent * relY


		local xShift = deltaX
		local yShift = self.scale*((deltaX*deltaX*dir) + (deltaX*quadB))


	
		pt = mesh:Point(n - i)

		pt.fPos.y = offset.y + yShift -- + deltaY
		pt.fPos.x = offset.x + xShift
	
--	line is curved unless CTRL

		local Curvature = HS_roundcorner
		if (mouseEvent.ctrlKey)
		then
			Curvature = MOHO.PEAKED
		end
		pt:SetCurvature(Curvature, drawingFrame)
	end
end







-- **************************************************
-- ********************************************************************************************************************* CONTROL POLYGON (SubTool Disptach)
-- **************************************************


HS_ShapeCtrl_Poly = {}

HS_ShapeCtrl_Poly.Sides = 6		-- sides on a control polygon
HS_ShapeCtrl_Poly.Concs = 1		-- how many concentric control polygons

HS_ShapeCtrl_Poly.startVec = LM.Vector2:new_local()



function HS_ShapeCtrl_Poly:Identify()

	local t =
		{ident="Tri/quadMesh",
		tip="Tri/Quad mesh: Shift: height=width. (Alt and Ctrl not used).",
		icon="hs_ctrl_web",						
		self=HS_ShapeCtrl_Poly,
		gKey="HS_ShapeCtrl_Poly"}

	return t

end


function HS_ShapeCtrl_Poly:About()

	local body = [==[

This tool creates a "web". The innermost polygon is subdivided into triangles; each successive outer polygon is made up of quadrilaterals.

Two data entry fields are enabled:
the number of spokes on the “web” which can be between 3 and 48 inclusive;
the number of concentric rings which can be between 1 and 24 inclusive. 

]==]

	return body .. "\n" ..  HS_Shape:UILabel()


end


function HS_ShapeCtrl_Poly:LoadPrefs(prefs)
	self.Sides = prefs:GetInt("HS_ShapeCtrl_Poly.Sides", 6)
	self.Concs = prefs:GetInt("HS_ShapeCtrl_Poly.Concs", 1)
end

function HS_ShapeCtrl_Poly:SavePrefs(prefs)
	prefs:SetInt("HS_ShapeCtrl_Poly.Sides", self.Sides) 
	prefs:SetInt("HS_ShapeCtrl_Poly.Concs", self.Concs)
end


function HS_ShapeCtrl_Poly:ResetPrefs()
	self.Sides = 6
	self.Concs = 1
end


function HS_ShapeCtrl_Poly:Labels ()
	HS_Shape:SetInt1 ("Radials", self.Sides) 
	HS_Shape:SetInt2 ("Concentric Rings", self.Concs)

end


function HS_ShapeCtrl_Poly:BigN (moho, view, value)
	self.Sides = LM.Clamp(value, 3, gridMax)
	return self.Sides
end


function HS_ShapeCtrl_Poly:LittleN (moho, view, value)
	self.Concs = LM.Clamp(value, 1, bigNmed)
	return self.Concs
end


-- ======================================



function HS_ShapeCtrl_Poly:OnMouseDown(moho, mouseEvent, mesh, drawingFrame)
	local j
	if self.Concs == 1 then
		j = self:OnMouseDownI(moho, mouseEvent, mesh, drawingFrame)
	else
		j = self:OnMouseDownM(moho, mouseEvent, mesh, drawingFrame)
	end
	return j
end


function HS_ShapeCtrl_Poly:OnMouseMoved(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	if self.Concs == 1 then
		self:OnMouseMovedI(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	else
		self:OnMouseMovedM(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	end
end



-- **************************************************
-- ********************************************************************************************************************* CONTROL POLYGON (INT Triangles)
-- **************************************************

function HS_ShapeCtrl_Poly:OnMouseDownI(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints() -- how many points before we started??

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

-- we make the first polygon outline points
	local drawPts = self.Sides
	local i


	mesh:AddLonePoint(self.startVec, drawingFrame)
	for i = 0, drawPts-1 do 
		mesh:AppendPoint(self.startVec, drawingFrame)
	end

-- then lose one by welding
	mesh:WeldPoints(n + drawPts, n, drawingFrame)



-- now the lines from the centre to the corners

	for i = 0, drawPts-1 do
		mesh:AddLonePoint(self.startVec, drawingFrame)
		mesh:AppendPoint(self.startVec, drawingFrame)
		mesh:WeldPoints(n + drawPts + i, n + i, drawingFrame) 
	end

-- next weld points in the centre
	for i = drawPts-1, 1, -1 do
		mesh:WeldPoints(n + drawPts + i, n + drawPts, drawingFrame) 
	end


	local Curvature = MOHO.PEAKED


	for i = n, n+drawPts do
		mesh:Point(i):SetCurvature(Curvature, drawingFrame)
	end



	for i = 0, drawPts-1 do
		mesh:SelectNone()
		mesh:Point(i+n).fSelected = true
		if i == drawPts-1 then 
			mesh:Point(n).fSelected = true
		else
			mesh:Point(i+n+1).fSelected = true
		end
		mesh:Point(n+drawPts).fSelected = true

		if (HS_Shape.autoFill) then
			local shapeID = moho:CreateShape(true, false, drawingFrame)
			if (shapeID >= 0) then
				if (not HS_Shape.autoOutline) then
					mesh:Shape(shapeID).fHasOutline = false
				end
			end
		elseif (HS_Shape.autoOutline) then
			moho:CreateShape(false, false, drawingFrame)
		end

	end

	mesh:SelectConnected()
	return drawPts+1
end

function HS_ShapeCtrl_Poly:OnMouseMovedI(moho, mouseEvent, mesh, drawingFrame, drawnPoints)
	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
--	if not (mouseEvent.altKey) then
		--  always draw from center point 
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
--	end




	local n = mesh:CountPoints()			-- 
	local ptID = n - drawnPoints		-- first to move is the first we drew (on the polygon edge)
	local numPoints = drawnPoints - 1	-- but we'll ignore the last drawn centre point
	local dAngle = math.rad(360) / numPoints 	-- angle between apex in regular polygon
	local angle = (math.rad(180) - dAngle)/2	-- starting position (in effect the "point" on an odd-numbered sided polygon)

	if (y2>y1) then 
		angle = angle + math.rad(180)		-- flip if mouse higher on the page...
	end



	local scale = LM.Vector2:new_local()
	local offset = LM.Vector2:new_local()


	scale.x = math.abs(x2 - x1) / math.sqrt(2)
	scale.y = math.abs(y2 - y1) / math.sqrt(2)		
	offset.x = (x1 + x2) / 2
	offset.y = (y1 + y2) / 2

	for i = 1, numPoints do
		
		local pt = mesh:Point(ptID)
		ptID = ptID + 1
		pt.fPos.x = (scale.x * math.cos(angle)) + offset.x
		pt.fPos.y = (scale.y * math.sin(angle)) + offset.y
		angle = angle + dAngle

	end
end



-- **************************************************
-- ********************************************************************************************************************************** CONCENTRIC CONTROL POLYGONS
-- **************************************************

function HS_ShapeCtrl_Poly:OnMouseDownM(moho, mouseEvent, mesh, drawingFrame)
	local n = mesh:CountPoints() -- how many points before we started??

	self.startVec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(self.startVec)
	end

-- we add start points
	local drawPts = self.Sides

	local i, j

-- first the concentrics
	for j = 0, self.Concs-1 do

		mesh:SelectNone()

		mesh:AddLonePoint(self.startVec, drawingFrame)
		for i = 0, self.Sides-1 do 
			mesh:AppendPoint(self.startVec, drawingFrame)
		end

		local startPt = n + j*drawPts

		mesh:WeldPoints(startPt + drawPts, startPt, drawingFrame)

		for i = startPt, startPt+drawPts-1 do
			mesh:Point(i):SetCurvature(Curvature, drawingFrame)
		end

	end


-- now the lines from the centre to the corners


	mesh:AddLonePoint(self.startVec, drawingFrame)
	drawPts = self.Sides * self.Concs -- how many points we've drawn

	for i = 0, self.Sides-1 do
		mesh:AddLonePoint(self.startVec, drawingFrame)
		for j = 0, self.Concs-1 do
			mesh:AppendPoint(self.startVec, drawingFrame)
		end
		for j = self.Concs-1, 0, -1 do
			mesh:WeldPoints(n + drawPts + i + j, n + i + j*self.Sides, drawingFrame) 
		end

	end


-- next weld points in the centre
	for i = self.Sides-1, 1, -1 do
		mesh:WeldPoints(n + drawPts + i, n + drawPts, drawingFrame) 
	end

-- do the inner ring triangle shapes: (high numbered points)

	local s = n + ((self.Concs - 1) * self.Sides)
	for i = 0, self.Sides-1 do
		mesh:SelectNone()
		mesh:Point(i+s).fSelected = true
		if i == self.Sides-1 then 
			mesh:Point(s).fSelected = true
		else
			mesh:Point(i+s+1).fSelected = true
		end
		mesh:Point(n+drawPts).fSelected = true -- centre

		if (HS_Shape.autoFill) then
			local shapeID = moho:CreateShape(true, false, drawingFrame)
			if (shapeID >= 0) then
				if (not HS_Shape.autoOutline) then
					mesh:Shape(shapeID).fHasOutline = false
				end
			end
		elseif (HS_Shape.autoOutline) then
			moho:CreateShape(false, false, drawingFrame)
		end

	end

-- save the outer quads for later...

	local p = {}
	local loop = {}
	for i = 1, self.Sides do
		loop [i] = i-1
	end
	loop [#loop+1] = 0

	for i = 0, self.Concs-2 do
		for j = 1, self.Sides do
			p[1] = n + loop[j] + i * self.Sides
			p[2] = n + loop[j+1] + i * self.Sides
			p[3] = n + loop[j] + (i+1) * self.Sides
			p[4] = n + loop[j+1] + (i+1) * self.Sides
			table.insert (shapePoints, {p[1], p[2], p[3], p[4]})
		end
	end


	for i  = 0, self.Sides * self.Concs do
		mesh:Point(i+n).fSelected = true
		mesh:Point(i+n):SetCurvature(MOHO.PEAKED, drawingFrame)
	end

	return 1 + self.Sides * self.Concs -- the +1 is the middle one.

end


function HS_ShapeCtrl_Poly:OnMouseMovedM(moho, mouseEvent, mesh, drawingFrame, drawnPoints)

	local vec = LM.Vector2:new_local()
	vec:Set(mouseEvent.drawingVec)
	if (moho.gridOn) then
		moho:SnapToGrid(vec)
	end

	local i, j

	local x1 = self.startVec.x
	local x2 = vec.x
	local y1 = self.startVec.y
	local y2 = vec.y

	if (mouseEvent.shiftKey) then
		-- constrain width = height
		if (x2 > x1) then
			if (y2 > y1) then
				y2 = y1 + (x2 - x1)
			else
				y2 = y1 - (x2 - x1)
			end
		else
			if (y2 > y1) then
				y2 = y1 - (x2 - x1)
			else
				y2 = y1 + (x2 - x1)
			end
		end
	end
--	if (mouseEvent.altKey) then
		-- always draw from center point
		x1 = x1 - (x2 - x1)
		y1 = y1 - (y2 - y1)
--	end

	local n = mesh:CountPoints()
	local ptID = n - drawnPoints			-- the first one we drew


	local dAngle = math.rad(360) / self.Sides 		-- angle between apex in regular polygon
	local angle = (math.rad(180) - dAngle)/2		-- starting position (in effect the "point" on an odd-numbered sided polygon)

	if (y2>y1) then 
		angle = angle + math.rad(180)		-- flip if mouse higher on the page...
	end

	local baseAngle = angle

	local scale = LM.Vector2:new_local()
	local offset = LM.Vector2:new_local()


	scale.x = math.abs(x2 - x1) / (math.sqrt(2) * self.Concs)
	scale.y = math.abs(y2 - y1) / (math.sqrt(2) * self.Concs)
	offset.x = (x1 + x2) / 2
	offset.y = (y1 + y2) / 2

		


	for j = self.Concs, 1, -1 do

		for i = 1, self.Sides do
		
			local pt = mesh:Point(ptID)
			ptID = ptID + 1
			pt.fPos.x = (scale.x * j * math.cos(angle)) + offset.x
			pt.fPos.y = (scale.y * j * math.sin(angle)) + offset.y
			angle = angle + dAngle
		end
		angle = baseAngle
	end

end


-- **************************************************
-- **************************************************
-- **************************************************
-- **************************************************
-- **************************************************
-- **************************************************
-- **************************************************
-- *************************************************************************************************************************************** UI
-- **************************************************



-- **************************************************
-- Sub tool control / info table
-- **************************************************



HS_Shape.subToolDispatch = {

	HS_ShapeSector:Identify(),
	HS_ShapeOval:Identify(),
	HS_ShapeRectangle:Identify(),
	HS_ShapeBone:Identify(),
	HS_ShapeAnnulus:Identify(),
	HS_ShapeConcentrics:Identify(),
	HS_ShapePolygon:Identify(),
	HS_ShapeCtrl_Poly:Identify(),
	HS_ShapeRtTriangle:Identify(),
	HS_ShapeStar:Identify(),
	HS_ShapeCross:Identify(),
	HS_ShapeLine:Identify(),
	HS_ShapeSine:Identify(),
	HS_ShapeParabola:Identify(),
	HS_ShapeGrid:Identify(),
	HS_ShapeTri_Grid:Identify(),


	{ident="Void",
		LabelUI=HS_Shape.Labels_Void,		self=HS_Shape,
		tip=nil,
		icon=nil,				
		gKey="HS_Shape",
		about=HS_Shape.About_Void,
		load=nil,	save=nil,	reset=nil,	onSel=nil,	onExit=nil,
		mdn=nil,	mmov=nil,	alt=nil,
		bigN=nil,	lilN=nil,	rat=nil,	but=nil,	box=nil}
}





-- **************************************************
-- Tool options - data values
-- **************************************************

HS_Shape.AUTOFILL = MOHO.MSG_BASE +10
HS_Shape.AUTOOUTLINE = MOHO.MSG_BASE + 11

HS_Shape.FRAME0 = MOHO.MSG_BASE + 12 		-- Create shape on frame 0 if true / layerframe if not

HS_Shape.EXTEND = MOHO.MSG_BASE + 20		-- a button to add extra functionality to the sub-tool buttons
local extendMid = HS_Shape.EXTEND + 1
HS_Shape.ALT_EXTEND = extendMid + 1		-- Alt-button -- must be 2 bigger than extend


HS_Shape.BIGN = MOHO.MSG_BASE + 30 		-- points in a curve
HS_Shape.LITTLEN = MOHO.MSG_BASE + 40 		-- arms on a star etc

HS_Shape.SCALE = MOHO.MSG_BASE + 50		-- scaling / roundness factor

HS_Shape.BUTTON = MOHO.MSG_BASE + 60		-- the option button
HS_Shape.ALT_BUTTON = HS_Shape.BUTTON + 1	-- alt + the option button

HS_Shape.CHECK = MOHO.MSG_BASE + 70		-- the checkbox



local extenderFncs = {
	{tag = "O", fnc = "onSel", 	tip = "Click or Alt-click for extended options."},
	{tag = "?", fnc = "about", 	tip = "Tool Information",	svc = "AboutTool"},
--	{tag = "A", fnc = "alt", 	tip = "Alternative option"},
	{tag = "R", fnc = "reset", 	tip = "Reset tool options"}
}

local ctExtOpts = #extenderFncs
local extenderChanged = true


--
-- ** sub tool identifiers -- the message codes are assigned when the buttons are generated ---
--
HS_Shape.First		= MOHO.MSG_BASE + 102
HS_Shape.Last = HS_Shape.First + #HS_Shape.subToolDispatch - 2  -- set a default value

--
-- **	message codes for alt-button (reserved for "edit") are asigned when generating the buttons
--
HS_Shape.ALT_First		= MOHO.MSG_BASE + 202
HS_Shape.ALT_Last = HS_Shape.ALT_First + #HS_Shape.subToolDispatch - 2


local longSpace =  "                        "             -- a sequence of spaces



-- **************************************************
-- Create and respond to tool's UI
-- **************************************************


function HS_Shape:DoLayout(moho, layout)

--
--	find the path to the subtool icons

	local path = moho:UserAppDir()
	local pc = string.find(path,"\\")
	local mac = string.find(path,"/")
	local delim = ""
	if mac ~= nil then
		delim =  "/"
	end

	if pc ~= nil then
		delim = "\\"
	end


	local i

	path = "ScriptResources" .. delim .. "hs_shape_icons" .. delim -- ?? offer alternative path for plug-ins?

--
--	how many subtools did we get from "initialise"?
--
	HS_Shape.Last = HS_Shape.First + #HS_Shape.toolSeq - 1
	HS_Shape.ALT_Last = HS_Shape.ALT_First + #HS_Shape.toolSeq - 1


	self.shapeButtons = {}

	self.autoFillCheck = LM.GUI.CheckBox("Auto-fill", self.AUTOFILL)
	layout:AddChild(self.autoFillCheck)

	self.autoOutlineCheck = LM.GUI.CheckBox("Auto-stroke", self.AUTOOUTLINE)
	layout:AddChild(self.autoOutlineCheck)

	layout:AddPadding(10)

--[[
	if LM.GUI.ShortButton then -- this widget added in 13.5.2 but doesn't support alt message
		self.extenderButt = LM.GUI.ShortButton(extenderFncs[1].tag, self.EXTEND)
	else
		self.extenderButt = LM.GUI.Button(extenderFncs[1].tag, self.EXTEND)
	end
]]


	self.extenderButt = LM.GUI.Button(extenderFncs[1].tag, self.EXTEND)
	self.extenderButt:SetAlternateMessage(self.ALT_EXTEND)
	layout:AddChild(self.extenderButt)

	layout:AddPadding(10)

	for i = 1, #HS_Shape.toolSeq do
		if self.subToolDispatch[HS_Shape.toolSeq[i]].icon then 
			table.insert(self.shapeButtons, LM.GUI.ImageButton(path .. self.subToolDispatch[HS_Shape.toolSeq[i]].icon,
				self.subToolDispatch[HS_Shape.toolSeq[i]].tip, true, i-1+HS_Shape.First, true))
		else
			table.insert(self.shapeButtons, LM.GUI.ImageButton(path .. "void",
				self.subToolDispatch[HS_Shape.toolSeq[i]].tip, true, i-1+HS_Shape.First, true))
		end
	end


	layout:PushH(LM.GUI.ALIGN_CENTER, 0)
	for i, but in ipairs(self.shapeButtons) do
		layout:AddChild(but)
		but:SetAlternateMessage(i+HS_Shape.ALT_First-1)
	end
	layout:Pop()


	self.optionButton = LM.GUI.Button ("                ", self.BUTTON)
	layout:AddChild(self.optionButton)
	self.optionButton:SetAlternateMessage(self.ALT_BUTTON)


	self.pointsText = LM.GUI.DynamicText("", 150)
	layout:AddChild(self.pointsText, LM.GUI.ALIGN_RIGHT)

	layout:AddPadding(-20)
	self.bigNIn = LM.GUI.TextControl (0, "000", self.BIGN, LM.GUI.FIELD_UINT)
	layout:AddChild(self.bigNIn, LM.GUI.ALIGN_RIGHT)


	self.cyclesText = LM.GUI.DynamicText("", 150)
	layout:AddChild(self.cyclesText, LM.GUI.ALIGN_RIGHT)

	layout:AddPadding(-20)
	self.littleNIn = LM.GUI.TextControl (0, "00", self.LITTLEN, LM.GUI.FIELD_UINT)
	layout:AddChild(self.littleNIn, LM.GUI.ALIGN_RIGHT)


	self.scaleText = LM.GUI.DynamicText("", 150)
	layout:AddChild(self.scaleText, LM.GUI.ALIGN_RIGHT)

	layout:AddPadding(-20)
	self.scaleIn = LM.GUI.TextControl (0, "0.0000", self.SCALE, LM.GUI.FIELD_UFLOAT)
	self.scaleIn:SetWheelInc(0.01)
	layout:AddChild(self.scaleIn, LM.GUI.ALIGN_RIGHT)


	layout:AddPadding(5)
	self.checkIn = LM.GUI.CheckBox("", self.CHECK)
	layout:AddChild(self.checkIn, LM.GUI.ALIGN_RIGHT)

	self.checkText = LM.GUI.DynamicText("", 150)
	layout:AddChild(self.checkText, LM.GUI.ALIGN_RIGHT)

	layout:AddPadding(5)
	self.drawOn0Check = LM.GUI.CheckBox("Create Shape on Frame 0?", self.FRAME0)
	layout:AddChild(self.drawOn0Check, LM.GUI.ALIGN_RIGHT)

end


function HS_Shape:UpdateWidgets(moho)
	self.autoFillCheck:SetValue(self.autoFill)
	self.autoOutlineCheck:SetValue(self.autoOutline)
	self.drawOn0Check:SetValue(self.drawOn0)

	for i, but in ipairs(self.shapeButtons) do
		but:SetValue(self.buttonNum == HS_Shape.First + i - 1)
	end

	self.DoInputLabels()
end



function HS_Shape:HandleMessage(moho, view, msg)

	local i, toolId, oldTool, temp

	toolId = 0 		-- for the data fields
	if (self.buttonNum >= self.First) and (self.buttonNum <= self.Last) then
		toolId = HS_Shape.toolSeq[(self.buttonNum - self.First + 1)]
--		HS_Test_harness:tracePrint (thisUUT, "last subtool was", self.buttonNum - self.First + 1, "gives tool:", toolId)

	else
		toolId = #self.subToolDispatch -- the "void" entry at the end of the dispatch table
--		HS_Test_harness:tracePrint (thisUUT, (self.buttonNum - self.First + 1), "is not a valid shape type - default void", toolId)
	end
	oldTool = toolId -- the tool id on entry


	if (msg == self.AUTOFILL) then
		self.autoFill = self.autoFillCheck:Value()

	elseif (msg == self.AUTOOUTLINE) then
		self.autoOutline = self.autoOutlineCheck:Value()

	elseif (msg == self.FRAME0) then
		self.drawOn0 = self.drawOn0Check:Value()

	elseif (msg == self.EXTEND) or (msg == self.ALT_EXTEND) then
		local inc = extendMid - msg
		self.extenderOpt = math.fmod(self.extenderOpt + ctExtOpts + inc, ctExtOpts) 
--		HS_Test_harness:tracePrint (thisUUT, "Extender butt press. Now opt", self.extenderOpt)
		extenderChanged = true
		HS_Shape:UpdateWidgets(moho) -- update anyway

	elseif (msg >= self.First and msg <= self.Last) then
		self.buttonNum = msg
		toolId = HS_Shape.toolSeq[(self.buttonNum - self.First + 1)]
		self.shapeType = HS_Shape.subToolDispatch[toolId].ident
--		HS_Test_harness:tracePrint (thisUUT, "new subtool", self.buttonNum - self.First + 1, "gives new tool:", toolId, self.shapeType)
		for i, but in ipairs(self.shapeButtons) do
			but:SetValue(self.buttonNum == HS_Shape.First + i - 1)
		end


		local fnc = extenderFncs[self.extenderOpt+1].fnc -- the function in the addressed tool
		local rtx
--		HS_Test_harness:tracePrint (thisUUT, "seeking extender fnc", fnc, toolId, self.shapeType)
		if fnc then
			if HS_Shape.subToolDispatch[toolId][fnc] then
--				HS_Test_harness:tracePrint (thisUUT, "old tool was", oldTool, "Extfnc", extenderFncs[self.extenderOpt+1].fnc, toolId, self.shapeType)
-- 					invoke the extender with "true" for first run
				rtx = self.subToolDispatch[toolId][fnc](self.subToolDispatch[toolId].self, moho, (oldTool ~= toolId), extenderChanged)
				extenderChanged = false
				local svc = extenderFncs[self.extenderOpt+1].svc
				if svc then -- the service here to handle any return
					self[svc] (HS_Shape, self.shapeType, rtx)
				end
			end
		end



		HS_Shape:UpdateWidgets(moho) -- update anyway

	elseif (msg >= self.ALT_First and msg <= self.ALT_Last) then
		self.buttonNum = msg - self.ALT_First + self.First

		toolId = HS_Shape.toolSeq[(msg - self.ALT_First + 1)]
		self.shapeType = self.subToolDispatch[toolId].ident
--		HS_Test_harness:tracePrint (thisUUT, "Alt Function request for", toolId, self.shapeType)

--[==[  alt key will be for edit // alt function is extender
		if HS_Shape.subToolDispatch[toolId].alt then
--			HS_Test_harness:diagnosePrint (thisUUT, "Dispatch to alt Function", toolId, self.shapeType)
			self.subToolDispatch[toolId].alt(self.subToolDispatch[toolId].self)
		end

]==]
		HS_Shape:UpdateWidgets(moho) -- update anyway -- the tool was selected!

	elseif (msg == self.BIGN) then
		bigN = self.bigNIn:IntValue()

		if self.subToolDispatch[toolId].bigN then
			temp = self.subToolDispatch[toolId].bigN(self.subToolDispatch[toolId].self, moho, view, bigN)
			bigN = temp or bigN
		end
		self.bigNIn:SetValue(bigN)
		self.DoInputLabels() 

	elseif (msg == self.LITTLEN) then
		littleN = self.littleNIn:IntValue()

		if self.subToolDispatch[toolId].lilN then
			temp = self.subToolDispatch[toolId].lilN(self.subToolDispatch[toolId].self, moho, view, littleN)
			littleN = temp or littleN
		end
		self.littleNIn:SetValue(littleN)
		self.DoInputLabels() 

	elseif (msg == self.SCALE) then
		rat = self.scaleIn:FloatValue()

		if self.subToolDispatch[toolId].rat then
			temp = self.subToolDispatch[toolId].rat(self.subToolDispatch[toolId].self, moho, view, rat)
			rat = temp or rat
		end
		self.scaleIn:SetValue(rat)
		self.DoInputLabels() 

		if (self.drawnPoints > 0)  -- if we have something drawn that is of the same shape type, we'll assume it's being edited
			and (moho:CountSelectedPoints() == self.drawnPoints) 
			and (self.startVec.x ~= nil)
			and (self.lastItem == self.shapeType)
			and (self.pointCt == moho:DrawingMesh():CountPoints())
			and (self.lastUUID == moho.drawingLayer:UUID())

		then
			self.editMode = true
			self.subToolDispatch[toolId].mmov(self.subToolDispatch[toolId].self, moho, FakeMouse, moho:Mesh(), drawingFrame, self.drawnPoints)
			if self.drawOn0 then
				moho:AddPointKeyframe(drawingFrame)
				moho:NewKeyframe(CHANNEL_POINT)
				moho:NewKeyframe(CHANNEL_CURVE)
			else
				moho:AddPointKeyframe(moho.drawingFrame)
				moho:NewKeyframe(CHANNEL_POINT)
				moho:NewKeyframe(CHANNEL_CURVE)
			end

			moho:UpdateUI()

		end



	elseif (msg == self.BUTTON) then
		if self.subToolDispatch[toolId].but then
			self.subToolDispatch[toolId].but(self.subToolDispatch[toolId].self, moho, view, 1)
		end
		self.DoInputLabels() 

	elseif (msg == self.ALT_BUTTON) then
		if self.subToolDispatch[toolId].but then
			self.subToolDispatch[toolId].but(self.subToolDispatch[toolId].self, moho, view, -1)
		end
		self.DoInputLabels() 


	elseif (msg == self.CHECK) then
		box = HS_Shape.checkIn:Value()
		if self.subToolDispatch[toolId].box then
			temp = self.subToolDispatch[toolId].box(self.subToolDispatch[toolId].self, moho, view, HS_Shape.checkIn:Value())
			if temp ~= nil then
				box = temp
			end
		end
		self.checkIn:SetValue(box)
		self.DoInputLabels()
	end

end



function HS_Shape:DoInputLabels()
--
--	generate the labels for the input fields
--

	HS_Shape:Labels_Void() 			-- clear out all

	local i = 0 
	if (HS_Shape.buttonNum >= HS_Shape.First) and (HS_Shape.buttonNum <= HS_Shape.Last) then
		i = HS_Shape.buttonNum - HS_Shape.First + 1
		if HS_Shape.subToolDispatch[HS_Shape.toolSeq[i]].LabelUI then
			HS_Shape.subToolDispatch[HS_Shape.toolSeq[i]].LabelUI(HS_Shape.subToolDispatch[HS_Shape.toolSeq[i]].self)  
		end
	end


	local len = string.len (longSpace)
	bigNText =  string.sub(longSpace .. bigNText, -len) 	-- right align the text by taking the righthand substring
	littleNText =  string.sub(longSpace .. littleNText, -len)
	floatText =  string.sub(longSpace .. floatText, -len)


	HS_Shape.pointsText:SetValue(bigNText)
	HS_Shape.cyclesText:SetValue(littleNText)
	HS_Shape.scaleText:SetValue(floatText)
	HS_Shape.optionButton:SetLabel(buttText, false)
	HS_Shape.checkText:SetValue(checkText)
	HS_Shape.extenderButt:SetLabel(extenderFncs[HS_Shape.extenderOpt+1].tag, false)
	HS_Shape.extenderButt:SetToolTip(extenderFncs[HS_Shape.extenderOpt+1].tip)


	HS_Shape.littleNIn:Redraw()
	HS_Shape.bigNIn:Redraw()
	HS_Shape.scaleIn:Redraw()
	HS_Shape.optionButton:Redraw()
	HS_Shape.checkText:Redraw()
	HS_Shape.extenderButt:Redraw()

end

Icon
HS Shape
Listed

Script type: Tool

Uploaded: Dec 10 2022, 06:57

Last modified: Jan 03 2023, 09:24

Draws various shapes
HS Shape version 10.01 – release date 3 January 2023
=====================================================

10.01: Bug fix. Error message generated when drawing but no moho shape is created. [The required path is, however, correctly created.]




What’s new in this version (since version 9.67)?
================================================

Totally re-architected. This version is the foundation for the next revision which is intended to allow user-written sub-tools to be accommodated.
Added the “additional functions” for help and re-set subtools (see below).
Enabled the “ratio / scale” (floating point input) so that it can be changed (and thus change the drawn shape) immediately after the shape is drawn.
A keystroke handler is introduced. Ctrl-key directs the keystroke to the factory SelectPoints keystroke handler (this only services “delete selected points” at present); key (or alt-key or shift-key) is handled within this tool – again only “delete selected points” is currently supported. This difference is that the factory tool will delete all selected points; this tool will only delete points from the last shape created by this tool if it is “editable”.


The main set of drawing tools is essentially unchanged from V967 (Moho 13.5.3) from 07 April 2022.
There is a new (version 1.04) sub-tool to draw card suit shapes. This is provided as a separate file in the Utility folder.

======================

This set of tools is based on the SHAPE tools that come with Moho.
This version will work only in Moho 12.0 or later.

Auto-fill and auto-stroke work as expected.
In general, the Shift and Alt keys work as the standard Shape tool. Exceptions are noted below.


Inputs
======
There is an “additional functions” button (to the left of the sub-tool buttons). This changes the behaviour of the sub-tool selection buttons.
Clicking (or alt-clicking) the button cycles forwards (or backwards) through the options:
>>>  O – normal operation of the sub-tool
>>>  R – reset the sub-tool’s options (and leave the sub-tool active)
>>>  ? – print the sub-tool “help/about” message (and leave the sub-tool active)


There are 3 numerical entry fields in the menu bar: two integers and one floating point. The data entered is context sensitive depending on the tool selected. Values are preserved across invocations of each sub-tool.
Values set outside limits are reset without any error message.
Note that where the choice of input values will result in a large number of shapes being created (e.g., a square grid with 48*48 cells) the elapsed time taken to create all these shapes will be high. (Where the potential for large numbers of individual shapes exists, shapes are created on mouse up and not on mouse down.)
There is a check-box to force all drawing on frame 0 (as is the approach in Moho 13.5.3 and later). If this is not checked, the shape is created or modified on the active frame.
There is also an options button that is context dependent to allow further options depending on the active sub-tool.

Editing a shape
================
Having drawn a shape using a sub-tool it is possible to change the detail of the shape by, optionally, changing a numeric input field (other than those that directly or indirectly define the number of points in the shape) and then holding down the control key and simultaneously clicking and dragging the mouse. Once the edit operation is in progress the control key may be released if its shape modifying action is not required. Note that the shift and alt keys remain set as they were when the shape was created.
NEW>>> Also, where the sub-tool uses the floating point input to control scale / ratio / roundness (etc) this value can be modified immediately after the shape has been drawn to change the drawn shape.
NEW>>> If a shape is created on frame 0 (e.g. with the “create on frame 0” checkbox checked) then modified on a different frame (with the “create on frame 0” checkbox unchecked) the shape will be animated.


SUB-TOOLS SUMMARY
=====================
Circle Sector Oval Round Cornered Rectangle
Bone Influence Shape Annulus Concentric Polygons
Polygon Tri/Quad Mesh Right-angled triangle
Star Swiss Cross
Multi-point line Sine Parabola
Rectangular Grid Triangular Grid Card Suit Shapes


TOOL DESCRIPTIONS
==================

CIRCLE SECTOR
This tool creates a sector of a circle. Dragging the mouse creates a sector of up (but excluding) a full circle. Ctrl constrains to a semi-circle. Shift constrains the sector to quadrant or semi-circle; Alt moves the initial radius from vertical to horizontal.
One data entry field is enabled:  this is the number of points and is constrained to be between 4 and 72 inclusive.

OVAL
Generates an ellipse. Ctrl forces the point curvatures to “circular” (otherwise the curvature is set to give the correct ellipsoidal shape – from a straight line where one dimension is very small to near circular). Shift distributes the points equidistant from the centre. Ctrl-Shift together create a circle.
One data entry field is enabled:  this is the number of points and is constrained to be between 4 and 72 inclusive. If the number of points chosen is not divisible by 4 the input is reduced to the nearest lower multiple of 4.

ROUND CORNERED RECTANGLE
One data entry field is enabled: the radius of the semicircle that forms the corner as a ratio of the length of the smallest side. It is constrained to be between 0.01 and 0.4.
There is a checkbox to make the corners smoother (additional points are created).

BONE INFLUENCE SHAPE
This is similar to a round cornered rectangle but has semicircles on the short edge.
The style of creation can be selected by a button which, when pressed, cycles through the options:
>> use a bounding box created by the mouse click-drag to define the shape. The shape will be aligned horizontally or vertically and the dimensions will be set from the bounding box
>> use the mouse drag to define the vector along which the shape is created. In this case the “radius of the end semicircle” needs to be manually entered.

ANNULUS
Creates a single shape composed of two concentric ovals. Shift creates concentric circles.
One data entry field is enabled:  the ratio of the width of the centre to outer radius; it is constrained to be between 0.001 and 0.9950 inclusive. (A large number creates a large hole in the centre)

CONCENTRIC POLYGONS
Creates concentric polygons: each is a separate shape with the largest at the bottom of the stack. Control makes the points rounded (default is peaked).
Two data entry fields are enabled:  the number of sides on each polygon which can be between 3 and 24 inclusive and the number of concentric shapes which can be between 1 and 24 inclusive.

POLYGON
One data entry field is enabled: the number of sides which can be between 3 and 24 inclusive.

TRI/QUAD WARP MESH
Two data entry fields are enabled: the number of spokes on the “web” which can be between This can be between 3 and 48 inclusive; and the number of concentric rings which can be between 1 and 24 inclusive. The innermost polygon is subdivided into triangles; each successive outer polygon is made up of quadrilaterals.

RIGHT ANGLED TRIANGLE
This generates a right angled triangle. No data entry fields are enabled.

STAR
Creates a Star with the chosen number of arms. Ctrl makes the points rounded (otherwise they are sharp).
Two data entry fields are enabled:  these are the number of arms on the star and the ratio of the width of the centre to total radius of the star. The star can have between 3 and 72 arms inclusive; the centre ratio can be between 0.001 and 0.9950 inclusive.

SWISS CROSS
Creates a Swiss Cross. Ctrl makes the points rounded (otherwise they are right angles).
One data entry field is enabled: width of the arm of the cross as a ratio of the shortest side. This can be between 0.01 and 0.99 inclusive.

MULTI-POINT LINE
There are two main modes of operation:
>> Generate one straight line with points evenly spaced along it.
>> Generate several lines.
Shift key makes the line horizontal or, with the Alt key also pressed, Vertical. Control makes the points rounded otherwise they are peaked.
Two data entry fields are always enabled:
>> the number of points on each line – this constrained to be between 2 and 72 inclusive.
>> how many lines to draw
If more than one line is to be drawn, there is a button that cycles through the options of which there are two:
>> create multiple parallel lines – in which case the spacing may be set manually or, by checking the “base on stroke width” box the spacing is set automatically to bring the lines close together so that a standard brush will fill the gap. This automatic setting may be further manually adjusted.
>> create radial lines – in which case one data entry field is enabled – this defines the distance from the origin to the innermost points as a ratio of the line length and this is constrained to be between 0 and .990. Note that if this option is set to be very small, there will be multiple points created at the same (or very close) location and it may prove difficult to select the origin point for a specific line.

SINE
Sine wave generates a curve drawn with the number of points to create the selected number of half-cycles and with a peak-to-peak amplitude as set by the scale factor. Dragging the “end” point extends the curve from zero to the selected number of cycles until the whole curve has been generated and then it stretches the curve. The Control key constrains the curve so that fewer cycles are drawn if there are insufficient points to keep the curve properly shaped (12 points for a half-cycle). The Alt key inverts the curve. The Shift key keeps the line of zero amplitude horizontal.
Three data entry fields are enabled: the number of points in the drawn curve (6 to 144 inclusive), the maximum number of cycles 1 to 12) and the peak-to-peak size of the curve (amplitude) relative to the default frame size (0.001 to 10 inclusive).

PARABOLA
This generates a parabolic line with points evenly spaced along its x axis. Shift key aligns the end points of the line horizontally. The apex of the curve is, by default, in the same quadrant as the end point, but with the Alt key pressed the apex of the curve is inverted. Control makes the points peaked otherwise they are rounded.
Two data entry fields are enabled:  the number of points, which is constrained to be between 3 and 72 inclusive and curvature (0.001 to 10 inclusive) – a smaller number makes a flatter curve.

GRID
Creates a grid of the chosen number of rows and columns.
Two data entry fields are enabled:  these are the number of rows and columns. Both are constrained to be between 1 and 48 inclusive.
There is an options button to select the type of fill. Clicking on the button cycles through the options. There are two options:
>> create just one shape for the grid lines and one for the background fill (if the relevant autostroke / autofill options are set)
>> create separate shapes for each cell

GRID OF TRIANGLES
The options button is used to select the style of the grid of triangles. Clicking on the button cycles through the options. There are three grid variants:
>> a grid based on hexagons - two data entry fields are enabled:  these are the number of rows and the number of triangles per row. The number of rows is constrained to be between 1 and 48 inclusive; the number of triangles per row is constrained to be between 2 and 48 inclusive. This produces a “ragged edge” grid of identical triangles.
>> a grid based on squares with alternate diagonals
>> a grid based on squares with the same diagonals
In these last two options two data entry fields are enabled:  these are the number of rows and the number of squares per row. Both are constrained to be between 1 and 48 inclusive.

CARD SUIT SHAPES
This draws a club, diamond, heart or spade.
The options button is used to select the desired shape. Clicking on the button cycles through the options.
There is one checkbox to use a fill/stroke that is usual for the suit (red fill for diamond/heart, black fill for club/spade).

For club there is a "rotundness" value which adjusts the overall shape:
-- 0 is most spread out, 10 is the most compact
For diamond, there is a "curvature" value which adjusts the straightness of the edges:
-- 0 is straight, 10 is maximum curvature.
For heart and spade there is a "roundness" which adjusts the overall shape:
-- 0 is "pointy", 10 is roundest



Installation
============

Copy (optionally rename) folder hs_shapeV10-0 to a convenient location and run the “scripts / install script” script. When prompted select the relevant folder.



Notes and known issues
======================

The “install script” process has not been tested on a mac.
There is no Localisation support.
Shapes if created with inconsistent options, exhibit “interesting” behaviours. Oval, if drawn with few points that are forced to curvature (ctrl-key), creates a bone-like shape; Star, if created with a ratio of outer:inner radius of (nearly) 1 creates a polygon; Sine if created with few points creates some crazy loops; polygons with more than about 20 sides are virtually indistinguishable from ellipses / circles.
There are no known issues; but please let me know about errors, inconsistencies, unfriendly UI, ...

Requests for changes are always welcome. Just ask. If it’s easy it’ll get done quite quickly. If it’s hard ... well, it’ll go on the list.


Next Version of this tool
=========================

Planned changes for the next release(s) are:
>>>  (over more than one release) Migrate sub-tools to be in separate .lua files
>>>  provide more support for user-written sub-tools
>>>  Additional features if/as requested
>>>  Bug fixes if/as reported or resolved
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: 237