mcneel / compute.rhino3d

REST geometry server based on RhinoCommon and headless Rhino
Other
284 stars 182 forks source link

Can Rhino.Curve.GetFilletPoints be made available in compute #143

Open JoshuaWilkes opened 3 years ago

JoshuaWilkes commented 3 years ago

https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_Geometry_Curve_GetFilletPoints.htm

Currently implementing a script that requires this function from RhinoCommon which is not currently available via rhino_compute.

Can this functionality be implemented?

Thanks

mcneel-build commented 3 years ago

Linked with COMPUTE-148

fraguada commented 3 years ago

I believe it is there: image For some reason it isn't showing up in the client libraries. In any case, you should be able to call it with the computeFetch method. (warning: untested pseudocode)

We'll investigate why it isn't showing up in the clients.

JoshuaWilkes commented 3 years ago

Following up on this issue,

I don't seem to get anything other than the webpage response from the api when calling it manually.

<Response [200]> <!DOCTYPE html>

GetFilletPoints

[bool, double t0, double t1, Rhino.Geometry.Plane filletPlane] GetFilletPoints(Rhino.Geometry.Curve curve0, Rhino.Geometry.Curve curve1, double radius, double t0Base, double t1Base)

I tried with both of the below and get the same response. in practice this throws an error in compute fetch at the r.json() line `def curve_fillet_points(curve0, curve1, radius, t0Base, t1Base, multiple=False): url = "/rhino/geometry/curve/getfilletpoints-curve_curve_double_double_double_double_double_plane" if multiple: url += "?multiple=true" args = [curve0, curve1, radius, t0Base, t1Base] if multiple: args = list(zip(curve0, curve1, radius, t0Base, t1Base)) response = rc.Util.ComputeFetch(url, args) return response` `def curve_fillet_points(curve0, curve1, radius, t0Base, t1Base, multiple=False): url = "/rhino/geometry/curve/getfilletpoints" if multiple: url += "?multiple=true" args = [curve0, curve1, radius, t0Base, t1Base] if multiple: args = list(zip(curve0, curve1, radius, t0Base, t1Base)) response = rc.Util.ComputeFetch(url, args) return response`
pearswj commented 3 years ago

I haven't figured out yet why GetFilletPoints isn't included in the python client library (we use code generation for the client libraries) but I can confirm that this sample works using the latest version of Rhino Compute from the master branch.

import compute_rhino3d.Util
import rhino3dm

compute_rhino3d.Util.url = 'http://localhost:8081/'

def curve_fillet_points(curve0, curve1, radius, t0Base, t1Base, multiple=False):
    url = "/rhino/geometry/curve/getfilletpoints-curve_curve_double_double_double_double_double_plane"
    if multiple: url += "?multiple=true"
    args = [curve0, curve1, radius, t0Base, t1Base]
    if multiple: args = list(zip(curve0, curve1, radius, t0Base, t1Base))
    response = compute_rhino3d.Util.ComputeFetch(url, args)
    return response

pt0 = rhino3dm.Point3d(0,0,0)
pt1 = rhino3dm.Point3d(10,0,0)
pt2 = rhino3dm.Point3d(0,10,0)
l1 = rhino3dm.LineCurve(pt0, pt1)
l2 = rhino3dm.LineCurve(pt0, pt2)

rv = curve_fillet_points(l1, l2, 1.5, 0, 0)

print(rv)

# [True, 1.4999999999999998, 1.4999999999999998, {'Origin': {'X': 1.5, 'Y': 1.5, 'Z': 0.0}, 'XAxis': {'X': 0.0, 'Y': -1.0, 'Z': 0.0}, 'YAxis': {'X': -1.0, 'Y': 0.0, 'Z': 0.0}, 'ZAxis': {'X': 0.0, 'Y': 0.0, 'Z': -1.0}, 'Normal': {'X': 0.0, 'Y': 0.0, 'Z': -1.0}}]
FergusH commented 3 years ago

I am unable to duplicate your experience @pearswj . We have updated to commit 49dba9f: "rhino":"7.2.21012.11001","compute":"1.0.0.607","git_sha":"49dba9fb"}

I've copy/pasted your code above:

import compute_rhino3d.Util
import rhino3dm
import os

if os.environ.get('userdomain') == "XXX":
    internal_proxy_address = "http://ZZZZ"
    os.environ["HTTP_PROXY"] = f"{internal_proxy_address}:999999"
    os.environ["HTTPS_PROXY"] = f"{internal_proxy_address}:999999"

compute_rhino3d.Util.apiKey = "ABCD"
compute_rhino3d.Util.url = "https://YYYY"   

