Giter Club home page Giter Club logo

openharmony's Introduction

OpenHarmony - The Toonboom Harmony Open Source DOM Library

Why did we make this library ?

Ever tried to make a simple script for toonboom Harmony, then got stumped by the numerous amount of steps required to execute the simplest action? Or bored of coding the same helper routines again and again for every studio you work for?

Toonboom Harmony is a very powerful software, with hundreds of functions and tools, and it unlocks a great amount of possibilities for animation studios around the globe. And... being the produce of the hard work of a small team forced to prioritise, it can also be a bit rustic at times!

We are users at heart, animators and riggers, who just want to interact with the software as simply as possible. Simplicity is at the heart of the design of openHarmony. But we also are developpers, and we made the library for people like us who can't resist tweaking the software and bend it in all possible ways, and are looking for powerful functions to help them do it.

This library's aim is to create a more direct way to interact with Toonboom through scripts, by providing a more intuitive way to access its elements, and help with the cumbersome and repetitive tasks as well as help unlock untapped potential in its many available systems. So we can go from having to do things like this:

  // adding a Drawing to the scene with the official API
  var myNodeName = "Drawing";
  var myColumnName = myNodeName;
  var myNode = node.add("Top", myNodeName, "READ",0,0,0);
  var myColumn = column.add(myColumnName, "DRAWING", "BOTTOM");
  var myElement = element.add (myNodeName, "COLOR", 12, "SCAN", "TVG");
  column.setElementIdOfDrawing(myColumnName, myElement);
  node.linkAttr (myNode, "DRAWING.ELEMENT", myColumnName);
  drawing.create (myElement, "1", false, false);
  column.setEntry (myColumnName, 0, 1, "1");

to simply writing :

  // with openHarmony
  var myNode = $.scene.root.addDrawingNode("Drawing");
  myNode.element.addDrawing(1);

Less time spent coding, more time spent having ideas!


Do I need any knowledge of toonboom scripting to use openHarmony?

OpenHarmony aims to be self contained and to reimplement all the basic functions of the Harmony API. So, while it might help to have prior experience to understand what goes on under the hood, knowledge of the official API is not required.

However, should you reach the limits of what openHarmony can offer at this time, you can always access the official API at any moment. Maybe you can submit a request and the missing parts will be added eventually, or you can even delve into the code and add the necessary functions yourself if you feel like it!

You can access a list of all the functions, how to use them, as well as examples, from the online documentation:

https://cfourney.github.io/OpenHarmony/$.html

To help you get started, here is a full example using the library to make and animate a small car, covering most of the basic features.

https://github.com/cfourney/OpenHarmony/blob/master/examples/openHarmonyExample.js


The OpenHarmony Document Object Model or DOM

OpenHarmony is based around the four principles of Object Oriented Programming: Abstraction, Encapsulation, Inheritance, Polymorphism.

This means every element of the Harmony scene has a corresponding abstraction existing in the code as a class. We have oNode, oScene, oColumn, etc. Unlike in the official API, each class is designed to create objects that are instances of these classes and encapsulate them and all their actions. It means no more storing the path of nodes, column abstract names and element ids to interact with them; if you can create or call it, you can access all of its functionalities. Nodes are declined as DrawingNodes and PegNodes, which inherint from the Node Class, and so on.

The openHarmony library doesn't merely provide access to the elements of a Toonboom Harmony file, it models them and their relationship to each others.

The Document ObjectModel

The Document Object Model is a way to organise the elements of the Toonboom scene by highlighting the way they interact with each other. The Scene object has a root group, which contains Nodes, which have Attributes which can be linked to Columns which contain Frames, etc. This way it's always easy to find and access the content you are looking for. The attribute system has also been streamlined and you can now set values of node properties with a simple attribution synthax.

We implemented a global access to all elements and functions through the standard dot notation for the hierarchy, for ease of use, and clarity of code.

Functions and methods also make extensive use of optional parameters so no more need to fill in all arguments when calling functions when the default behavior is all that's needed.

On the other hand, the "o" naming scheme allows us to retain full access to the official API at all times. This means you can use it only when it really makes your life better.


