How to convert SVG icons into an icon font

In this post we'll go over how to combine or convert SVG files into an icon font file. In short, we'll git clone a repository svgtofont, modify src/cli.js and then run a node script to generate the font.

svgtofont

We'll use the library svgtofont to combine all the SVGs into a font file. It supports generating eot, ttf, woff, woff2 and svg files. Svgtofont uses other projects such as svgicons2svgfont and svg2ttf.

Cloning the repository

Command to clone it and set it up locally in your terminal: git clone https://github.com/jaywcjlove/svgtofont.git

Generating the icon files

Add two folders in the newly cloned svgtofont folder: icons and output. "icons" is the folder where you will put all the svg files.

Modify existing src/cli.ts file by adding the website property roughly on line 46. website: { ... }, full example below: Change your-font-name to what you want to call the font.


svgtofont({
  src: sourcesPath, // svg path
  dist: outputPath, // output path
  // emptyDist: true, // Clear output directory contents
  fontName: (argv.fontName) || "svgfont", // font name
  css: true, // Create CSS files.
  website: {
    description: 'your-font-name',
    logo: 'no-logo',
    links: [],
  },
  outSVGReact: true,
  outSVGPath: true,
  svgicons2svgfont: {
    fontHeight: 1000,
    normalize: true,
  },
})

The output

Run the converter with a node script node src/cli.js --sources ./icons --output ./output --fontName your-font-name in your terminal. If it doesn't create the files in output folder then check that the SVG files are valid. I had invalid SVG files and scratched my head wondering why the library did not work.

The generated icon css classes follow the naming convention of your-font-name-{svg name}. For example for muumi.svg it would be your-font-name-muumi. You can check all the generated icons in output/index.html. That's it, now add the css and font files to you project and create elements like <i class="icon your-font-name-muumi"></i>

Easily download all DALL-E prompt images with a button

There is currently no way (September 2022) to download all images for DALL-E at https://labs.openai.com/. Which made me write a quick and dirty script to download all images for the current prompt or for all of the prompts. If you need such functionality then you can copy-paste the code and paste it into the browser's developer tools (F12 as default). After pasting the code it should add two buttons in the header that you can click.
download buttons in header

