Giter Club home page Giter Club logo

cmdx's Issues

Move docs into gh-pages branch

Command reference is currently generated with Sphinx locally, and pushed to the repository alongside code. This is problematic because..

  1. The user need all dependencies (Maya, Sphinx, nose, plug-ins) in order to update the docs
  2. Repository bloated with generated content, hard to know what to edit when making changes.

Instead, each push should trigger a re-build of the documentation and upload into the gh-pages branch of this repository.

Multiple versions simultaneously

You can embed cmdx in a library and distribute it alongside your tool. This has the benefit of not depending on anything external and guarantee the functionality and behavior of your tool. As well as enabling you to make changes to the embedded version of cmdx specific to your tool.

But, the user currently can't have two or more versions of cmdx loaded at the same time if their versions differ. E.g. one version embedded in toolA and another version embedded in toolB and maybe a third version installed globally. That's because the undo/redo mechanism is built as a Maya plug-in, and there can only ever be one plug-in by the name of cmdx. That's a bummer! And a bug.

Plugs not taking and returning UI units

Hey, whilst implementing the AnimCurve updates (almost done), I noticed this bug with the way plugs are read and written.

Under the Units section of the README it mentions that cmdx takes and returns values in units used by the UI, but this doesn't seem to be the case. It looks like it's currently using Maya's internal units by default. You can confirm this by running this (with Maya UI set to degrees):

node = cmdx.createNode(cmdx.tTransform)
node["rx"] = 5
node["rx", cmdx.Degrees]
# Result: 286.478897565 #

I believe I know how to fix this and am happy to do it, but it will be a bit of work so I'm just checking that using the UI units is the intended functionality before I start?

It seems like using the UI units would make the most sense.

Compound of Compounds

For some reason, compound children of a compound locks up Maya, presumably there's an infinite loop somewhere.

import cmdx

node = cmdx.createNode("transform")

# This works
node.addAttr(
    cmdx.Compound("parent", children=[
        cmdx.Double("age"),
        cmdx.Double("height")
    ])
)

# This does *not*
node.addAttr(
    cmdx.Compound("parent", children=[
        cmdx.Compound("child", children=[
            cmdx.Double("age"),
            cmdx.Double("height")
        ])
    ])
)

How to debug Maya crashes?

Apologies for the perhaps noobish question, but I keep getting hard crashes (Maya shows a "fatal error" dialog, then the crash reporter, then exits), which I'm 90% sure come from cmdx (more specifically, modifying existing compound attributes on a DagNode).

I don't have debug symbols (tips on where to find them? I installed the 2020 devkit but I don't see any pdbs), so the dump doesn't tell me much, except for a partial callstack (somewhat better in the crash log). The crash comes from python > OpenMaya > DependEngine.dll plug::name.

Here's the crash dump in case someone's feeling generous :) ! But I'd appreciate any tips on how to debug myself.
dumpdata.zip

Modifiers and data types

This bit me recently.

node = cmdx.createNode("translate")
node["translateX"] = 5
node["translateY"] = 5
node["translateZ"] = 5

with cmdx.DagModifier() as mod:
  mod.setAttr(node["translate"], 0)

What value do you expect translate to have?

  1. [0, 0, 0]?
  2. [5, 5, 5]?
  3. [5, 0, 0]?
  4. [0, 5, 5]?
  5. [15161353, 135135136425, 63513]?

That's right! The numbers will be garbage!

What happened?

Here's what the underlying code looks like.

if isinstance(value, int):
    mod.newPlugValueInt(mplug, value)

Where value is what you gave the modifier, in this case 0. 0 is an integer, which would prompt a call to MDGModifier.newPlugValue(). Notice how it has Int int its name? Whatever value you give it, it will assume that it is an int. An int has a pre-defined number of bytes, so what Maya will do here is reach into your int for that number of bytes, even though the actual attribute needs many more bytes than that, as it is a compound attribute. translateX, Y and Z.

So, translateX migth be given the correct value, but the remaining values would get garbage. You didn't pass enough bytes, it would likely try and reach into memory it doesn't own and assign that. Resulting in gibberish values, possibly random, possibly random unless that space in memory is occupied by something else, like a YouTube video or whatever.

How do I solve it?

Make sure you pass enough data. Maya will not tell you whether or not you did, you'll have to know what you are doing.

