Scripts, Macros, and Experiments
Some of my explorations in writing code.

Canadian Tire AR Project (Unity)

At Canadian Tire I worked on very large products - Products that could sometimes not be merchandised properly on-shelf. Because of this, my team was always thinking of ways we could allow customers to view products in the store. One idea we explored was augmented reality. When we started to explore this idea I built a prototype in Unity so we could explain it to the merchandising and brand teams and show the potential of the idea off. The actual project is more complex than a single code snippet, but I have shown a select bit of my code on the right (2 Scripts separated by comments)

/**********************************************************************************/
//Game Manager
/**********************************************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class gameManager : MonoBehaviour
{

    public bool useCursor = false;
    public GameObject menu;
    public GameObject menuArrow;
    public bool menuOpen = false;

    public GameObject selectedUI;
    public GameObject selectedTitle;
    public GameObject backButton;

    public collectionItem activeProduct;

    public void setCursor() {
        useCursor = !useCursor;
    }

    public void clearScene() {
        GameObject[] objects = GameObject.FindGameObjectsWithTag("cubeTag");

        foreach (GameObject obj in objects) {
            Destroy(obj);
        }
    }
    
    public void openMenu() {
        Animation menuAnimation = menu.GetComponent<Animation>();

        if(menuOpen == false){
            menuAnimation.Play("Slide Up");
            menuArrow.transform.Rotate(0, 0, 180.0f);
            menuOpen = true;
            } else{
                menuAnimation.Play("Slide Down");
                menuArrow.transform.Rotate(0, 0, 180.0f);
                menuOpen = false;
            };

    }

    public void setActiveProduct(collectionItem product) {
        activeProduct = product;
        Debug.Log(string.Format(activeProduct.name));
    }

    public void setActiveUI(GameObject UIElement) {
        selectedUI.SetActive(false);

        selectedUI = UIElement;

        selectedUI.SetActive(true);

        if(backButton.activeSelf){
            backButton.SetActive(false);
        } else {
            backButton.SetActive(true);
        }
    }

        public void setActiveTitle(GameObject titleItem) {
        selectedTitle.SetActive(false);

        selectedTitle = titleItem;

        selectedTitle.SetActive(true);
    }
}


/**********************************************************************************/
//Cursor Script (For placing objects)
/**********************************************************************************/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.EventSystems;

public class ARCursor : MonoBehaviour
{

    public GameObject cursorChildObject;

    public gameManager gameManager;

    public GameObject objectToPlace;

    public ARRaycastManager raycastManager;

    // Start is called before the first frame update

    //public bool useCursor = false;
    void Start()
    {
        cursorChildObject.SetActive(gameManager.useCursor);
    }

    // Update is called once per frame
    void Update()
    {
        objectToPlace = gameManager.activeProduct.productModel;

        cursorChildObject.SetActive(gameManager.useCursor);
        if(gameManager.useCursor){
            updateCursor();
        }

        if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began && !EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId)) {
            if(gameManager.useCursor){
                GameObject.Instantiate(objectToPlace, transform.position, transform.rotation);
            }   else {
                List<ARRaycastHit> hits = new List<ARRaycastHit>();
                raycastManager.Raycast(Input.GetTouch(0).position, hits, TrackableType.Planes);

                if(hits.Count > 0){
                    GameObject.Instantiate(objectToPlace, hits[0].pose.position, hits[0].pose.rotation);
                }
            }
        }
    }

    void updateCursor(){
        Vector2 screenPosition = Camera.current.ViewportToScreenPoint(new Vector2(0.5f, 0.5f));
        List<ARRaycastHit> hits = new List<ARRaycastHit>();
        raycastManager.Raycast(screenPosition, hits, TrackableType.Planes);

        if(hits.Count > 0){
            transform.position = hits[0].pose.position;
            transform.rotation = hits[0].pose.rotation;
        }
    }
}

Adobe Macros (JSX)

