Automate GitHub repo creation and configuration in nbdev_new

I would personally use this feature a lot! Here's a hacky script I use at the moment which automates the whole First steps section:
#!/bin/bash
set -e -o pipefail

user=seem
repo=sketch-tmpdir
desc='Sketch of a temp dir interface for notebook-driven development'

gh repo create $repo -c -d $desc -h $user.github.io/$repo --public --disable-wiki

cd $repo
nbdev_new
git add .
git commit -m'Initial commit'
git push

jq -n '{"source":{"branch":"gh-pages"}}' | gh api repos/$user/$repo/pages --input -

Closing this. With @hamelsmu's recent improvements, the homepage and description are configured by nbdev_new. The only thing remaining is to create the repo, which I don't think nbdev needs to handle (cc @jph00)


ipptool: DELAY directive should be applied to each retry

If you have an ipptool test file like so:
{
DELAY 2
NAME "Get printer-uuid using an IPP Get-Printer-Attributes operation"
OPERATION Get-Printer-Attributes
Version 2.0
GROUP operation
ATTR charset attributes-charset utf-8
ATTR language attributes-natural-language en
ATTR uri printer-uri $uri
ATTR keyword requested-attributes printer-uuid
STATUS successful-ok
EXPECT printer-fake-attribute REPEAT-NO-MATCH REPEAT-LIMIT 10
}
I am seeing a 10 repeat limit and a DELAY of 2 seconds. So would that mean it would take:

2 seconds for delay + 11 tries as fast as it could go (first try + 10 retries)
11 tries, each with a 2 second delay
10 tries, each with a 2 second delay
Something else?

It is my belief that it should be #2.

OK, so the existing code uses a semi-random delay between repeats (a fibonacci sequence: 1 1 2 3 5 8). What I've done is change this to use a default repeat interval (delay) of 5 seconds, which is overridden by the DELAY directive. The DELAY directive now also allows you to specify two numbers to control the initial delay and the repeat interval separately, e.g.:
DELAY 30,10

specifies an initial delay of 30 seconds and 10 seconds between repeated tests.
[master f292e13] Update DELAY directive to control repeat interval (Issue #38)

(this will get mirrored to the ippeveselfcert repository as soon as I finish up the CUPS 2.2.4 changes, which will get mirrored to ippsample and ippeveselfcert...)


Upgrade to Vuetify 3

Hi there,
in the documentation, it is mentioned that an update to Vue 3 will be done as soon as Vuetify supports it. As Vuetify 3 is already released, I want to ask, when this will happen.
Thank you

+1 ^ would really love if this supports Vue 3

Any updates here?


internal/vibrate: crash with iOS 12

iOS 12 doesn't support CoreHaptics. Should we drop supporting iOS 12, or create a fallback?
Exception Note:  EXC_CORPSE_NOTIFY
Termination Description: DYLD, Library not loaded: /System/Library/Frameworks/CoreHaptics.framework/CoreHaptics | ...

/CC @wasedaigo

purego would enable dynamic loading of the framework.


Problem to connect at Metalnx-web using SSL

Dear,
I am trying to connect at my Metalnx-web new installation through SSL and it is not working. Could you help me please?
I installed the latest rpm that I found at https://bintray.com/metalnx/rpm/emc-metalnx-web .
I run the script python /opt/emc/setup_metalnx.py and it cannot connect to iRODS. So I commented the lines subprocess.check_call(irods_auth_params, stdout=os_devnull) to connect to iRODS in the file lib/utils.py.
The war generated by this script didn't deploy well. So I download the project from the github and compiled it, changing the configurations at the files database.properties, irods.environment.properties, mysql.properties and security.properties.
I am using the URL https://<IP_ADDRESS>:8443/emc-metalnx-web/login/ .  Do you have any clue of how can I fix it?
Kind Regards, Felipe

What do your config files look like?

File security.properties only the line encoded.properties=
File mysql.properties >
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://<IP_ADDRESS>:3306/metalnx
db.username=metalnx
db.password=metalnx
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.hbm2ddl.auto=update
connection.pool_size=10

File irods.environment.properties >
irods.host=<IP_ADDRESS>
irods.port=1247
irods.zoneName=tempZone
irods.auth.scheme=STANDARD
jobs.irods.username=rods
jobs.irods.password=123456
jobs.irods.auth.scheme=STANDARD
runSyncJobs=true
rmd.connection.timeout=500
rmd.connection.port=8000
reverse.dns.lookup=false

File database.properties >
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://<IP_ADDRESS>:3306/metalnx
db.username=metalnx
db.password=metalnx
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.hbm2ddl.auto=update
connection.pool_size=10

Your config files look fine... Can you post the beginning of your core.re file as well?

the only property I changed on the core.re file is acPreConnect(*OUT) { *OUT="CS_NEG_REQUIRE"; } to use SSL in iCommands. Which is working on the server and on a remote client.

I apologize. The core.re file isn't in play here. I misunderstood the problem. Let me take a quick look at the code here.

I'm not sure if it is related, but I made some changes in methods that change the database... Maybe if you drop Metalnx db and create it again might solve the problem.

I assume that you are using PAM auth in iRODS, right @felipegutierrez ?

Hi @neogeo68 . I drop the database and created it again with setup_metalnx.py. I noticed that when I set the configurations of iRODS the scripts does not ask about SSL certificates.
iRODS Paramaters:
    * irods.port = 1247
    * irods.auth.scheme = STANDARD
    * irods.zoneName = tempZone
    * jobs.irods.username = rods
    * irods.host = <IP_ADDRESS>

On my /var/lib/irods/.irods/irods_environment.json file I have these parameters to use SSL certificate.
{
    "irods_host": "irodsprodvm.ebioscience.amc.nl",
    "irods_port": 1247,
    "irods_default_resource": "demoResc",
    "irods_home": "/tempZone/home/rods",
    "irods_cwd": "/tempZone/home/rods",
    "irods_user_name": "rods",
    "irods_zone_name": "tempZone",
    "irods_client_server_negotiation": "request_server_negotiation",
    "irods_client_server_policy": "CS_NEG_REQUIRE",
    "irods_encryption_key_size": 32,
    "irods_encryption_salt_size": 8,
    "irods_encryption_num_hash_rounds": 16,
    "irods_encryption_algorithm": "AES-256-CBC",
    "irods_default_hash_scheme": "SHA256",
    "irods_match_hash_policy": "compatible",
    "irods_server_control_plane_port": 1248,
    "irods_server_control_plane_key": "TEMPORARY__32byte_ctrl_plane_key",
    "irods_server_control_plane_encryption_num_hash_rounds": 16,
    "irods_server_control_plane_encryption_algorithm": "AES-256-CBC",
    "irods_maximum_size_for_single_buffer_in_megabytes": 32,
    "irods_default_number_of_transfer_threads": 4,
    "irods_transfer_buffer_size_for_parallel_transfer_in_megabytes": 4,
    "irods_ssl_certificate_chain_file": "/etc/irods/ssl/irods.crt",
    "irods_ssl_certificate_key_file": "/etc/irods/ssl/irods.key",
    "irods_ssl_dh_params_file": "/etc/irods/ssl/dhparams.pem",
    "irods_ssl_ca_certificate_file": "/etc/irods/ssl/irods.crt"
}

Hi @ajguerra . No. I am only using SSL, but no PAM anymore.

@felipegutierrez I posted an article on our Wiki about how to handle SSL in iRODS and Metalnx. I also added some information about PAM based on your first issue you opened here.
The article has 4 sections: SSL config in iRODs, PAM, Metalnx and Tomcat. Since you mentioned that you have an iRODS grid already set up with SSL, I think you can skip the first two sections and go straight to the Metalnx and Tomcat configuration parts.
There is an explaination about how you tell the setup_script to look for the certificates as you mentioned. It may not be the best solution yet, but it is working. We can talk about adding some more intelligence to it later on if you want.
At any rate, take a look at the Wiki post when you can. Let me know how things go.
Cheers!

many thanks. I will do =)