def curve_fillet_points(curve0, curve1, radius, t0Base, t1Base, multiple=False):
    url = "/rhino/geometry/curve/getfilletpoints-curve_curve_double_double_double_double_double_plane"
    if multiple: url += "?multiple=true"
    args = [curve0, curve1, radius, t0Base, t1Base]
    if multiple: args = list(zip(curve0, curve1, radius, t0Base, t1Base))
    response = compute_rhino3d.Util.ComputeFetch(url, args)
    return response

pt0 = rhino3dm.Point3d(0,0,0)
pt1 = rhino3dm.Point3d(10,0,0)
pt2 = rhino3dm.Point3d(0,10,0)
l1 = rhino3dm.LineCurve(pt0, pt1)
l2 = rhino3dm.LineCurve(pt0, pt2)

rv = curve_fillet_points(l1, l2, 1.5, 0, 0)

print(rv)

I get the following result:

File "C:\Program Files\Python37\lib\json\__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "C:\Program Files\Python37\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Program Files\Python37\lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
pearswj commented 3 years ago

What happens if you temporarily replace return r.json() at the end of computeFetch() (compute_rhino3d/Util.py) with return r.text? That will allow us to see what text the json parser is failing to parse.

FergusH commented 3 years ago

I had to use str(r) (r is a requests.mode.Response but r.text() gives "TypeError: 'str' object is not callable").

From print(str(r)) I get: "<Response [200]>"

pearswj commented 3 years ago

r.text returns the body as a string – that's why it complains if you call it as a method with (). Good to know it's returning 200 though!

We need to add some request error handling to the client library, but for now this should get us some useful information.

FergusH commented 3 years ago

I read a set of parentheses that you hadn't written...

FergusH commented 3 years ago

r.text is:

<!DOCTYPE html><html><body><H1>GetFilletPoints</H1>
<p>
[bool, double t0, double t1, Rhino.Geometry.Plane filletPlane] GetFilletPoints(Rhino.Geometry.Curve curve0, Rhino.Geometry.Curve curve1, double radius, double t0Base, double t1Base)<br>
</p></body></html>
FergusH commented 3 years ago

@pearswj bumping...

pearswj commented 3 years ago

Very strange. The response is the same as if you called GET /rhino/geometry/curve/getfilletpoints-curve_curve_double_double_double_double_double_plane, but the ComputeFetch() function should be using POST. Are you able to call other functions, such as compute_rhino3d.Curve.GetLength()? Can you test it without the proxy?

FergusH commented 3 years ago

Removing the proxy doesn't change the behaviour (we run from both our company network, where the proxy is necessary, and from AWS instances where it isn't. Currently we're working from home due to covid lockdown, so the proxy isn't needed):

import compute_rhino3d.Util
import rhino3dm
import os

compute_rhino3d.Util.apiKey = ""
compute_rhino3d.Util.url = ""   

def curve_fillet_points(curve0, curve1, radius, t0Base, t1Base, multiple=False):
    url = "/rhino/geometry/curve/getfilletpoints-curve_curve_double_double_double_double_double_plane"
    if multiple: url += "?multiple=true"
    args = [curve0, curve1, radius, t0Base, t1Base]
    if multiple: args = list(zip(curve0, curve1, radius, t0Base, t1Base))
    response = compute_rhino3d.Util.ComputeFetch(url, args)
    return response

pt0 = rhino3dm.Point3d(0,0,0)
pt1 = rhino3dm.Point3d(10,0,0)
pt2 = rhino3dm.Point3d(0,10,0)

l1 = rhino3dm.LineCurve(pt0, pt1)
l2 = rhino3dm.LineCurve(pt0, pt2)

rv = curve_fillet_points(l1, l2, 1.5, 0, 0)

print(rv)

Ouput (with Util.ComputeFetch() still returning r.text):

<!DOCTYPE html><html><body><H1>GetFilletPoints</H1>
<p>
[bool, double t0, double t1, Rhino.Geometry.Plane filletPlane] GetFilletPoints(Rhino.Geometry.Curve curve0, Rhino.Geometry.Curve curve1, double radius, double t0Base, double t1Base)<br>
</p></body></html>
FergusH commented 3 years ago

compute_rhino3d.Curve.GetLength() doesn't work (I actually ran into this problem with GetLength() a long time ago but had forgotten, because I got around it by using Curve.GetLength1() which works just fine).


import compute_rhino3d.Util
import compute_rhino3d.Curve
import rhino3dm
import os

compute_rhino3d.Util.apiKey = ""
compute_rhino3d.Util.url = ""   

pt0 = rhino3dm.Point3d(0,0,0)
pt1 = rhino3dm.Point3d(10,0,0)