# Alternative 1
with cmdx.DagModifier() as mod:
  mod.setAttr(node["translateX"], 0)
  mod.setAttr(node["translateY"], 0)
  mod.setAttr(node["translateZ"], 0)

# Alternative 2

with cmdx.DagModifier() as mod:
  mod.setAttr(node["translateX"], [0, 0, 0])

Future Work

Normal attribute setters account for this.

node = cmdx.createNode("transform")
node["translate"] = 5

This figures out that you meant (5, 5, 5) because translate is a compound of 3 children.

We could address this for modifiers in the same way. If it's a Vector3, and you've passed a single int, convert that int to 3 floats. It would come at a performance cost (likely from querying of the attribute type), so we could put it behind a safe=True that defaults to True and enables the user to optimise by disabling it.

Support keyframing of pairBlended attributes

Whenever you keyframe a constrained channel, it'll create a pairBlend. If you then subsequently attempt to..

node["translateX"] = {1: 0.0, 5: 10.0, 7: 11.0}

Then that new animation will break thee pairBlend, rather than get redirected to wherever it's blending.

Attribute reuse and undo

Whenever we access a plug, we initiate a call to MFnDependencyNode.findPlug which searches for a plug by name. We speed that up by caching any previous search and store the found plug like this.

if "plugName" in cache:
  return cache["plugName"]

Which means we no longer perform the search and instead return it by hashing this native Python dictionary. Fast. And that works great, with one exception. Undo!

When an attribute is created and found, it is stored in the cache. If the user then undos the creation, the found attribute remains behind. So any subsequent accesses to this plug would leave Maya scratching it's head.

image

Any attempt at removing the "ghost connection" results in..

// Error: line 1: There is no connection from 'l_hair_4_2.blendSimulation' to 'pairBlend2.weight' to disconnect //

You are however able to re-connect to the attribute. But that's not good enough.

Direct passing of cmdx.Node to vanilla cmds

Using cmds.listRelatives as example, are there plans to allow direct passing of cmdx.Node objects, without explicitly converting them?
At the moment this is possible using cmds.listRelatives( str( myNode ) ) but will error out if the object is passed directly.
I tried this myself a while ago, but providing str() on the object is not enough to fool cmds, you actually need to subclass str itself. My solution never worked too well, I wondered if you had any other ideas on it, or plans for this in general.

MObjectHandle hashCode...

Hi:),
first of all, thank you: cmdx is beautifully written and indeed a nice performance boost compared to PyMel and maya.cmds.

I've just a question: wrappers are stored in the Singleton._instances dictionary by using node MObjectHandle::hashCode(); however the doc explicitly says that these are just hashes and clashes are possible for nodes in the same scene... Apparently it's just a hash and not an UUID.

In case of a clash, the original wrapper will be forgotten and I didn't check yet if that's an issue (probably not:))

What do you think?

Gui

cmdx.DgNode: sample usage?

I'm trying to use your declarative plugin syntax. I pasted your sample class, but then I'm not sure how to instantiate it?

I tried:
node = cmdx.create_node(MyNode)
but Maya complains:
TypeError: '<class 'MyNode'>' is not a valid node type

I then tried:
node = cmdx.create_node("MyNode")
but now:
TypeError: 'MyNode' is not a valid node type

I can simply do:
node = MyNode()
... but then I don't know what to do with the node (how to register it in the DAG).

Apologies for being quite the Maya noob, so what I'm trying to do might be misguided. What I really want to do is create my own node type, so I can add it to the scene, and serialize it/store information in it. I'm not sure it's easily done, I could I guess use custom attributes for this, but I was looking for a more "strongly typed" approach.

Oh, and I believe there's a typo in your example, there's no MTypeId in cmdx, IMO it should be simply typeid = cmdx.TypeId(0x85006) (no M).

cmdx.listRelatives always returns None

Steps to reproduce:

  1. Create cube primitive.
  2. run
    `import cmdx
    import maya.cmds as cmds

print cmdx.listRelatives("pCube1")
print cmds.listRelatives("pCube1")`

cmds.listRelatives returns [u'pCubeShape1'] while
cmdx.listRelatives("pCube1") returns None

Example from the command reference also returns false and not true.

cmdx 0.4.6
Maya 2020.2
Windows 10