Hi @ajguerra , I got to put SSL to work with Metalnx through your Wiki post. THanks very much =)
By now I am getting the message You do not have permissions to access information in the target collection or for the selected object. when I try to search for collection of all users (rods and normal user). I am gonna put on the dev environment to debug the code. Maybe it is the same problem of PostgreSQL x MySQL. If this is the problem I fork again the new version of Metalnx-web and create the queries and services like you mentioned before.
by the way, are you from Campinas, Brasil? Because I saw on the SSL certificate.

Felipe,
Jumping in as Arthur is away at present.  Looking into the source code this error message happens only if you try to look at the object into tab (in the Collections view) on a file or object you have no permission to.  The other place would be if you try to enter a location on the breadcrumb you do not have access to.    Can you provide details on the steps you performed so we can try to minic what you see on our systems?

Hi @felipegutierrez ,
Did you figure out whether or not the problem was related to the sql queries? I think it's a good idea for you to try using your queries for a MySQL DB.

hi, sorry for the late. I will try to figure out what is. The problem is that I have to update my irule that it is the priority. But after that Metalnx is on the queue. Lasts days I look at the Tomcat log and irods log and I didn't see any thing strange. So it is why I think I need to debug. But when I have some answer I will communicate here. Best Regards

What do you mean by updating your irule?

our production environment. it is not related to Metalnx-web

So, is everything working now?

Hi @ajguerra , I changed the attributes hibernate.show_sql=true, log4j.rootLogger=DEBUG, stdout, log4j.logger.org.springframework=DEBUG, log4j.logger.org.thymeleaf=DEBUG, log4j.logger.org.irods=DEBUG, log4j.logger.org.hibernate.SQL=DEBUG, log4j.logger.org.hibernate.type=DEBUG but I cannot see from where comes the message You do not have permissions to access information in the target collection or for the selected object.      Back.
I am checking the logs from tomcat /usr/share/tomcat/logs.
Do you have any clue?
Thanks

@felipegutierrez I can see this message in two places: collectionInfo.html and collectionsBrowser.html (this text is replaced on the span tag with th:text="#{collections.management.msg.access.denied}").
collectionInfo.html is returned from the server when a request to /info/ is made (check out CollectionController).
collectionsBrowser.html I think is more related to your problem because it is returned by the server everytime you browse through collections. So, take a look again at CollectionController, more specifically at the getSubdirectories and getCollBrowserView methods. This last one actually retrieves permissions for the user on the current path, so the problem might be there.
Let me know if it helped.

@felipegutierrez did you get your stuff working?

Hi @ajguerra ,
I think I have a problem with SSL on my iRODS server. Because I can deploy the same Metalnx-web.war on an iRODS server with no SSL and it works. So the queries are fine. On my iRODS server with SSL I can connect using iCommands and also using Metalnx-web, but I still cannot search for collections. If I change the IP to search for Collections on the remote MySQL with no SSL it works. It is strange, because I access the Metalnx-web using SSL https://<IP_ADDRESS>:8443/emc-metalnx-web/collections/ and use the MySQL host to the server with no SSL and it access. When I change back to my localhost MySQL it does not show the collections. And does not show SQL errors.
First I was thinking the problem was Grant privileges and I fixed it. Both servers have the same grants but only the one that does not use SSL can list the COllections.

I have some clue. This error appears on iRODS server only when I login Metalnx with SSL. Therefore I still can login, but I cannot list collections.
Jun  7 09:14:56 pid:13061 NOTICE: Agent exiting with status = 0
Jun  7 09:14:56 pid:13060 ERROR: [-]	iRODS/server/re/src/rules.cpp:670:actionTableLookUp :  status [PLUGIN_ERROR_MISSING_SHARED_OBJECT]  errno [] -- message []
	[-]	iRODS/server/re/src/irods_ms_plugin.cpp:110:load_microservice_plugin :  status [PLUGIN_ERROR_MISSING_SHARED_OBJECT]  errno [] -- message [Failed to create ms plugin entry.]
		[-]	iRODS/lib/core/include/irods_load_plugin.hpp:175:load_plugin :  status [PLUGIN_ERROR_MISSING_SHARED_OBJECT]  errno [] -- message [shared library does not exist [/var/lib/irods/plugins/microservices/libmsiobjget_microservices.so]]

Jun  7 09:14:56 pid:13060 ERROR: executeRuleAction Failed for msiobjget_microservices status = -1097000 NO_RULE_OR_MSI_FUNCTION_FOUND_ERR

@felipegutierrez those errors you see when you login showed up because you either have an old version of the Metalnx MSI package or don't have it installed at all. The msiobjget_microservices in particular is used on the dashboard page to list the microservices you have available in iRODS. So, I don't think this is related to the collections page issue.
Back to the problem... I just put up an iRODS instance with SSL turned on. Everything worked fine in Metalnx. I also set up Metalnx with HTTP and HTTPS and in both cases the app was able to show items on the collections page. If you are in a dev environment it might be worth setting up a Postgres instance just to confirm everything works fine on your end too.
What I can tell you is that SSL has been a real issue for us. Jargon actually released two other versions trying to fix those SSL and performance issues we found (by we I include you as well because you found the first issues on PAM authentication).
Some of those issues could be fixed in Jargon, but some couldn't since they were iRODS bugs. I'm not fully aware of all of them, but the Jargon devs run into issues with the iRODS API, the consortium's confirmed that issues do exist and they will be fixed in the next version (I think 4.1.11). Perharps your bug may be part of it (if it's not something else).


Two excplicit implementations of generic interface are indistinguishable

Hi,
when I have a generic interface and ....  Who cares about text description when an example is much better way of description:
Steps To Reproduce
https://deck.net/dcfbd181768de167d401304dc80c670c
this C# code
void IFace<T>.Method() => Console.WriteLine("single");
void IFace<List<T>>.Method() => Console.WriteLine("list");

produces this JS code
Demo$Program$IFace$1$Method: function () {
    System.Console.WriteLine("single");
},
Demo$Program$IFace$1$Method: function () {
    System.Console.WriteLine("list");
}

Both methods have the same name. Calling the first and then the second method produces incorrect output.
((IFace<int>)c).Method();
((IFace<List<int>>)c).Method();

Expected Result
single
list
Actual Result
list
list

The pull request with fixes resolving this issue has just been merged upstream! The fix will be available next Bridge version (see attached milestone).


Deprecated on npm?

What are we supposed to use instead if this is deprecated? The Electron docs still recommend this.

@rwwagner90 Did you find any alternatives? There may be node.js modules that allow us to do the same but I havent found any.

I did not find any alternatives. We are still using this.

@rwwagner90 @LeBlackDonut
There are 2 npm packages for this project, one of them is deprecated and shows the version as 1.0.4, the other one is 1.0.0 like this project so I do not know what happened. None of them seem to be published by @felixrieseberg.
https://www.npmjs.com/package/electron-notification-state
https://www.npmjs.com/package/@meetfranz/electron-notification-state

@rwwagner90 @LeBlackDonut
There are 2 npm packages for this project, one of them is deprecated and shows the version as 1.0.4, the other one is 1.0.0 like this project so I do not know what happened. None of them seem to be published by @felixrieseberg.
https://www.npmjs.com/package/electron-notification-state
https://www.npmjs.com/package/@meetfranz/electron-notification-state

