feat: Get all of Rostamian's old pages working #44

Merged
glen merged 4 commits from rostamian_old into main 2023-10-18 01:07:58 +00:00
2 changed files with 155 additions and 63 deletions
Showing only changes of commit 85ad82d9e2 - Show all commits

View File

@ -101,7 +101,7 @@ export interface AppletObject {
getXcoord(objName: string): number; getXcoord(objName: string): number;
getYcoord(objName: string): number; getYcoord(objName: string): number;
getZcoord(objName: string): number; getZcoord(objName: string): number;
setCoords(objName: string, x: number, y: number, z: number): void; setCoords(objName: string, x: number, y: number, z?: number): void;
getValue(objName: string): number; getValue(objName: string): number;
getVersion(): string; getVersion(): string;
getScreenshotBase64(callback: (data: string) => void, scale?: number): void; getScreenshotBase64(callback: (data: string) => void, scale?: number): void;

View File

@ -49,6 +49,28 @@ adapParams: AdapParams :=
? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []} ? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []}
: JSON.parse(adapptScript.dataset.params ?? '') as AdapParams : JSON.parse(adapptScript.dataset.params ?? '') as AdapParams
type ConstructionData
id: string
bg: RGB
is3d: boolean
width: number
height: number
elements: JoyceElements
pivot: string
// Global data setup for pivoting (ugh, but necessary because the api
// is not passed to the callback, so we have to look up the slider in
// a global list):
type PivotData
api: AppletObject
pivot: string
lastAngle: number
rotatable: string[]
fixed: Record<string, boolean>
pivotData: Record<string, PivotData> := {}
function postApplets(jApplets: AppletDescription[], codebase = '') function postApplets(jApplets: AppletDescription[], codebase = '')
for each jApp of jApplets for each jApp of jApplets
params := { params := {
@ -58,10 +80,8 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
jApp.width, jApp.width,
jApp.height, jApp.height,
appletOnLoad: (api: AppletObject) => appletOnLoad: (api: AppletObject) =>
elements: JoyceElements := {} is3d := contains3d jApp.params
backgroundRGB := [255, 255, 255] as RGB if is3d
config3d := contains3d jApp.params
if config3d
api.enable3D true api.enable3D true
api.setPerspective 'T' api.setPerspective 'T'
// Get rid of the xy-plane indicator // Get rid of the xy-plane indicator
@ -72,10 +92,24 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
api.setPerspective 'G' api.setPerspective 'G'
if adapParams.config?.algebra if adapParams.config?.algebra
api.setPerspective '+A' api.setPerspective '+A'
elements := {}
pivot .= ''
if 'pivot' in jApp.params
if is3d
console.warn('Geometry Applet "pivot" only supported for '
+ '2D constrcutions. Ignoring.')
else
pivot = geoname(jApp.id, elements, 'point') + 'Pivot'
pivotData[pivot] = {
api, jApp.params.pivot,
lastAngle: 0, rotatable: [], fixed: {}}
cdata: ConstructionData := {
bg: ([255, 255, 255] as RGB),
is3d, jApp.id, jApp.width, jApp.height, elements, pivot}
for name, value in jApp.params for name, value in jApp.params
dispatchJcommand dispatchJcommand
api, name, value, elements, backgroundRGB, config3d api, name, value, cdata
if config3d if is3d
depth .= jApp.width depth .= jApp.width
if jApp.height > depth then depth = jApp.height if jApp.height > depth then depth = jApp.height
api.setCoordSystem api.setCoordSystem
@ -135,7 +169,7 @@ type ClassHandler = (
method: string, method: string,
args: JoyceArguments, args: JoyceArguments,
index: number, index: number,
is3d: boolean) => Commander cdata: ConstructionData) => Commander
type RGB = [number, number, number] type RGB = [number, number, number]
type XYZ = RGB type XYZ = RGB
@ -146,28 +180,26 @@ function dispatchJcommand(
api: AppletObject, api: AppletObject,
name: string, name: string,
value: string, value: string,
elements: JoyceElements cdata: ConstructionData): void
backgroundRGB: RGB,
is3d: boolean): void
switch name switch name
'background' 'background'
newback := joyce2rgb value, backgroundRGB cdata.bg = joyce2rgb value, cdata.bg
if adapParams.config?.commands if adapParams.config?.commands
console.log 'Setting background to', value, 'interpreted as', console.log 'Setting background to', value, 'interpreted as',
newback cdata.bg
for i of [0..2] api.setGraphicsOptions 1, bgColor: colorsea(cdata.bg).hex()
backgroundRGB[i] = newback[i]
api.setGraphicsOptions 1, bgColor: colorsea(backgroundRGB).hex()
'title' 'title'
if adapParams.config?.commands if adapParams.config?.commands
console.log 'Setting title to', value console.log 'Setting title to', value
corner := is3d ? 'Corner(-1,1)' : 'Corner(1,1)' corner := cdata.is3d ? 'Corner(-1,1)' : 'Corner(1,1)'
api.evalCommand `TitlePoint = ${corner} api.evalCommand `TitlePoint = ${corner}
Text("${value}", TitlePoint + (2,5))` Text("${value}", TitlePoint + (2,5))`
'pivot'
return // already handled in postApplets
/e\[\d+\]/ /e\[\d+\]/
num := parseInt(name.slice(2)) num := parseInt(name.slice(2))
{commands, callbacks, parts} := {commands, callbacks, parts} :=
jToG value, elements, num, backgroundRGB, is3d jToG value, num, cdata
if commands.length if commands.length
lastTried .= 0 lastTried .= 0
if commands.filter((&)).every (cmd) => if commands.filter((&)).every (cmd) =>
@ -180,16 +212,14 @@ function dispatchJcommand(
(part of translation of '${value}') (part of translation of '${value}')
failed.` failed.`
else console.warn `Could not parse command '${value}'` else console.warn `Could not parse command '${value}'`
else console.warn `Unkown param ${name} = ${value}` else console.warn `Unknown param ${name} = ${value}`
// Parses a Joyce element-creating command, extending the elements // Parses a Joyce element-creating command, extending the elements
// by side effect: // by side effect:
function jToG( function jToG(
jCom: string, jCom: string,
elements: JoyceElements,
index: number, index: number,
backgroundRGB: RGB cdata: ConstructionData): Commander
is3d: boolean): Commander
[jname, klass, method, data, ...colors] := jCom.split ';' [jname, klass, method, data, ...colors] := jCom.split ';'
if adapParams.config?.commands if adapParams.config?.commands
console.log 'Defining', jname, 'as a', klass, 'constructed by', console.log 'Defining', jname, 'as a', klass, 'constructed by',
@ -199,7 +229,9 @@ function jToG(
console.warn `Unknown entity class ${klass}` console.warn `Unknown entity class ${klass}`
return cmdr return cmdr
assertJoyceClass klass // shouldn't need to do that :-/ assertJoyceClass klass // shouldn't need to do that :-/
name := if /^\p{L}\w*$/u.test jname then jname else geoname jname, elements isPivot := !!cdata.pivot and jname is pivotData[cdata.pivot].pivot
name := geoname jname, cdata.elements, klass
if isPivot then pivotData[cdata.pivot].pivot = name
args: JoyceArguments := {} args: JoyceArguments := {}
usesCaptions := [] usesCaptions := []
for each jdep of data.split ',' for each jdep of data.split ','
@ -207,17 +239,17 @@ function jToG(
if scalar is scalar // not NaN if scalar is scalar // not NaN
(args.scalar ?= []).push scalar (args.scalar ?= []).push scalar
continue continue
unless jdep in elements unless jdep in cdata.elements
console.warn `Reference to unknown geometric entity ${jdep} in $jCom}` console.warn `Reference to unknown geometric entity ${jdep} in $jCom}`
return cmdr return cmdr
usesCaptions.push jdep usesCaptions.push jdep
{klass: depKlass, otherName: depGeo, ends} := elements[jdep] {klass: depKlass, otherName: depGeo, ends} := cdata.elements[jdep]
(args[depKlass] ?= []).push depGeo (args[depKlass] ?= []).push depGeo
if depKlass is 'point' if depKlass is 'point'
(args.subpoints ?= []).push depGeo (args.subpoints ?= []).push depGeo
else if depKlass is 'line' else if depKlass is 'line'
(args.subpoints ?= []).push ...ends ?? [] (args.subpoints ?= []).push ...ends ?? []
cmdr = classHandler[klass] name, method, args, index, is3d cmdr = classHandler[klass] name, method, args, index, cdata
unless name is jname then cmdr.callbacks.push (api: AppletObject) => unless name is jname then cmdr.callbacks.push (api: AppletObject) =>
api.setCaption name, jname api.setCaption name, jname
api.setLabelStyle name, 3 // style CAPTION = 3 api.setLabelStyle name, 3 // style CAPTION = 3
@ -226,6 +258,25 @@ function jToG(
for each aux of cmdr.auxiliaries for each aux of cmdr.auxiliaries
api.setAuxiliary aux, true api.setAuxiliary aux, true
api.setVisible aux,false api.setVisible aux,false
// set up the pivot if there is one
if isPivot
unless klass is 'point'
console.warn(`Can only pivot around a point, not the ${klass}`
+ `named ${jname}. Ignoring.`)
cdata.pivot = ''
cmdr.commands.push(`${cdata.pivot} = `
+ `Slider(0°,360°,1°,1,${cdata.width/3},true,true,false,false)`)
cmdr.callbacks.push (api: AppletObject) =>
api.setCaption cdata.pivot, 'Rotate Display'
api.setLabelStyle cdata.pivot, 3
api.setCoords(cdata.pivot, 2*cdata.width/3, cdata.height-10)
// Not sure how to let TypeScript deal with putting a new function
// on the global window object, so punting at least for now:
// @ts-ignore
window.pivotListener = pivotListener
api.registerObjectUpdateListener(cdata.pivot, 'pivotListener')
// Create callback to assign colors // Create callback to assign colors
if colors.length is 4 and colors.every (color) => invisible color if colors.length is 4 and colors.every (color) => invisible color
cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false
@ -243,11 +294,11 @@ function jToG(
console.log 'Fading out interior of', face if trace console.log 'Fading out interior of', face if trace
// hide the interior by making it transparent // hide the interior by making it transparent
api.setFilling face, 0 api.setFilling face, 0
else if face not in elements else if face not in cdata.elements
console.log 'Hiding face', face if trace console.log 'Hiding face', face if trace
api.setVisible face, false api.setVisible face, false
else else
faceRGB := joyce2rgb(colors[3] or 'brighter', backgroundRGB) faceRGB := joyce2rgb(colors[3] or 'brighter', cdata.bg)
deep := ['circle', 'polygon', 'sector'] deep := ['circle', 'polygon', 'sector']
filling := deep.includes(klass) ? 0.7 : 0.2 filling := deep.includes(klass) ? 0.7 : 0.2
for each face of parts[2] for each face of parts[2]
@ -259,28 +310,30 @@ function jToG(
// Lines default to black: // Lines default to black:
if invisible colors[2] if invisible colors[2]
for each line of parts[1] for each line of parts[1]
if line is name or line not in elements if line is name or line not in cdata.elements
console.log 'Hiding line', line if trace console.log 'Hiding line', line if trace
api.setVisible line, false api.setVisible line, false
else else
lineRGB := joyce2rgb(colors[2] or 'black', backgroundRGB) lineRGB := joyce2rgb(colors[2] or 'black', cdata.bg)
for each line of parts[1] for each line of parts[1]
console.log 'Coloring line', line, 'to', colors[2] if trace console.log 'Coloring line', line, 'to', colors[2] if trace
api.setVisible line, true api.setVisible line, true
api.setColor line, ...lineRGB api.setColor line, ...lineRGB
// Now color the points: // Now color the points:
console.log 'Considering point colors for', name if trace if trace
console.log
'Considering point colors for', name, 'of dimension', dimension
if invisible colors[1] if invisible colors[1]
// Hide all the dim-0 elements that are not distinct independent // Hide all the dim-0 elements that are not distinct independent
// items: // items:
for each point of parts[0] for each point of parts[0]
if point is name or point not in elements if point is name or point not in cdata.elements
console.log 'Hiding point', point if trace console.log 'Hiding point', point if trace
api.setVisible point, false api.setVisible point, false
else if dimension is 0 or colors[1] // Need to color the points else if dimension is 0 or colors[1] // Need to color the points
ptRGB := colors[1] ? joyce2rgb colors[1], backgroundRGB ptRGB := colors[1] ? joyce2rgb colors[1], cdata.bg
: pointDefaultRGB name, method : pointDefaultRGB name, method, isPivot
for each point of parts[0] for each point of parts[0]
console.log 'Coloring point', point, 'to', colors[1] if trace console.log 'Coloring point', point, 'to', colors[1] if trace
api.setVisible point, true api.setVisible point, true
@ -290,12 +343,12 @@ function jToG(
if invisible colors[0] if invisible colors[0]
console.log 'Hiding label', name if trace console.log 'Hiding label', name if trace
api.setLabelVisible name, false api.setLabelVisible name, false
else if colors[dimension] and colors[dimension] is not colors[0] else if colors[dimension+1] and colors[dimension+1] is not colors[0]
// Have to make a text to provide the caption, since GeoGebra // Have to make a text to provide the caption, since GeoGebra
// doesn't allow caption different color from entity. // doesn't allow caption different color from entity.
textName := 'GeoText' + index textName := 'GeoText' + index
locationExpr := switch klass locationExpr := switch klass
when 'point' then `1.02${name})` when 'point' then `1.02${name}`
when 'line' when 'line'
(`Midpoint(${name}) + ` (`Midpoint(${name}) + `
+ `Rotate(Direction(${name})*Length(${name})*0.02, pi/2)`) + `Rotate(Direction(${name})*Length(${name})*0.02, pi/2)`)
@ -315,8 +368,12 @@ function jToG(
unless parts[0].includes ex1 then ex1 = `Centroid(${ex1})` unless parts[0].includes ex1 then ex1 = `Centroid(${ex1})`
unless parts[0].includes ex2 then ex2 = `Centroid(${ex2})` unless parts[0].includes ex2 then ex2 = `Centroid(${ex2})`
`(4*${ex1}+${ex2})/5` `(4*${ex1}+${ex2})/5`
api.evalCommand `${textName} = Text("${jname}", ${locationExpr})` textCmd := `${textName} = Text("${jname}", ${locationExpr})`
api.setColor textName, ...joyce2rgb colors[0], backgroundRGB textCol := joyce2rgb colors[0], cdata.bg
if trace
console.log `Making text '${textCmd}' colored`, textCol
api.evalCommand textCmd
api.setColor textName, ...textCol
// and hide the underlying GeoGebra label // and hide the underlying GeoGebra label
api.setLabelVisible name, false api.setLabelVisible name, false
else if colors[0] else if colors[0]
@ -331,19 +388,35 @@ function jToG(
console.log 'Setting label vis of', name, 'to', show if trace console.log 'Setting label vis of', name, 'to', show if trace
api.setLabelVisible name, show api.setLabelVisible name, show
// window[hideListener] = (arg) => cdata.elements[jname] = {otherName: name, usesCaptions, klass, cmdr.ends}
// api.setVisible name, false cdata.elements[name] = {otherName: jname, usesCaptions, klass, cmdr.ends}
// api.registerObjectUpdateListener name, hideListener
if cmdr.ends // line or sector
elements[jname] =
{otherName: name, usesCaptions, klass, cmdr.ends}
elements[name] =
{otherName: jname, usesCaptions, klass, cmdr.ends}
else // any other geometry
elements[jname] = {otherName: name, usesCaptions, klass}
elements[name] = {otherName: jname, usesCaptions, klass}
cmdr cmdr
function pivotListener(slider: string)
pd := pivotData[slider]
newval := pd.api.getValue slider
if newval is pd.lastAngle then return
rotation := newval - pd.lastAngle
pd.lastAngle = newval
pX := pd.api.getXcoord pd.pivot
pY := pd.api.getYcoord pd.pivot
relX: Record<string, number> := {}
relY: Record<string, number> := {}
for each anchor of pd.rotatable
relX[anchor] = pd.api.getXcoord(anchor) - pX
relY[anchor] = pd.api.getYcoord(anchor) - pY
console.log 'Unfixing', anchor, relX[anchor], relY[anchor]
pd.api.setFixed anchor, false
ct := Math.cos rotation
st := Math.sin rotation
for each anchor of pd.rotatable
rX := relX[anchor]*ct - relY[anchor]*st
rY := relY[anchor]*ct + relX[anchor]*st
pd.api.setCoords(anchor, rX + pX, rY + pY)
if pd.fixed[anchor]
console.log 'Re-fixing', anchor, rX, rY
pd.api.setFixed anchor, true
function invisible(cname: string): boolean function invisible(cname: string): boolean
if adapParams.config?.showall then return false if adapParams.config?.showall then return false
cname is '0' or cname is 'none' cname is '0' or cname is 'none'
@ -397,8 +470,8 @@ function joyce2rgb(cname: string, backgroundRGB?: RGB): RGB
console.warn 'Could not parse color:', cname console.warn 'Could not parse color:', cname
[128, 128, 128] [128, 128, 128]
function pointDefaultRGB(name: string, method: string): RGB function pointDefaultRGB(name: string, method: string, isPivot: boolean): RGB
// Need to short-circuit with green for pivot point, once that is implemented if isPivot then return joyce2rgb 'green'
switch method switch method
'free' 'free'
joyce2rgb 'red' joyce2rgb 'red'
@ -406,7 +479,14 @@ function pointDefaultRGB(name: string, method: string): RGB
joyce2rgb 'orange' joyce2rgb 'orange'
else joyce2rgb 'black' else joyce2rgb 'black'
function geoname(jname: JoyceName, elements: JoyceElements): GeoName function geoname(
jname: JoyceName, elements: JoyceElements, klass: JoyceClass): GeoName
unless jname.substring(0,3) is 'Geo' // those might clash
// Names with word characters starting with a capital are always good:
if /^[A-Z]['\w]*$/.test jname then return jname
// If it's not a point, can start with any letter:
if klass !== 'point' and /^\p{L}['\w]*$/u.test jname then return jname
// GeoGebra won't deal with this name, so hash it:
numCode .= 0n numCode .= 0n
numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname
return .= 'Geo' + numCode.toString(36); return .= 'Geo' + numCode.toString(36);
@ -426,11 +506,12 @@ function cutoffExtend(
// All of the detailed semantics of each available command lies in this // All of the detailed semantics of each available command lies in this
// function. // function.
classHandler: Record<JoyceClass, ClassHandler> := classHandler: Record<JoyceClass, ClassHandler> :=
point: (name, method, args, index, is3d): Commander => point: (name, method, args, index, cdata): Commander =>
return := freshCommander() return := freshCommander()
{commands, callbacks, parts, auxiliaries} := return.value {commands, callbacks, parts, auxiliaries} := return.value
zeroVector := is3d ? 'Vector((0,0,0))' : 'Vector((0,0))' zeroVector := cdata.is3d ? 'Vector((0,0,0))' : 'Vector((0,0))'
aux := name + 'aUx' aux := name + 'aUx'
pivotable := cdata.pivot and name !== pivotData[cdata.pivot].pivot
parts[0].push name parts[0].push name
switch method switch method
/angle(?:Bisector|Divider)/ /angle(?:Bisector|Divider)/
@ -445,7 +526,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push `${destination} = Segment(${start}, ${end})` commands.push `${destination} = Segment(${start}, ${end})`
else destination = args.line[0] else destination = args.line[0]
n := method is 'angleBisector' ? 2 : args.scalar?[0] n := method is 'angleBisector' ? 2 : args.scalar?[0]
inPlane := is3d ? `, Plane(${start}, ${center}, ${end})` : '' inPlane := cdata.is3d ? `, Plane(${start}, ${center}, ${end})` : ''
commands.push commands.push
`${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})` `${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})`
`${aux}3 = If(${aux}2 > pi, ${aux}2 - 2*pi, ${aux}2)` `${aux}3 = If(${aux}2 > pi, ${aux}2 - 2*pi, ${aux}2)`
@ -454,7 +535,12 @@ classHandler: Record<JoyceClass, ClassHandler> :=
auxiliaries.push ...[2..4].map (i) => `${aux}${i}` auxiliaries.push ...[2..4].map (i) => `${aux}${i}`
'circleSlider' 'circleSlider'
unless args.circle then return unless args.circle then return
commands.push `${name} = Point(${args.circle[0]})` circ := args.circle[0]
commands.push `${name} = Point(${circ})`
if (pivotable
and cdata.elements[circ].ends?[0] // center
is pivotData[cdata.pivot].pivot)
pivotData[cdata.pivot].rotatable.push name
if args.scalar and args.scalar.length if args.scalar and args.scalar.length
callbacks.push (api: AppletObject) => callbacks.push (api: AppletObject) =>
api.setCoords name, ...args.scalar as XYZ api.setCoords name, ...args.scalar as XYZ
@ -466,12 +552,15 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push `${name} = Translate(${source}, ${displacement})` commands.push `${name} = Translate(${source}, ${displacement})`
'first' 'first'
unless args.subpoints then return unless args.subpoints then return
commands.push `${name} = Point(${args.subpoints[0]},${zeroVector})` commands.push `${name} = ${args.subpoints[0]}`
/fixed|free/ /fixed|free/
coords := args.scalar coords := args.scalar
unless coords then return unless coords then return
commands.push `${name} = Point({${coords.join ','}})` scoord := coords.join ','
if pivotable then pivotData[cdata.pivot].rotatable.push name
commands.push `${name} = (${scoord})`
if method is 'fixed' if method is 'fixed'
if pivotable then pivotData[cdata.pivot].fixed[name] = true
callbacks.push (api: AppletObject) => api.setFixed name, true callbacks.push (api: AppletObject) => api.setFixed name, true
'foot' 'foot'
pt := args.subpoints pt := args.subpoints
@ -492,7 +581,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
'last' 'last'
unless args.subpoints then return unless args.subpoints then return
commands.push commands.push
`${name} = Point(${args.subpoints.at(-1)}, ${zeroVector})` `${name} = ${args.subpoints.at(-1)}`
'lineSegmentSlider' 'lineSegmentSlider'
segment .= args.line?[0] segment .= args.line?[0]
unless segment unless segment
@ -524,7 +613,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
unless pt.length is 5 then return unless pt.length is 5 then return
sourcePlane .= '' sourcePlane .= ''
destPlane .= '' destPlane .= ''
if is3d if cdata.is3d
unless args.plane then return unless args.plane then return
destPlane = `, ${args.plane[0]}` destPlane = `, ${args.plane[0]}`
if args.plane.length > 1 if args.plane.length > 1
@ -544,7 +633,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push commands.push
`${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})` `${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})`
line: (name, method, args, index, is3d) => line: (name, method, args, index, cdata) =>
return := freshCommander() return := freshCommander()
return.value.ends = ['', ''] return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value {commands, callbacks, parts, auxiliaries, ends} := return.value
@ -561,7 +650,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
unless cr return unless cr return
commands.push ...[1..2].map (n) => commands.push ...[1..2].map (n) =>
`${aux}${n} = Intersect(${cr[0]}, ${cr[1]}, ${n})` `${aux}${n} = Intersect(${cr[0]}, ${cr[1]}, ${n})`
inPlane := is3d ? `Plane(${cr[0]})` : '' inPlane := cdata.is3d ? `Plane(${cr[0]})` : ''
ctr := cr.map (c) => `Center(${c})` ctr := cr.map (c) => `Center(${c})`
condition := (`Angle(${aux}1,${ctr[0]},${ctr[1]}${inPlane})` condition := (`Angle(${aux}1,${ctr[0]},${ctr[1]}${inPlane})`
+ `< Angle(${aux}2,${ctr[0]},${ctr[1]}${inPlane})`) + `< Angle(${aux}2,${ctr[0]},${ctr[1]}${inPlane})`)
@ -630,7 +719,8 @@ classHandler: Record<JoyceClass, ClassHandler> :=
circle: (name, method, args) => circle: (name, method, args) =>
return := freshCommander() return := freshCommander()
{commands, callbacks, parts, auxiliaries} := return.value return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value
parts[2].push name parts[2].push name
parts[1].push name parts[1].push name
switch method switch method
@ -641,10 +731,12 @@ classHandler: Record<JoyceClass, ClassHandler> :=
switch pt.length switch pt.length
when 2 when 2
[center, point] := pt [center, point] := pt
ends[0] = center
commands.push commands.push
`${name} = Circle(${center}, ${point}${inPlane})` `${name} = Circle(${center}, ${point}${inPlane})`
when 3 when 3
center := pt[0] center := pt[0]
ends[0] = center
radius := `Distance(${pt[1]}, ${pt[2]})` radius := `Distance(${pt[1]}, ${pt[2]})`
commands.push commands.push
`${name} = Circle(${center}, ${radius}${inPlane})` `${name} = Circle(${center}, ${radius}${inPlane})`