Making my own block that has a body and is not a top block

I am trying to make a block that groups other blocks. In my case, these groups form a group of shapes on a 3D surface.

The repeat block shape is perfect for this. And with some difficulty, I managed to convert the forever block from the pxt-sample into the shape of a repeat block.

subtractshapes

//% blockId=subtract_shapes block="subtract shapes" 
//% topblock=false
//% handlerStatement=true
export function subtractShapes(body: RefAction): void {
    board().addBlock("difference( <CHILDREN> )"); // add a JSCad statement to the interpreter.

    thread.runInBackground(body)

}

//% block="cylinder radius $radius|height $height"
//% inlineInputMode=inline
//% radius.defl=10
//% height.defl=10
export function cylinder(radius: number, height: number) {
    board().addStatement(`cylinder({r1: ${radius}, r2: ${radius}, h: ${height}})`);
}

My question is I have no idea what to do with the body. I have run thread.runInBackground but I don’t think that is correct, because I need the statements to be added deterministically. This code works fine but when I add in a repeat loop it all goes to crazytown. What should I do to make sure that the code for cylinder and cube gets called?

You should be able to “call” body:

export function subtractShapes(body: RefAction): void {
   board().addBlock("difference( <CHILDREN> )"); // add a JSCad statement to the interpreter.
   body()
}

Neat! Will give that a go.

So close, but no typescript love.

Thoughts?
bodyhasnocallsig

you need to use runFiberAsync(). Like this:

//% blockId=subtract_shapes block="subtract shapes" 
//% topblock=false
//% handlerStatement=true
//% promise
export function subtractShapesAsync(body: RefAction): Promise<void> {
    board().addBlock("difference( <CHILDREN> )"); // add a JSCad statement to the interpreter.

    return pxsim.runtime.runFiberAsync(body)

}

Note that I also added the promise annotation and changed the name to end in Async. Makecode will emit a declaration that looks like this to the user:

export function subtractShapes(body: () => void): void

So they won’t see anything async related :slight_smile:

1 Like

Also, I should mention, generally it’s best to do APIs that take in non-primitives inside of libs/ instead of in the simulator. Then you don’t have to deal with internals directly like this. For example, you could have an API in the simulator like this:

//%
export function addBlock(block: string) {
    board().addBlock(block)
}

and then define the public function in the libs folder like this:

//% blockId=subtract_shapes block="subtract shapes" 
//% topblock=false
//% handlerStatement=true
export function subtractShapes(body: () => void): void {
    internalNamespace.addBlock("difference( <CHILDREN> )")
    body()
}

If you start the name of the internal function with an underscore (e.g. _addBlock) then it won’t show up to the user in completions.

1 Like

Can I ask which file you would pop it into in /libs… does it matter? This bit seems to be missing from the documentation… I started from the pxt-sample project… so I am assuming it’s ns.ts cause that’s got Turtle stuff in it?

where

You can think of all the TypeScript code in libs as “user code”, the files in there don’t have any special importance; any TS file that is listed in pxt.json will be added to the user’s project as a dependency. If you look at the pxt.json inside of the blocksprj project, you’ll see it has a dependency on the “core” package that contains ns.ts.

In general, writing as much code as possible in libs is helpful because it makes the code more portable between editors. It also lets you use the MakeCode debugger with your code.

BTW, the name ns.ts is for “namespace” because that’s the file that defines the namespaces and their colors/names in the UI.

1 Like

Legend! Thanks this helps a lot. I will give it all a go.
JFo

Hello, I’m trying to use the inner event blocks as a callback for a subscription from a websocket event. I’m storing the websocket connection as part of the simulator Board. The event block is defined in /libs/core/ns.ts below
/**

  • Subscribe to a topic
  • @param topic
  • @param response
  • @param body
    */
    //% blockId=subscribeMessage block=“on $message as type: $type from topic: $topic”
    //% draggableParameters
    export function subscribeBlock(topic: string, type: MessageTypes, handler: (message: messages.RosMessage) => void){
    subscribe(topic, type, handler);
    }

It calls a function defined in /sim/api.ts:
/**

  • Subscribe to a topic
  • @param topic
  • @param response
  • @param body
    */
    //%
    export function subscribe(topic: string, type: MessageTypes, handler: (message: messages.RosMessage) => void){
    const b = board()
    function callback(msg: object){
    const pxtMsg = messages.createMessage(type)
    pxtMsg.data = msg
    handler(pxtMsg)
    }
    console.log(callback.toString())
    b.ros.subscribe(topic, type, callback)
    }

However, when a message is received and the callback runs, I get the error.
pxtsim.js:1 Uncaught Error: sim error: undefined
at oops (pxtsim.js:1)
at inline__P201 (eval at Runtime (pxtsim.js:1), :116:12)
at callback (sim.js:339)
at Topic. (sim.js:294)
at Topic.EventEmitter.emit (roslib.js:762)
at Ros.Topic._messageCallback (roslib.js:3140)
at Ros.EventEmitter.emit (roslib.js:762)
at handleMessage (roslib.js:2969)
at handlePng (roslib.js:2987)
at WebSocket.onMessage (roslib.js:3054)

Would any of you have any idea how to use the function handler as a callback?

So… I am having a bit of trouble following your callstack… but perhaps there are more expert folks who can help with that.

The way I solved this in BuildBee was to use blocks like this
image

Which so happen to resolve in the JavaScript pane in a way you might want. The cube code is actually run inside of a callback.

operators.move(10, Axis.X, function () {
    shapes.cube(10, 10, 10)
})

The downside to this is that while my technique works for javascript it seems to not work in python, which is why python is currently turned off in my pxt.

You’re welcome to look at the source code here:

What happens is that for moveAsync (which is what it’s called in operators.ts) the cube code is inside of the body (represented by the RefAction)

export function moveAsync(mm: number, direction: Axis, body: RefAction): Promise<void> 

I so happen to make that body code run straight away, by calling runFiberAsync - but I imagine you could hold onto that body variable and call it later.

function _makeBlock(someData: string, body: RefAction) {
    return new Promise<void>((resolve, reject) => {

        board().doStuffToSimulator(someData)

        // execute the child blocks
        pxsim.runtime.runFiberAsync(body).then((result) => {
            resolve(result)
        }).catch((error) => {
            reject(error)
        })
    })
}

Someone may have a waaaaay better idea but that’s just going off what I know. Good luck!

hello, thank you for your response. I’ve actually been going off your repo a ton and its been insanely helpful. I feel like the official docs are missing quite a bit of information. Anyways I managed to figure it out using the RefAction.

However, I was hoping that I could have local parameters defined in the callback function within the body of the block like so,

function myBlock(param1: string, handler: (localParam: number) => void){
  //insert block code here
}

image

The makecode dev team response above said that it’s possible if I define the block function in ./libs/core, but I’m having difficulty trying to link up the block in ./libs/core with the ./sim functions. :sob:
After a mental breakdown trying to figure that out, I just gave up. Would you have any idea how to pull it off?

Yeah I have to admit I had significant trouble trying to do it “the right way” - it is on my list to go back and shuffle the code into the right spot. But I have to admit I am forcing myself to learn the bare minimum of typescript as I go along.

Perhaps if we work it out together we’ll both sort it? What did you try?

@khayliang - I just discovered a huge clue yesterday - the typescript in my project was waaaay out of date. I had forked my project from pxt-sample, which must be ageing.

I was adding a field editor to my project and it just kept throwing TS1005 errors pulling in type definitions. I upgraded my package.json from:
“typescript”: “2.6.1”
to
“typescript”: “3.7.5”

And the problem went away.