Auto offset group

Zero out translate/rotate channels by adding an intermediate parent group to hold those values. This can be preferable to offsetParentMatrix since that's only supported by Maya 2020+.

Usage

Select one or more nodes with non-zero translate/rotate channels, and run this.

import cmdx

with cmdx.DagModifier() as mod:
    for src in cmdx.selection():
        group = mod.create_node("transform",
                                 name=src.name() + "_parent",
                                 parent=src.parent())
        mod.set_attr(group["translate"], src["translate"])
        mod.set_attr(group["rotate"], src["rotate"])
        mod.set_attr(src["translate"], (0, 0, 0))
        mod.set_attr(src["rotate"], (0, 0, 0))

        mod.parent(src, group)
offsetgroup.mp4

Support for proxy attributes

Namely..

cmds.addAttr("nodeA", ln="proxyTranslateX", proxy="nodeB.translateX")

The only question is, what should it look like?

nodeA["proxyTranslate", cmdx.Proxy] = nodeB["translateX"]

Also taking into account that we also need undo, and preferably handle it via the DG/DagModifier.

DGModifier.disconnect and Undo

For whatever reason, you can't disconnect and connect in the same "do_it" batch without breaking undo.

tx = cmdx.createNode("animCurveTL")
tx.keys(times=[1, 2, 3], values=[0.0, 1.0, 0.0])

tm = cmdx.createNode("transform")
tm["tx"] << tx["output"]

with cmdx.DagModifier() as mod:
  mod.disconnect(tm["tx"])
  mod.connect(tm["sx"], tm["tx"])

cmds.undo()
# Error: (kFailure): Unexpected Internal Failure
# Traceback (most recent call last):
#   File "C:/cmdx.py", line 5548, in undoIt
#     shared.undos[self.undo]()
# RuntimeError: (kFailure): Unexpected Internal Failure # 

attributes' default value not saved in scene

If I create an attribute with a default value, but don't change it, the value is lost after saving & reloading the scene.
I don't know if it's by design or not, but I do see the default value I set appear correctly in the attribute editor, so it's extremely confusing to have the attribute be wiped after reloading the scene.

Here's an all-in-one example:

  • create a new scene, add a sphere
  • add 3 attributes to the sphere, with default values
  • change the value of the first one
  • assign the second one's to the same value
  • don't touch the third one
  • save & reload the scene: you'll see the first attribute's value is "1a", but the second & third are empty!
# create new scene, no prompt
cmds.file(new=True, force=True) # new file, no prompt

sphere = cmdx.encode(cmds.sphere()[0])
sphere.add_attr(cmdx.String("string_1", default="1"))
sphere.add_attr(cmdx.String("string_2", default="2"))
sphere.add_attr(cmdx.String("string_3", default="3"))
sphere["string_1"] = "1a"  # will be saved
sphere["string_2"] = "2"   # will be lost!
                           # string_3 will also be lost!

# save & reload scene, no prompt
scene_path = "test.ma"
cmds.file(rename=scene_path)
cmds.file(save=True, type="mayaAscii")
cmds.file(scene_path, open=True, force=True)

DagNode.addChild crashes in certain circumstances

i identified a crash with addChild easy to reproduce

try this :

import cmdx as mx
j0 = mx.create_node(mx.tJoint)
j1 = mx.create_node(mx.tJoint, parent=j0)
j2 = mx.create_node(mx.tJoint, parent=j1)
j0.add_child(j2)

it crashes probably because j2 is a descendent of j0. you can fix it by removing the hierarchy link before like this

    def addChild(self, child, index=Last):
        mobject = child._mobject
        parent = child.parent()
        if parent is not None:
            parent._fn.removeChild(mobject)
        self._fn.addChild(mobject, index)

i think this is the simplest form possible since checking ancestors would require to find parents anyway. but i'm not sure about the performance impact of removing children if not necessary
what do you think?

unsupported type on cloned attribute

Am I doing it wrong? In this case I cannot copy the attribute.

        import cmdx
        cmds.file(new=True, force=True)
        src_node, src_node_shape = cmds.polyCube()
        dst_node, dst_node_shape = cmds.polyCube()
        attr_name = "test"
        cmds.addAttr(src_node, ln=attr_name, dt="string")

        s = cmdx.encode(src_node)

        print(s[attr_name].type())  # -> kTypedAttribute
        attr2 = s[attr_name].clone(attr_name)
        print(attr2.type())  # -> 4 ?

        d = cmdx.encode(dst_node)
        d.add_attr(attr2)