l1 = rhino3dm.LineCurve(pt0, pt1)

rv = compute_rhino3d.Curve.GetLength( l1 )

print(rv)

Outputs: {"statusCode":404,"message":"The resource you have requested cannot be found.","details":""}

If I use GetLength1():

import compute_rhino3d.Util
import compute_rhino3d.Curve
import rhino3dm
import os

compute_rhino3d.Util.apiKey = ""
compute_rhino3d.Util.url = ""   

pt0 = rhino3dm.Point3d(0,0,0)
pt1 = rhino3dm.Point3d(10,0,0)

l1 = rhino3dm.LineCurve(pt0, pt1)

#rv = compute_rhino3d.Curve.GetLength( l1 )
rv = compute_rhino3d.Curve.GetLength1( l1, 1 )

print(rv)

it outputs: 10.0

pearswj commented 3 years ago

@FergusH I'm perplexed. I've tested the script as you wrote it against several instances of Compute on different machines and I can't reproduce this behaviour. Can you try calling our test server to see if you get the same result as I do?

# mcneel test server connection settings
compute_rhino3d.Util.url = "https://compute.rhino3d.com/"
compute_rhino3d.Util.authToken = "" # token from http://rhino3d.com/compute/login

p.s. The GetLength() was fixed recently (#147). Try build 1.0.0.619 or newer.

FergusH commented 3 years ago

I've just tried your server @pearswj , I still get the same results.

I've tried from my local machine; in fresh venvs; from vms in the cloud - all the same. I've had colleagues here try it, they get the same result as me. JoshuaWilkes who started this thread is from an external contractor that is doing work for us, he gets the same.

I can't think of any other commonality between everything I've tried at this end....

pearswj commented 3 years ago

I appreciate you being thorough. Here's a functionally equivalent python 3 script that doesn't use any modules, except the built-in urllib. Could you test this against our compute.rhino3d.com server? It might be worth trying this script (and the previous script, if successful) against an instance of Compute running on your local machine too. It should be clear in the logs whether Compute is receiving a GET or a POST request.

import urllib.request

baseurl = 'https://compute.rhino3d.com'
token = '' # token from https://rhino3d.com/compute/login
headers = { 'Authorization': 'Bearer ' + token }

# baseurl = 'https://my.server.com'
# headers = { 'RhinoComputeKey': 'API_KEY' }

# baseurl = 'http://localhost:8081'

url = baseurl + '/rhino/geometry/curve/getfilletpoints-curve_curve_double_double_double_double_double_plane'
data = b'[{"version": 10000, "archive3dm": 60, "opennurbs": -1912572244, "data": "+n8CAIEAAAAAAAAA+/8CABQAAAAAAAAA29TXTkfp0xG/5QAQgwEi8PSb6SP8/wIASQAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAMAAAAexxcp/38CgAAAAAAAAAAA"}, {"version": 10000, "archive3dm": 60, "opennurbs": -1912572244, "data": "+n8CAIEAAAAAAAAA+/8CABQAAAAAAAAA29TXTkfp0xG/5QAQgwEi8PSb6SP8/wIASQAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAMAAACIZNn3/38CgAAAAAAAAAAA"}, 1.5, 0, 0]'

req = urllib.request.Request(url, data, headers, method='POST')

with urllib.request.urlopen(req) as f:
    print(f.getcode())
    print(f.read())

# 200
# b'[true,1.4999999999999998,1.4999999999999998,{"Origin":{"X":1.5,"Y":1.5,"Z":0.0},"XAxis":{"X":0.0,"Y":-1.0,"Z":0.0},"YAxis":{"X":-1.0,"Y":0.0,"Z":0.0},"ZAxis":{"X":0.0,"Y":0.0,"Z":-1.0},"Normal":{"X":0.0,"Y":0.0,"Z":-1.0}}]'
FergusH commented 3 years ago

Thanks very much @pearswj for your patience and responses throughout this - the new code works correctly with your server, but not with ours.

I can't explain why I was getting unexpected responses from your server earlier, but in any case it's now obvious that our server has a problem. As luck would have it it's being rebuilt tomorrow anyway, I'll ask for this to be investigated/checked during.

FergusH commented 3 years ago

@pearswj it was the trailing slash, e.g.: compute_rhino3d.Util.url = "https://compute.rhino3d.com/"

Your web server apparently strips it away if it's present, ours doesn't - removing the trailing slash in our url has fixed the problem...

pearswj commented 3 years ago

Glad you figured it out and thanks for letting me know. I've opened #218 to make sure that the client constructs well-formed urls without missing or double slashes. I'll leave this open to track getting GetFilletPoints added to the client.