During my time at Canadian Tire our teams often needed to export Illustrator  files as PDFs repeatedly and in large numbers when working on tech packs. This macro I wrote on the right can identify and export all Illustrator files in a folder and export them as PDFs named following our required naming convention. This code is annotated so others can read it and change it as needed. I also have written scripts for auto-filling document templates in Indesign (not shown).

//This line will open up a dialog box asking you what folder you would like to execute the script in.
var dir = Folder.selectDialog("Please Select a Folder");

//This line will take the folder you have selected, identify all ".ai" files, and put them into a list indexing at 0 (i.e. 0 = file 1, 1 = file 2, 2 = file 3, etc.). The list is named "allFiles".
var allFiles = dir.getFiles("*.ai");

//This alert will indicate how many PDFs will be generated.
alert(allFiles.length + " PDFs will be generated.");

//This is where the magic happens.
function saveAllPDF() {
    //This will apply the code of this function to every file in the list generated above.
    for(var i = 0; i < allFiles.length; i++) {
        
        //This line will open the selected file in the "allFiles" list (i.e. allFiles[0] will open the first file in the "allFiles" list.
        app.open(allFiles[i]);
        
        //This line will set the active document to the file you just opened.
        var document = app.activeDocument;
        
        //This line creates the file path you are going to save in. "app.activeDocument.path" refers to the file path to the folder you selected at the start. The "/" adds a slash. "app.activeDocument.name.split(".")[0]" takes the name of the active document (ex. "myFile.ai"), separates it by ".", then returns the first part, eliminating the file extension. " Copy" adds the word copy to the end of the name. You can change these to anything you feel like. Anything past the "/" will only change the file name
        var newPath = new File(app.activeDocument.path + "/" + app.activeDocument.name.split(".")[0] + " Copy");
        
        //To save PDFs via script, you need to have a PDF save options object that contains all of the PDF save settings. This line creates one named "pdfOptions".
        var pdfOptions = new PDFSaveOptions();
        
        //In this line I have set the pdfOptions preset. You can change this as needed.
        pdfOptions.preset = "[Smallest File Size]";
        
        //This line saves the active document using the new path you defined and the PDF options object you created.
        app.activeDocument.saveAs(newPath, pdfOptions);
        
        //This will close the document.
        app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
        }
    }

//To run a function you have defined you need to call it. The saveAllPDF function is being called here.
saveAllPDF();

Keyshot Frame Sorter (Python)

When using Keyshot for animations, I like to render frames as well as the final output. This can be problematic, as it means I need to sort the frames if I also rendered a mask layer which creates an additional set of frames. Manually this can take a long time, so I wrote a Python script to sort these files and place them in appropriately named folders. The script to the right is actually 2 scripts separated by a comment: one for the actual function and one for the GUI.

#########################################################################
#File Organizer - Inner Workings
#########################################################################

import shutil, os
from pathlib import Path

base = "F:\\"
target_folder = "animation 2"

print("Writing to "+base+target_folder)

def makeFolders(targetLocation):
    Path(targetLocation+"//clown").mkdir(exist_ok=True)
    Path(targetLocation+"//diffuse").mkdir(exist_ok=True)
    Path(targetLocation+"//normals").mkdir(exist_ok=True)
    Path(targetLocation+"//frames").mkdir(exist_ok=True)

def sortFiles(targetLocation):
    for filename in Path(targetLocation).glob("*"):

        if str(filename).__contains__("clown"):
            shutil.move(str(filename), targetLocation+"\\clown")

        elif str(filename).__contains__("diffuse"):
            shutil.move(str(filename), targetLocation+"\\diffuse")

        elif str(filename).__contains__("normals"):
            shutil.move(str(filename), targetLocation+"\\normals")

        else:
            shutil.move(str(filename), targetLocation+"\\frames")
    
makeFolders(base+target_folder)

sortFiles(base+target_folder)

#########################################################################
#File Organizer - GUI
#########################################################################

import os
from pathlib import Path
import PySimpleGUI as sg
import FileOrganizer1 as FO