Hmm, maybe this package is still fine to use then, it just never had an official npm release?

Hmm, maybe this package is still fine to use then, it just never had an official npm release?

Yeah this package is @meetfranz/electron-notification-state and it is not deprecated.

has anyone one found a way to use this package? I am having trouble importing this. When I build the electron app (.dmg), it is giving me a 'file name undefined' error.


Color adjustments from Kelin

Kelin (UA programme manager with an architecture background) kindly joined a UX session, and we generated lots of ideas to improve ATIP. Here are a few simple ones.
First, change the colors for routes and areas, and add an outline to areas. The outline emphasizes it as a clickable object more, and matches how Felt looks. Before:

After:

And second, change the color scale used for showing speed limits. It's based on the first big map in https://knightlab.northwestern.edu/2016/07/18/three-tools-to-help-you-make-colorblind-friendly-graphics/, but we adjusted the middle color, since it's too close to the basemap background color. We adjusted it by using the "mix" tool in gpick, mixing it with the category for 40 mph. Before:

After:

Also note that the new color for routes (grey) is sort of a "continuation" of that color scale, a darker version of the "< 20" speed category. This is meant to suggest new route infrastructure proposed will be even better than an naturally slow-traffic street.
We can take any or all of the ideas here. Any opinions?

I think the first changes look great, and the reasoning sounds sound for all changes.
To me the second changes... well it's quite noisy in either case. I think it's hard to follow the map with speed limits but I think that might be a tricky problem to solve. I think the changes make it look better but still difficult, so I kinda like this as what seems like a step in the right direction

These are clear improvements I think, except for the speed limits. I think dark red for the fastest road category is good. Overall thumbs up and will add my review accordingly.

Thank you for the reviews / feedback! I reverted the changes to the speed limit layer for now, since that needs more thought. Just changing base intervention colors and adding area outlines.

can some of these configs be usefully split out as, for example, speed-limit-cols.json?

We partly do: https://github.com/acteng/atip/blob/main/src/colors.ts. For some projects, I've tried centralizing most color choices. A downside is loss of context and requiring sufficiently explanatory names for everything. At the moment, I think the codebase is small enough to work as it is (and easy to live-edit with Vite's hot reloading). If/when we properly handle multiple basemaps, then we could revisit how we organize color code. That's a much harder problem -- all color choices depend on the maptiler style, and don't look good against satellite.