Adopting openHarmony for your project

This library is made available under the Mozilla Public license 2.0.

OpenHarmony can be downloaded from this repository directly. In order to make use of its functions, it needs to be unzipped next to the scripts you will be writing.

All you have to do is call :

include("openHarmony.js");

at the beggining of your script.

You can ask your users to download their copy of the library and store it alongside, or bundle it as you wish as long as you include the license file provided on this repository.

The entire library is documented at the address :

https://cfourney.github.io/OpenHarmony/$.html

This include a list of all the available functions as well as examples and references (such as the list of all available node attributes).

As time goes by, more functions will be added and the documentation will also get richer as more examples get created.


Installation

simple install:

advanced install (for developers):

  • clone the repository to the location of your choice

-- or --

  • download the zip from the releases page
  • unzip the contents where you want to store the library,

-- then --

  • run install.bat.

This last step will tell Harmony where to look to load the library, by setting the environment variable LIB_OPENHARMONY_PATH to the current folder.

It will then create a openHarmony.js file into the user scripts folder which calls the files from the folder from the LIB_OPENHARMONY_PATH variable, so that scripts can make direct use of it without having to worry about where openHarmony is stored.

Troubleshooting:
  • to test if the library is correctly installed, open the Script Editor window and type:
include ("openHarmony.js");
$.alert("hello world");

Run the script, and if there is an error (for ex MAX_REENTRENCY ), check that the file openHarmony.js exists in the script folder, and contains only the line:

include(System.getenv('LIB_OPENHARMONY_PATH')+'openHarmony.js');

Check that the environment variable LIB_OPENHARMONY_PATH is set correctly to the remote folder.


How to add openHarmony to vscode intellisense for autocompletion

Although not fully supported, you can get most of the autocompletion features to work by adding the following lines to a jsconfig.json file placed at the root of your working folder. The paths need to be relative which means the openHarmony source code must be placed directly in your developping environnement.

For example, if your working folder contains the openHarmony source in a folder called OpenHarmony and your working scripts in a folder called myScripts, place the jsconfig.json file at the root of the folder and add these lines to the file:

{
  include : [
    "OpenHarmony/*",
    "OpenHarmony/openHarmony/*",
    "myScripts/*",
    "*"
  ]
}

More information on vs code and jsconfig.json.


Let's get technical. I can code, and want to contribute, where do I start?

Reading and understanding the existing code, or at least the structure of the lib, is a great start, but not a requirement. You can simply start adding your classes to the $ object that is the root of the harmony lib, and start implementing. However, try to follow these guidelines as they are the underlying principles that make the library consistent:

  • There is a $ global object, which contains all the class declarations, and can be passed from one context to another to access the functions.

  • Each class is an abstract representation of a core concept of Harmony, so naming and consistency (within the lib) is essential. But we are not bound by the structure or naming of Harmony if we find a better way, for example to make nomenclatures more consistent between the scripting interface and the UI.

  • Each class defines a bunch of class properties with getter/setters for the values that are directly related to an entity of the scene. If you're thinking of making a getter function that doesn't require arguments, use a getter setter instead!

  • Each class also defines methods which can be called on the class instances to affect its contents, or its children's contents. For example, you'd go to the scene class to add the things that live in the scene, such as elements, columns and palettes. You wouldn't go to the column class or palette class to add one, because then what are you adding it to?

  • We use encapsulation over having to pass a function arguments every time we can. Instead of adding a node to the scene, and having to pass a group as argument, adding a node is done directly by calling a method of the parent group. This way the parent/child relationship is always clear and the arguments list kept to a minimum.

  • The goal is to make the most useful set of functions we can. Instead of making a large function that does a lot, consider extracting the small useful subroutines you need in your function into the existing classes directly.

  • Each method argument besides the core one (for example, for adding nodes, we have to specify the type of the new node we create) must have a default fallback to make the argument optional.

  • Don't use globals ever, but maybe use a class property if you need an enum for example.

  • Don't use the official API namespace, any function that exists in the official API must remain accessible otherwise things will break. Prefix your class names with "o" to avoid this and to signify this function is part of openHarmony.

  • We use the official API as little as we can in the code, so that if the implementation changes, we can easily fix it in a minimal amount of places. Wrap it, then use the wrapper. (ex: oScene.name)

  • Users of the lib should almost never have to use "new" to create instances of their classes. Create accessors/factories that will do that for them. For example, $.scn creates and return a oScene instance, and $.scn.nodes returns new oNodes instances, but users don't have to create them themselves, so it's like they were always there, contained within. It also lets you create different subclasses for one factory. For example, $.scn.$node("Top/myNode") will either return a oNode, oDrawingNode, oPegNode or oGroupNode object depending on the node type of the node represented by the object. Exceptions are small useful value containing objects that don't belong to the Harmony hierarchy like oPoint, oBox, oColorValue, etc.

  • It's a JS Library, so use camelCase naming and try to follow the google style guide for JS : https://google.github.io/styleguide/jsguide.html

  • Document your new functions using the JSDocs synthax : https://devdocs.io/jsdoc/howto-es2015-classes

  • Make a branch, create a merge request when you're done, and we'll add the new stuff you added to the lib :)