layout = [
    [sg.Text("Select a folder to sort:")],
    [sg.In(size=(25, 1), enable_events=True, key="--FOLDER--"),sg.FolderBrowse()],
    [sg.Button("Sort", key="--SORT--")]
]

window = sg.Window("File Sorter", layout)

while True:
    events, values = window.read()

    if events == "Exit" or events == sg.WIN_CLOSED:
        break

    if events == "--SORT--":
        folder = values["--FOLDER--"]

        try:
            print(f"Making Folders {folder} in...")
            FO.makeFolders(folder)

            print("Sorting Files...")
            FO.sortFiles(folder)

            sg.Popup("Finished!", keep_on_top=True)

        except:
            sg.Popup("Something went wrong...", keep_on_top=True)
            
window.close()

Ethereum/Polygon NFTS (Solidity)

My Moon, My Stars

During the 2021 Crypto rush, I tried my hand at programming NFTs using Solidity and Ethers.js. At the time, I found that most NFTs were static images, but I had found some projects that had featured reactive NFTs. I discovered that NFTs could be blocks of HTML and Javascript instead of a static image, so I developed my own to try and see what was possible. To the left I will show my NFT script written in solidity, as well as the Javascript that is the NFT's image. The NFT displays the time elapsed from a certain date, changes color and weather based on the time of day, and the heart will pump faster if the cursor gets close to the center.

/****************************************************/
// NFT Code (Solidity)
/****************************************************/
pragma solidity ^0.8.4;

import '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Counters.sol';
import "hardhat/console.sol";

//Import base64 encoding functions
import { Base64 } from "./libraries/Base64.sol";

contract myMoonMyStars is ERC721URIStorage, Ownable{
    using Counters for Counters.Counter;

    Counters.Counter private tokenIds;

    constructor() ERC721("My Moon, My Stars","HER") {
        mint();
        console.log(tokenURI(0));
    }

    function mint() public {
        require(tokenIds.current() == 0);
        uint256 tokenId = tokenIds.current();

        string memory tokenURI = Base64.encode(bytes(string(abi.encodePacked('{"name": "My Moon, My Stars.", "description": "As sure as the sunrise, as persistent as time. My heart is forever yours in body, soul, and blockchain.", "image": "https://ipfs.io/ipfs/QmbtSEDbYao16zCZrMRiXjJfEVuPvwmrho8JhSmQCB7zYu?filename=MMMS.png", "animation_url": "https://ipfs.io/ipfs/QmZ7u7y1mm4MAF7q6ZACnVDWuJSBn4xUzsQRsm9B3MphDA/myMoonMyStars.html"}'))));

        string memory finalTokenUri = string(
            abi.encodePacked("data:application/json;base64,", tokenURI)
        );

        _safeMint(msg.sender, tokenId);
        _setTokenURI(tokenId, finalTokenUri); 
        tokenIds.increment();
    }

}

/****************************************************/
// Art Code (Javascript)
/****************************************************/

//Heartbeat Variables
var size = 211;
var mod = 1;

//Time Variables                
let timeRef = 1441684800000;
let seconds;
let minutes;
let hours;
let days;
let months;
let years;
const minute = 1000 * 60;
const hour = minute * 60;
const day = hour * 24;
const year = day * 365;

