Skip to main content

Custom Block Shape API

This API allows you to change the shape of a reporter block to whatever you wish. It can be used for clarifying custom types and making blocks for certain data-types!

You should have a decent knowledge of ScratchBlocks and SVG Paths to use this API.

Note that the name ScratchBlocks is mentioned, but ScratchBlocks is simply a custom modification of Blockly so that name also appears in some functions.

Making the shape

note

This tutorial uses SvgPathEditor since it's the path editor we used to make the new shapes seen in PenguinMod.

Base path

The easiest way to make your shapes is to start with an existing shape's path. You can easily get one of these by pasting one of these into the DevTools Console in your browser:

  • ScratchBlocks.BlockSvg.INPUT_SHAPE_HEXAGONAL
  • ScratchBlocks.BlockSvg.INPUT_SHAPE_LEAF
  • ScratchBlocks.BlockSvg.INPUT_SHAPE_PLUS
  • ScratchBlocks.BlockSvg.INPUT_SHAPE_ROUND

To copy the path, you just need to press enter (to get the output of the variable) and then right-click the output string and click "Copy string contents."

If you want to use the Square shape, it's actually recommended to use the path below since the one in BlockSvg is reused for non-reporters, and is thus built differently.

M 16 0 h 16 h 12 a 4 4 0 0 1 4 4 l 0 24 a 4 4 0 0 1 -4 4 h -12 h -16 h -12 a 4 4 0 0 1 -4 -4 l 0 -24 a 4 4 0 0 1 4 -4 z

See the notes in each section if you don't want to use a base path.

Importing the Base path

In SvgPathEditor you now want to paste the base path in the top left, and click "Convert to absolute" in the menu.

SvgPathEditor UI

This is the easiest way to design the path since you won't be moving other parts of the path as much if they were kept relative.

Some of the base paths have some padding before the left-side and right-side of the shape, and others don't.

To find the right-side and left-side of the shape, basically just find where 2 dots align on the Y-axis for either side and edit the parts past the dots.

SvgPathEditor UI
note

If you don't want to use a base path, simply hover your mouse into the main canvas and add a "Move to" command.

Then, set this location to the start of the right-side of the shape. Keep it at 0 on the Y-axis, and positive in the X-axis. Leave a lot of room on the left of your movement for the left-side of your shape, but make sure it looks right since this path also determines how the empty argument hole looks for this shape.

SvgPathEditor UI

Making the right-side of your shape

Note that the right-side of the shape is actually drawn before the left-side. Messing up how this side is placed will likely mess up how your left-side is placed.

Edit your base path as much as you want, but make sure when you are done that you didn't change the height of the shape or move the Y position of either of the 2 dots you found earlier.

The shape should always have a height of 32 units (or at least really close to it.) The lowest points in the shape should have their Y position at 32.

Also make sure you don't make any extra holes deeper than the right-side of the shape starts. If you want to make deep holes then you need to move the entire right-side either by adding horizontal lines after and before the 2 aligned dots respectively, or moving elements after the 2 dots.

SvgPathEditor UI
note

If you don't want to use a base path, make sure you end the right-side of the shape at 32 units on the Y-axis and on the same X position you started the right-side on.

SvgPathEditor UI

You then need to make a horizontal line left, to move the pointer to the start of the left-side.

Make sure you don't let the shape go past the center Y-axis line or the empty argument hole will clip into text on the block that uses it. You can use the "Convert to relative" button to move the entire left-side from this point if it does end up going past the Y-axis line later.

SvgPathEditor UI

Making the left-side of your shape

This is basically the same process as the right-side, just note that the left-side starts at the bottom left.

For the left-side, the highest points in the shape should have their Y position at 0.

note

If you don't want to use a base path, make sure you add a "Close Path" command when you are done.

Final setup