Looking good for Manchester where I plan to demo ATIP v2-latest (OK with that as a version name for now Dustin, with thinking still being that v3 will have the updated schemas after #231 ?) to transport planners.

@EmmaVinterATE shared a link to guidance on colourschemes for data visualisation, could you share the link to that if you get a chance Emma?

No problem, here is the link: https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/ . Hope this helps!


Shadow properties created for Foreign key by copying over principal k…

…ey properties types use nullable types
resolves #4895

:shipit:


Architecture

I was trying to tackle the problem of animation export (all animation, correct naming etc.) and I ran into a few problems, mainly to do with code duplication and comprehension.
Currently it looks like the exporter is trying to generate the final export data in-place, this is admirable, as it saves time by avoiding intermediate representation, but I propose that it is a bad practice, as it makes code really tangled. One of the major problems with this approach is notion of identity, currently name string is being used for identification in functions like this:
def get_index(elements, name):
    """
    Return index of a glTF element by a given name.
    """

    if elements is None or name is None:
        return -1

    index = 0
    for element in elements:
        if element.get('name') is None:
            return -1

        if element['name'] == name:
            return index

        index += 1

    return -1

problem is - name is non-unique according to spec.
Another problem with this approach is validation - there is little in code that would ensure validity of generated export.
I propose using an intermediate representation, building that, and then converting it to final export. Using such intermediate format would allow us to bypass some discomfort imposed by the format (index as ID for example), as well as perform various kinds of validation in-pace to make sure the export adheres to json schema defined by glTF spec.
Below is such an intermediate representation, I will not have time to work on this for the foreseeable future, so I thought I would share my thoughts as well as current work, in case there is interest to continue that work.
class GLTFProperty:
    def __init__(self):
        self.extensions = {}
        self.extras = {}

class ChildOfRootProperty(GLTFProperty):
    def __init__(self, name=None):
        super(ChildOfRootProperty, self).__init__();

        self.name = name
        """
        :type: str
        The user-defined name of this object.  This is not necessarily unique, e.g., an accessor and a buffer could have the same name, or two accessors could even have the same name.
        """

class Texture(ChildOfRootProperty):
    """
    A texture and its sampler.
    """

    def __init__(self, sampler, source, name=None):
        super(Texture, self).__init__(name=name)

        if sampler is not None:
            if not isinstance(sampler, Sampler):
                raise ValueError("sampler must be instance of Sampler")

        if not isinstance(source, Image):
            raise ValueError("source must be instance of Image")

        self.sampler = sampler
        """
        :type: Sampler
        The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used.
        """
        self.source = source
        """
        :type: Image
        The index of the image used by this texture.
        """

class Scene(ChildOfRootProperty):
    """
    The root nodes of a scene.
    """

    def __init__(self, nodes, name=None):
        super(Scene, self).__init__(name=name)
        self.nodes = nodes
        """
        :type: list of Node
        """

class Node(ChildOfRootProperty):
    """
    A node in the node hierarchy.  When the node contains `skin`, all `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes.  A node can have either a `matrix` or any combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted to matrices and postmultiplied in the `T * R * S` order to compose the transformation matrix; first the scale is applied to the vertices, then the rotation, and then the translation. If none are provided, the transform is the identity. When a node is targeted for animation (referenced by an animation.channel.target), only TRS properties may be present; `matrix` will not be present.
    """

    def __init__(self, camera, children, skin, matrix, mesh, rotation, scale, translation, weights, name=None):
        super(Node, self).__init__(name=name)
        # TODO add validation
        # TODO add documentation
        self.camera = camera
        self.children = children
        self.skin = skin
        self.matrix = matrix
        self.mesh = mesh
        self.rotation = rotation
        self.scale = scale
        self.translation = translation
        self.weights = weights

class MeshPrimitive(GLTFProperty):
    """
    Geometry to be rendered with the given material.
    """
    POINTS = 0
    LINES = 1
    LINE_LOOP = 2
    LINE_STRIP = 3
    TRIANGLES = 4
    TRIANGLE_STRIP = 5
    TRIANGLE_FAN = 6

    def __init__(self, attributes, indices, material, targets, mode=TRIANGLES):
        super(MeshPrimitive, self).__init__()

        # TODO add validation

        self.attributes = attributes
        """
        :type: dict
        A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data.
        """
        self.indices = indices
        """
        :type: Accessor
        The index of the accessor that contains mesh indices.  When this is not defined, the primitives should be rendered without indices using `drawArrays()`.  When defined, the accessor must contain indices: the `bufferView` referenced by the accessor should have a `target` equal to 34963 (ELEMENT_ARRAY_BUFFER); `componentType` must be 5121 (UNSIGNED_BYTE), 5123 (UNSIGNED_SHORT) or 5125 (UNSIGNED_INT), the latter may require enabling additional hardware support; `type` must be `\"SCALAR\"`. For triangle primitives, the front face has a counter-clockwise (CCW) winding order.
        """
        self.material = material
        """
        :type: Material
        The index of the material to apply to this primitive when rendering.
        """
        self.mode = mode
        """
        :type: int
        The type of primitives to render. All valid values correspond to WebGL enums
        """
        self.targets = targets
        """
        :type: list of dict
        An array of Morph Targets, each  Morph Target is a dictionary mapping attributes (only `POSITION`, `NORMAL`, and `TANGENT` supported) to their deviations in the Morph Target.
        """

class Mesh(ChildOfRootProperty):
    """
    A set of primitives to be rendered.  A node can contain one mesh.  A node's transform places the mesh in the scene.
    """

    def __init__(self, primitives, weights, name=None):
        super(Mesh, self).__init__(name=name)

        # TODO add validation

        self.primitives = primitives
        """
        :type: list of MeshPrimitive
        An array of primitives, each defining geometry to be rendered with a material.
        """
        self.weights = weights
        """
        :type: list of double
        Array of weights to be applied to the Morph Targets.
        """

class CameraOrthographic(GLTFProperty):
    """
    An orthographic camera containing properties to create an orthographic projection matrix.
    """

    def __init__(self, xmag, ymax, zfar, znear):
        super(CameraOrthographic, self).__init__()

        if zfar <= 0.0:
            raise ValueError("zfar must be greater than 0.0")

        if znear < 0.0:
            raise ValueError("znear must be greater than or equal to 0.0")

        self.xmag = xmag
        """
        :type: double
        The floating-point horizontal magnification of the view. Must not be zero.
        """
        self.ymax = ymax
        """
        :type: double
        The floating-point vertical magnification of the view. Must not be zero.
        """
        self.zfar = zfar
        """
        :type: double
        The floating-point distance to the far clipping plane. `zfar` must be greater than `znear`.
        """
        self.znear = znear
        """
        :type: double
        The floating-point distance to the near clipping plane.
        """

class CameraPerspective(GLTFProperty):
    """
    A perspective camera containing properties to create a perspective projection matrix.
    """

    def __init__(self, yfov, znear, zfar, aspectRatio):
        super(CameraPerspective, self).__init__()

        # TODO add validation

        self.aspectRatio = aspectRatio
        """
        :type: double
        The floating-point aspect ratio of the field of view. When this is undefined, the aspect ratio of the canvas is used.
        """
        self.yfov = yfov
        """
        :type: double
        The floating-point vertical field of view in radians.
        """
        self.zfar = zfar
        """
        :type: double
        The floating-point distance to the far clipping plane. When defined, `zfar` must be greater than `znear`. If `zfar` is undefined, runtime must use infinite projection matrix.
        """
        self.znear = znear
        """
        :type: double
        The floating-point distance to the near clipping plane.
        """

class Camera(ChildOfRootProperty):
    """
    A camera's projection.  A node can reference a camera to apply a transform to place the camera in the scene.
    """
    PERSPECTIVE = "perspective"
    ORTHOGRAPHIC = "orthographic"

    valid_types = [PERSPECTIVE, ORTHOGRAPHIC]

    def __init__(self, type, perspective=None, orthographic=None, name=None):
        super(Camera, self).__init__(name=name)

        if type not in Camera.valid_types:
            raise ValueError("type must be one of " + ', '.join(Camera.valid_types) + ", instead was '" + type + "'");

        self.orthographic = orthographic
        """
        :type: CameraOrthographic
        An orthographic camera containing properties to create an orthographic projection matrix.
        """
        self.perspective = perspective
        """
        :type: CameraPerspective
        A perspective camera containing properties to create a perspective projection matrix.
        """
        self.type = type
        """
        :type: str
        Specifies if the camera uses a perspective or orthographic projection.  Based on this, either the camera's `perspective` or `orthographic` property will be defined.
        """

class Skin(ChildOfRootProperty):
    """
    Joints and matrices defining a skin.
    """

    def __init__(self, joints, inverseBindMatrices, skeleton=None, name=None):
        super(Skin, self).__init__(name=name)

        self.inverseBindMatrices = inverseBindMatrices
        """
        :type: Accessor
        The index of the accessor containing the floating-point 4x4 inverse-bind matrices.  The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied.
        """
        self.skeleton = skeleton
        """
        :type: Node
        The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root.
        """
        self.joints = joints
        """
        :type: list of Node
        Indices of skeleton nodes, used as joints in this skin.  The array length must be the same as the `count` property of the `inverseBindMatrices` accessor (when defined).
        """

class TextureInfo(GLTFProperty):
    """
    Reference to a texture.
    """

    def __init__(self, texture, texCoord=0):
        super(TextureInfo, self).__init__()

        self.texture = texture
        """
        :type: Texture
        """
        self.texCoord = texCoord
        """
        :type: int
        "This integer value is used to construct a string in the format `TEXCOORD_<set index>` which is a reference to a key in mesh.primitives.attributes (e.g. A value of `0` corresponds to `TEXCOORD_0`). Mesh must have corresponding texture coordinate attributes for the material to be applicable to it.
        """

class MaterialPBRMetallicRoughness(GLTFProperty):
    """
    A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology.
    """

    def __init__(
            self,
            baseColorTexture,
            metallicRoughnessTexture,
            baseColorFactor=None,
            metallicFactor=1.0,
            roughnessFactor=1.0
    ):
        super(MaterialPBRMetallicRoughness, self).__init__()

        if baseColorFactor is None:
            baseColorFactor = [1.0, 1.0, 1.0, 1.0]
        else:
            if len(baseColorFactor) != 4:
                raise ValueError("baseColorFactor length must be equal to 4")
            for v in baseColorFactor:
                if v < 0.0 or v > 1.0:
                    raise ValueError("baseColorFactor elements must be between 0.0 and 1.0 (inclusive)")

        if metallicFactor < 0.0 or metallicFactor > 1.0:
            raise ValueError("metallicFactor must be between 0.0 and 1.0 (inclusive)")

        if roughnessFactor < 0.0 or roughnessFactor > 1.0:
            raise ValueError("roughnessFactor must be between 0.0 and 1.0 (inclusive)")

        self.baseColorFactor = baseColorFactor
        """
        :type: list of double
        he RGBA components of the base color of the material. The fourth component (A) is the alpha coverage of the material. The `alphaMode` property specifies how alpha is interpreted. These values are linear. If a baseColorTexture is specified, this value is multiplied with the texel values.
        """
        self.baseColorTexture = baseColorTexture
        """
        :type: TextureInfo
        The base color texture. This texture contains RGB(A) components in sRGB color space. The first three components (RGB) specify the base color of the material. If the fourth component (A) is present, it represents the alpha coverage of the material. Otherwise, an alpha of 1.0 is assumed. The `alphaMode` property specifies how alpha is interpreted. The stored texels must not be premultiplied.
        """
        self.metallicFactor = metallicFactor
        """
        :type: double
        The metalness of the material. A value of 1.0 means the material is a metal. A value of 0.0 means the material is a dielectric. Values in between are for blending between metals and dielectrics such as dirty metallic surfaces. This value is linear. If a metallicRoughnessTexture is specified, this value is multiplied with the metallic texel values.
        """
        self.roughnessFactor = roughnessFactor
        """
        :type: double
        The roughness of the material. A value of 1.0 means the material is completely rough. A value of 0.0 means the material is completely smooth. This value is linear. If a metallicRoughnessTexture is specified, this value is multiplied with the roughness texel values.
        """
        self.metallicRoughnessTexture = metallicRoughnessTexture
        """
        :type: TextureInfo
        The metallic-roughness texture. The metalness values are sampled from the B channel. The roughness values are sampled from the G channel. These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
        """

class MaterialNormalTextureInfo(TextureInfo):
    """
    Material Normal Texture Info
    """

    def __init__(self, texture, texCoord=0, scale=1.0):
        super(MaterialNormalTextureInfo, self).__init__(texture=texture, texCoord=texCoord)

        self.scale = scale
        """
        :type: double
        The scalar multiplier applied to each normal vector of the texture. This value scales the normal vector using the formula: `scaledNormal =  normalize((normalize(<sampled normal texture value>) * 2.0 - 1.0) * vec3(<normal scale>, <normal scale>, 1.0))`. This value is ignored if normalTexture is not specified. This value is linear.
        """

class MaterialOcclusionTextureInfo(TextureInfo):
    """
    Material Occlusion Texture Info
    """

    def __init__(self, texture, texCoord=0, strength=1.0):
        super(MaterialOcclusionTextureInfo, self).__init__(texture=texture, texCoord=texCoord)

        if strength < 0.0 or strength > 1.0:
            raise ValueError("strength must be between 0.0 and 1.0 (inclusive)")

        self.strength = strength
        """
        :type: double
        A scalar multiplier controlling the amount of occlusion applied. A value of 0.0 means no occlusion. A value of 1.0 means full occlusion. This value affects the resulting color using the formula: `occludedColor = lerp(color, color * <sampled occlusion texture value>, <occlusion strength>)`. This value is ignored if the corresponding texture is not specified. This value is linear.
        """

class Material(ChildOfRootProperty):
    """
    The material appearance of a primitive.
    """
    OPAQUE = "OPAQUE"
    """
    The alpha value is ignored and the rendered output is fully opaque
    """
    MASK = "MASK"
    """
    The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified alpha cutoff value.
    """
    BLEND = "BLEND"
    """
    The alpha value is used to composite the source and destination areas. The rendered output is combined with the background using the normal painting operation (i.e. the Porter and Duff over operator).
    """

    def __init__(
            self,
            pbrMetallicRoughness=None,
            normalTexture=None,
            occlusionTexture=None,
            emissiveTexture=None,
            emissiveFactor=None,
            alphaMode=OPAQUE,
            alphaCutoff=0.5,
            doubleSided=False,
            name=None
    ):
        super(Material, self).__init__(name=name)

        if emissiveFactor is None:
            emissiveFactor = [0.0, 0.0, 0.0]
        else:
            if len(emissiveFactor) != 3:
                raise ValueError("emissiveFactor must have length equal to 3")
            for v in emissiveFactor:
                if v < 0 or v > 1:
                    raise ValueError("emissiveFactor values must be between 0 and 1 (inclusive)")

        self.pbrMetallicRoughness = pbrMetallicRoughness
        """
        :type: MaterialPBRMetallicRoughness
        A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of `pbrMetallicRoughness` apply.
        """
        self.normalTexture = normalTexture
        """
        :type: MaterialNormalTextureInfo
        A tangent space normal map. The texture contains RGB components in linear space. Each texel represents the XYZ components of a normal vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green [0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z [1/255 to 1]. The normal vectors use OpenGL conventions where +X is right and +Y is up. +Z points toward the viewer. In GLSL, this vector would be unpacked like so: `float3 normalVector = tex2D(<sampled normal map texture value>, texCoord) * 2 - 1`.
        """
        self.occlusionTexture = occlusionTexture
        """
        :type: MaterialOcclusionTextureInfo
        The occlusion map texture. The occlusion values are sampled from the R channel. Higher values indicate areas that should receive full indirect lighting and lower values indicate no indirect lighting. These values are linear. If other channels are present (GBA), they are ignored for occlusion calculations.
        """
        self.emissiveTexture = emissiveTexture
        """
        :type: TextureInfo
        The emissive map controls the color and intensity of the light being emitted by the material. This texture contains RGB components in sRGB color space. If a fourth component (A) is present, it is ignored.
        """
        self.emissiveFactor = emissiveFactor
        """
        :type: list of double
        The RGB components of the emissive color of the material. These values are linear. If an emissiveTexture is specified, this value is multiplied with the texel values.
        """
        self.alphaMode = alphaMode
        """
        :type: str
        The material's alpha rendering mode enumeration specifying the interpretation of the alpha value of the main factor and texture.
        """
        self.alphaCutoff = alphaCutoff
        """
        :type: double
        Specifies the cutoff threshold when in `MASK` mode. If the alpha value is greater than or equal to this value then it is rendered as fully opaque, otherwise, it is rendered as fully transparent. A value greater than 1.0 will render the entire material as fully transparent. This value is ignored for other modes.
        """
        self.doubleSided = doubleSided
        """
        :type: bool
        Specifies whether the material is double sided. When this value is false, back-face culling is enabled. When this value is true, back-face culling is disabled and double sided lighting is enabled. The back-face must have its normals reversed before the lighting equation is evaluated.
        """

class Sampler(ChildOfRootProperty):
    """
    Texture sampler properties for filtering and wrapping modes.
    """
    NEAREST = 9728
    LINEAR = 9729

    NEAREST_MIPMAP_NEAREST = 9984
    LINEAR_MIPMAP_NEAREST = 9985
    NEAREST_MIPMAP_LINEAR = 9986
    LINEAR_MIPMAP_LINEAR = 9987

    CLAMP_TO_EDGE = 33071
    MIRRORED_REPEAT = 33648
    REPEAT = 10497

    valid_mag_filters = [NEAREST, LINEAR]
    valid_min_filters = [
        NEAREST,
        LINEAR,
        NEAREST_MIPMAP_NEAREST,
        LINEAR_MIPMAP_NEAREST,
        NEAREST_MIPMAP_LINEAR,
        LINEAR_MIPMAP_LINEAR
    ]

    valid_wrapping_modes = [CLAMP_TO_EDGE, MIRRORED_REPEAT, REPEAT]

    def __init__(self, magFilter, minFilter, wrapS, wrapT, name=None):
        super(Sampler, self).__init__(name=name)

        if magFilter not in Sampler.valid_mag_filters:
            raise ValueError("magFilter must be one of " + ', '.join(
                str(x) for x in Sampler.valid_mag_filters)) + ", instead was " + magFilter

        if minFilter not in Sampler.valid_min_filters:
            raise ValueError("minFilter must be one of " + ', '.join(
                str(x) for x in Sampler.valid_min_filters)) + ", instead was " + minFilter

        if wrapS not in Sampler.valid_wrapping_modes:
            raise ValueError("wrapS must be one of " + ', '.join(
                str(x) for x in Sampler.valid_wrapping_modes)) + ", instead was " + wrapS

        if wrapT not in Sampler.valid_wrapping_modes:
            raise ValueError("wrapT must be one of " + ', '.join(
                str(x) for x in Sampler.valid_wrapping_modes)) + ", instead was " + wrapT

        self.magFilter = magFilter
        """
        :type: int
        Magnification filter.  Valid values correspond to WebGL enums: `9728` (NEAREST) and `9729` (LINEAR).
        """
        self.minFilter = minFilter
        """
        :type: int
        Minification filter.  All valid values correspond to WebGL enums.
        """
        self.wrapS = wrapS
        """
        :type: int
        S (U) wrapping mode.  All valid values correspond to WebGL enums.
        """
        self.wrapT = wrapT
        """
        :type: int
        T (V) wrapping mode.  All valid values correspond to WebGL enums.
        """

class Image(ChildOfRootProperty):
    """
    Image data used to create a texture. Image can be referenced by URI or `bufferView` index. `mimeType` is required in the latter case.
    """
    valid_mime_types = [
        "image/jpeg",
        "image/png"
    ]

    def __init__(self, uri, bufferView, mimeType, name=None):
        super(Image, self).__init__(name=name)

        if uri is None and bufferView is None:
            raise ValueError("either uri or bufferView must be given, both were None instead")

        if uri is not None and bufferView is not None:
            raise ValueError("either uri or bufferView must be given, both were given instead")

        if bufferView is not None:
            if not isinstance(bufferView, BufferView):
                raise ValueError("bufferView must be instance of BufferView")

            if mimeType is None:
                raise ValueError("mimeType must be given if bufferView is used")

            if mimeType not in Image.valid_mime_types:
                raise ValueError(
                    "mimeType must be one of " + ', '.join(Image.valid_mime_types) + ", instead was '" + mimeType + "'")

        self.uri = uri
        """
        :type: str
        The uri of the image.  Relative paths are relative to the .gltf file.  Instead of referencing an external file, the uri can also be a data-uri.  The image format must be jpg or png.
        """
        self.bufferView = bufferView
        """
        :type: BufferView
        The index of the bufferView that contains the image. Use this instead of the image's uri property.
        """
        self.mimeType = mimeType

class AccessorSparseValues(GLTFProperty):
    """
    Array of size `accessor.sparse.count` times number of components storing the displaced accessor attributes pointed by `accessor.sparse.indices`.
    """

    def __init__(self, bufferView, byteOffset=0):
        super(AccessorSparseValues, self).__init__()

        if not isinstance(bufferView, BufferView):
            raise ValueError("bufferView must be instance of BufferView")

        self.bufferView = bufferView
        """
        :type: BufferView
        The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target.
        """

        self.byteOffset = byteOffset
        """
        :type:int
        The offset relative to the start of the bufferView in bytes. Must be aligned.
        """

class AccessorSparseIndices(GLTFProperty):
    """
    Indices of those attributes that deviate from their initialization value
    """

    UNSIGNED_BYTE = 5121
    UNSIGNED_SHORT = 5123
    UNSIGNED_INT = 5125

    def __init__(self, bufferView, componentType, byteOffset=0):
        super(AccessorSparseIndices, self).__init__()

        if not isinstance(bufferView, BufferView):
            raise ValueError("bufferView must be instance of BufferView")

        valid_component_types = [
            AccessorSparseIndices.UNSIGNED_BYTE,
            AccessorSparseIndices.UNSIGNED_SHORT,
            AccessorSparseIndices.UNSIGNED_INT
        ]

        if componentType not in valid_component_types:
            raise ValueError("componentType must be one of " + ','.join(
                str(x) for x in valid_component_types) + ", instead was " + componentType)

        if bufferView.target in [BufferView.ARRAY_BUFFER, BufferView.ELEMENT_ARRAY_BUFFER]:
            raise ValueError("Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target");

        self.bufferView = bufferView
        """
        :type: BufferView
        The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target.
        """
        self.byteOffset = byteOffset
        """
        :type: int
        The offset relative to the start of the bufferView in bytes. Must be aligned.
        """
        self.componentType = componentType
        """
        :type: int
        The indices data type.  Valid values correspond to WebGL enums: `5121` (UNSIGNED_BYTE), `5123` (UNSIGNED_SHORT), `5125` (UNSIGNED_INT).
        """

class AccessorSparse(GLTFProperty):
    """
    Sparse storage of attributes that deviate from their initialization value.
    """

    def __init__(self, count, indices, values):
        super(AccessorSparse, self).__init__()
        self.count = count
        """
        :type: int
        The number of attributes encoded in this sparse accessor.
        """
        self.indices = indices
        """
        :type: AccessorSparseIndices
        Index array of size `count` that points to those accessor attributes that deviate from their initialization value. Indices must strictly increase.
        """
        self.values = values
        """
        :type: AccessorSparseValues
        Array of size `count` times number of components, storing the displaced accessor attributes pointed by `indices`. Substituted values must have the same `componentType` and number of components as the base accessor.
        """

class Accessor(ChildOfRootProperty):
    """
    A typed view into a bufferView.  A bufferView contains raw binary data.  An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's `vertexAttribPointer()` defines an attribute in a buffer.
    """

    BYTE = 5120
    UNSIGNED_BYTE = 5121
    SHORT = 5122
    UNSIGNED_SHORT = 5123
    UNSIGNED_INT = 5125
    FLOAT = 5126

    SCALAR = "SCALAR"
    VEC2 = "VEC2"
    VEC3 = "VEC3"
    VEC4 = "VEC4"
    MAT2 = "MAT2"
    MAT3 = "MAT3"
    MAT4 = "MAT4"

    valid_component_types = [
        BYTE,
        UNSIGNED_BYTE,
        SHORT,
        UNSIGNED_SHORT,
        UNSIGNED_INT,
        FLOAT
    ]

    valid_types = [
        SCALAR,
        VEC2,
        VEC3,
        VEC4,
        MAT2,
        MAT3,
        MAT4,
    ]

    def __init__(self, componentType, count, type, bufferView=None, byteOffset=0, normalized=False, name=None):
        super(Accessor, self).__init__(name=name)

        if componentType not in Accessor.valid_component_types:
            raise ValueError("componentType must be one of " + ', '.join(
                str(x) for x in Accessor.valid_component_types) + ", instead was '" + componentType + "'")

        if type not in Accessor.valid_types:
            raise ValueError("type must be one of " + ', '.join(
                str(x) for x in Accessor.valid_types) + ", instead was '" + type + "'")

        if count < 0:
            raise ValueError("count must be non-negative")

        self.bufferView = bufferView
        """
        :type: BufferView
        The index of the bufferView. When not defined, accessor must be initialized with zeros; `sparse` property or extensions could override zeros with actual values.
        """
        self.byteOffset = byteOffset
        """
        :type: int
        The offset relative to the start of the bufferView in bytes.  This must be a multiple of the size of the component datatype.
        """
        self.componentType = componentType
        """
        :type: int
        The datatype of components in the attribute.  All valid values correspond to WebGL enums.  The corresponding typed arrays are `Int8Array`, `Uint8Array`, `Int16Array`, `Uint16Array`, `Uint32Array`, and `Float32Array`, respectively.  5125 (UNSIGNED_INT) is only allowed when the accessor contains indices, i.e., the accessor is only referenced by `primitive.indices`.
        """
        self.normalized = normalized
        """
        :type: bool
        Specifies whether integer data values should be normalized (`true`) to [0, 1] (for unsigned types) or [-1, 1] (for signed types), or converted directly (`false`) when they are accessed. This property is defined only for accessors that contain vertex attributes or animation output data.
        """
        self.count = count
        """
        :type: int
        The number of attributes referenced by this accessor, not to be confused with the number of bytes or number of components.
        """
        self.type = type
        """
        :type: str
        Specifies if the attribute is a scalar, vector, or matrix.
        """
        self.max = []
        """
        :type: double
        Maximum value of each component in this attribute.  Array elements must be treated as having the same data type as accessor's `componentType`. Both min and max arrays have the same length.  The length is determined by the value of the type property; it can be 1, 2, 3, 4, 9, or 16.\n\n`normalized` property has no effect on array values: they always correspond to the actual values stored in the buffer. When accessor is sparse, this property must contain max values of accessor data with sparse substitution applied.
        """
        self.min = []
        """
        :type: double
        Minimum value of each component in this attribute.  Array elements must be treated as having the same data type as accessor's `componentType`. Both min and max arrays have the same length.  The length is determined by the value of the type property; it can be 1, 2, 3, 4, 9, or 16.\n\n`normalized` property has no effect on array values: they always correspond to the actual values stored in the buffer. When accessor is sparse, this property must contain min values of accessor data with sparse substitution applied.
        """
        self.sparse = None
        """
        :type: AccessorSparse
        Sparse storage of attributes that deviate from their initialization value.
        """

class Buffer(ChildOfRootProperty):
    """
    A buffer points to binary geometry, animation, or skins.
    """

    def __init__(self, name=None):
        super(Buffer, self).__init__(name=name)

        self.uri = None
        """
        :type: str
        :format: uniref
        The uri of the buffer.  Relative paths are relative to the .gltf file.  Instead of referencing an external file, the uri can also be a data-uri.
        """
        self.byteLength = 0
        """
        :type: int
        The length of the buffer in bytes.
        """

class BufferView(ChildOfRootProperty):
    """
    A view into a buffer generally representing a subset of the buffer.
    """

    ARRAY_BUFFER = 34962
    ELEMENT_ARRAY_BUFFER = 34963

    valid_targets = [ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER]

    def __init__(self, buffer, byteLength, byteStride=None, bufferOffset=0, target=None, name=None):
        super(BufferView, self).__init__(name=name)

        if not isinstance(buffer, Buffer):
            raise ValueError("buffer must be instance of Buffer")

        if byteLength < 0:
            raise ValueError("byteLength must be non-negative")

        if byteStride is not None:
            if byteStride < 4:
                raise ValueError("byteStride must not be less than 4")

            if byteStride > 252:
                raise ValueError("byteStride must not be greater than 252")

            if not byteStride % 4 == 0:
                raise ValueError("byteStride must be multiple of 4")

        if target is not None:
            if target not in BufferView.valid_targets:
                raise ValueError("target must be one of " + ','.join(
                    str(x) for x in BufferView.valid_targets) + ", instead was " + target)

        self.buffer = buffer
        """
        :type: Buffer
        The index of the buffer.
        """
        self.byteOffset = bufferOffset
        """
        :type: int
        The offset into the buffer in bytes.
        """
        self.byteLength = byteLength
        """
        :type: int
        The length of the bufferView in bytes.
        """
        self.byteStride = byteStride
        """
        :type: int
        The stride, in bytes, between vertex attributes.  When this is not defined, data is tightly packed. When two or more accessors use the same bufferView, this field must be defined.
        """
        self.target = target
        """
        :type: int
        The target that the GPU buffer should be bound to.
        """

class AnimationPath:
    TRANSLATION = "translation"
    ROTATION = "rotation"
    SCALE = "scale"
    WEIGHTS = "weights"

class AnimationChannelTarget(GLTFProperty):
    """
    Animation Channel Target
    """

    def __init__(self, node, path):
        super(AnimationChannelTarget, self).__init__()

        if not isinstance(node, Node):
            raise ValueError("node must be instance of Node")

        valid_paths = [
            AnimationPath.TRANSLATION,
            AnimationPath.ROTATION,
            AnimationPath.SCALE,
            AnimationPath.SCALE
        ]

        if path not in valid_paths:
            raise ValueError("path must be one of " + ','.join(
                str(x) for x in valid_paths) + ", instead was '" + path + "'")

        self.node = node
        """
        :type: Node
        The index of the node to target.
        """
        self.path = path
        """
        :type: str
        The name of the node's TRS property to modify, or the \"weights\" of the Morph Targets it instantiates. For the \"translation\" property, the values that are provided by the sampler are the translation along the x, y, and z axes. For the \"rotation\" property, the values are a quaternion in the order (x, y, z, w), where w is the scalar. For the \"scale\" property, the values are the scaling factors along the x, y, and z axes.
        """

class AnimationChannel(GLTFProperty):
    def __init__(self, sampler, target):
        super(AnimationChannel, self).__init__()

        if not isinstance(sampler, AnimationSampler):
            raise ValueError("sampler must be instance of AnimationSampler")

        if not isinstance(target, AnimationChannelTarget):
            raise ValueError("target must be instance of AnimationChannelTarget")

        self.sampler = sampler
        """
        :type: AnimationSampler
        The index of a sampler in this animation used to compute the value for the target, e.g., a node's translation, rotation, or scale (TRS).
        """
        self.target = target
        """
        :type: AnimationChannelTarget
        The index of the node and TRS property to target.
        """

class InterpolationType:
    """
      Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target).
      """
    LINEAR = "LINEAR"
    """
    The animated values are linearly interpolated between keyframes. When targeting a rotation, spherical linear interpolation (slerp) should be used to interpolate quaternions. The number output of elements must equal the number of input elements.
    """
    STEP = "STEP"
    """
    The animated values remain constant to the output of the first keyframe, until the next keyframe. The number of output elements must equal the number of input elements.
    """
    CATMULLROMSPLINE = "CATMULLROMSPLINE"
    """
    The animation's interpolation is computed using a uniform Catmull-Rom spline. The number of output elements must equal two more than the number of input elements. The first and last output elements represent the start and end tangents of the spline. There must be at least four keyframes when using this interpolation.
    """
    CUBICSPLINE = "CUBICSPLINE"
    """
    The animation's interpolation is computed using a cubic spline with specified tangents. The number of output elements must equal three times the number of input elements. For each input element, the output stores three elements, an in-tangent, a spline vertex, and an out-tangent. There must be at least two keyframes when using this interpolation.
    """

class AnimationSampler(GLTFProperty):

    def __init__(self, input, output, interpolation=InterpolationType.LINEAR):
        super(AnimationSampler, self).__init__()

        if not isinstance(input, Accessor):
            raise ValueError("input must be instance of Accessor")

        if not isinstance(output, Accessor):
            raise ValueError("output must be instance of Accessor")

        valid_interpolations = [
            InterpolationType.LINEAR,
            InterpolationType.STEP,
            InterpolationType.CATMULLROMSPLINE,
            InterpolationType.CUBICSPLINE
        ]

        if interpolation not in valid_interpolations:
            raise ValueError(
                "interpolation must be one of " + ','.join(
                    str(x) for x in valid_interpolations) + ", instead was '" + interpolation + "'")

        self.input = input
        """
        :type: Accessor
        The index of an accessor containing keyframe input values, e.g., time. That accessor must have componentType `FLOAT`. The values represent time in seconds with `time[0] >= 0.0`, and strictly increasing values, i.e., `time[n + 1] > time[n]`.
        """
        self.interpolation = interpolation
        """
        :type: str
        Interpolation algorithm.
        """
        self.output = output
        """
        :type: Accessor
        The index of an accessor containing keyframe output values. When targeting TRS target, the `accessor.componentType` of the output values must be `FLOAT`. When targeting morph weights, the `accessor.componentType` of the output values must be `FLOAT` or normalized integer where each output element stores values with a count equal to the number of morph targets.
        """

class Animation(ChildOfRootProperty):
    """
    A keyframe animation.
    """

    def __init__(self, name):
        super(Animation, self).__init__(name=name)

        self.samplers = []
        """
        :type: list of AnimationSampler
        An array of channels, each of which targets an animation's sampler at a node's property. Different channels of the same animation can't have equal targets.
        """
        self.channels = []
        """
        :type: list of AnimationChannel
        An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target).
        """

class Context(GLTFProperty):
    """
    The root object for a glTF asset.
    """

    def __init__(self):
        super(Context, self).__init__()

        self.animations = []
        """
        :type: list of Animation
        An array of keyframe animations.
        """
        self.buffers = []
        """
        :type: list of Buffer
        An array of buffers.  A buffer points to binary geometry, animation, or skins.
        """
        self.bufferViews = []
        """
        :type: list of BufferView
        An array of bufferViews.  A bufferView is a view into a buffer generally representing a subset of the buffer.
        """
        self.accessors = []
        """
        :type: list of Accessor
        An array of accessors.  An accessor is a typed view into a bufferView.
        """
        self.images = []
        """
        :type: list of Image
        An array of images.  An image defines data used to create a texture.
        """
        self.cameras = []
        """
        :type: list of Camera
        An array of cameras.  A camera defines a projection matrix.
        """
        self.materials = []
        """
        :type: list of Material
        An array of materials.  A material defines the appearance of a primitive.
        """
        self.meshes = []
        """
        :type: list of Mesh
        An array of meshes.  A mesh is a set of primitives to be rendered.
        """
        self.nodes = []
        """
        :type: list of Node
        An array of nodes.
        """
        self.samplers = []
        """
        :type: list of Sampler
        An array of samplers.  A sampler contains properties for texture filtering and wrapping modes.
        """
        self.scenes = []
        """
        :type: list of Scene
        An array of scenes.
        """
        self.skins = []
        """
        :type: list of Skin
        An array of skins.  A skin is defined by joints and matrices.
        """
        self.textures = []
        """
        :type: list of Texture
        An array of textures.
        """

        self.asset = {}
        """
        Metadata about the glTF asset.
        """

        self.scene = None
        """
        :type: Scene
        The index of the default scene.
        """

        self.extensionsRequired = []
        """
        :type: list of str
        Names of glTF extensions required to properly load this asset.
        """
        self.extensionsUsed = []
        """
        :type: list of str
        Names of glTF extensions used somewhere in this asset.
        """

    def createAnimation(self):
        result = Animation()
        self.animations.append(result)
        return result

    def createBuffer(self):
        result = Buffer()
        self.buffers.append(result)
        return result

    def createBufferView(self):
        result = BufferView()
        self.bufferViews.append(result)
        return result

    def createAccessor(self):
        result = Accessor()
        self.accessors.append(result)
        return result

    def createImage(self):
        result = Image()
        self.images.append(result)
        return result

I think this is largely resolved in https://github.com/KhronosGroup/glTF-Blender-IO.


better catching when cfn-lint isn't installed

Issue #, if available:
Description of changes:

Better catching errors when cfn-lint isn't installed

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Should we more directly point towards installation instructions?
Worried this'll just lead to issues in a different repo rather than being clear about separate installations being required


#551: AAF Writer provide media references to UNC Paths

Josh, this looks great. Since the cxx branch has landed into master, could you rebase this onto the master branch?
Also, it would be good to include a unit test for this so it doesn't break in the future. Some of the AAFs in the existing unit tests have UNC Paths in them (e.g. adapters/tests/sample_data/simple.aaf) that you could use.

Will do.

I'm getting some weird conflicts doing the rebase, so I'm gonna close this PR and manually create a new one against master. Sorry for the trouble.


Field map service may return outdated content

Hi, I have this use case in which I believe the FieldMapService returns old data. It only happens with modular content and the scenario is as follows:

I get an item with modular content via DeliveryClient.item().get().
I go to app.kenticocloud.com and update one of item's modular items.
I get the same item with modular content again.
The raw response (response.debug.rawResponse.xhr.responseText) contains fresh data, however the processed item returned from the SDK is of an old version.

IMHO, the problem lies in the fact, that same instance of DeliveryClient is used for both requests, which has same instance of QueryService and somewhere inside same instance of FieldMapService caches/memoizes its processed items without clearing the cache in between consecutive requests.
To be precise, the item root item is always recomputed aside from being added to processedItems. However, the modular content is taken from (possibly outdated) cache, which results in outdated data being returned.
I am not 100% sure I have investigated this correctly, could you please have a look?

You were absolutely right, thank you for reporting the issue! The FieldMapService didn't clear resolved items unless it was reinitialized. This could be an issue especially for SPA. I've released a fix in 2.5.1 version.
Can you try that and let me know?

Works as intended, thanks for a quick fix! :)