for some reason, the cloned attribute type is not supported when I add it to the destination node:
TypeError: Unsupported Python type '<class 'OpenMaya.MObject'>'

broken when imported before maya initialisation in mayapy

maya 2024 on windows 10.

run mayapy
import cmdx
this breaks however:
from cmdx import ContainerNode

with this error:
ImportError: cannot import name 'ContainerNode' from 'cmdx' (cmdx.py)

since there is no maya cmds yet the version check fails:

try:
    __maya_version__ = int(cmds.about(version=True))
except (AttributeError, ValueError):
    __maya_version__ = 2015  # E.g. Preview Release 95

and it is set to maya 2015. this in turn disables ContainerNode:

if __maya_version__ >= 2017:
    class ContainerNode(Node):

which makes the code fail in commandline tools that initialise maya after cmdx was imported.

2024-07-25 18:24:17:  0: STDOUT:   File "cmdx.py", line 7357, in listRelatives
2024-07-25 18:24:17:  0: STDOUT:     _parent = node.parent(type=type)
2024-07-25 18:24:17:  0: STDOUT:   File "cmdx.py", line 1853, in parent
2024-07-25 18:24:17:  0: STDOUT:     return cls(mobject)
2024-07-25 18:24:17:  0: STDOUT:   File "cmdx.py", line 480, in __call__
2024-07-25 18:24:17:  0: STDOUT:     sup = ContainerNode
2024-07-25 18:24:17:  0: STDOUT: NameError: name 'ContainerNode' is not defined

it is not always easy to control the order of imports in more complicated pipelines.

Apply Shader

In cmdx, object sets work like Python's native set(), and Maya's shaders are nothing but object sets. Add mesh to a shader like this.

import cmdx
tm = cmdx.createNode("transform")
shape = cmdx.createNode("mesh", parent=tm)
cube = cmdx.createNode("polyCube")
cube["width"] = 2.0
cube["output"] >> shape["inMesh"]

# Add to default Lambert
default_shader = cmdx.encode("initialShadingGroup")
default_shader.add(shape)

Animation Import/Export

Compatible with Maya 2015 SP4 --> Maya 2021+

Out of the many ways to export/import animation in Maya, from the built-in .atom format from 2013 to the .anim file format form 1993, here's a different take.

  1. ---> Export related animCurve nodes as a mayaAscii scene
  2. <--- Import animCurve nodes from the mayaAscii scene, elsewhere
  3. -><- Connect each node to their corresponding target

It is..

  • Simple ~100 lines of Python, no "clipboard", no "views", no "templates", no MEL, no smarts
  • Robust In that the original animation is preserved exactly
  • Lightweight In that only animated channels are exported, no pre-processing required
  • Fast As fast as Maya is able to export and import scene files
  • Native As a regular Maya Ascii file, import it yourself, anywhere and without the need for Python

With a few caveats..

  • No static channels
  • No animation layers
  • No partial-range export or import
  • Names must match exactly between export/import targets

Each of which could potentially be solved with some further tinkering.

animio1


Usage

  1. Download cmdx to your ~/maya/scripts folder
  2. Copy/paste the implementation below, and use it like this..

Based on your current selection

# Custom suffix
fname = cmds.file("anim1.manim", expandName=True, query=True)

# From scene A
export_animation(fname)

# From scene B
import_animation(fname)

Implementation

import cmdx
from maya import cmds  # for select()