Click the "Convert to relative" option and move things around if they are no longer in-bounds (the height of the shape isn't 32 units or the shape extends beyond one of the axis lines.)

SvgPathEditor UI

Defining the shape

In order to make custom block shapes, you first need to grab ScratchBlocks itself:

/* get ScratchBlocks if availiable... */
if (Scratch.gui) {
Scratch.gui.getBlockly().then(ScratchBlocks => {
// here you have access to ScratchBlocks safely
});
}

Make sure you don't try to grab ScratchBlocks without checking for the GUI, since packaged projects will not have ScratchBlocks present.

You then need to use the ScratchBlocks.BlockSvg.registerCustomShape function inside the then callback to register your shape:

Scratch.gui.getBlockly().then(ScratchBlocks => {
ScratchBlocks.BlockSvg.registerCustomShape("[EXTENSION ID]-[SHAPE NAME]", shape);
});

The first argument to registerCustomShape is the ID of your shape. This must be set to your extension's ID, then your shape's name, with a dash between.

An example would be myReallyCoolExtension-triangleCut. The extension ID is myReallyCoolExtension and the shape is a triangleCut.

Next, you need to provide an object specifying your shape's details.

Entering your shape's details

The shape parameter in registerCustomShape is an object, containing the following properties:

emptyInputPath

{
emptyInputPath: "",
}

Set emptyInputPath to the entire path you just made. This can be found in the top-left of SvgPathEditor, same place where you pasted the base path at first.

Example:

{
emptyInputPath: "m 16 0 h 16 h 33 a 4 4 0 0 1 4 4 l -27 12 l 27 12 a 4 4 0 0 1 -4 4 h -33 h -16 h -12 a 4 4 0 0 1 -4 -4 l 0 -24 a 4 4 0 0 1 4 -4 z",
}

emptyInputWidth (optional)

note

This property is optional.

This determines how wide your emptyInputPath is. The default value for this property (if not provided) is 12 * ScratchBlocks.BlockSvg.GRID_UNIT.

For this example shape, we will use 19 * ScratchBlocks.BlockSvg.GRID_UNIT.

leftPath

{
emptyInputPath: "(path)",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
return [];
},
}

leftPath is a function that constructs the left-side of your SVG path. To fill this out, do the following:

Copy this template:

To start the leftPath, use this template:

leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
return [];
},

block.height is the height of the block. We divide this by 2 to match with the value used in rightPath because it makes the math easier. Using block.height in leftPath also allows this block shape to work properly when it is given a branch input. The s variable should now scale to 1 unit in the path you just made.

Find the first command after the left-side of the shape starts.

The left-side is (some-what confusingly) at the end of your shape's path, and it's a little complicated to find the commands here.

Hover over each command in the COMMANDS list in the editor, until you see the first item added after the left-side starts. The dot for this item should turn red, aswell as the line behind it.

You then want to find the actual command in the output path. It's easiest to look for the letter and then see if the number matches, then see if the previous commands match like that aswell.

In this diagram, the first item after the left-side starts has it's command circled yellow. The left-side starting dot is also colored green.

SvgPathEditor UI

Ignore this diagram using absolute commands, make sure the path is relative now.

Once you find the command in the output path, copy the command and everything after it into the return []; section like so:

leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
return [`h -12 a 4 4 0 0 1 -4 -4 l 0 -24 a 4 4 0 0 1 4 -4 z`];
},

Then, remove the ending z command and it's space beforehand.

leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
return [`h -12 a 4 4 0 0 1 -4 -4 l 0 -24 a 4 4 0 0 1 4 -4`];
},

Make the shape scale properly

Replace all of the numbers in the commands you just copied, and make them multiplied by s.

Note that some numbers in certain commands (like arcs) are actually flags to do something else (like the arc's sweep flag) so you shouldn't multiply these.

You can tell where your commands have a flag by either looking at the commands in SvgPathEditor and seeing where the checkboxes line up with the actual command, or by ticking the checkboxes in your commands in SvgPathEditor and seeing where values change between 0 and 1.

leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
return [`h ${-12 * s} a ${4 * s} ${4 * s} ${0 * s} 0 1 ${-4 * s} ${-4 * s} l ${0 * s} ${-24 * s} a ${4 * s} ${4 * s} ${0 * s} 0 1 ${4 * s} ${-4 * s}`];
},

(Optionally) make parts of the shape not scale

Our example shape has rounded corners that shouldn't scale with the entire block when it changes size.

This shape's corners are 4 units in width and height, so we need to make it's corners not use the s variable:

leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-24 * s} a 4 4 0 0 1 4 -4`];
},

and make the center line between the 2 corners double the block's height, but remove 8 units (for the 2 corners):

leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},

Still using edgeWidth so height is the same value in rightPath.

Example (with leftPath and rightPath set)

Example block shape

rightPath

{
emptyInputPath: "(path)",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},
rightPath: (block) => {
return [];
},
}

rightPath is a function that constructs the right-side of your SVG path. The process is essentially the same as leftPath after the beginning few steps.

To fill this out, do the following:

Copy this template:

To start the rightPath, use this template:

rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
return [];
},

block.edgeShapeWidth_ is half of the width of the edge of the block. Essentially, this is half of the height of the block. Unlike leftPath, we actually don't have access to the block.height variable in rightPath yet. The block's height is calculated after the rightPath is drawn, since the rightPath is actually drawn before the leftPath. This means we need to use block.edgeShapeWidth_ as the best guess for the block's height for now. The s variable should now scale to 1 unit in the path you just made.

Find the first command after the right-side of the shape starts.

The right-side starts very early into the path, so it should be simple to find the first command after it starts.

Hover over each command in the COMMANDS list in the editor, until you see the first item added after the right-side starts. The dot for this item should turn red, aswell as the line behind it.

You then want to copy every command up until before the command that starts the left-side. This is the command we skipped in the left-side, and we also skip it here.

SvgPathEditor UI
rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
return [`h 33 a 4 4 0 0 1 4 4 l -27 12 l 27 12 a 4 4 0 0 1 -4 4 h -33`];
},

Unlike leftPath, we don't need to remove anything extra from this.

Apply the "Make the shape scale properly" step from leftPath

Now you can handle this section the same as the other steps previously mentioned in leftPath.

This shape also needs the "(Optionally) make parts of the shape not scale" step since the right-side also contains some corners.

In this case, our shape has a large cut hole on the right-side made of 2 lines about half the height of the edge. We can apply this like so:

rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${33 * s} a 4 4 0 0 1 4 4 l ${-27 * s} ${(height / 2) - 4} l ${27 * s} ${(height / 2) - 4} a 4 4 0 0 1 -4 4 h ${-33 * s}`];
},

Example (with leftPath and rightPath set)

Example block shape

blockPadding (optional)

note

This property is optional.

{
emptyInputPath: "(path)",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},
rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${33 * s} a 4 4 0 0 1 4 4 l ${-27 * s} ${(height / 2) - 4} l ${27 * s} ${(height / 2) - 4} a 4 4 0 0 1 -4 4 h ${-33 * s}`];
},
blockPadding: {
internal: {},
external: {},
},
}

blockPadding is an object that applies padding based on if this block is inside a specific type of block, or if a specific type of block is within this block.

This object also needs to have an internal property and an external property, both being objects.

You mostly need to play around with this value until it "feels right" for your shape, since you might want your shape to look a certain way in another block or be padded a certain way when it has one type of block in it.

internal

This object defines padding added to this block shape when the specified block shape is put inside of it.

Using the number 0 here refers to a Field. This is not usable in the external property since blocks cannot be put into Fields.

Example:

{
internal: {
0: 5 * ScratchBlocks.BlockSvg.GRID_UNIT, // Field in custom shape.
[ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL]: 2 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
}
}

You can also use the literal value of OUTPUT_SHAPE enums since they are not intended to change:

{
internal: {
0: 5 * ScratchBlocks.BlockSvg.GRID_UNIT, // Field in custom shape.
1: 2 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
}
}

external

This object defines padding added to the specified block shapes when this block shape is inside of them.

Example:

{
external: {
[ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL]: 2 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
}
}

You can also use the literal value of OUTPUT_SHAPE enums since they are not intended to change:

{
external: {
1: 2 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
}
}

Example

This example makes the padding extreme to make it obvious:

{
blockPadding: {
internal: {
1: 25 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
},
external: {
1: 50 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
},
},
}

Normal shape:

Example without any padding

Internal padding:

Example internal padding

External padding:

Example external padding

See more

The default padding values for each shape can be seen in code here.

blockPaddingStart (optional)

note

This property is optional.

{
emptyInputPath: "(path)",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},
rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${33 * s} a 4 4 0 0 1 4 4 l ${-27 * s} ${(height / 2) - 4} l ${27 * s} ${(height / 2) - 4} a 4 4 0 0 1 -4 4 h ${-33 * s}`];
},
blockPadding: {
internal: {
1: 25 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
},
external: {
1: 50 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
},
},
blockPaddingStart: (block, otherShape, firstInput, firstField, row) => {
return 0;
},
}

