Giter VIP home page Giter VIP logo

specklesystems / speckle-sharp Goto Github PK

View Code? Open in Web Editor NEW
334.0 17.0 150.0 356.91 MB

.NET SDK, Schema and Connectors: Revit, Rhino, Grasshopper, Dynamo, ETABS, AutoCAD, Civil3D & more.

Home Page: https://speckle.systems

License: Apache License 2.0

C# 89.53% CMake 0.16% C 0.01% C++ 9.99% Python 0.30% Shell 0.01%
speckle grasshopper revit rhino desktop aec interop 3d interoperability autodesk

speckle-sharp's Introduction


Speckle | Sharp

.NET SDK, tooling, schema and Connectors

Speckle is data infrastructure for the AEC industry.


Twitter Follow Community forum users website docs

.NET Core

About Speckle

What is Speckle? Check our YouTube Video Views

Features

  • Object-based: say goodbye to files! Speckle is the first object based platform for the AEC industry
  • Version control: Speckle is the Git & Hub for geometry and BIM data
  • Collaboration: share your designs collaborate with others
  • 3D Viewer: see your CAD and BIM models online, share and embed them anywhere
  • Interoperability: get your CAD and BIM models into other software without exporting or importing
  • Real time: get real time updates and notifications and changes
  • GraphQL API: get what you need anywhere you want it
  • Webhooks: the base for a automation and next-gen pipelines
  • Built for developers: we are building Speckle with developers in mind and got tools for every stack
  • Built for the AEC industry: Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!

Try Speckle now!

Give Speckle a try in no time by:

  • speckle ⇒ creating an account
  • create a droplet ⇒ deploying an instance in 1 click

Resources

  • Community forum users for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
  • website our tutorials portal is full of resources to get you started using Speckle
  • docs reference on almost any end-user and developer functionality

Untitled

Repo structure

This monorepo is the home to our Speckle 2.0 .NET projects:

Other repos

Make sure to also check and ⭐️ these other Speckle repositories:

Developing and Debugging

Clone this monorepo; each section has its own readme, so then just follow those instructions.

Issues or questions? We encourage everyone interested to debug / hack / contribute / give feedback to this project.

A note on Accounts: The connectors themselves don't have features to manage your Speckle accounts; this functionality is delegated to the Speckle Manager desktop app. You can install it from here.

Contributing

Please make sure you read the contribution guidelines for an overview of the best practices we try to follow.

Security

For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.

License

Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via email.

speckle-sharp's People

Contributors

alanrynne avatar bovineox avatar chuongmep avatar clairekuang avatar connorivy avatar cristi8 avatar dependabot[bot] avatar didimitrie avatar dtnaughton avatar eestrado avatar gjedlicska avatar hudumannaia avatar iainsproat avatar izzylys avatar jenessaman avatar jozseflkiss avatar jr-morgan avatar jsdbroughton avatar katkatkateryna avatar kissandras avatar malacpok avatar oguzhankoral avatar phliberato avatar psarras avatar reynold-chan avatar robclaessensrhdhv avatar teocomi avatar thoffeller avatar vwnd avatar zhuangkh avatar

Stargazers

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

Watchers

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

speckle-sharp's Issues

(Remote) Transport Error States

Remote transports should handle error states gracefully. Perhaps we need to introduce an onError event?
PS: throwing errors from inside the ConsumeQueue function does not help, as it can't be caught anywhere.

ShallowCopy() changes type to Base

Expected vs. Actual Behavior

Should not change the base type. If it's a Objects.Circle, it should return an Objects.Circle

Reproduction Steps & System Config (win, osx, web, etc.)

Shallow copy an implemented Base type.

Convert Object management components to async

All components are implemented but some of them completely screw up the solution process when hitting F5. Specially if you hammer it several times (as impatient people like me do... 😅), the UI will get locked up for several seconds.

I think the main culprit for this is my implementation of in the Create Object component. Have to do some tests to be sure.

Copied over from specklesystems/ConnectorGrasshopper#32.

Add basic MEP objects

The current objects mainly tackle general geometry and some structural elements. We can't forget about MEP!

A tentative list of basic objects to start:

  • ducts
  • pipes
  • connectors / fittings (eg terminals, elbows, transitions)
  • more . . .

Feel free to add to the list 🖋

Add Surfaces

Simple nurbs surfaces should be (may not be!) easy to add.

Move Kit selection logic to an application wide menu