def export_animation(fname):
    """Export animation for selected nodes to `fname`

    Animation is exported as native Maya nodes, e.g. animCurveTU
    and later imported and re-connected to their original nodes
    
    Limitations:
        - No animation layers
        - Names much match exactly between exported and imported nodes

    """

    animation = []
    for node in cmdx.selection(type="transform"):  # Optionally limited to transform nodes

        # Find any curve connecting to this node
        for curve in node.connections():
            if not isinstance(curve, cmdx.AnimCurve):
                continue

            # Encode target connection as string attribute
            # for retrieval during `import_animation`
            if not curve.has_attr("target"):
                curve["target"] = cmdx.String()

            # Find the attribute to which this curve connects
            plug = curve.connection(plug=True)
            curve["target"] = plug.path()
    
            animation.append(curve)

    if not animation:
        cmds.warning(
            "Select objects in the scene to "
            "export any connected animation"
        )

        return cmds.warning("No animation found, see Script Editor for details")

    previous_selection = cmds.ls(selection=True)
    cmds.select(map(str, animation))

    try:
        cmds.file(
            fname,

            # Overwrite existing
            force=True,

            # Use our own suffix
            defaultExtensions=False,

            # Internal format, despite our format
            type="mayaAscii",

            exportSelected=True,

            # We don't want anything but the selected animation curves
            constructionHistory=False
        )

    except Exception:
        import traceback
        traceback.print_exc()
        cmds.warning("Something unexpected happened when trying to export, see Script Editor for details.")

    finally:
        cmds.select(previous_selection)

    print("Successfully exported '%s'" % fname)


def import_animation(fname):
    previous_selection = cmds.ls(selection=True)

    try:
        animation = cmds.file(fname, i=True, returnNewNodes=True)

    except Exception:
        import traceback
        traceback.print_exc()
        return cmds.warning(
            "Something unexpected happened when trying "
            "to import '%s', see Script Editor for details"
            % fname
        )

    finally:
        cmds.select(previous_selection)

    for curve in animation:
        curve = cmdx.encode(curve)

        if not curve.has_attr("target"):
            cmds.warning("Skipped: '%s' did not have the `target` "
                                  "attribute used to reconnect the animation"
                                  % curve)
            continue

        # Stored as "<node>.<attr>" e.g. "pCube1.tx"
        node, attr = curve["target"].read().rsplit(".", 1)

        try:
            target = cmdx.encode(node)
        except cmdx.ExistError:
            cmds.warning("Skipped: '%s' did not exist" % node)
            continue

        # Make the connection, replacing any existing
        curve["output"] >> target[attr]

    print("Successfully imported '%s'!" % fname)

Next Steps

  1. Avoid nameclashes When importing animation the second time, the curve nodes share a name which throws a warning. By importing into a namespace, any node matching the name outside of this namespace can be removed prior to import
  2. Search-and-replace To support alternative names, namespaces and multiple instances of the same character or scene
  3. Animation Layers It'd be a matter of exporting not just the curve, but the network leading into the node being animated, which would include the animation layer setup.

An example of how names clash.

animio2


When to Use

That is, why not use .anim or .atom? If you can, you probably should. The are already installed (or are they?), they've got documentation (or do they?), they've got more features (of value?) and others may be able to help out when things go south (or can they?).

I would use it when what I want is to export/import animation from one scene to another without fuss; when I don't remember my login details to HighEnd3D and aren't interested in experimenting with the various MEL alternatives on there, and when I haven't got the budget nor time to understand or implement support for ATOM.

YMMV :) Let me know what you think!

design proposition for components

So i tried to implement some design for components (i use them a lot in my rigging scripts)

At the beggining i wanted to make a standalone class that would use cmdx. But the more I went further, the more I needed to modify the existing classes. that's why i also tried to implement them directly in cmdx

My biggest concern was the need to update how encode and ObjectSet should work with components. actually it's only designed to work with nodes. and i found it very tricky to work with deformers for example

Have a look at my last commit here wougzy@1d4aa2d

Check first how encode was updated. I made it more flexible but at the price of a very small check, so i'm not entirely sure about it. I also added a encodeList function that is faster than map(encode) (only if the list exceed 5 elements) and is more likely to work with components

I noticed how you had to deal with the legacy of MFnSet too. I modified that a bit to be able to use the api2 Fn. but i'm not sure about it. I should make some profiling to see if we have some performance benefits of using api2 when possible. in any case i made the ObjectSet compliant with components now (and even plugs)

see for example how easy it is to add vertex to some deformerSet :

dfm = cmdx.encode('cluster1')
points = cmdx.encode('pCube1.vtx[3]')
dfm.deformerSet.add(points)

print dfm.deformerSet.member().elements

i also added some deformer and shape to be able to cast components more easily. it's still very basic and there's probably a lot to do to implement the most useful functions of them. for now i kept it to the minimum before bringing more.

