Skip to content

Commit 72d27dc

Browse files
committed
Ensure support for non-finite floats
jupyter_client has deprecated support for non-finite floats, and aggressively prints to stderr if you try to serialize them. Notes: We do not need a deserializer, as CFloats will handle the conversion in its validator. We were already relying on this fact, so no change. Similarly, the JS convertFloat[...] functions continue to serialize/deserialize there. This also extends Vector/Matrix/Euler to support IEEE floats.
1 parent b97785d commit 72d27dc

File tree

6 files changed

+145
-59
lines changed

6 files changed

+145
-59
lines changed

js/scripts/prop-types.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class Float extends BaseType {
303303
if (this.maxValue !== undefined) {
304304
limits += `, max=${this.maxValue}`;
305305
}
306-
return `CFloat(${this.getPythonDefaultValue()}, ${nullableStr}${limits})${this.getTagString()}`;
306+
return `IEEEFloat(${this.getPythonDefaultValue()}, ${nullableStr}${limits})${this.getTagString()}`;
307307
}
308308
getPropertyConverterFn() {
309309
return 'convertFloat';

js/src/_base/Three.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,9 @@ class ThreeModel extends widgets.WidgetModel {
501501
}
502502

503503
convertFloatThreeToModel(v) {
504-
if (isFinite(v)) { // Most common first
504+
if (Number.isFinite(v)) { // Most common first
505505
return v;
506-
} else if (isNaN(v)) {
506+
} else if (Number.isNaN(v)) {
507507
return 'nan';
508508
} else if (v === Infinity) {
509509
return 'inf';
@@ -554,12 +554,12 @@ class ThreeModel extends widgets.WidgetModel {
554554
default:
555555
throw new Error('model vector has invalid length: ' + v.length);
556556
}
557-
result.fromArray(v);
557+
result.fromArray(v.map(this.convertFloatModelToThree));
558558
return result;
559559
}
560560

561561
convertVectorThreeToModel(v) {
562-
return v.toArray();
562+
return v.toArray().map(this.convertFloatThreeToModel);
563563
}
564564

565565
assignVector(obj, key, value) {
@@ -568,11 +568,13 @@ class ThreeModel extends widgets.WidgetModel {
568568

569569
// Euler
570570
convertEulerModelToThree(v) {
571-
return new THREE.Euler().fromArray(v);
571+
// The float conversions will ignore the "XYZ" order strings
572+
return new THREE.Euler().fromArray(v.map(this.convertFloatModelToThree));
572573
}
573574

574575
convertEulerThreeToModel(v) {
575-
return v.toArray();
576+
// The float conversions will ignore the "XYZ" order strings
577+
return v.toArray().map(this.convertFloatThreeToModel);
576578
}
577579

578580
assignEuler(obj, key, value) {
@@ -676,12 +678,12 @@ class ThreeModel extends widgets.WidgetModel {
676678
default:
677679
throw new Error('model matrix has invalid length: ' + m.length);
678680
}
679-
result.fromArray(m);
681+
result.fromArray(m.map(this.convertFloatModelToThree));
680682
return result;
681683
}
682684

683685
convertMatrixThreeToModel(m) {
684-
return m.toArray();
686+
return m.toArray().map(this.convertFloatThreeToModel);
685687
}
686688

687689
assignMatrix(obj, key, value) {

pythreejs/_base/renderable.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from ipywidgets import DOMWidget, Widget, widget_serialization
2-
from traitlets import Unicode, CInt, CFloat, Enum, Bool, Int, Instance, List
2+
from traitlets import Unicode, CInt, Enum, Bool, Int, Instance, List
33

44
from .._package import npm_pkg_name
55
from .._version import EXTENSION_SPEC_VERSION
@@ -8,6 +8,7 @@
88
from ..enums import ToneMappings
99
from ..math.Plane_autogen import Plane
1010
from ..renderers.webgl.WebGLShadowMap_autogen import WebGLShadowMap
11+
from ..traits import IEEEFloat
1112

1213

1314
class RenderableWidget(DOMWidget):
@@ -28,7 +29,7 @@ class RenderableWidget(DOMWidget):
2829
autoClearDepth = Bool(True).tag(sync=True)
2930
autoClearStencil = Bool(True).tag(sync=True)
3031
clippingPlanes = List(Instance(Plane)).tag(sync=True, **widget_serialization)
31-
gammaFactor = CFloat(2.0).tag(sync=True)
32+
gammaFactor = IEEEFloat(2.0).tag(sync=True)
3233
gammaInput = Bool(False).tag(sync=True)
3334
gammaOutput = Bool(False).tag(sync=True)
3435
localClippingEnabled = Bool(False).tag(sync=True)
@@ -38,11 +39,11 @@ class RenderableWidget(DOMWidget):
3839
shadowMap = Instance(WebGLShadowMap, args=(), allow_none=True).tag(sync=True, **widget_serialization)
3940
sortObject = Bool(True).tag(sync=True)
4041
toneMapping = Enum(ToneMappings, 'LinearToneMapping').tag(sync=True)
41-
toneMappingExposure = CFloat(1.0).tag(sync=True)
42-
toneMappingWhitePoint = CFloat(1.0).tag(sync=True)
42+
toneMappingExposure = IEEEFloat(1.0).tag(sync=True)
43+
toneMappingWhitePoint = IEEEFloat(1.0).tag(sync=True)
4344

4445
clearColor = Unicode('#000000').tag(sync=True)
45-
clearOpacity = CFloat(1.0).tag(sync=True)
46+
clearOpacity = IEEEFloat(1.0).tag(sync=True)
4647

4748
def send_msg(self, message_type, payload=None):
4849
if payload is None:

pythreejs/_base/uniforms.py

+53-34
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,70 @@
11

2+
import numbers
3+
import math
4+
25
from ipywidgets import Widget, widget_serialization
36

7+
8+
def _serialize_item(value):
9+
if value is None:
10+
return { 'value': None }
11+
12+
elif isinstance(value, Widget):
13+
return {
14+
'type': 't',
15+
'value': widget_serialization['to_json'](value, None)
16+
}
17+
18+
elif isinstance(value, str):
19+
return {
20+
'type': 'c',
21+
'value': value
22+
}
23+
24+
elif isinstance(value, numbers.Real):
25+
# cast non-finite floats to repr
26+
if math.isnan(value) or math.isinf(value):
27+
value = repr(value)
28+
return {
29+
'value': value
30+
}
31+
32+
elif isinstance(value, (list, tuple)):
33+
ret = {
34+
'value': list(value)
35+
}
36+
for i, e in enumerate(value):
37+
if isinstance(e, numbers.Real) and (math.isnan(e) or math.isinf(e)):
38+
ret["value"][i] = repr(e)
39+
40+
ll = len(value)
41+
if 2 <= ll <= 4:
42+
ret['type'] = 'v%d' % ll
43+
elif ll == 9:
44+
ret['type'] = 'm3'
45+
elif ll == 16:
46+
ret['type'] = 'm4'
47+
return ret
48+
49+
else:
50+
return {
51+
'value': value
52+
}
53+
454
def serialize_uniforms(uniforms, obj):
55+
"""Serialize a uniform dict"""
556

657
serialized = {}
758

859
for name, uniform in uniforms.items():
9-
value = uniform['value']
10-
11-
if value is None:
12-
serialized[name] = { 'value': None }
13-
14-
elif isinstance(value, Widget):
15-
serialized[name] = {
16-
'type': 't',
17-
'value': widget_serialization['to_json'](value, None)
18-
}
19-
20-
elif isinstance(value, str):
21-
serialized[name] = {
22-
'type': 'c',
23-
'value': value
24-
}
25-
26-
elif isinstance(value, (list, tuple)):
27-
serialized[name] = {
28-
'value': value
29-
}
30-
31-
ll = len(value)
32-
if 2 <= ll <= 4:
33-
serialized[name]['type'] = 'v%d' % ll
34-
elif ll == 9:
35-
serialized[name]['type'] = 'm3'
36-
elif ll == 16:
37-
serialized[name]['type'] = 'm4'
38-
39-
else:
40-
serialized[name] = {
41-
'value': value
42-
}
60+
serialized[name] = _serialize_item(uniform['value'])
4361

4462
return serialized
4563

4664

4765

4866
def deserialize_uniforms(serialized, obj):
67+
"""Deserialize a uniform dict"""
4968

5069
uniforms = {}
5170

pythreejs/animation/AnimationAction.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from ipywidgets import register, DOMWidget, Widget
2-
from traitlets import Unicode, Union, CInt, CFloat
2+
from traitlets import Unicode, Union, CInt
3+
4+
from ..traits import IEEEFloat, ieee_float_serializers
35

46
from .._package import npm_pkg_name
57
from .._version import EXTENSION_SPEC_VERSION
@@ -21,7 +23,11 @@ class AnimationAction(AnimationActionBase, DOMWidget):
2123
_previewable = False
2224

2325
# Normally an int, but can also be inf:
24-
repititions = Union([CInt(), CFloat()], default_value=float('inf'), allow_none=False).tag(sync=True)
26+
repititions = Union(
27+
[CInt(), IEEEFloat()],
28+
default_value=float('inf'),
29+
allow_none=False
30+
).tag(sync=True, **ieee_float_serializers)
2531

2632
def play(self):
2733
content = {

0 commit comments

Comments
 (0)