The blockPaddingStart property is a function that returns a numerical amount of padding to add to the block.

This can be useful if you have parts of your shape that need to be extremely padded when the block is taller, like the Boolean shape.

You mostly need to play around with this value until it "feels right" for your shape, since you might want your shape to look a certain way in another block or be padded a certain way when it has one type of block in it.

Below is a reimplementation of the Boolean shapes' settings:

blockPaddingStart: (block, otherShape, firstInput, firstField, row) => {
return Math.max(((firstInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 2, 0);
},

And here is what we will use for our block:

blockPaddingStart: (block, otherShape, firstInput, firstField, row) => {
return Math.max(((firstInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 8, 0);
},

We can use less of the firstInput's renderHeight since there's not actually anything on the left side of the shape, we just need to pad the left-side a bit so it doesn't start to clip into the block's text.

Example blockPaddingStart

There are unused inputs in the function, and that's just if you find some way to use them in a useful way. You can usually just modify the formula the Boolean shape uses to get the padding you want.

blockPaddingEnd (optional)

note

This property is optional.

{
emptyInputPath: "(path)",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},
rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${33 * s} a 4 4 0 0 1 4 4 l ${-27 * s} ${(height / 2) - 4} l ${27 * s} ${(height / 2) - 4} a 4 4 0 0 1 -4 4 h ${-33 * s}`];
},
blockPadding: {
internal: {
1: 25 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
},
external: {
1: 50 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
},
},
blockPaddingStart: (block, otherShape, firstInput, firstField, row) => {
return Math.max(((firstInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 8, 0);
},
blockPaddingEnd: (block, otherShape, lastInput, lastField, row) => {
return 0;
},
}

Copy of blockPaddingStart, but the padding is applied to the right-side of the block.

Below is a reimplementation of the Boolean shapes' settings which we will use for our block:

blockPaddingEnd: (block, otherShape, lastInput, lastField, row) => {
return Math.max(((lastInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 2, 0);
},
Example blockPaddingEnd

Example Block Shape

Implementing all of the above properties, we get this shape:

{
emptyInputPath: "m 16 0 h 16 h 33 a 4 4 0 0 1 4 4 l -27 12 l 27 12 a 4 4 0 0 1 -4 4 h -33 h -16 h -12 a 4 4 0 0 1 -4 -4 l 0 -24 a 4 4 0 0 1 4 -4 z",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},
rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${33 * s} a 4 4 0 0 1 4 4 l ${-27 * s} ${(height / 2) - 4} l ${27 * s} ${(height / 2) - 4} a 4 4 0 0 1 -4 4 h ${-33 * s}`];
},
blockPadding: {
internal: {
1: 25 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
},
external: {
1: 50 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
},
},
blockPaddingStart: (block, otherShape, firstInput, firstField, row) => {
return Math.max(((firstInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 8, 0);
},
blockPaddingEnd: (block, otherShape, lastInput, lastField, row) => {
return Math.max(((lastInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 2, 0);
},
}
Example Block

Using a Custom Shape in your Extension

Now that your shape is registered (with no errors), you can safely use the shape in your extension.

Make an output block

In your extension's category, make a basic block:

blocks: [
{
opcode: 'triangleCutShape',
text: 'triangle cut',
blockType: Scratch.BlockType.REPORTER,
},
]

To give it our custom shape, we need to add the blockShape parameter:

{
opcode: 'triangleCutShape',
text: 'triangle cut',
blockType: Scratch.BlockType.REPORTER,
blockShape: "myReallyCoolExtension-triangleCut",
},
Example Output Block

forceOutputType (optional)

If you only want this block to fit into other block's arguments using the same shape, then you need to add the forceOutputType parameter:

{
opcode: 'triangleCutShape',
text: 'triangle cut',
blockType: Scratch.BlockType.REPORTER,
blockShape: "myReallyCoolExtension-triangleCut",
forceOutputType: "myReallyCoolExtension-triangleCut",
},

You also need the input block to add the check parameter into it's argument.

Make an input block

In your extension's category, make a basic block with an argument:

{
opcode: 'triangleCutWanted',
text: 'I want a triangle cut [TRIANGLECUT]',
blockType: Scratch.BlockType.REPORTER,
arguments: {
TRIANGLECUT: {
// ???
}
}
},

Then, add the shape property to specify the shape that the argument should take:

{
opcode: 'triangleCutWanted',
text: 'I want a triangle cut [TRIANGLECUT]',
blockType: Scratch.BlockType.REPORTER,
arguments: {
TRIANGLECUT: {
shape: "myReallyCoolExtension-triangleCut",
}
}
},
Example Input Block

If you added forceOutputType to the output block from earlier, you also need to specify the check property:

{
opcode: 'triangleCutWanted',
text: 'I want a triangle cut [TRIANGLECUT]',
blockType: Scratch.BlockType.REPORTER,
arguments: {
TRIANGLECUT: {
shape: "myReallyCoolExtension-triangleCut",
check: "myReallyCoolExtension-triangleCut",
}
}
},
Example Input Block with Output Block inside

Example Block Extension Code

Here's the full code example for our block from earlier:

(function(Scratch) {
'use strict';

/* get ScratchBlocks if availiable... */
if (Scratch.gui) {
Scratch.gui.getBlockly().then(ScratchBlocks => {
// here you have access to ScratchBlocks safely
ScratchBlocks.BlockSvg.registerCustomShape("myReallyCoolExtension-triangleCut", {
emptyInputPath: "m 16 0 h 16 h 33 a 4 4 0 0 1 4 4 l -27 12 l 27 12 a 4 4 0 0 1 -4 4 h -33 h -16 h -12 a 4 4 0 0 1 -4 -4 l 0 -24 a 4 4 0 0 1 4 -4 z",
emptyInputWidth: 19 * ScratchBlocks.BlockSvg.GRID_UNIT,
leftPath: (block) => {
const edgeWidth = block.height / 2;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${-12 * s} a 4 4 0 0 1 -4 -4 l ${0 * s} ${-(height - 8)} a 4 4 0 0 1 4 -4`];
},
rightPath: (block) => {
const edgeWidth = block.edgeShapeWidth_;
const s = edgeWidth / 16;
const height = edgeWidth * 2;
return [`h ${33 * s} a 4 4 0 0 1 4 4 l ${-27 * s} ${(height / 2) - 4} l ${27 * s} ${(height / 2) - 4} a 4 4 0 0 1 -4 4 h ${-33 * s}`];
},
blockPadding: {
internal: {
1: 25 * ScratchBlocks.BlockSvg.GRID_UNIT, // Hexagon in custom shape.
},
external: {
1: 50 * ScratchBlocks.BlockSvg.GRID_UNIT, // Custom shape in hexagon.
},
},
blockPaddingStart: (block, otherShape, firstInput, firstField, row) => {
return Math.max(((firstInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 8, 0);
},
blockPaddingEnd: (block, otherShape, lastInput, lastField, row) => {
return Math.max(((lastInput.renderHeight - ScratchBlocks.BlockSvg.MIN_BLOCK_Y_REPORTER)) / 2, 0);
},
});
});
}

class MyReallyCoolExtension {
getInfo() {
return {
id: 'myReallyCoolExtension',
name: 'My Really Cool Extension',
blocks: [
{
opcode: 'triangleCutShape',
text: 'triangle cut',
blockType: Scratch.BlockType.REPORTER,
blockShape: "myReallyCoolExtension-triangleCut",
forceOutputType: "myReallyCoolExtension-triangleCut",
},
{
opcode: 'triangleCutWanted',
text: 'I want a triangle cut [TRIANGLECUT]',
blockType: Scratch.BlockType.REPORTER,
arguments: {
TRIANGLECUT: {
shape: "myReallyCoolExtension-triangleCut",
check: "myReallyCoolExtension-triangleCut",
}
}
},
]
}
}
}

Scratch.extensions.register(new MyReallyCoolExtension());
})(Scratch);
Example Extension