i liked the way you can smuggle data cache into Node. i took advantage of it by caching dagpath or iterators already built in Node instances.

Deploy on CI

@monkeez I've moved CI from Azure to GitHub Actions, since Azure wasn't playing ball anymore. It wasn't providing a good UI on GitHub either, making it hard to spot when and where things went wrong.

The documentation deployment got lost in the transition, and there are a few too many things I don't recognise in the setup. Would you happen to have a moment to see if you can port it over?

  # -----------------------------------------------------------------------
  #
  # Deploy docs
  #
  # -----------------------------------------------------------------------

  - job: Deploy_Docs
    condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
    pool:
      vmImage: "ubuntu-latest"
    steps:
      - task: DownloadPipelineArtifact@2
        inputs:
          artifact: docs
          path: $(Build.SourcesDirectory)/build/html

      - script: |
          git init
          git config --local user.name "Azure Pipelines"
          git config --local user.email "[email protected]"
          git add .
          touch .nojekyll
          git add .nojekyll
          git commit -m "Commit generated documentation"
        displayName: "Commit pages"
        workingDirectory: build/html

      - task: InstallSSHKey@0
        inputs:
          knownHostsEntry: $(KNOWN_HOST)
          sshKeySecureFile: deploy_key

      - script: |
          git remote add origin [email protected]:mottosso/cmdx.git
          git push -f origin HEAD:gh-pages
        displayName: "Publish GitHub Pages"
        workingDirectory: build/html

    dependsOn: Maya

I think it's safe to write as a separate "workflow" that only triggers on tags, similar to the PyPI deployment workflow currently in place.

AnimCurve class functionality is limited

I noticed that cmdx.AnimCurve is limited in what it can do. I was going to add some functionality but wanted to check with you first if you had any thoughts?

Matrix access API

Maya 2020 and beyond has a greatly improved UI for dealing with matrices. Let's facilitate that.

Sometimes, you just want to view or modify members of a matrix.

>>> import cmdx
>>> mat = cmdx.Matrix4()
>>> mat[1, 3]
5.5
>>> mat[0, 3] = 12  # I.e. translate X
>>> mat[0]  # Print row
(1, 0, 0, 12)
>>> mat[, 2]  # Print column
(1, 0, 0, 0)
>>> mat[0] = (1, 0, 0, 11)  # Write row
>>> someNode["myMatrix"] = cmdx.MatrixAttribute(default=mat)

Replicate NumPy's array interface for familiarity.

Add type annotations

Ive been using cmdx on and off for a few months now and I've been very happy with it!
My only (mild) pain point with it is the lack of type annotations

Would you be open for me to add that in?

I'm asking here before making a PR because I feel like this is something that should be supported by every contributor going forward so I don't want to start working on it if that's not something you want ๐Ÿ™‚

Mypy can be used to ensure the annotations are good as part of the CI/CD so it can be enforced and made consistent.

Type annotations can either be using the python 3 syntax:

def createNode(type: Union[str, MTypeId], name: Optional[str] = None, parent: Optional[DagNode] = None) -> Node:
    ...

or the python 2 compatible comment based syntax:

def createNode(type, name=None, parent=None):
    # type: (Union[str, MTypeId], Optional[str], Optional[DagNode]) -> Node
    ...

The python 3 syntax is more comfortable to use though considering cmdx supports older versions of Maya, the comment based syntax is probably the only viable option. We use this at work and works very well with both Mypy and Pylance (Apparently PyCharm supports it as well)

Let me know your thoughts on this ๐Ÿ‘

Set a nodes transform

Hey!

I am trying to align the transform of one node to another, but have got to a point where I can't see a way forward.
Here is my function:

def align_transforms_x(node1, node2):
    """
    Align the transform of node2 to node1

        Args:
            - node1: The target node.
            - node2: The node to align.

        Returns:
            None
    """
    node1 = cmdx.encode(node1)
    node2 = cmdx.encode(node2)
    transform = node1.transform()
    # this method does not exist:
    node2.set_transform(transform)

Is there something I am missing?

Two users, one machine

To support undo, cmdx installs itself as a plug-in using a unique filename. However! The file is written using the currently logged on users permission bits and places it in the system-wide temp directory. Therefore, if another user logs in and attempts to use cmdx, bam!

This is a bug.

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.