Following discussions we had in the past, (I think) it was agreed to move the kit selection to an application wide menu so the end users could select a default kit for Gh in general and not on a node basis.
We could even consider a systems-wide implementation and have the default kit selection be in Manager & Core.

Copied over from specklesystems/ConnectorGrasshopper#36

Receiver: check for new commits

When a file is opened again, a receiver should check if there are any newer commits on the stream that it's listening at (alternatively, the branch).

This needs to happen ONLY if it's a stream (or branch) receiver, and not if it's a commit receiver.

Needs methods in Core/GQL for getting the

StreamCommits
BranchCommits

Copied over from specklesystems/ConnectorGrasshopper#14

Interaction for selections that rely on a Server search (collaborators, existing streams)

The most obvious solution for interactions like adding a collaborator or an existing stream was a ComboBox, but I've run into some difficulty with making it work. Searching using the ComboBox input works fine, but selecting an option clears the text and makes it appear to the user that nothing was selected. This is because the SelectedItem and the text input Query are both bound to different props and it's causing conflicts. Need to figure out a better way to handle this or come up with a workaround (eg add selections as chips so they are visually displayed to the user?)

Receiver for Transports (Receive Local)

We need another node that can receive objects from transports rather than streams/commits/etc. This one should be less fancy, no auto updates, etc; should just take in a transport and an object id (hash).

Copied over from specklesystems/ConnectorGrasshopper#15

Add Breps

BREP Roadmap:

  • Speckle BREP Class with topology subclasses
  • GH Brep conversion/serialization + Send/Receive from server (GH to GH)
  • Revit conversion routines (~1 week max to have working prototype)
  • Dynamo conversion routines (this one should only take a couple days)
  • Edge case testing and error handling.
  • Unify our BREP implementation with any extra stuff from other connectors.

Pass GraphQL Errors From Client Operations

Expected vs. Actual Behavior

At the moment, GraphQL Client Operations all return standard string errors (eg "Could not create stream"). It would be more helpful if the specific GraphQL errors could be passed on in the message so you don't have to dig all the way into core to find out what the actual error is.

Reproduction Steps & System Config (win, osx, web, etc.)

Make any bad GraphQL request using Client.GraphqlClientOperations.

Proposed Solution (if any)

Pass the actual GraphQL errors into the GraphQLException.

Failing Tests: Wall and Opening

WallTests.WallToNative failing bc it can't create a wall

The referenced object is not valid, possibly because it has been deleted from the database, or its creation was undone.

   at Autodesk.Revit.DB.Element.get_Parameter(BuiltInParameter parameterId)
   at SpeckleRevitTests.SpeckleConversionTest.AssertEqualParam(Element expected, Element actual, BuiltInParameter param) in C:\Users\izzy lyseggen\Documents\dev\next\ConverterRevit\SpeckleRevitTests\SpeckleConversionTest.cs:line 137
   at SpeckleRevitTests.WallTests.AssertWallEqual(Wall sourceElem, Wall destElem) in C:\Users\izzy lyseggen\Documents\dev\next\ConverterRevit\SpeckleRevitTests\WallTests.cs:line 59
   at SpeckleRevitTests.SpeckleConversionTest.SpeckleToNative[T](Action`2 assert) in C:\Users\izzy lyseggen\Documents\dev\next\ConverterRevit\SpeckleRevitTests\SpeckleConversionTest.cs:line 67
   at SpeckleRevitTests.WallTests.WallToNative() in C:\Users\izzy lyseggen\Documents\dev\next\ConverterRevit\SpeckleRevitTests\WallTests.cs:line 42

OpeningTests.OpeningToNative failing bc the opening is not going through anything

One or more errors occurred.

   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at SpeckleRevitTests.SpeckleConversionTest.SpeckleToNative[T](Action`2 assert) in C:\Users\izzy lyseggen\Documents\dev\next\ConverterRevit\SpeckleRevitTests\SpeckleConversionTest.cs:line 56
   at SpeckleRevitTests.OpeningTests.OpeningToNative() in C:\Users\izzy lyseggen\Documents\dev\next\ConverterRevit\SpeckleRevitTests\OpeningTests.cs:line 60

Add basic api calls

What's needed (list open to edits!):

streams:

  • create stream
  • update stream
  • delete stream
  • branch create
  • branch delete
  • branch update
  • create commit (on branch)
  • get stream
    • get stream commits
    • get stream users
    • get stream branches
  • get user streams
  • share stream with users
  • un-share stream with user
  • stream search (⚠ needs server implementation too)