Credits

This library was created by Mathieu Chaptel and Chris Fourney.

If you're using openHarmony, and are noticing things that you would like to see in the library, please feel free to contribute to the code directly, or send us feedback through Github. This project will only be as good as people working together can make it, and we need every piece of code and feedback we can get, and would love to hear from you!


Community

Join the discord community for help with the library and to contribute: https://discord.gg/kgT38MG


Acknowledgements

  • Yu Ueda for his help to understand Harmony coordinate systems
  • Dash for his help to debug, test and develop the Pie Menus widgets
  • All the contributors for their precious help.

openharmony's People

Contributors

actions-user avatar bbfjagadish avatar bob-ross27 avatar cfourney avatar fuzzkingcool avatar jonathan-fontaine avatar jorgepozo avatar joshdresner avatar mchaptel avatar miwgel avatar waterwheels avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openharmony's Issues

color override or color selector support

Hi. I'd like to know if these nodes are supported and I'm missing something or they are just in the works still. Thanks.
Sorry if this is not the proper channel to ask.

EDIT: I found the color-selector under the type of the node: TbdColorSelector. I'll try to pass the string of selected colors in the same way I do with the official API to see if it works.
If I understand this properly, color-override will become a new class (oColorOverride?) with methods to choose individual or palette overrides?
Thanks for the hard work, it is much more pleasurable scripting like this :D

Issue with frame class

if( oColumnObject._type == "attribute" ){ //Direct access to an attribute, when not keyable. We still provide a frame access for consistency.
this.column = false;
this.attributeObject = oColumnObject;
}else{
this.column = oColumnObject;
this.attributeObject = this.column.attributeObject;
}

Surely this will break everything? How can we expect a oColumn and oAttribute to behave the same? (line 90)

Also setting the column to false is wrong imo. We're changing the type. If not present, it should be null or better undefined.