query/gallery vehicle id and cam id.

Hi, I notice that there is no query and gallery ids in the data. In your code, the query vehicle ids are all '2' and cam ids are 1. I do not see how to evaluate models with these labels. Or do I miss something?

Thanks.

I am working on the AIC2020 DATA.

Because it is a competition, there is no label for the test set. Therefore, we assume that PID = 2 and camid = 1 which are consistent with the training process dataset format.
Thank you.


converting .ply to obj - any solutions?

python3 ply2obj.py cloud.ply cloud.obj
Traceback (most recent call last):
  File "ply2obj.py", line 52, in <module>
    main()
  File "ply2obj.py", line 16, in main
    convert(PlyData.read(args.ply_filename), args.obj_filename)
  File "ply2obj.py", line 35, in convert
    n = [ v['nx'], v['ny'], v['nz'] ]
ValueError: no field of name nx

used https://gist.github.com/Helias/268d48e8a925b6681fb7a6afe8169593
also tried pptk commands
>>> import pptk
>>> import numpy as np
>>> import plyfile
Note

If pip install plyfile does not work, simply save a local copy of plyfile.py from plyfile’s github page.

Read vertices in Truck.ply.

>>> data = plyfile.PlyData.read('Truck.ply')['vertex']
Use per-vertex attributes to make numpy arrays xyz, rgb, and n.