users:

  • get user
  • user search (⚠ needs server implementation too)

GQL Api Calls Cleanup - StreamGet breakdown

Expected vs. Actual Behavior

It's a bit messy at the moment! The StreamGet query returns too many things, and at the same time not enough things - or some things that are needed are not there yet 🙃

Where this started from: currently, to get the latest commit in a stream, regardless of branch, i have to search through all the branches returned by the original StreamGet, and then dig inside all of their commits. There's a different field in the schema, the stream's commits, that... just returns the latest commits.

Proposed Solution (if any)

I propose we break this down a bit, and add a few extra calls that will be more on point.

  • StreamGet: gets the stream basic info, total branches count, and total commit count (basically the current query, sans the branches part, or, at least without the branches.commits part)
  • (collection query) StreamGetCommits: gets the stream's commits. I need this to easily check which is the latest commit in the case of stream bound receivers.
  • (collection query) StreamGetBranches: the current branches subquery, more or less
  • (item query) StreamGetBranch - gets the branch as it is now.
  • (item query) CommitGet - gets the full info of a specific commit (already implemented)

Optional: Affected Projects

It's an open questions for all the connector implementations!

AutoSend in Send node

Send node has no option to automatically send when receiving new data, this should be added it we want feature parity with Gh. We might decide not to add it if we want to keep the node simpler and prevent risky behaviours.

Create resource file to hold localised strings (names, descriptions)

In order to unify all the different names and descriptions we are going to have to put together, I suggest we assemble some sort of resource file where we can locate and easily change those strings without affecting the underlying code.

This would also act as a single "source of truth" for any other place we might need it in, and it'd be a nice step that might allow people creating speckle translations. 🇪🇸🇫🇷🇮🇹🇩🇪🇧🇷

Not sure what this "resource file" should look like though (format, etc...), if we add it in Core it could be a .resx file. Don't know if we can access that from a dependent project though. I'll see what I can come up with.

Cleanup models from 1.0

There's some leftover garbage around from 1.0. It had a reason to exist, but not anymore.

Proposed Solution (if any)

  • Point class - should have just simple x, y and z props. No more value array of doubles.
  • Vector class - likewise
  • Line class - ditto
  • Arc class - needs consolidation, there are props that are not used in any conversions AFAIK
  • I'm sure there's more here!

Optional: Affected Projects

Probably minor refactoring will be needed in converters.

Separate Transports into individual projects

Expected vs. Actual Behavior

We envision having multiple transports in the future, with this being a potential area of high community contributions. In the current setup - transports being embedded in the core .proj file - we'll end up with a bazillion third party dependencies.

Proposed Solution (if any)

Move transports out of Core into separate projects. This option leads us towards a monorepo-like situation - which is not particularly bad, as it does imply less management overhead.

Each project will then need to produce its own packages, e.g.:

  • Core
  • TransportA
  • TransportB
  • etc.

In the current "reference things locally" phase that we're in, this would mean that connectors will need to locally reference the transports they're using (until we create packages later down the line).

Server Transport Progress Reporting - in Mb/s

Expected vs. Actual Behaviour

A nitpick: the server transport reports its upload progress per object. This is not bad per se, nevertheless it can be misleading UX wise: a user can be maxing out the upload speed on their connection (which we take care to do this time round), and the progress can still seem slow.

We need to manage expectations better here!

Proposed Solution (if any)

Find a clean way of reporting also bytes sent and eventually bubble up a Mb/s statistic. Ironically, git does this :D

Optional: Affected Projects

All clients.

Receiver displays connections as selected (green cables)

Minor issue with the receiver node. No matter if the node is selected or not, the cables connected to it keep their "selected" state (i.e. green).

This doesn't seem to affect the overall behaviour of the component, just the display state.

Copied over from specklesystems/ConnectorGrasshopper#28

Remove objects from stream

You should of course be allowed to remove objects from a stream in a Desktop connector! This involves:

  1. deciding on a good user flow for this
  2. designing the controls in desktopui
  3. implementing it in the revit connector to see how it works in practice

Receiver should clear internal data when cables are disconnected

The receiver should clear its internal data whenever its stream input is disconnected.

Currently, the Write method will persist the previously received info, but I haven't found it affects in any way the functionality of the Receiver node.

Copied over from specklesystems/ConnectorGrasshopper#35

Add Icons

Add new icons for the various components/nodes

Integration tests sometimes hang randomly

Expected vs. Actual Behavior