If you really want a frame object on the oAttribute class (why? If it's not animated, then frames are irrelevant), it can't be the same class.

This is a big design flaw.

Compatibility issue with keyword

this.keyword = attributeObject.keyword();

FullKeyword and keyword work differently, I think this is why the getter setter wasn't working. FullKeyword includes the parent attr keyword with dot notation, so we need to fully implement this here to get the right result when calling attr.keyword. we should get it from the parent attribute and construct it completely

getSelectedNodes raises error

Hi guys,

I am trying to test the new version in harmony 17 and i am greeted with a Result of expression 'attributeObject.keyword' [undefined] is not a function. error. creating a issue to keep it tracked.

SetAttrGetterSetter

Object.defineProperty( this[parent_attr], _keyword, getSet_obj );

Here I don't see where we create This[parent_attr] which should be a dummy object. I used to pass it as an object ref as context, but now we pass a string called parent_attr right? So this object is never created it seems.

$.oGroupNode.backdrops returns empty array

Expected behavior

Calling backdrops method on a $.oGroupNode object should return an array of $.oBackdrop objects.

Current behavior

Returns an array with empty values for each backdrop in the group.

Possible Solution

Seems to be an issue with the .map Array method. It's not a method described in ES3, so maybe some porting issues from Toon Boom? A function within the .map returns empty values.

Compatibility issue with hasSubAttributes

//Older versions of Harmony don't have hasSubAttributes, or getSubAttributes

This is a big one, I hadn't realised... We can't possibly hard code this for every single attribute that has subattributes ?

Is there a way to list attributes of the attribute object maybe?

We could start a compatibility branch that would include a Json or xml dictionary of node attributes exported from 17? How far do we want to go with this idea of supporting older versions? Our power in bringing new features to old versions might be limited.

Setting attributes that have subattributes

case "POSITION_3D" :

So the way I had set it up was that I would have a dummy object for the attributes that have subattributes with the getter setters inside for the subattributes.

The question is how do we do it so we can set the attributes as well as the subattributes? We can't really make the attribute a getter setter since it needs to hold the subattributes as objects.

I was looking at the most common nodes and it seemed you'd set up the subattributes anyway most of the time (like offset.x, offset.y etc seperately) but I can see moments where we'd want to set the whole thing at once by passing a oPoint object, and also for colors where we might want to pass a hex string.

I'm a bit stuck there...

Review new dynamic getter/setter

for (var i in _attributes){
var _attr = _attributes[i]
// create getter setters only for attributes without subattributes,
// otherwise create an object to host the subattributes getter setters
this.setAttrGetterSetter( _attr );
if ( _attr.subAttributes.length >0 ){
var _keyword = _attr.keyword.toLowerCase();
for (var j in _attr.subAttributes){
this.setAttrGetterSetter(_attr.subAttributes[j], _keyword );
}
}
}
}
/**
* Private function to create attributes setters and getters as properties of the node
* @private
* @return {void} Nothing returned.
*/
oNode.prototype.setAttrGetterSetter = function ( attr, parent_attr ){
var _keyword = attr.shortKeyword.toLowerCase();
// hard coding a fix for 3DPath attribute name which starts with a number
if (_keyword == "3dpath") _keyword = "path3d";
//System.println( "Setting getter setters for attribute: "+_keyword+" of node: "+this.name );
var has_parent_attr = true;
if (typeof context === 'undefined') has_parent_attr = false;
//Only set the getter/setter if this base class doesn't already have it.
//There were issued with X,Y,Z,POSITION, ECT. So, we prevent the overlap.
if( typeof( has_parent_attr ? this[parent_attr][_keyword] : this[_keyword] ) === 'undefined' ){
try{
var getSet_obj = {
get : function(){
// System.println( "getting attribute "+attr.keyword+". animated: "+(attr.column != null) );
//MessageLog.trace("getting attribute "+attr.keyword+". animated: "+(attr.column != null))
// if attribute has animation, return the frames
if (attr.column != null) return attr.frames
// otherwise return the value
return attr.getValue()
},
set : function(newValue){
//MessageLog.trace("setting attribute "+attr.keyword+" to value: ")
// if attribute has animation, passed value must be a frame object
if (attr.column != null) {
if (!newValue instanceof oFrame) throw "must pass an oFrame object to set an animated attribute"
attr.setValue(newValue.value, newValue.frameNumber)
}else{
return attr.setValue(newValue);
}
}
}
//The exact object has to be used, using a variable to provide the object to extends results in extending that temporary variable, not this as the source.
if( has_parent_attr ){
Object.defineProperty( this[parent_attr], _keyword, getSet_obj );
}else{
Object.defineProperty( this, _keyword, getSet_obj );
}
}catch( err ){
System.println( err + " (" + err.lineNumber + ")" );
}
}else{
// System.println( "Failed in setting getter setters for attribute: "+_keyword+" of node: "+this.name );
}
};

What are your thoughts on this? the preview version of the dynamic getter/setter simply didnt seem to work, and there was uncaught errors when the node's attributes already existed as members of that class.

This new method seems to work, but I'm not convinced that keeping attributes and members in the same place. This at least prevents issues.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.