Some notes:

  • It helps a lot to disable "Ask where to save each file before downloading" as otherwise you'll be spammed with prompts to download the files. And to change the download folder as it's 200 images for all the items.
    In chrome: Settings -> Downloads -> Uncheck the toggle button.
  • Since the items in the list to the right are lazy loaded it's good to scroll all the way to the bottom if downloading all the images.
  • Because this script uses timings it might fail to find the download buttons, and it'll then log the item in the list to the right, so you can mouseover the item, and it should highlight the element. You can then click the current prompt button for those items.
  • This searches for the text "Download" to find the download button. If DALL-E has localization you will have to change the DOWNLOAD for this line of code to what it is for you.
    if (item.textContent.toUpperCase().includes('DOWNLOAD')) {

The code to copy paste into developer tools:


/**
 * Get the items in the right panel and download for each item.
 */
async function downloadAllImages() {
  var items = document.querySelectorAll('.hist-task-link');
  for (let i = 0; i < items.length; i++) {
    const item = items.item(i);
    item.click();
    await awaitTimeout(2000);
    await downloadImagesForCurrent(item);
    await awaitTimeout(10);
  }
}

/**
 * Download all the items for an item in the right side panel.
 * Since the items are lazy loaded it helps to scroll all the way to the bottom first so all the items are loaded.
 */
function downloadImagesForCurrent(listItem) {
  const promise = new Promise(async (res) => {
    const extraButtons = document.querySelectorAll('.task-page-quick-actions-button');
    for (let i = 0; i < extraButtons.length; i++) {
      await downloadImage(extraButtons.item(i), listItem);
      await awaitTimeout(100);
    }

    res();
  });

  return promise;
}

/**
 * Download an image by clicking the ... and then the download button that appears.
 * It helps to disable "Ask where to save each file before downloading", eg in chrome:  Settings -> Downloads -> Uncheck that toggle button.
 */
async function downloadImage(extraButton, listItem) {
  extraButton.click();
  await awaitTimeout(50);
  const downloadButton = getLastDownloadButton();
  if (!downloadButton) {
    console.log('Could not find download button for', listItem);
    return;
  }

  downloadButton.click();
}

/**
 * We need to get the last download button because there might be multiple open at once.
 */
function getLastDownloadButton() {
  const selectableItems = document.querySelectorAll('.menu-item-selectable');
  for (let i = selectableItems.length - 1; i >= 0; i--) {
    const item = selectableItems.item(i);
    if (item.textContent.toUpperCase().includes('DOWNLOAD')) {
      return item;
    }
  }
}

/**
 * Adds an awaitable setTimeout since we need to wait for buttons.
 */
function awaitTimeout(delay) {
  return new Promise(res => setTimeout(() => res(), delay));
}

!function() {
  const header = document.querySelector('.app-header-contents');
  if (!header) {
    console.log('Could not find header element to add buttons, you need to use code manually.')
    console.log('downloadAllImages() - to download all images');
    console.log('downloadImagesForCurrent() - to download current prompt');
    return;
  }

  const downloadAllButton = document.createElement('button');
  downloadAllButton.innerText = 'Download All';
  downloadAllButton.onclick = downloadAllImages;

  const downloadCurrent = document.createElement('button');
  downloadCurrent.innerText = 'Download Current Prompt';
  downloadCurrent.onclick = downloadImagesForCurrent;

  header.appendChild(downloadAllButton);
  header.appendChild(downloadCurrent);
}();

Making a FFXIV ACT Chat extractor

This is about the making of Nuusie's Final Fantasy XIV Chat Extractor for the ACT Plugin.. And some of the obstacles encountered along the way. Some of the obstacles were custom colours, manipulating text to look like an emote, *Luna is writing a blog post*, quotation marks.
The chat extractor can be found here: https://github.com/saaratrix/nuu-ffxiv-act-chat-extractor

Introduction

The idea came from a friend who wanted to extract their stored chat logs and the tools she found was not as trustworthy to her as something made by me. For example this tool was hosted through a web server and she didn't want to upload her chat logs there. https://github.com/ErstonGreatman/ffxiv-act-chat-extractor.

Which lead to the first obstacle that I wanted to design the tool so you can run it locally and I wanted it to be lightweight. So that if someone does look through the code they can hopefully easily see that it just does what's expected. You could add the script and css bundles inside the html page so that it's still 1 single page but the file wouldn't look as readable. Even something smaller like Svelte still has 23 kb of minified javascript.

Developing everything in 1 file meant that I started using comments to try and separate things into sections. For example like this:


// ******************
// A section!
// ******************

Angular E2E Protractor Introduction

This is an introduction to Angular E2E tests with two examples using Angular 10. The first example is the default test generated by the Angular 10 CLI and the second test does the following:

  1. Logs in
  2. Creates an item
  3. Modifies the item
  4. Deletes the item
  5. Logs out

Angular uses Protractor to do the e2e tests. If you generate a project with the Angular CLI it's very simple to run your e2e tests by simply running ng e2e.

Source code can be found at https://github.com/saaratrix/angular-e2e-example

Debugging a Blender Add-on in PyCharm

This is a guide on how to set up debugging in a Blender Add-on version 2.80 or higher using PyCharm. I used this guide: https://code.blender.org/2015/10/debugging-python-code-with-pycharm/ to set up debugging.
Important: You need Pycharm Professional for debugging Blender because it requires the remote debugging feature which isn't available in community edition in 2019 or lower.

Create a Blender Add-on project with PyCharm

We'll be setting up a fresh Blender Add-on project with PyCharm that works with source control. This guide was written for Blender 2.80 but it works the same in later versions as of May 2024.

I have made a boilerplate template for Blender 4.x, 3.x that you can copy and use as a base. https://github.com/saaratrix/empty-blender-add-on-template .

How to add user preferences for a Blender Add-on

When trying to implement user preferences for a 2.80 Blender add-on the official documentation was confusing for me and it didn't work with their example code. Their comment on what to set bl_idname as was the confusing part. Probably because of my Python inexperience.
https://docs.blender.org/api/current/bpy.types.AddonPreferences.html

After some searching I found this article that helped me understand what I had done wrong and make it work.
https://b3d.interplanety.org/en/add-on-preferences-panel/

Example code with preferences in own file

The code to get add-on preferences in Blender 2.80 in its own file was this code below. bl_idname should be __name__ if the class is inside __init__.py otherwise __package__.


import bpy


class EXAMPLE_addonPreferences(bpy.types.AddonPreferences):
    bl_idname = __package__

    # Code below copied from https://b3d.interplanety.org/en/add-on-preferences-panel/
    add_bevel: bpy.props.EnumProperty(
        items=[
            ('bevel', 'Add bevel', '', '', 0),
            ('no_bevel', 'No bevel', '', '', 1)
        ],
        default='no_bevel'
    )

    def draw(self, context):
        layout = self.layout
        layout.label(text='Add bevel modifier:')
        row = layout.row()
        row.prop(self, 'add_bevel', expand=True)


Here is an image of the add-on preferences UI from the code above.

Reload modified 2.80 Blender Add-on

I started working on my first Blender add-on for 2.80 and quickly wondered: how do I reload my blender add-on without restarting Blender!? After spending roughly a day researching on how to reload I was able to just hit a key (F8) inside Blender to reload the add-on.

After searching and testing on different approaches for reloading I came across this post: https://developer.blender.org/T67387 . I had gone through many of their steps in trying to figure out how to reload the add-ons :).

The solution:

Two steps are required to easily reload the add-on.
One is setting up the reload key binding because reload scripts action is unbound.
The other one is writing the proper reload code in the __init__.py file.

Using threejs with angular

I used Angular 7 with three.js to make a 3D viewer. The goal was to write a generic Angular component loading three.js that can easily be used by any project and then apply the application logic afterwards.

The project's source code can be found here: github.com/saaratrix/generic-3Dproduct-viewer
The generic Angular component can be found here: github.com/saaratrix/generic-3Dproduct-viewer/tree/master/src/app/viewer-threejs
The application can be demoed here: saaratrix.github.io/generic-3Dproduct-viewer/build/

2019 Winter UE4 Jam

I worked together again with Ronja and Vilma during the 2019 Winter #ue4jam and made a game.
The theme this time was All's fair in love and war..
The game is available on itch.io at: IceWuffels.
The gameplay can be seen in this video: https://youtu.be/mpGeZYeIYCI.

This game jam was different, we tried to come up an idea that fits the theme but we couldn't come up anything. So on friday (day 2) we decided that I'll just play around with particle effects because that's what I wanted to try and get better at. I had seen this video and I thought it would be fun to do a more advanced particle like that.

So over the course of friday to early monday I worked every now and then learning about particles, watching the video to see how it looked like in the particle system. Here is some progress over the days:

Added debris & mist:

Added frost on the ground, and sparkles:

The final result, it has extra sparkles when it fades out and a distortion blur effect for some power.

2018 Spring UE4 Jam

Me, Ronja and Vilma worked together during the 2018 Spring #ue4jam and made a game. The theme for the game jam this time was: Transformation. Our game can be downloaded here on the itch.io page: Transforester A full play through video can be found here: https://www.youtube.com/watch?v=TypANH5k9Lo. The game logic was implemented using only blueprint. This post is about making the game from start to finish.

Learning Angular 5

I wrote an app using Angular 5 that does many of the basic features in Angular. The backend uses Node.js, Express and MySQL.

Localized routing using ASP.NET Core MVC 2

This is one implementation of localized routes using ASP.NET Core 2 MVC. [Attributes] are used to control the localized route data for the controllers and actions for simplicity.
Source code: https://github.com/saaratrix/asp.net-core-mvc-localized-routing

Table of content:

Abstract

A localized routing solution using ASP.NET Core 2.1 MVC. The implementation uses attributes on the controllers and actions to determine the localization for each culture. A convention is added in Startup.cs to iterate over all the controllers and actions to set their cultural routes based on the [LocalizationRoute] attributes.

Location Based Saturation in Unreal Engine 4

During the Epic MegaJam 2017 I made one implementation of a Location Based Saturation that over time transitions from being desaturated to fully saturated. This is the technical implementation of that feature. The effect is demonstrated in this clip.

Epic Megajam 2017

Me and Ronja (@Roniatrix) made a game together for the Epic MegaJam 2017. The theme for this gamejam was: However vast the darkness, we must supply our own light. Our game is called E For Hugs. This post is about our way from start to finish making the game. Gameplay of the game can be found here: https://youtu.be/A-0y85LOBo4. The game was implemented using only blueprints for the code.
The game can be downloaded here: https://www.dropbox.com/s/ypmgekycdpldaht/EForHugs.zip?dl=0



GLSL to Babylon.js ShadersStore code

This is about writing a .NET Core application to automatically convert GLSL into Babylon.js ShaderStore code. The project is available on github here.

Learning Angular 2

I've been wanting to try out angular2 for a while and decided to add angular2 to an existing node.js express project that I used to learn express. The project code can be found here: github.com/saaratrix/glossarytraining/tree/angular2
There is also a demo here where I removed the backend part so it works for gh-pages: saaratrix.github.io/glossarytraining/dist/
The finished project covers the basics of angular2 with input & output variables for components and services.

Localized routing ASP.NET MVC Core 1.0

This is one implementation of having localized routing in ASP.NET Core MVC 1.0 (asp.net 5 mvc 6 RC 1)
Source code: github.com/saaratrix/asp.net-core-mvc-localized-routing

NOTE: A reader notified me of some breaking changes in newer ASP.NET Core versions so there is an updated blog post here for version: ASP.NET Core 2.1 MVC


Abstract

A working localized routing example for ASP.NET Core MVC 1.0 release candidate 1. The solution uses attributes on the controllers & actions to determine the localized routing for each different culture. A convention is added to the MvcOptions in startup.cs to iterate over all the controllers & actions to set their routes based on the localized attributes.

Making of a table of contents script

I wanted to use an automatic table of content script to generate a TOC if necessary for this blog. I picked a script written by Ates Goral on stackoverflow: stackoverflow.com/a/187946.

When I was writing the Localization Routes post I noticed that some of the table content links weren't working properly. The error was that some headings were called ____________Id____________. So instead of trying to understand why the table of content script wasn't working I decided to write my own for fun.

I made the automatic table of content generation using typescript and the code is in the snippet below. What the code does is look for a #toc and #content element. Then it finds all the h1 to h6 elements inside the #content element. The cool part is that querySelectorAll() returns them in order of appearance so there's no need to sort them. So it's just a matter of iterating over each heading element and increase/decrease the level of the ToC.
The code can be found on github or in the snippet below: here.