Integration tests should run, and fail or pass normally. What happens one in three times is that they hang for ever and CircleCI times them out at one point.

Reproduction Steps & System Config (win, osx, web, etc.)

See Core's pipeline. If manually run again, they pass. The log is not very informative:

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Too long with no output (exceeded 10m0s): context deadline exceeded

Modal window display

We'll need a way to display the ui inside a host wpf element. Currently we can't as it's structured inside one main Window. This can be abstracted to a UserControl(?).

NOTE: this is not urgent, can be relegated to jan 2021 release.

Handle Int32 vs Int64 in Deserialisation

Expected vs. Actual Behavior

When deserializing a List of integers, we get errors re conversion from Int64 to Int32. We need to fiddle inside the HandleValue function and potentially pass in the innerType of the List/Dictionary for raw values.

Reproduction Steps & System Config (win, osx, web, etc.)

This seems to happen currently for List but I suspect Dictionary<string, int> will be affected as well.

Refractoring proposal

OK so I've been thinking that some naming in Core is a bit confusing, maybe it's just me, maybe it's some of the git lingo we're using; but let me put down my thoughts and we can discuss next week. I might have also overlooked some other aspects, anyways here we go:

  • rename ITransport to IRemote. I find it confusing that we have both transports and remotes and that Remote is a type of transport and that we also have a class named Remote
  • this means renaming MemoryTransport > InMemoryRemote, SqlLiteObjectTransport > SqlLiteRemote, RemoteTransport > SpeckleRemote
  • rename Remote.cs to SpeckleServer.cs, because that's what it is
  • add a SpeckleServer prop to the SpeckleRemote instead of passing it as an extra argument to the Upload / Download methods
  • rename Upload to Send and Download to Receive (sorry did't think about this before)
  • maybe move Send&Receive to the IRemote interface, but this might not be possible since we wanted to allow sending to multiple remotes etc...

I'm sure I have overlooked a few aspects but I'm happy to give it a try if we decide to go ahead.

Add a `ShallowCopy()` method to `Base` class

Expected vs. Actual Behavior

In the Grasshopper connector, I needed to make shallow copies of any Base object to prevent the object from updating backwards in the node graph.

I'm pretty sure we will be needing this elsewhere, so It might be a good idea to add it to the Base object directly as ShallowCopy().

Proposed Solution

My quick implementation is here:

https://github.com/specklesystems/ConnectorGrasshopper/blob/cdbb957ee85851090bab7894294b8ee0c982110d/ConnectorGrashopper/Objects/ExtendSpeckleObject.cs#L52-L56

Cleanup Base class methods

There's quite a few base class methods which are not named correctly, and generally confusing. We need to clean them up and normalise them a wee bit:

  • GetDynamicMemberNames -> required by deserialisation, can't change. returns all prop names. To investigate if we can't get rid of or rename this method to something less confusing.
  • GetDynamicPropertyNames
  • GetTypedProperties

Not sure we need:

  • HasMember and HasMember<T>
  • GetInstanceMemberNames -> can be replaced with GetTypedProperties

Add cancellation logic

Expected vs. Actual Behavior

Currently there's no way to exit form a Send or Receive operation before it's done. This is, obviously, no good!

Proposed Solution (if any)

Pass in an CancellationToken and honour the cancellation requests throughout the codebase. It can be optional to prevent client work disruption.

Optional: Affected Projects

It might affect existing 2.0 client work being done in Gh, Revit, Dynamo and Rhino. But not dramatically 😅

🐛 ModelCurve loses its Revit UniqueId after send

When sending everything in the standard structural sample model, everything sends to the server fine for the initial commit. However, there is a single ModelCurve that loses its applicationId which causes SendStream to fail when trying to update the stream.

Skipping the element for now, but needa figure out how it's escaping 🕵️‍♀️

UI Structural Cleanup

Just dumping in here the latest discussion takeways. @izzylys feel free to edit/add (this will be an incomplete list):

Sidebar and options: sidebar goes away. The only missing option will be the "Dark/Light" mode button, that can go in the current settings view. The "Pin Window" option can go as we'll rely on the ownership of elements, e.g.:

var helper = new System.Windows.Interop.WindowInteropHelper(Bootstrapper.Application.MainWindow);
helper.Owner = RhinoApp.MainWindowHandle();

Top Bar:

  • main view: will keep its title, and have a button in the top right for opening up the settings view.
  • stream detail view: title will be replaced with the stream name, and the rest we'll fiddle later.

Hopefully this makes sense, if not happy to clarify.

Validate Base dynamic key names

Expected vs. Actual Behavior

You can currently set dynamic props key names to whatever you want them to be, including potentially illegal chars. This is problematic, as there are some caveats that will make things difficult.

Ie,

  • dots in key names - they nuke postgres jsonb querying. eg: myCommit["test.the.best.dots"] = etc should throw an error.
  • too many @s. e.g., eg: myCommit["@@@@@test.the.best.dots"] = etc - works fine but it's stupid and can cause confusion (@teocomi spotted this)
  • slashes in key names?
  • protected fields (e.g., speckle_type)

Proposed Solution (if any)

Come up with the scenarios that we want to avoid, and implement them as deep as we can in core so we don't need to freak out later!

There are some that we took care of in 1.0 in the CreateSpeckleObject component, so we could start from there. We also need to investigate other limitations coming from postgres' jsonb column operations this time, as I believe that will be what we'll want to test against.

📂 Restructure filters UI to make the flow more clear

There are some problems with the filtering objects UI and it should be re-thought

  • multiple options do the same thing (add all selected objects button vs selection tab using filters)
  • might want the option of more quick filters
  • not clear that the quick filter buttons and the more advanced filters (category, view, parameter) are alternatives and not in addition to
  • ((category and view filters not always binding the user selection))

yo it be filtered

An option would be to separate them into visual sections: "quick filters" for the ez buttons and "advanced / all filters" for the rest. It would be easier to have "all" vs "advanced" so the filters list wouldn't have to be filtered (lol) to exclude the selection filter.

Alternatively, the two (quick vs all filters) could be in two separate tabs? Would require reconsideration of the backing view model.

Some sketches to be made in figma before this restructuring is executed.

IGeometry shared props

I see the IGeometry interface is scaffolded, but not filled in yet :) The main purpose of that is to enforce a few shared properties across all objects that are "spatial" in one way or another:

  1. units - we need to be able to, at the conversion stage, match element units. These need to be populated from the base document during to speckle conversion; conversely matched against it (or ignored) on to native conversion.

  2. length - for linear elements (lines, curves, etc.)

  3. area - for surfaces, meshes, breps, and closed planar curves (TBD if the last is feasible) etc.

  4. volume - for closed surfaces, meshes, and breps