>>> xyz = np.c_[data['x'], data['y'], data['z']]
>>> rgb = np.c_[data['red'], data['green'], data['blue']]
>>> n = np.c_[data['nx'], data['ny'], data['nz']]

also not able to visualize ply in  ctmviewer

Hi @AndreV84   If you are able to make use of the Python point cloud library pyntcloud, perhaps you could use it to convert a ply file to a solid mesh by computing a convex hull with the guide in the link below:
https://stackoverflow.com/questions/44997054/point-cloud-to-volume
pyntcloud can read and write ply and obj formats:
https://pyntcloud.readthedocs.io/en/latest/io.html#i-o

Thank you very much
Trying it

python3
Python 3.6.9 (default, Oct  8 2020, 12:12:24)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyntcloud import PyntCloud
>>> diamond = PyntCloud.from_file("cloud.ply")
>>> convex_hull_id = diamond.add_structure("convex_hull")
>>> convex_hull = diamond.structures[convex_hull_id]
>>> diamond.to_file("diamond_hull.obj", also_save=["mesh"])

like that? or I need to specify actually custom [convex_hull_id] in the line
convex_hull = diamond.structures[convex_hull_id] ?

colors seem missed

input file
https://storage.googleapis.com/nvidia-dev/113620.ply

@MartyG-RealSense
from maintainers of the proposed tool:
Hola @AndreV84 , I'm afraid that pyntcloud does not currently support reading/writing to/from .obj files with per-vertex color information.
As far as I remember when adding the .obj code, vertex coloring is not considered part of the format "standard" and it is only supported by a few applications (i.e. Meshlab).
Looking at how it appears to be implemented in Meshlab (https://gamedev.stackexchange.com/a/66270) it might be feasible to add support to vertex coloring for the .obj format in pyntcloud with minor modifications, will change the label to Feature Request. Contributions are allways wellcome.

meshlab seems to do the export
also https://github.com/daavoo/pyntcloud/issues/293

Apologies for the delay in responding further.   Great news that you arrived at a solution!


If MAP_ALIGNED_SUPER is not available, don't use it.

Only freebsd provides it of the BSDs.

I'm a little unclear on this one. So, if that define isn't set to 0, the it will be used?

if MAP_ALIGNED_SUPER is not available, do | 0 in the mmap flags, which is a no-op

Makes sense. Could you amend the commit message to include that information. It might be handy in the future to have around for a quite reference.

Thanks @coypoop!


add permataindonesia

@onlyirul First stage of verification passed, official domain is permataindonesia.ac.id as far as I verified. This is just an informational note. Pull request is still in the review. I appreciate your patience while waiting for the final decision.

@onlyirul Please provide us with an URL of a page on the official website where the school offers an IT-related long-term (>1 year) course.

Hi @philipto, We have a study program with the name Medical Records. Several courses in the medical record program are creating an application/software related to medical records in the hospital.
Here is the page for the Medical Records study program.
https://permataindonesia.ac.id/program-studi/program-studi/d-iii-rekam-medis-dan-infokes