//Stars
let stars = [];
let starTime = false;

           function setup() {
                let renderer = createCanvas(450, 450);
                renderer.parent("container");
                frameRate(24);
                background(0);
                stroke(255, 50, 50, 50);
                fill(255,0,0, 50);
                textSize(25);
                textAlign(CENTER, CENTER);

                for(let i=0; i<250;i++){
                    stars[i] = new Star();
                }
            }

            function draw(){

                let date = new Date();
                let time = date.getTime();
 
                clear();
                
                //Heartbeat
                calculateFramerate();

                //Background Draw
                drawGradient(date);

                if(starTime) {
                    for(let i=0; i < stars.length; i++){
                    stars[i].drawStars();
                    }
                }

                //Heart Stuff
                stroke(255, 50, 50, 50);
                calculateFill(); //fill(187,55,57);
                if(size <= 210|| size >= 225){
                    mod = -mod;
                }
                size += mod;
                heart(224,150, size);

                //Text
                fill(255);
                text(String(calculateTime(time,timeRef)), 225, 60);
                
            }

            //Classes
            class Star {
                constructor() {
                    this.x = random(450);
                    this.y = random(175);
                    this.size = random(0.25, 1);
                    this.t = random(TAU);
                }

                drawStars() {
                    this.t += 0.1;
		            var scale = this.size + sin(this.t) * 2;
                    noStroke();
		            ellipse(this.x, this.y, scale, scale);
                }
            }

            //Functions

            function heart(x, y, size) {
                beginShape();
                vertex(x, y);
                bezierVertex(x - size / 2, y - size / 2, x - size, y + size / 3, x, y + size);
                bezierVertex(x + size, y + size / 3, x + size / 2, y - size / 2, x, y);
                endShape(CLOSE);
            }

            function calculateTime(T, T2){
                let result = T-T2;
                seconds = Math.floor((result/(1000)) % 60);
                minutes = Math.floor((result/(1000*60)) % 60);
                hours = Math.floor((result/(1000*60*60)) % 24);
                days = Math.floor((result/(1000*60*60*24)) % 365);
                years = Math.floor((result/(1000*60*60*24)) / 365);

                return(`${years} : ${days} : ${hours} : ${minutes} : ${seconds}`);

            }

            function calculateFramerate(){
                let x1 = 225;
                let y1 = 225;
                let x2 = mouseX;
                let y2 = mouseY;
                
                let d = dist(x1, y1, x2, y2);

                if(d > 225){
                    d = 225;
                }

                let r = 44-((d/225)*20);

                frameRate(r);
            }

            function calculateFill(){
                let x1 = 225;
                let y1 = 225;
                let x2 = mouseX;
                let y2 = mouseY;
                
                let d = dist(x1, y1, x2, y2);

                if(d > 225){
                    d = 225;
                }

                let r = 245-((d/225)*58);

                fill(r, 55, 57); 
            }

            function drawGradient(Tref) {
                colorMode(RGB);

                let hours = Tref.getHours();

                let colors = [
                color(224,237,195), //Dawn Colors
                color(240,81,84),
                color(128,210,232), //Morning Colors
                color(254,209,148),
                color(89,202,240), //Day Colors
                color(73,121,189),
                color(1,91,167), //Evening Colors
                color(254,252,230),
                color(8,72,107), //Sunset Colors
                color(242,148,146),
                color(18,9,12), //Night Colors
                color(45,83,100)
            ];

                var col1;
                var col2;

                if(hours >= 4 && hours <= 6){
                    col1 = colors[0];
                    col2 = colors[1];
                    starTime = false;
                } else if(hours >= 7 && hours <= 9) {
                    col1 = colors[2];
                    col2 = colors[3];
                    starTime = false;
                } else if(hours >= 10 && hours <= 15) {
                    col1 = colors[4];
                    col2 = colors[5];
                    starTime = false;
                } else if(hours >= 16 && hours <= 18) {
                    col1 = colors[6];
                    col2 = colors[7];
                    starTime = false;
                } else if(hours == 19) {
                    col1 = colors[8];
                    col2 = colors[9];
                    starTime = false;
                } else if(hours >=20) {
                    col1 = colors[10];
                    col2 = colors[11];
                    starTime = true;
                } else {
                    col1 = colors[10];
                    col2 = colors[11];
                    starTime = true;
                }
 
                var step = 0;

                for ( i = 0; i <= 450; ++i) {
                step = map(i, 0, 450, 0.0, 1.0);

                currentStroke = lerpColor(col1, col2, step);
                stroke(currentStroke);

                line(0, i, 450, i);
                }
            }