Other props for online viewer optimisation:

  • bounding box (tbd the most efficient way to compute & store it - we can just use its diagonal line)
  • centre point (possibly included in bbox)

These last ones will be necessary for view angle optimisations, loading order, etc. and should be implemented in a way they can easily be queried from the db.

😈 Handle failed conversions

Currently a list is created of all the Revit elements that failed to convert...then it goes poof bc idk what to do with it.

Decide on a strategy for this. Do we write to local logs in addition to sending to our error tracking? Think having local logs printed would be useful for individual debugging.

At a minimum, we'd want a popup in DesktopUI that displays raw text of the errors with maybe a button to export a txt or something.

Repalace GetDynamicMembers and GetInstanceMembers methods

Brief intro to the problem:

The Expand Speckle Object is not picking up instance fields because it's only looking for dynamic members: https://github.com/specklesystems/speckle-sharp/blob/master/ConnectorGrasshopper/ConnectorGrasshopper/Objects/ExpandSpeckleObject.cs#L79

This is an issue with BuiltElement schema classes (img at the bottom). A similar issue appeared in Dynamo when receiveing an unknown object and its properties are expanded in a dictionary.

Proposed solution:

I'd suggest introducing an [SchemaVisibility(Enum.Visibility)] attribute with a couple of values eg:

  • Enum.Visibility.Hidden
  • Enum.Visibility.Internal
  • Enum.Visibility.Visible

This attribute would replace the [SchemaBuilderIgnore] attribute I just created. And to add it to various sensitive fields eg speckle_type, id etc...

Then we can repalace the GetDynamicMembers and GetInstanceMembers methods with a GetMembers(Enum.Visibility), so that we have more flexibility when requesting the members of an object (by default visibility would be visible).

Thoughts?

image

UI to change Kit

The Dynamo connector has no interface for swapping kits, one should be added.

GraphQL Subscriptions

Expected vs. Actual Behavior

Subscriptions don't work currently, as they fail. It's possibly a combination of:

  • "binary messages not supported" in .net (for some weird reason) - see this
  • authentication header doesn't seem to be passed along.

Reproduction Steps & System Config (win, osx, web, etc.)

There's an example usage in the console sketches project on the subs branch.

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.