Merge remote-tracking branch 'goog/stage-aosp-master' into HEAD
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..8e74d28
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,7 @@
+# This is the official list of authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+# Names should be added to this file as:
+# Name or Organization <email address>
+# The email address is not required for organizations.
+Google Inc.
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..98d3764
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+Want to contribute? Great! First, read this page (including the small print at the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement]
+(https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review. We
+use Github pull requests for this purpose.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than
+the one above, the
+[Software Grant and Corporate Contributor License Agreement]
+(https://cla.developers.google.com/about/google-corporate).
\ No newline at end of file
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..772f3f8
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,15 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# Names should be added to this file as:
+# Name <email address>
+
+Mark Koudritsky <kamrik@google.com>
+Don Turner <donturner@google.com>
+Benjamin Fair <benjaminfair@google.com>
+Philip Quinn <pquinn@google.com>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.google b/README.google
new file mode 100644
index 0000000..fc3d2a0
--- /dev/null
+++ b/README.google
@@ -0,0 +1,91 @@
+Overview
+========
+
+This project is included in the internal Android source tree to support
+automated latency testing with Salad Fingers, a robot designed and built by
+Steve Pfetsch (spfetsch@). The three main components we use and modify are:
+
+ - the android app (android/)
+ - the WALT device firmware (arduino/)
+ - the TCP-to-serial bridge (pywalt/)
+
+Salad Fingers uses a single Teensyduino running WALT firmware to test multiple
+devices without human intervention. The devices under test are connected to the
+same host as the Teensy. An end-to-end connection for a single device (only one
+device can use the WALT at a time) looks like this:
+
+ Device ------ Host ------ Teensy
+ (android/) (pywalt/) (arduino/)
+
+For the device to communicate with the host over TCP on a physical USB
+connection, a "reverse" port has to be set up with adb. For example:
+
+ $ adb reverse tcp:50007 tcp:45454
+
+Any traffic the device sends to 127.0.0.1:50007 will come into the host on port
+45454 and vice versa. Port 50007 is defined in the app source, but the device
+port can be selected arbitrarily.
+
+The TCP-to-serial bridge runs on the host and connects the app's TCP pipe to
+the Teensy's serial pipe. However, there are two special commands the app can
+send to the bridge to synchronize the clocks between the device and the Teensy:
+"bridge sync" and "bridge update".
+
+This setup requires some modifications from the original source, which are
+explained in the next section, but behaves very similarly to a direct, Teensy-
+to-device USB connection.
+
+
+Modifications
+=============
+
+- Clock synchronization
+ Despite the reliability and accuracy of NTP, device and host wall clocks can
+ become significantly out of sync, especially if a device loses Wi-Fi
+ connectivity. To avoid this problem and take advantage of the low-latency
+ connection between the host and device, the clocks are synchronized based on
+ the time difference between when the bridge zeroed the Teensy's clock and
+ when the reply to the "bridge sync" command was sent to the device. This
+ required parallel changes in pywalt/ and android/.
+
+- Automation intents
+ The test scripts which control the robot (Salad Fingers) on which the Teensy
+ is mounted require the ability to control certain aspects of the app running
+ on a device. These are defined in a separate RobotAutomationEvent interface.
+ This required changes to android/.
+
+- Reverse port support
+ The WaltTcpConnection class was originally intended to communicate over a
+ true network from a Chromebook to a dedicated bridge with a specific IP
+ address. This address was changed to localhost in android/.
+
+- Strict networking and request ordering
+ To prevent crashes due to network accesses performed on the main thread,
+ which is disallowed in strict mode, all such accesses were moved to a
+ dedicated network thread. As a side benefit, all requests sent to the bridge
+ now wait for a response before the next request is sent. This complies with
+ the serial nature of the device and guarantees correct ordering. This
+ required changes to android/.
+
+- Hardware-specific firmware
+ The Teensy on Salad Fingers uses sensors that are not bundled with the
+ standard WALT hardware, so new thresholds for accelerometer shocks and photo-
+ diode readings were required to achieve accurate results. This required
+ changes to arduino/.
+
+
+Usage
+=====
+
+This project is not intended to be automatically built. Instead, as needed, the
+app will be built manually and checked in as a prebuilt that PTS will pick up.
+The firmware cannot be built or loaded automatically, as it requires a modded
+version of the Arduino project for Teensy. The required software can be loaded
+on a laptop to flash the Teensy mounted on Salad Fingers.
+
+
+See Also
+========
+
+Project metadata: vendor/google_meta/platform/external/walt/
+PTS integration: vendor/google_testing/pts/tests/salad_fingers/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5926409
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+## WALT Latency Timer ##
+
+**DISCLAIMER:** This is not an official Google product.
+
+ * [Post about WALT on Android Developers Blog](http://android-developers.blogspot.ca/2016/04/a-new-method-to-measure-touch-and-audio.html)
+ * [Instructional videos showing how to use WALT](https://www.youtube.com/playlist?list=PLd6Fi7WgXfcCEJg1FDqNCoQfpWo7W3J5a)
+ * [Detailed usage instructions](docs/usage/WALT_usage.md)
+ * Mailing list - [walt-discuss](https://groups.google.com/forum/#!forum/walt-discuss)
+ * Low-traffic mailing list for major announcements [walt-announce](https://groups.google.com/forum/#!forum/walt-announce)
+
+WALT is designed to measure the latency of physical sensors and outputs on phones and computers. It can currently perform the following measurements:
+
+ * [Tap latency](docs/TapLatency.md) - time from the moment a finger-like probe touches down (or up) on the screen
+ until the kernel timestamps an ACTION_DOWN (or ACTION_UP) event. This physical contact with
+ the screen is timed using an accelerometer mounted on the probe.
+ * [Drag latency](docs/DragLatency.md) (scroll).
+ * [Screen draw latency](docs/ScreenLatency.md) - using a photodiode that detects whether the screen is black or white.
+ * [Audio output and microphone latencies](docs/AudioLatency.md).
+ * MIDI input and output latencies
+
+The WALT app for Android can be
+[installed from Google Play](https://play.google.com/store/apps/details?id=org.kamrik.latency.walt)
+or downloaded in the [releases section](https://github.com/google/walt/releases); the iOS app must be built from source.
+
+![WALT photo](docs/WALT_photo_audio_r07.jpg)
+
+
+## Notes
+* Hardware build instructions can be found in this repository under `hardware/`.
+* Clock synchronization details are described [here](android/WALT/app/src/main/jni/README.md).
+* The Android/iOS device and Teensy clocks have a tendency to diverge due to
+ differing clock frequencies. This means they will go out of sync after
+ several minutes. The workaround is to use the app to re-sync the
+ clocks. Some, but not all tests in the app will sync the clocks when starting a measurement.
+* Python code used to communicate with WALT from Linux and ChromeOS can be found
+ [here](https://chromium.googlesource.com/chromiumos/platform/touchbot/+/master/quickstep/).
+
diff --git a/android/WALT/.gitignore b/android/WALT/.gitignore
new file mode 100644
index 0000000..579b981
--- /dev/null
+++ b/android/WALT/.gitignore
@@ -0,0 +1,10 @@
+.gradle
+.idea
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+/app/src/main/obj
+/app/src/main/libs
diff --git a/android/WALT/WALT.iml b/android/WALT/WALT.iml
new file mode 100644
index 0000000..628d221
--- /dev/null
+++ b/android/WALT/WALT.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module external.linked.project.id="WALT" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="java-gradle" name="Java-Gradle">
+ <configuration>
+ <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
+ <option name="BUILDABLE" value="false" />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/.gradle" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/android/WALT/app/.gitignore b/android/WALT/app/.gitignore
new file mode 100644
index 0000000..cc037c4
--- /dev/null
+++ b/android/WALT/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+app.iml
diff --git a/android/WALT/app/build.gradle b/android/WALT/app/build.gradle
new file mode 100644
index 0000000..531142e
--- /dev/null
+++ b/android/WALT/app/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.model.application'
+
+model {
+ android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+
+ defaultConfig {
+ applicationId "org.chromium.latency.walt"
+ minSdkVersion.apiLevel 17
+ targetSdkVersion.apiLevel 23
+ versionCode 8
+ versionName "0.1.7"
+ }
+ ndk {
+ moduleName "sync_clock_jni"
+ CFlags.addAll "-I${project.rootDir}/app/src/main/jni".toString(), "-g", "-DUSE_LIBLOG", "-Werror"
+ ldLibs.addAll "OpenSLES", "log"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles.add(file("proguard-rules.pro"))
+ }
+ debug {
+ ndk {
+ debuggable true
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:25.1.0'
+ compile 'com.android.support:design:25.1.0'
+ compile 'com.android.support:preference-v7:25.1.0'
+ compile 'com.android.support:preference-v14:25.1.0'
+ compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile ('org.powermock:powermock-api-mockito:1.6.2') {
+ exclude module: 'hamcrest-core'
+ exclude module: 'objenesis'
+ }
+ testCompile ('org.powermock:powermock-module-junit4:1.6.2') {
+ exclude module: 'hamcrest-core'
+ exclude module: 'objenesis'
+ }
+}
diff --git a/android/WALT/app/proguard-rules.pro b/android/WALT/app/proguard-rules.pro
new file mode 100644
index 0000000..2d2fcf0
--- /dev/null
+++ b/android/WALT/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in <SDK dir>/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/android/WALT/app/src/main/AndroidManifest.xml b/android/WALT/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..869b5e8
--- /dev/null
+++ b/android/WALT/app/src/main/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.latency.walt">
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name="org.chromium.latency.walt.MainActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleTask"
+ android:screenOrientation="portrait">
+ <meta-data
+ android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+ android:resource="@xml/device_filter" />
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="org.chromium.latency.walt.CrashLogActivity"
+ android:label="@string/title_activity_crash_log"
+ android:theme="@style/AppTheme" />
+ </application>
+
+</manifest>
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/AboutFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/AboutFragment.java
new file mode 100644
index 0000000..08b4e4f
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/AboutFragment.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+
+/**
+ * A screen that shows information about WALT.
+ */
+public class AboutFragment extends Fragment {
+
+ public AboutFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_about, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ TextView textView = (TextView) getActivity().findViewById(R.id.txt_build_info);
+ String text = String.format("WALT v%s (versionCode=%d)\n",
+ BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);
+ text += "WALT protocol version: " + WaltDevice.PROTOCOL_VERSION + "\n";
+ text += "Android Build ID: " + Build.DISPLAY + "\n";
+ text += "Android API Level: " + Build.VERSION.SDK_INT + "\n";
+ text += "Android OS Version: " + System.getProperty("os.version");
+ textView.setText(text);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/AudioFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/AudioFragment.java
new file mode 100644
index 0000000..65452ff
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/AudioFragment.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.Description;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static org.chromium.latency.walt.Utils.getIntPreference;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class AudioFragment extends Fragment implements View.OnClickListener,
+ BaseTest.TestStateListener {
+
+ enum AudioTestType {
+ CONTINUOUS_PLAYBACK,
+ CONTINUOUS_RECORDING,
+ COLD_PLAYBACK,
+ COLD_RECORDING,
+ DISPLAY_WAVEFORM
+ }
+
+ private SimpleLogger logger;
+ private TextView textView;
+ private AudioTest audioTest;
+ private View startButton;
+ private View stopButton;
+ private Spinner modeSpinner;
+ private LineChart chart;
+ private HistogramChart latencyChart;
+ private View chartLayout;
+
+ private static final int PERMISSION_REQUEST_RECORD_AUDIO = 1;
+
+ public AudioFragment() {
+ // Required empty public constructor
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ logger = SimpleLogger.getInstance(getContext());
+
+ audioTest = new AudioTest(getActivity());
+ audioTest.setTestStateListener(this);
+
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_audio, container, false);
+ textView = (TextView) view.findViewById(R.id.txt_box_audio);
+ textView.setMovementMethod(new ScrollingMovementMethod());
+ startButton = view.findViewById(R.id.button_start_audio);
+ stopButton = view.findViewById(R.id.button_stop_audio);
+ chartLayout = view.findViewById(R.id.chart_layout);
+ chart = (LineChart) view.findViewById(R.id.chart);
+ latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
+
+ view.findViewById(R.id.button_close_chart).setOnClickListener(this);
+ enableButtons();
+
+ // Configure the audio mode spinner
+ modeSpinner = (Spinner) view.findViewById(R.id.spinner_audio_mode);
+ ArrayAdapter<CharSequence> modeAdapter = ArrayAdapter.createFromResource(getContext(),
+ R.array.audio_mode_array, android.R.layout.simple_spinner_item);
+ modeAdapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item);
+ modeSpinner.setAdapter(modeAdapter);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Register this fragment class as the listener for some button clicks
+ startButton.setOnClickListener(this);
+ stopButton.setOnClickListener(this);
+
+ textView.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ audioTest.teardown();
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.button_start_audio:
+ chartLayout.setVisibility(View.GONE);
+ disableButtons();
+ AudioTestType testType = getSelectedTestType();
+ switch (testType) {
+ case CONTINUOUS_PLAYBACK:
+ case CONTINUOUS_RECORDING:
+ case DISPLAY_WAVEFORM:
+ audioTest.setAudioMode(AudioTest.AudioMode.CONTINUOUS);
+ audioTest.setPeriod(AudioTest.CONTINUOUS_TEST_PERIOD);
+ break;
+ case COLD_PLAYBACK:
+ case COLD_RECORDING:
+ audioTest.setAudioMode(AudioTest.AudioMode.CONTINUOUS);
+ audioTest.setPeriod(AudioTest.COLD_TEST_PERIOD);
+ break;
+ }
+ if (testType == AudioTestType.DISPLAY_WAVEFORM) {
+ // Only need to record 1 beep to display wave
+ audioTest.setRecordingRepetitions(1);
+ } else {
+ audioTest.setRecordingRepetitions(
+ getIntPreference(getContext(), R.string.preference_audio_in_reps, 5));
+ }
+ if (testType == AudioTestType.CONTINUOUS_PLAYBACK ||
+ testType == AudioTestType.COLD_PLAYBACK ||
+ testType == AudioTestType.CONTINUOUS_RECORDING ||
+ testType == AudioTestType.COLD_RECORDING) {
+ latencyChart.setVisibility(View.VISIBLE);
+ latencyChart.clearData();
+ latencyChart.setLegendEnabled(false);
+ final String description =
+ getResources().getStringArray(R.array.audio_mode_array)[
+ modeSpinner.getSelectedItemPosition()] + " [ms]";
+ latencyChart.setDescription(description);
+ }
+ switch (testType) {
+ case CONTINUOUS_RECORDING:
+ case COLD_RECORDING:
+ case DISPLAY_WAVEFORM:
+ attemptRecordingTest();
+ break;
+ case CONTINUOUS_PLAYBACK:
+ case COLD_PLAYBACK:
+ // Set media volume to max
+ AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ am.setStreamVolume(AudioManager.STREAM_MUSIC, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
+ audioTest.beginPlaybackMeasurement();
+ break;
+ }
+ break;
+ case R.id.button_stop_audio:
+ audioTest.stopTest();
+ break;
+ case R.id.button_close_chart:
+ chartLayout.setVisibility(View.GONE);
+ break;
+ }
+ }
+
+ private AudioTestType getSelectedTestType() {
+ return AudioTestType.values()[modeSpinner.getSelectedItemPosition()];
+ }
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ textView.append(msg + "\n");
+ }
+ };
+
+ private void attemptRecordingTest() {
+ // first see if we already have permission to record audio
+ int currentPermission = ContextCompat.checkSelfPermission(this.getContext(),
+ Manifest.permission.RECORD_AUDIO);
+ if (currentPermission == PackageManager.PERMISSION_GRANTED) {
+ disableButtons();
+ audioTest.beginRecordingMeasurement();
+ } else {
+ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
+ PERMISSION_REQUEST_RECORD_AUDIO);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ switch (requestCode) {
+ case PERMISSION_REQUEST_RECORD_AUDIO:
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ disableButtons();
+ audioTest.beginRecordingMeasurement();
+ } else {
+ logger.log("Could not get permission to record audio");
+ }
+ return;
+ }
+ }
+
+ @Override
+ public void onTestStopped() {
+ if (getSelectedTestType() == AudioTestType.DISPLAY_WAVEFORM) {
+ drawWaveformChart();
+ } else {
+ if (!audioTest.deltas_mic.isEmpty()) {
+ latencyChart.setLegendEnabled(true);
+ latencyChart.setLabel(String.format(Locale.US, "Median=%.1f ms", Utils.median(audioTest.deltas_mic)));
+ } else if (!audioTest.deltas_queue2wire.isEmpty()) {
+ latencyChart.setLegendEnabled(true);
+ latencyChart.setLabel(String.format(Locale.US, "Median=%.1f ms", Utils.median(audioTest.deltas_queue2wire)));
+ }
+ }
+ LogUploader.uploadIfAutoEnabled(getContext());
+ enableButtons();
+ }
+
+ @Override
+ public void onTestStoppedWithError() {
+ enableButtons();
+ latencyChart.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onTestPartialResult(double value) {
+ latencyChart.addEntry(value);
+ }
+
+ private void drawWaveformChart() {
+ final short[] wave = AudioTest.getRecordedWave();
+ List<Entry> entries = new ArrayList<>();
+ int frameRate = audioTest.getOptimalFrameRate();
+ for (int i = 0; i < wave.length; i++) {
+ float timeStamp = (float) i / frameRate * 1000f;
+ entries.add(new Entry(timeStamp, (float) wave[i]));
+ }
+ LineDataSet dataSet = new LineDataSet(entries, "Waveform");
+ dataSet.setColor(Color.BLACK);
+ dataSet.setValueTextColor(Color.BLACK);
+ dataSet.setCircleColor(ContextCompat.getColor(getContext(), R.color.DarkGreen));
+ dataSet.setCircleRadius(1.5f);
+ dataSet.setCircleColorHole(Color.DKGRAY);
+ LineData lineData = new LineData(dataSet);
+ chart.setData(lineData);
+
+ LimitLine line = new LimitLine(audioTest.getThreshold(), "Threshold");
+ line.setLineColor(Color.RED);
+ line.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
+ line.setLineWidth(2f);
+ line.setTextColor(Color.DKGRAY);
+ line.setTextSize(10f);
+ chart.getAxisLeft().addLimitLine(line);
+
+ final Description desc = new Description();
+ desc.setText("Wave [digital level -32768 to +32767] vs. Time [ms]");
+ desc.setTextSize(12f);
+ chart.setDescription(desc);
+ chart.getLegend().setEnabled(false);
+ chart.invalidate();
+ chartLayout.setVisibility(View.VISIBLE);
+ }
+
+ private void disableButtons() {
+ startButton.setEnabled(false);
+ stopButton.setEnabled(true);
+ }
+
+ private void enableButtons() {
+ startButton.setEnabled(true);
+ stopButton.setEnabled(false);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/AudioTest.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/AudioTest.java
new file mode 100644
index 0000000..6987d7c
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/AudioTest.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Handler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import static org.chromium.latency.walt.Utils.getIntPreference;
+
+class AudioTest extends BaseTest {
+
+ static {
+ System.loadLibrary("sync_clock_jni");
+ }
+
+ static final int CONTINUOUS_TEST_PERIOD = 500;
+ static final int COLD_TEST_PERIOD = 5000;
+
+ enum AudioMode {COLD, CONTINUOUS}
+
+ private Handler handler = new Handler();
+ private boolean userStoppedTest = false;
+
+ // Sound params
+ private final double duration = 0.3; // seconds
+ private final int sampleRate = 8000;
+ private final int numSamples = (int) (duration * sampleRate);
+ private final byte generatedSnd[] = new byte[2 * numSamples];
+ private final double freqOfTone = 880; // hz
+
+ private AudioMode audioMode;
+ private int period = 500; // time between runs in ms
+
+ // Audio in
+ private long last_tb = 0;
+ private int msToRecord = 1000;
+ private final int frameRate;
+ private final int framesPerBuffer;
+
+ private int initiatedBeeps, detectedBeeps;
+ private int playbackRepetitions;
+ private static final int playbackSyncAfterRepetitions = 20;
+
+ // Audio out
+ private int requestedBeeps;
+ private int recordingRepetitions;
+ private static int recorderSyncAfterRepetitions = 10;
+ private final int threshold;
+
+ ArrayList<Double> deltas_mic = new ArrayList<>();
+ private ArrayList<Double> deltas_play2queue = new ArrayList<>();
+ ArrayList<Double> deltas_queue2wire = new ArrayList<>();
+ private ArrayList<Double> deltasJ2N = new ArrayList<>();
+
+ long lastBeepTime;
+
+ public static native long playTone();
+ public static native void startWarmTest();
+ public static native void stopTests();
+ public static native void createEngine();
+ public static native void destroyEngine();
+ public static native void createBufferQueueAudioPlayer(int frameRate, int framesPerBuffer);
+
+ public static native void startRecording();
+ public static native void createAudioRecorder(int frameRate, int framesToRecord);
+ public static native short[] getRecordedWave();
+ public static native long getTeRec();
+ public static native long getTcRec();
+ public static native long getTePlay();
+
+ AudioTest(Context context) {
+ super(context);
+ playbackRepetitions = getIntPreference(context, R.string.preference_audio_out_reps, 10);
+ recordingRepetitions = getIntPreference(context, R.string.preference_audio_in_reps, 5);
+ threshold = getIntPreference(context, R.string.preference_audio_in_threshold, 5000);
+
+ //Check for optimal output sample rate and buffer size
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ String frameRateStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+ String framesPerBufferStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+ logger.log("Optimal frame rate is: " + frameRateStr);
+ logger.log("Optimal frames per buffer is: " + framesPerBufferStr);
+
+ //Convert to ints
+ frameRate = Integer.parseInt(frameRateStr);
+ framesPerBuffer = Integer.parseInt(framesPerBufferStr);
+
+ //Create the audio engine
+ createEngine();
+ createBufferQueueAudioPlayer(frameRate, framesPerBuffer);
+ logger.log("Audio engine created");
+ }
+
+ AudioTest(Context context, AutoRunFragment.ResultHandler resultHandler) {
+ this(context);
+ this.resultHandler = resultHandler;
+ }
+
+ void setPlaybackRepetitions(int beepCount) {
+ playbackRepetitions = beepCount;
+ }
+
+ void setRecordingRepetitions(int beepCount) {
+ recordingRepetitions = beepCount;
+ }
+
+ void setPeriod(int period) {
+ this.period = period;
+ }
+
+ void setAudioMode(AudioMode mode) {
+ audioMode = mode;
+ }
+
+ AudioMode getAudioMode() {
+ return audioMode;
+ }
+
+ int getOptimalFrameRate() {
+ return frameRate;
+ }
+
+ int getThreshold() {
+ return threshold;
+ }
+
+ void stopTest() {
+ userStoppedTest = true;
+ }
+
+ void teardown() {
+ destroyEngine();
+ logger.log("Audio engine destroyed");
+ }
+
+ void beginRecordingMeasurement() {
+ userStoppedTest = false;
+ deltas_mic.clear();
+ deltas_play2queue.clear();
+ deltas_queue2wire.clear();
+ deltasJ2N.clear();
+
+ int framesToRecord = (int) (0.001 * msToRecord * frameRate);
+ createAudioRecorder(frameRate, framesToRecord);
+ logger.log("Audio recorder created; starting test");
+
+ requestedBeeps = 0;
+ doRecordingTestRepetition();
+ }
+
+ private void doRecordingTestRepetition() {
+ if (requestedBeeps >= recordingRepetitions || userStoppedTest) {
+ finishRecordingMeasurement();
+ return;
+ }
+
+ if (requestedBeeps % recorderSyncAfterRepetitions == 0) {
+ try {
+ waltDevice.syncClock();
+ } catch (IOException e) {
+ logger.log("Error syncing clocks: " + e.getMessage());
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ return;
+ }
+ }
+
+ requestedBeeps++;
+ startRecording();
+ switch (audioMode) {
+ case CONTINUOUS:
+ handler.postDelayed(requestBeepRunnable, msToRecord / 2);
+ break;
+ case COLD: // TODO: find a more accurate method to measure cold input latency
+ requestBeepRunnable.run();
+ break;
+ }
+ handler.postDelayed(stopBeepRunnable, msToRecord);
+ }
+
+ void beginPlaybackMeasurement() {
+ userStoppedTest = false;
+ if (audioMode == AudioMode.CONTINUOUS) {
+ startWarmTest();
+ }
+ try {
+ waltDevice.syncClock();
+ waltDevice.startListener();
+ } catch (IOException e) {
+ logger.log("Error starting test: " + e.getMessage());
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ return;
+ }
+ deltas_mic.clear();
+ deltas_play2queue.clear();
+ deltas_queue2wire.clear();
+ deltasJ2N.clear();
+
+ logger.log("Starting playback test");
+
+ initiatedBeeps = 0;
+ detectedBeeps = 0;
+
+ waltDevice.setTriggerHandler(playbackTriggerHandler);
+
+ handler.postDelayed(playBeepRunnable, 300);
+ }
+
+ private WaltDevice.TriggerHandler playbackTriggerHandler = new WaltDevice.TriggerHandler() {
+ @Override
+ public void onReceive(WaltDevice.TriggerMessage tmsg) {
+ // remove the far away playBeep callback(s)
+ handler.removeCallbacks(playBeepRunnable);
+
+ detectedBeeps++;
+ long enqueueTime = getTePlay() - waltDevice.clock.baseTime;
+ double dt_play2queue = (enqueueTime - lastBeepTime) / 1000.;
+ deltas_play2queue.add(dt_play2queue);
+
+ double dt_queue2wire = (tmsg.t - enqueueTime) / 1000.;
+ deltas_queue2wire.add(dt_queue2wire);
+
+ logger.log(String.format(Locale.US,
+ "Beep detected, initiatedBeeps=%d, detectedBeeps=%d\n" +
+ "dt native playTone to Enqueue = %.2f ms\n" +
+ "dt Enqueue to wire = %.2f ms\n",
+ initiatedBeeps, detectedBeeps,
+ dt_play2queue,
+ dt_queue2wire
+ ));
+
+ if (traceLogger != null) {
+ traceLogger.log(lastBeepTime + waltDevice.clock.baseTime,
+ enqueueTime + waltDevice.clock.baseTime,
+ "Play-to-queue",
+ "Bar starts at play time, ends when enqueued");
+ traceLogger.log(enqueueTime + waltDevice.clock.baseTime,
+ tmsg.t + waltDevice.clock.baseTime,
+ "Enqueue-to-wire",
+ "Bar starts at enqueue time, ends when beep is detected");
+ }
+ if (testStateListener != null) testStateListener.onTestPartialResult(dt_queue2wire);
+
+ // Schedule another beep soon-ish
+ handler.postDelayed(playBeepRunnable, (long) (period + Math.random() * 50 - 25));
+ }
+ };
+
+ private Runnable playBeepRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // debug: logger.log("\nPlaying tone...");
+
+ // Check if we saw some transitions without beeping, might be noise audio cable.
+ if (initiatedBeeps == 0 && detectedBeeps > 1) {
+ logger.log("Unexpected beeps detected, noisy cable?");
+ return;
+ }
+
+ if (initiatedBeeps >= playbackRepetitions || userStoppedTest) {
+ finishPlaybackMeasurement();
+ return;
+ }
+
+ initiatedBeeps++;
+
+ if (initiatedBeeps % playbackSyncAfterRepetitions == 0) {
+ try {
+ waltDevice.stopListener();
+ waltDevice.syncClock();
+ waltDevice.startListener();
+ } catch (IOException e) {
+ logger.log("Error re-syncing clock: " + e.getMessage());
+ finishPlaybackMeasurement();
+ return;
+ }
+ }
+
+ try {
+ waltDevice.command(WaltDevice.CMD_AUDIO);
+ } catch (IOException e) {
+ logger.log("Error sending command AUDIO: " + e.getMessage());
+ return;
+ }
+ long javaBeepTime = waltDevice.clock.micros();
+ lastBeepTime = playTone() - waltDevice.clock.baseTime;
+ double dtJ2N = (lastBeepTime - javaBeepTime)/1000.;
+ deltasJ2N.add(dtJ2N);
+ if (traceLogger != null) {
+ traceLogger.log(javaBeepTime + waltDevice.clock.baseTime,
+ lastBeepTime + waltDevice.clock.baseTime, "Java-to-native",
+ "Bar starts when Java tells native to beep and ends when buffer written in native");
+ }
+ logger.log(String.format(Locale.US,
+ "Called playTone(), dt Java to native = %.3f ms",
+ dtJ2N
+ ));
+
+
+ // Repost doBeep to some far away time to blink again even if nothing arrives from
+ // Teensy. This callback will almost always get cancelled by onIncomingTimestamp()
+ handler.postDelayed(playBeepRunnable, (long) (period * 3 + Math.random() * 100 - 50));
+
+ }
+ };
+
+
+ private Runnable requestBeepRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // logger.log("\nRequesting beep from WALT...");
+ String s;
+ try {
+ s = waltDevice.command(WaltDevice.CMD_BEEP);
+ } catch (IOException e) {
+ logger.log("Error sending command BEEP: " + e.getMessage());
+ return;
+ }
+ last_tb = Integer.parseInt(s);
+ logger.log("Beeped, reply: " + s);
+ handler.postDelayed(processRecordingRunnable, (long) (msToRecord * 2 + Math.random() * 100 - 50));
+ }
+ };
+
+ private Runnable stopBeepRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ waltDevice.command(WaltDevice.CMD_BEEP_STOP);
+ } catch (IOException e) {
+ logger.log("Error stopping tone from WALT: " + e.getMessage());
+ }
+ }
+ };
+
+ private Runnable processRecordingRunnable = new Runnable() {
+ @Override
+ public void run() {
+ long te = getTeRec() - waltDevice.clock.baseTime; // When a buffer was enqueued for recording
+ long tc = getTcRec() - waltDevice.clock.baseTime; // When callback receiving a recorded buffer fired
+ long tb = last_tb; // When WALT started a beep (according to WALT clock)
+ short[] wave = getRecordedWave();
+ int noisyAtFrame = 0; // First frame when some noise starts
+ while (noisyAtFrame < wave.length && wave[noisyAtFrame] < threshold)
+ noisyAtFrame++;
+ if (noisyAtFrame == wave.length) {
+ logger.log("WARNING: No sound detected");
+ doRecordingTestRepetition();
+ return;
+ }
+
+ // Length of recorded buffer
+ double duration_us = wave.length * 1e6 / frameRate;
+
+ // Duration in microseconds of the initial silent part of the buffer, and the remaining
+ // part after the beep started.
+ double silent_us = noisyAtFrame * 1e6 / frameRate;
+ double remaining_us = duration_us - silent_us;
+
+ // Time from the last frame in the buffer until the callback receiving the buffer fired
+ double latencyCb_ms = (tc - tb - remaining_us) / 1000.;
+
+ // Time from the moment a buffer was enqueued for recording until the first frame in
+ // the buffer was recorded
+ double latencyEnqueue_ms = (tb - te - silent_us) / 1000.;
+
+ logger.log(String.format(Locale.US,
+ "Processed: L_cb = %.3f ms, L_eq = %.3f ms, noisy frame = %d",
+ latencyCb_ms,
+ latencyEnqueue_ms,
+ noisyAtFrame
+ ));
+
+ if (testStateListener != null) testStateListener.onTestPartialResult(latencyCb_ms);
+ if (traceLogger != null) {
+ traceLogger.log((long) (tb + waltDevice.clock.baseTime + remaining_us),
+ tc + waltDevice.clock.baseTime,
+ "Beep-to-rec-callback",
+ "Bar starts when WALT plays beep and ends when recording callback received");
+ }
+
+ deltas_mic.add(latencyCb_ms);
+ doRecordingTestRepetition();
+ }
+ };
+
+ private void finishPlaybackMeasurement() {
+ stopTests();
+ waltDevice.stopListener();
+ waltDevice.clearTriggerHandler();
+ waltDevice.checkDrift();
+
+ // Debug: logger.log("deltas_play2queue = array(" + deltas_play2queue.toString() +")");
+ logger.log(String.format(Locale.US,
+ "\n%s audio playback results:\n" +
+ "Detected %d beeps out of %d initiated\n" +
+ "Median Java to native time is %.3f ms\n" +
+ "Median native playTone to Enqueue time is %.1f ms\n" +
+ "Buffer length is %d frames at %d Hz = %.2f ms\n" +
+ "-------------------------------\n" +
+ "Median time from Enqueue to wire is %.1f ms\n" +
+ "-------------------------------\n",
+ audioMode == AudioMode.COLD? "Cold" : "Continuous",
+ detectedBeeps, initiatedBeeps,
+ Utils.median(deltasJ2N),
+ Utils.median(deltas_play2queue),
+ framesPerBuffer, frameRate, 1000.0 / frameRate * framesPerBuffer,
+ Utils.median(deltas_queue2wire)
+ ));
+
+ if (resultHandler != null) {
+ resultHandler.onResult(deltas_play2queue, deltas_queue2wire);
+ }
+ if (testStateListener != null) testStateListener.onTestStopped();
+ if (traceLogger != null) traceLogger.flush(context);
+ }
+
+ private void finishRecordingMeasurement() {
+ waltDevice.checkDrift();
+
+ // Debug: logger.log("deltas_mic: " + deltas_mic.toString());
+
+ logger.log(String.format(Locale.US,
+ "\nAudio recording/microphone results:\n" +
+ "Recorded %d beeps.\n" +
+ "-------------------------------\n" +
+ "Median callback latency - " +
+ "time from sampling the last frame to recorder callback is %.1f ms\n" +
+ "-------------------------------\n",
+ deltas_mic.size(),
+ Utils.median(deltas_mic)
+ ));
+
+ if (resultHandler != null) {
+ resultHandler.onResult(deltas_mic);
+ }
+ if (testStateListener != null) testStateListener.onTestStopped();
+ if (traceLogger != null) traceLogger.flush(context);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/AutoRunFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/AutoRunFragment.java
new file mode 100644
index 0000000..f2f2a7f
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/AutoRunFragment.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Iterator;
+
+public class AutoRunFragment extends Fragment {
+
+ static final String TEST_ACTION = "org.chromium.latency.walt.START_TEST";
+ static final String MODE_COLD = "Cold";
+
+ private WaltDevice waltDevice;
+ private AudioTest toTearDown; // TODO: figure out a better way to destroy the engine
+ Handler handler = new Handler();
+
+ private class AudioResultHandler implements ResultHandler {
+ private FileWriter fileWriter;
+
+ AudioResultHandler(String fileName) throws IOException {
+ fileWriter = new FileWriter(fileName);
+ }
+
+ @Override
+ public void onResult(Iterable[] results) {
+ if (results.length == 0) {
+ logger.log("Can't write empty data!");
+ return;
+ }
+ logger.log("Writing data file");
+
+ Iterator its[] = new Iterator[results.length];
+
+ for (int i = 0; i < results.length; i++) {
+ its[i] = results[i].iterator();
+ }
+ try {
+ while (its[0].hasNext()) {
+ for (Iterator it : its) {
+ if (it.hasNext()) {
+ fileWriter.write(it.next().toString() + ",");
+ }
+ }
+ fileWriter.write("\n");
+ }
+ } catch (IOException e) {
+ logger.log("Error writing output file: " + e.getMessage());
+ } finally {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ logger.log("Error closing output file: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ private void doTest(@NonNull Bundle args) {
+ final int reps = args.getInt("Reps", 10);
+ String fileName = args.getString("FileName", null);
+ ResultHandler r = null;
+ if (fileName != null) {
+ try {
+ r = new AudioResultHandler(fileName);
+ } catch (IOException e) {
+ logger.log("Unable to open output file " + e.getMessage());
+ return;
+ }
+ }
+ final String mode = args.getString("Mode", "");
+ final ResultHandler resultHandler = r;
+ Runnable testRunnable = null;
+ switch (args.getString("TestType", "")) {
+ case "MidiIn": {
+ testRunnable = new Runnable() {
+ @Override
+ public void run() {
+ MidiTest midiTest = new MidiTest(getContext(), resultHandler);
+ midiTest.setInputRepetitions(reps);
+ midiTest.testMidiIn();
+ }
+ };
+ break;
+ }
+ case "MidiOut": {
+ testRunnable = new Runnable() {
+ @Override
+ public void run() {
+ MidiTest midiTest = new MidiTest(getContext(), resultHandler);
+ midiTest.setOutputRepetitions(reps);
+ midiTest.testMidiOut();
+ }
+ };
+ break;
+ }
+ case "AudioIn": {
+ testRunnable = new Runnable() {
+ @Override
+ public void run() {
+ AudioTest audioTest = new AudioTest(getContext(), resultHandler);
+ audioTest.setRecordingRepetitions(reps);
+ audioTest.setAudioMode(MODE_COLD.equals(mode) ?
+ AudioTest.AudioMode.COLD : AudioTest.AudioMode.CONTINUOUS);
+ audioTest.beginRecordingMeasurement();
+ toTearDown = audioTest;
+ }
+ };
+ break;
+ }
+ case "AudioOut": {
+ final int period = args.getInt("Period", -1);
+ testRunnable = new Runnable() {
+ @Override
+ public void run() {
+ AudioTest audioTest = new AudioTest(getContext(), resultHandler);
+ audioTest.setPlaybackRepetitions(reps);
+ audioTest.setAudioMode(MODE_COLD.equals(mode) ?
+ AudioTest.AudioMode.COLD : AudioTest.AudioMode.CONTINUOUS);
+ if (period > 0) {
+ audioTest.setPeriod(period);
+ } else {
+ audioTest.setPeriod(MODE_COLD.equals(mode) ?
+ AudioTest.COLD_TEST_PERIOD : AudioTest.CONTINUOUS_TEST_PERIOD);
+ }
+ audioTest.beginPlaybackMeasurement();
+ toTearDown = audioTest;
+ }
+ };
+ break;
+ }
+ }
+
+ // Not sure we need the handler.post() here, but just in case.
+ final Runnable finalTestRunnable = testRunnable;
+ waltDevice.setConnectionStateListener(new WaltConnection.ConnectionStateListener() {
+ @Override
+ public void onConnect() {
+ handler.post(finalTestRunnable);
+ }
+
+ @Override
+ public void onDisconnect() {}
+ });
+
+ }
+
+ interface ResultHandler {
+ void onResult(Iterable... r);
+ }
+
+ @Override
+ public void onDestroyView() {
+ if (toTearDown != null) {
+ toTearDown.teardown();
+ }
+ super.onDestroyView();
+ }
+
+ private TextView txtLogAutoRun;
+ private SimpleLogger logger;
+
+ public AutoRunFragment() {
+ // Required empty public constructor
+ }
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ txtLogAutoRun.append("\n" + msg);
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ logger = SimpleLogger.getInstance(getContext());
+ waltDevice = WaltDevice.getInstance(getContext());
+
+ View view = inflater.inflate(R.layout.fragment_auto_run, container, false);
+
+ Bundle args = getArguments();
+ if (args != null) {
+ doTest(args);
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ txtLogAutoRun = (TextView) getActivity().findViewById(R.id.txt_log_auto_run);
+ txtLogAutoRun.setMovementMethod(new ScrollingMovementMethod());
+ txtLogAutoRun.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/BaseTest.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/BaseTest.java
new file mode 100644
index 0000000..e0e3b17
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/BaseTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+
+import static org.chromium.latency.walt.Utils.getBooleanPreference;
+
+abstract class BaseTest {
+
+ interface TestStateListener {
+ void onTestStopped();
+ void onTestStoppedWithError();
+ void onTestPartialResult(double value);
+ }
+
+ Context context;
+ SimpleLogger logger;
+ TraceLogger traceLogger = null;
+ WaltDevice waltDevice;
+ TestStateListener testStateListener = null;
+ AutoRunFragment.ResultHandler resultHandler = null;
+
+ BaseTest(Context context) {
+ this.context = context;
+ waltDevice = WaltDevice.getInstance(context);
+ logger = SimpleLogger.getInstance(context);
+ if (getBooleanPreference(context, R.string.preference_systrace, true)) {
+ traceLogger = TraceLogger.getInstance();
+ }
+ }
+
+ void setTestStateListener(TestStateListener listener) {
+ this.testStateListener = listener;
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/BaseUsbConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/BaseUsbConnection.java
new file mode 100644
index 0000000..f0e6c62
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/BaseUsbConnection.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+import android.support.v4.content.LocalBroadcastManager;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+public abstract class BaseUsbConnection {
+ private static final String USB_PERMISSION_RESPONSE_INTENT = "usb-permission-response";
+ private static final String CONNECT_INTENT = "org.chromium.latency.walt.CONNECT";
+
+ protected SimpleLogger logger;
+ protected Context context;
+ private LocalBroadcastManager broadcastManager;
+ private BroadcastReceiver currentConnectReceiver;
+ private WaltConnection.ConnectionStateListener connectionStateListener;
+
+ private UsbManager usbManager;
+ protected UsbDevice usbDevice = null;
+ protected UsbDeviceConnection usbConnection;
+
+ public BaseUsbConnection(Context context) {
+ this.context = context;
+ usbManager = (UsbManager) this.context.getSystemService(Context.USB_SERVICE);
+ logger = SimpleLogger.getInstance(context);
+ broadcastManager = LocalBroadcastManager.getInstance(context);
+ }
+
+ public abstract int getVid();
+ public abstract int getPid();
+
+ // Used to distinguish between bootloader and normal mode that differ by PID
+ // TODO: change intent strings to reduce dependence on PID
+ protected abstract boolean isCompatibleUsbDevice(UsbDevice usbDevice);
+
+ public void onDisconnect() {
+ if (connectionStateListener != null) {
+ connectionStateListener.onDisconnect();
+ }
+ }
+
+ public void onConnect() {
+ if (connectionStateListener != null) {
+ connectionStateListener.onConnect();
+ }
+ }
+
+
+ private String getConnectIntent() {
+ return CONNECT_INTENT + getVid() + ":" + getPid();
+ }
+
+ private String getUsbPermissionResponseIntent() {
+ return USB_PERMISSION_RESPONSE_INTENT + getVid() + ":" + getPid();
+ }
+
+ public boolean isConnected() {
+ return usbConnection != null;
+ }
+
+ public void registerConnectCallback(final Runnable r) {
+ if (currentConnectReceiver != null) {
+ broadcastManager.unregisterReceiver(currentConnectReceiver);
+ currentConnectReceiver = null;
+ }
+
+ if (isConnected()) {
+ r.run();
+ return;
+ }
+
+ currentConnectReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ broadcastManager.unregisterReceiver(this);
+ r.run();
+ }
+ };
+ broadcastManager.registerReceiver(currentConnectReceiver,
+ new IntentFilter(getConnectIntent()));
+ }
+
+ public void connect() {
+ UsbDevice usbDevice = findUsbDevice();
+ connect(usbDevice);
+ }
+
+ public void connect(UsbDevice usbDevice) {
+ if (usbDevice == null) {
+ logger.log("Device not found.");
+ return;
+ }
+
+ if (!isCompatibleUsbDevice(usbDevice)) {
+ logger.log("Not a valid device");
+ return;
+ }
+
+ this.usbDevice = usbDevice;
+
+ // Request permission
+ // This displays a dialog asking user for permission to use the device.
+ // No dialog is displayed if the permission was already given before or the app started as a
+ // result of intent filter when the device was plugged in.
+
+ PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(getUsbPermissionResponseIntent()), 0);
+ context.registerReceiver(respondToUsbPermission,
+ new IntentFilter(getUsbPermissionResponseIntent()));
+ logger.log("Requesting permission for USB device.");
+ usbManager.requestPermission(this.usbDevice, permissionIntent);
+ }
+
+ public void disconnect() {
+ onDisconnect();
+
+ usbConnection.close();
+ usbConnection = null;
+ usbDevice = null;
+
+ context.unregisterReceiver(disconnectReceiver);
+ }
+
+ private BroadcastReceiver disconnectReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (isConnected() && BaseUsbConnection.this.usbDevice.equals(usbDevice)) {
+ logger.log("WALT was detached");
+ disconnect();
+ }
+ }
+ };
+
+ private BroadcastReceiver respondToUsbPermission = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ if (usbDevice == null) {
+ logger.log("USB device was not properly opened");
+ return;
+ }
+
+ if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) &&
+ usbDevice.equals(intent.getParcelableExtra(UsbManager.EXTRA_DEVICE))){
+ usbConnection = usbManager.openDevice(usbDevice);
+
+ BaseUsbConnection.this.context.registerReceiver(disconnectReceiver,
+ new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
+
+ onConnect();
+
+ broadcastManager.sendBroadcast(new Intent(getConnectIntent()));
+ } else {
+ logger.log("Could not get permission to open the USB device");
+ }
+ BaseUsbConnection.this.context.unregisterReceiver(respondToUsbPermission);
+ }
+ };
+
+ public UsbDevice findUsbDevice() {
+
+ logger.log(String.format("Looking for TeensyUSB VID=0x%x PID=0x%x", getVid(), getPid()));
+
+ HashMap<String, UsbDevice> deviceHash = usbManager.getDeviceList();
+ if (deviceHash.size() == 0) {
+ logger.log("No connected USB devices found");
+ return null;
+ }
+
+ logger.log("Found " + deviceHash.size() + " connected USB devices:");
+
+ UsbDevice usbDevice = null;
+
+ for (String key : deviceHash.keySet()) {
+
+ UsbDevice dev = deviceHash.get(key);
+
+ String msg = String.format(Locale.US,
+ "USB Device: %s, VID:PID - %x:%x, %d interfaces",
+ key, dev.getVendorId(), dev.getProductId(), dev.getInterfaceCount()
+ );
+
+ if (isCompatibleUsbDevice(dev)) {
+ usbDevice = dev;
+ msg = "Using " + msg;
+ } else {
+ msg = "Skipping " + msg;
+ }
+
+ logger.log(msg);
+ }
+ return usbDevice;
+ }
+
+ public void setConnectionStateListener(WaltConnection.ConnectionStateListener connectionStateListener) {
+ this.connectionStateListener = connectionStateListener;
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/CrashLogActivity.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/CrashLogActivity.java
new file mode 100644
index 0000000..00e80ed
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/CrashLogActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.text.method.ScrollingMovementMethod;
+import android.widget.TextView;
+
+
+/**
+ * A separate activity to display exception trace on the screen in case of a crash.
+ * This is useful because we dont have the USB cable connected for debugging in many cases, because
+ * the USB port is taken by the WALT device.
+ */
+public class CrashLogActivity extends AppCompatActivity {
+
+ TextView txtCrashLog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_crash_log);
+ txtCrashLog = (TextView) findViewById(R.id.txt_crash_log);
+ txtCrashLog.setText(getIntent().getStringExtra("crash_log"));
+ txtCrashLog.setMovementMethod(new ScrollingMovementMethod());
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/CustomNumberPicker.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/CustomNumberPicker.java
new file mode 100644
index 0000000..27f5b50
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/CustomNumberPicker.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.NumberPicker;
+
+public class CustomNumberPicker extends NumberPicker {
+
+ public CustomNumberPicker(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void addView(View child) {
+ super.addView(child);
+ initEditText(child);
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ super.addView(child, index);
+ initEditText(child);
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+ initEditText(child);
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ super.addView(child, params);
+ initEditText(child);
+ }
+
+ @Override
+ public void addView(View child, int width, int height) {
+ super.addView(child, width, height);
+ initEditText(child);
+ }
+
+ private void initEditText(View view) {
+ if (view instanceof EditText) {
+ EditText inputText = (EditText) view;
+ inputText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ try {
+ CustomNumberPicker.this.setValue(Integer.parseInt(s.toString()));
+ } catch (NumberFormatException ignored) {}
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+ });
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/DiagnosticsFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/DiagnosticsFragment.java
new file mode 100644
index 0000000..65ec3bf
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/DiagnosticsFragment.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+
+/**
+ * This screen allows to perform different tasks useful for diagnostics.
+ */
+public class DiagnosticsFragment extends Fragment {
+
+ private SimpleLogger logger;
+ private TextView logTextView;
+
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ DiagnosticsFragment.this.appendLogText(msg);
+ }
+ };
+
+ public DiagnosticsFragment() {
+ // Required empty public constructor
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ logger = SimpleLogger.getInstance(getContext());
+ // Inflate the layout for this fragment
+ final View view = inflater.inflate(R.layout.fragment_diagnostics, container, false);
+ logTextView = (TextView) view.findViewById(R.id.txt_log_diag);
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ logTextView.setMovementMethod(new ScrollingMovementMethod());
+ logTextView.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+
+ public void appendLogText(String msg) {
+ logTextView.append(msg + "\n");
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java
new file mode 100644
index 0000000..af03e36
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.ScatterChart;
+import com.github.mikephil.charting.components.Description;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.ScatterData;
+import com.github.mikephil.charting.data.ScatterDataSet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class DragLatencyFragment extends Fragment
+ implements View.OnClickListener, RobotAutomationListener {
+
+ private SimpleLogger logger;
+ private WaltDevice waltDevice;
+ private TextView logTextView;
+ private TouchCatcherView touchCatcher;
+ private TextView crossCountsView;
+ private TextView dragCountsView;
+ private View startButton;
+ private View restartButton;
+ private View finishButton;
+ private ScatterChart latencyChart;
+ private View latencyChartLayout;
+ int moveCount = 0;
+
+ ArrayList<UsMotionEvent> touchEventList = new ArrayList<>();
+ ArrayList<WaltDevice.TriggerMessage> laserEventList = new ArrayList<>();
+
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ DragLatencyFragment.this.appendLogText(msg);
+ }
+ };
+
+ private View.OnTouchListener touchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ int histLen = event.getHistorySize();
+ for (int i = 0; i < histLen; i++){
+ UsMotionEvent eh = new UsMotionEvent(event, waltDevice.clock.baseTime, i);
+ touchEventList.add(eh);
+ }
+ UsMotionEvent e = new UsMotionEvent(event, waltDevice.clock.baseTime);
+ touchEventList.add(e);
+ moveCount += histLen + 1;
+
+ updateCountsDisplay();
+ return true;
+ }
+ };
+
+ public DragLatencyFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ logger = SimpleLogger.getInstance(getContext());
+ waltDevice = WaltDevice.getInstance(getContext());
+
+ // Inflate the layout for this fragment
+ final View view = inflater.inflate(R.layout.fragment_drag_latency, container, false);
+ logTextView = (TextView) view.findViewById(R.id.txt_log_drag_latency);
+ startButton = view.findViewById(R.id.button_start_drag);
+ restartButton = view.findViewById(R.id.button_restart_drag);
+ finishButton = view.findViewById(R.id.button_finish_drag);
+ touchCatcher = (TouchCatcherView) view.findViewById(R.id.tap_catcher);
+ crossCountsView = (TextView) view.findViewById(R.id.txt_cross_counts);
+ dragCountsView = (TextView) view.findViewById(R.id.txt_drag_counts);
+ latencyChart = (ScatterChart) view.findViewById(R.id.latency_chart);
+ latencyChartLayout = view.findViewById(R.id.latency_chart_layout);
+ logTextView.setMovementMethod(new ScrollingMovementMethod());
+ view.findViewById(R.id.button_close_chart).setOnClickListener(this);
+ restartButton.setEnabled(false);
+ finishButton.setEnabled(false);
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ logTextView.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+
+ // Register this fragment class as the listener for some button clicks
+ startButton.setOnClickListener(this);
+ restartButton.setOnClickListener(this);
+ finishButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+ public void appendLogText(String msg) {
+ logTextView.append(msg + "\n");
+ }
+
+ void updateCountsDisplay() {
+ crossCountsView.setText(String.format(Locale.US, "↕ %d", laserEventList.size()));
+ dragCountsView.setText(String.format(Locale.US, "⇄ %d", moveCount));
+ }
+
+ /**
+ * @return true if measurement was successfully started
+ */
+ boolean startMeasurement() {
+ logger.log("Starting drag latency test");
+ try {
+ waltDevice.syncClock();
+ } catch (IOException e) {
+ logger.log("Error syncing clocks: " + e.getMessage());
+ return false;
+ }
+ // Register a callback for triggers
+ waltDevice.setTriggerHandler(triggerHandler);
+ try {
+ waltDevice.command(WaltDevice.CMD_AUTO_LASER_ON);
+ waltDevice.startListener();
+ } catch (IOException e) {
+ logger.log("Error: " + e.getMessage());
+ waltDevice.clearTriggerHandler();
+ return false;
+ }
+ touchCatcher.setOnTouchListener(touchListener);
+ touchCatcher.startAnimation();
+ touchEventList.clear();
+ laserEventList.clear();
+ moveCount = 0;
+ updateCountsDisplay();
+ return true;
+ }
+
+ void restartMeasurement() {
+ logger.log("\n## Restarting drag latency test. Re-sync clocks ...");
+ try {
+ waltDevice.syncClock();
+ } catch (IOException e) {
+ logger.log("Error syncing clocks: " + e.getMessage());
+ }
+
+ touchCatcher.startAnimation();
+ touchEventList.clear();
+ laserEventList.clear();
+ moveCount = 0;
+ updateCountsDisplay();
+ }
+
+ void finishAndShowStats() {
+ touchCatcher.stopAnimation();
+ waltDevice.stopListener();
+ try {
+ waltDevice.command(WaltDevice.CMD_AUTO_LASER_OFF);
+ } catch (IOException e) {
+ logger.log("Error: " + e.getMessage());
+ }
+ touchCatcher.setOnTouchListener(null);
+ waltDevice.clearTriggerHandler();
+
+ waltDevice.checkDrift();
+
+ logger.log(String.format(Locale.US,
+ "Recorded %d laser events and %d touch events. ",
+ laserEventList.size(),
+ touchEventList.size()
+ ));
+
+ if (touchEventList.size() < 100) {
+ logger.log("Insufficient number of touch events (<100), aborting.");
+ return;
+ }
+
+ if (laserEventList.size() < 8) {
+ logger.log("Insufficient number of laser events (<8), aborting.");
+ return;
+ }
+
+ // TODO: Log raw data if enabled in settings, touch events add lots of text to the log.
+ // logRawData();
+ reshapeAndCalculate();
+ LogUploader.uploadIfAutoEnabled(getContext());
+ }
+
+ // Data formatted for processing with python script, y.py
+ void logRawData() {
+ logger.log("#####> LASER EVENTS #####");
+ for (int i = 0; i < laserEventList.size(); i++){
+ logger.log(laserEventList.get(i).t + " " + laserEventList.get(i).value);
+ }
+ logger.log("#####< END OF LASER EVENTS #####");
+
+ logger.log("=====> TOUCH EVENTS =====");
+ for (UsMotionEvent e: touchEventList) {
+ logger.log(String.format(Locale.US,
+ "%d %.3f %.3f",
+ e.kernelTime,
+ e.x, e.y
+ ));
+ }
+ logger.log("=====< END OF TOUCH EVENTS =====");
+ }
+
+ void reshapeAndCalculate() {
+ double[] ft, lt; // All time arrays are in _milliseconds_
+ double[] fy;
+ int[] ldir;
+
+ // Use the time of the first touch event as time = 0 for debugging convenience
+ long t0_us = touchEventList.get(0).kernelTime;
+ long tLast_us = touchEventList.get(touchEventList.size() - 1).kernelTime;
+
+ int fN = touchEventList.size();
+ ft = new double[fN];
+ fy = new double[fN];
+
+ for (int i = 0; i < fN; i++){
+ ft[i] = (touchEventList.get(i).kernelTime - t0_us) / 1000.;
+ fy[i] = touchEventList.get(i).y;
+ }
+
+ // Remove all laser events that are outside the time span of the touch events
+ // they are not usable and would result in errors downstream
+ int j = laserEventList.size() - 1;
+ while (j >= 0 && laserEventList.get(j).t > tLast_us) {
+ laserEventList.remove(j);
+ j--;
+ }
+
+ while (laserEventList.size() > 0 && laserEventList.get(0).t < t0_us) {
+ laserEventList.remove(0);
+ }
+
+ // Calculation assumes that the first event is generated by the finger obstructing the beam.
+ // Remove the first event if it was generated by finger going out of the beam (value==1).
+ while (laserEventList.size() > 0 && laserEventList.get(0).value == 1) {
+ laserEventList.remove(0);
+ }
+
+ int lN = laserEventList.size();
+
+ if (lN < 8) {
+ logger.log("ERROR: Insufficient number of laser events overlapping with touch events," +
+ "aborting."
+ );
+ return;
+ }
+
+ lt = new double[lN];
+ ldir = new int[lN];
+ for (int i = 0; i < lN; i++){
+ lt[i] = (laserEventList.get(i).t - t0_us) / 1000.;
+ ldir[i] = laserEventList.get(i).value;
+ }
+
+ calculateDragLatency(ft,fy, lt, ldir);
+ }
+
+ /**
+ * Handler for all the button clicks on this screen.
+ */
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.button_restart_drag) {
+ latencyChartLayout.setVisibility(View.GONE);
+ restartButton.setEnabled(false);
+ restartMeasurement();
+ restartButton.setEnabled(true);
+ return;
+ }
+
+ if (v.getId() == R.id.button_start_drag) {
+ latencyChartLayout.setVisibility(View.GONE);
+ startButton.setEnabled(false);
+ boolean startSuccess = startMeasurement();
+ if (startSuccess) {
+ finishButton.setEnabled(true);
+ restartButton.setEnabled(true);
+ } else {
+ startButton.setEnabled(true);
+ }
+ return;
+ }
+
+ if (v.getId() == R.id.button_finish_drag) {
+ finishButton.setEnabled(false);
+ restartButton.setEnabled(false);
+ finishAndShowStats();
+ startButton.setEnabled(true);
+ return;
+ }
+
+ if (v.getId() == R.id.button_close_chart) {
+ latencyChartLayout.setVisibility(View.GONE);
+ }
+ }
+
+ public void onRobotAutomationEvent(String event) {
+ if (event.equals(RobotAutomationListener.RESTART_EVENT)) {
+ onClick(restartButton);
+ } else if (event.equals(RobotAutomationListener.START_EVENT)) {
+ onClick(startButton);
+ } else if (event.equals(RobotAutomationListener.FINISH_EVENT)) {
+ onClick(finishButton);
+ }
+ }
+
+ private WaltDevice.TriggerHandler triggerHandler = new WaltDevice.TriggerHandler() {
+ @Override
+ public void onReceive(WaltDevice.TriggerMessage tmsg) {
+ laserEventList.add(tmsg);
+ updateCountsDisplay();
+ }
+ };
+
+ public void calculateDragLatency(double[] ft, double[] fy, double[] lt, int[] ldir) {
+ // TODO: throw away several first laser crossings (if not already)
+ double[] ly = Utils.interp(lt, ft, fy);
+ double lmid = Utils.mean(ly);
+ // Assume first crossing is into the beam = light-off = 0
+ if (ldir[0] != 0) {
+ // TODO: add more sanity checks here.
+ logger.log("First laser crossing is not into the beam, aborting");
+ return;
+ }
+
+ // label sides, one simple label is i starts from 1, then side = (i mod 4) / 2 same as the 2nd LSB bit or i.
+ int[] sideIdx = new int[lt.length];
+
+ // This is one way of deciding what laser events were on which side
+ // It should go above, below, below, above, above
+ // The other option is to mirror the python code that uses position and velocity for this
+ for (int i = 0; i<lt.length; i++) {
+ sideIdx[i] = ((i+1) / 2) % 2;
+ }
+ /*
+ logger.log("ft = " + Utils.array2string(ft, "%.2f"));
+ logger.log("fy = " + Utils.array2string(fy, "%.2f"));
+ logger.log("lt = " + Utils.array2string(lt, "%.2f"));
+ logger.log("sideIdx = " + Arrays.toString(sideIdx));*/
+
+ double averageBestShift = 0;
+ for(int side = 0; side < 2; side++) {
+ double[] lts = Utils.extract(sideIdx, side, lt);
+ // TODO: time this call
+ double bestShift = Utils.findBestShift(lts, ft, fy);
+ logger.log(String.format(Locale.US, "bestShift = %.2f", bestShift));
+ averageBestShift += bestShift / 2;
+ }
+
+ drawLatencyGraph(ft, fy, lt, averageBestShift);
+ logger.log(String.format(Locale.US, "Drag latency is %.1f [ms]", averageBestShift));
+ }
+
+ private void drawLatencyGraph(double[] ft, double[] fy, double[] lt, double averageBestShift) {
+ final ArrayList<Entry> touchEntries = new ArrayList<>();
+ final ArrayList<Entry> laserEntries = new ArrayList<>();
+ final double[] laserT = new double[lt.length];
+ for (int i = 0; i < ft.length; i++) {
+ touchEntries.add(new Entry((float) ft[i], (float) fy[i]));
+ }
+ for (int i = 0; i < lt.length; i++) {
+ laserT[i] = lt[i] + averageBestShift;
+ }
+ final double[] laserY = Utils.interp(laserT, ft, fy);
+ for (int i = 0; i < laserY.length; i++) {
+ laserEntries.add(new Entry((float) laserT[i], (float) laserY[i]));
+ }
+
+ final ScatterDataSet dataSetTouch = new ScatterDataSet(touchEntries, "Touch Events");
+ dataSetTouch.setScatterShape(ScatterChart.ScatterShape.CIRCLE);
+ dataSetTouch.setScatterShapeSize(8f);
+
+ final ScatterDataSet dataSetLaser = new ScatterDataSet(laserEntries,
+ String.format(Locale.US, "Laser Events Latency=%.1f ms", averageBestShift));
+ dataSetLaser.setColor(Color.RED);
+ dataSetLaser.setScatterShapeSize(10f);
+ dataSetLaser.setScatterShape(ScatterChart.ScatterShape.X);
+
+ final ScatterData scatterData = new ScatterData(dataSetTouch, dataSetLaser);
+ final Description desc = new Description();
+ desc.setText("Y-Position [pixels] vs. Time [ms]");
+ desc.setTextSize(12f);
+ latencyChart.setDescription(desc);
+ latencyChart.setData(scatterData);
+ latencyChartLayout.setVisibility(View.VISIBLE);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/FastPathSurfaceView.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/FastPathSurfaceView.java
new file mode 100644
index 0000000..449627f
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/FastPathSurfaceView.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.widget.Toast;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class FastPathSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+
+ private boolean isActive = false;
+
+ public FastPathSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ getHolder().addCallback(this);
+ setZOrderOnTop(true);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Surface surface = holder.getSurface();
+ if (surface == null)
+ return;
+
+ try {
+ Method setSharedBufferMode = Surface.class.getMethod("setSharedBufferMode", boolean.class);
+ setSharedBufferMode.invoke(surface, true);
+ displayMessage("Using shared buffer mode.");
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ displayMessage("Shared buffer mode is not supported.");
+ }
+ Canvas canvas = surface.lockCanvas(null);
+ canvas.drawColor(Color.GRAY);
+ surface.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ isActive = true;
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ isActive = false;
+ }
+
+ private void displayMessage(String message) {
+ Toast toast = Toast.makeText(getContext(), message, Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ public void setRectColor(int color) {
+ Surface surface = getHolder().getSurface();
+ if (surface == null || !isActive)
+ return;
+ Rect rect = new Rect(10, 10, 310, 310);
+ Canvas canvas = surface.lockCanvas(rect);
+ Paint paint = new Paint();
+ paint.setColor(color);
+ canvas.drawRect(rect, paint);
+ surface.unlockCanvasAndPost(canvas);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/FrontPageFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/FrontPageFragment.java
new file mode 100644
index 0000000..cb125e3
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/FrontPageFragment.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class FrontPageFragment extends Fragment {
+
+ public FrontPageFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_front_page, container, false);
+
+ if (MidiFragment.hasMidi(container.getContext())) {
+ final ImageView midiImage = (ImageView) view.findViewById(R.id.midi_image);
+ final TextView midiText = (TextView) view.findViewById(R.id.midi_text);
+ midiImage.setColorFilter(Color.TRANSPARENT);
+ midiText.setTextColor(Color.BLACK);
+ }
+ return view;
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java
new file mode 100644
index 0000000..3fc68f6
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.components.AxisBase;
+import com.github.mikephil.charting.components.Description;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.formatter.IAxisValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
+import com.github.mikephil.charting.utils.ColorTemplate;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+
+public class HistogramChart extends RelativeLayout implements View.OnClickListener {
+
+ static final float GROUP_SPACE = 0.1f;
+ private HistogramData histogramData;
+ private BarChart barChart;
+
+ public HistogramChart(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ inflate(getContext(), R.layout.histogram, this);
+
+ barChart = (BarChart) findViewById(R.id.bar_chart);
+ findViewById(R.id.button_close_bar_chart).setOnClickListener(this);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HistogramChart);
+ final String descString;
+ final int numDataSets;
+ final float binWidth;
+ try {
+ descString = a.getString(R.styleable.HistogramChart_description);
+ numDataSets = a.getInteger(R.styleable.HistogramChart_numDataSets, 1);
+ binWidth = a.getFloat(R.styleable.HistogramChart_binWidth, 5f);
+ } finally {
+ a.recycle();
+ }
+
+ ArrayList<IBarDataSet> dataSets = new ArrayList<>(numDataSets);
+ for (int i = 0; i < numDataSets; i++) {
+ final BarDataSet dataSet = new BarDataSet(new ArrayList<BarEntry>(), "");
+ dataSet.setColor(ColorTemplate.MATERIAL_COLORS[i]);
+ dataSets.add(dataSet);
+ }
+
+ BarData barData = new BarData(dataSets);
+ barData.setBarWidth((1f - GROUP_SPACE)/numDataSets);
+ barChart.setData(barData);
+ histogramData = new HistogramData(numDataSets, binWidth);
+ groupBars(barData);
+ final Description desc = new Description();
+ desc.setText(descString);
+ desc.setTextSize(12f);
+ barChart.setDescription(desc);
+
+ XAxis xAxis = barChart.getXAxis();
+ xAxis.setGranularityEnabled(true);
+ xAxis.setGranularity(1);
+ xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
+ xAxis.setValueFormatter(new IAxisValueFormatter() {
+ DecimalFormat df = new DecimalFormat("#.##");
+
+ @Override
+ public String getFormattedValue(float value, AxisBase axis) {
+ return df.format(histogramData.getDisplayValue(value));
+ }
+ });
+
+ barChart.setFitBars(true);
+ barChart.invalidate();
+ }
+
+ BarChart getBarChart() {
+ return barChart;
+ }
+
+ /**
+ * Re-implementation of BarData.groupBars(), but allows grouping with only 1 BarDataSet
+ * This adjusts the x-coordinates of entries, which centers the bars between axis labels
+ */
+ static void groupBars(final BarData barData) {
+ IBarDataSet max = barData.getMaxEntryCountSet();
+ int maxEntryCount = max.getEntryCount();
+ float groupSpaceWidthHalf = GROUP_SPACE / 2f;
+ float barWidthHalf = barData.getBarWidth() / 2f;
+ float interval = barData.getGroupWidth(GROUP_SPACE, 0);
+ float fromX = 0;
+
+ for (int i = 0; i < maxEntryCount; i++) {
+ float start = fromX;
+ fromX += groupSpaceWidthHalf;
+
+ for (IBarDataSet set : barData.getDataSets()) {
+ fromX += barWidthHalf;
+ if (i < set.getEntryCount()) {
+ BarEntry entry = set.getEntryForIndex(i);
+ if (entry != null) {
+ entry.setX(fromX);
+ }
+ }
+ fromX += barWidthHalf;
+ }
+
+ fromX += groupSpaceWidthHalf;
+ float end = fromX;
+ float innerInterval = end - start;
+ float diff = interval - innerInterval;
+
+ // correct rounding errors
+ if (diff > 0 || diff < 0) {
+ fromX += diff;
+ }
+ }
+ barData.notifyDataChanged();
+ }
+
+ public void clearData() {
+ histogramData.clear();
+ for (IBarDataSet dataSet : barChart.getBarData().getDataSets()) {
+ dataSet.clear();
+ }
+ barChart.getBarData().notifyDataChanged();
+ barChart.invalidate();
+ }
+
+ public void addEntry(int dataSetIndex, double value) {
+ histogramData.addEntry(barChart.getBarData(), dataSetIndex, value);
+ recalculateXAxis();
+ }
+
+ public void addEntry(double value) {
+ addEntry(0, value);
+ }
+
+ private void recalculateXAxis() {
+ final XAxis xAxis = barChart.getXAxis();
+ xAxis.setAxisMinimum(0);
+ xAxis.setAxisMaximum(histogramData.getNumBins());
+ barChart.notifyDataSetChanged();
+ barChart.invalidate();
+ }
+
+ public void setLabel(int dataSetIndex, String label) {
+ barChart.getBarData().getDataSetByIndex(dataSetIndex).setLabel(label);
+ barChart.getLegendRenderer().computeLegend(barChart.getBarData());
+ barChart.invalidate();
+ }
+
+ public void setLabel(String label) {
+ setLabel(0, label);
+ }
+
+ public void setDescription(String description) {
+ getBarChart().getDescription().setText(description);
+ }
+
+ public void setLegendEnabled(boolean enabled) {
+ barChart.getLegend().setEnabled(enabled);
+ barChart.notifyDataSetChanged();
+ barChart.invalidate();
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.button_close_bar_chart:
+ this.setVisibility(GONE);
+ }
+ }
+
+ static class HistogramData {
+ private float binWidth;
+ private final ArrayList<ArrayList<Double>> rawData;
+ private double minBin = 0;
+ private double maxBin = 100;
+ private double min = 0;
+ private double max = 100;
+
+ HistogramData(int numDataSets, float binWidth) {
+ this.binWidth = binWidth;
+ rawData = new ArrayList<>(numDataSets);
+ for (int i = 0; i < numDataSets; i++) {
+ rawData.add(new ArrayList<Double>());
+ }
+ }
+
+ float getBinWidth() {
+ return binWidth;
+ }
+
+ double getMinBin() {
+ return minBin;
+ }
+
+ void clear() {
+ for (int i = 0; i < rawData.size(); i++) {
+ rawData.get(i).clear();
+ }
+ }
+
+ private boolean isEmpty() {
+ for (ArrayList<Double> data : rawData) {
+ if (!data.isEmpty()) return false;
+ }
+ return true;
+ }
+
+ void addEntry(BarData barData, int dataSetIndex, double value) {
+ if (isEmpty()) {
+ min = value;
+ max = value;
+ } else {
+ if (value < min) min = value;
+ if (value > max) max = value;
+ }
+
+ rawData.get(dataSetIndex).add(value);
+ recalculateDataSet(barData);
+ }
+
+ void recalculateDataSet(final BarData barData) {
+ minBin = Math.floor(min / binWidth) * binWidth;
+ maxBin = Math.floor(max / binWidth) * binWidth;
+
+ int[][] bins = new int[rawData.size()][getNumBins()];
+
+ for (int setNum = 0; setNum < rawData.size(); setNum++) {
+ for (Double d : rawData.get(setNum)) {
+ ++bins[setNum][(int) (Math.floor((d - minBin) / binWidth))];
+ }
+ }
+
+ for (int setNum = 0; setNum < barData.getDataSetCount(); setNum++) {
+ final IBarDataSet dataSet = barData.getDataSetByIndex(setNum);
+ dataSet.clear();
+ for (int i = 0; i < bins[setNum].length; i++) {
+ dataSet.addEntry(new BarEntry(i, bins[setNum][i]));
+ }
+ }
+ groupBars(barData);
+ barData.notifyDataChanged();
+ }
+
+ int getNumBins() {
+ return (int) (((maxBin - minBin) / binWidth) + 1);
+ }
+
+ double getDisplayValue(float value) {
+ return value * getBinWidth() + getMinBin();
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/LogFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/LogFragment.java
new file mode 100644
index 0000000..069d032
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/LogFragment.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+
+/**
+ * A screen that shows the log.
+ */
+public class LogFragment extends Fragment {
+
+ private Activity activity;
+ private SimpleLogger logger;
+ TextView textView;
+
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ LogFragment.this.appendLogText(msg);
+ }
+ };
+
+ public LogFragment() {
+ // Required empty public constructor
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ activity = getActivity();
+ logger = SimpleLogger.getInstance(getContext());
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_log, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ textView = (TextView) activity.findViewById(R.id.txt_log);
+ textView.setMovementMethod(new ScrollingMovementMethod());
+ textView.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+ public void appendLogText(String msg) {
+ textView.append(msg + "\n");
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/LogUploader.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/LogUploader.java
new file mode 100644
index 0000000..a73f456
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/LogUploader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.support.v4.content.AsyncTaskLoader;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+class LogUploader extends AsyncTaskLoader<Integer> {
+
+ private String urlString;
+ private SimpleLogger logger;
+
+ LogUploader(Context context) {
+ super(context);
+ urlString = Utils.getStringPreference(context, R.string.preference_log_url, "");
+ logger = SimpleLogger.getInstance(context);
+
+ }
+
+ LogUploader(Context context, String urlString) {
+ super(context);
+ this.urlString = urlString;
+ logger = SimpleLogger.getInstance(context);
+ }
+
+ @Override
+ public Integer loadInBackground() {
+ if (urlString.isEmpty()) return -1;
+ try {
+ URL url = new URL(urlString);
+ HttpURLConnection urlConnection =
+ (HttpURLConnection) url.openConnection();
+ urlConnection.setRequestMethod("POST");
+ urlConnection.setDoOutput(true);
+ urlConnection.setRequestProperty("Content-Type", "text/plain");
+ BufferedOutputStream out =
+ new BufferedOutputStream(urlConnection.getOutputStream());
+ PrintWriter writer = new PrintWriter(out);
+ writer.write(logger.getLogText());
+ writer.flush();
+ final int responseCode = urlConnection.getResponseCode();
+ if (responseCode / 100 == 2) {
+ logger.log("Log successfully uploaded");
+ } else {
+ logger.log("Log upload may have failed. Server return status code " + responseCode);
+ }
+ return responseCode;
+ } catch (IOException e) {
+ logger.log("Failed to upload log");
+ return -1;
+ }
+ }
+
+ void startUpload() {
+ super.forceLoad();
+ }
+
+ static void uploadIfAutoEnabled(Context context) {
+ if (Utils.getBooleanPreference(context, R.string.preference_auto_upload_log, false)) {
+ new LogUploader(context).startUpload();
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java
new file mode 100644
index 0000000..ac1df47
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.Manifest;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.StrictMode;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.Loader;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import org.chromium.latency.walt.programmer.Programmer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+import java.util.Locale;
+
+import static org.chromium.latency.walt.Utils.getBooleanPreference;
+
+public class MainActivity extends AppCompatActivity {
+ private static final String TAG = "WALT";
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG = 2;
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SYSTRACE = 3;
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG = 4;
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG = 5;
+
+ private static final String LOG_FILENAME = "qstep_log.txt";
+
+ private Toolbar toolbar;
+ LocalBroadcastManager broadcastManager;
+ private SimpleLogger logger;
+ private WaltDevice waltDevice;
+ public Menu menu;
+
+ public Handler handler = new Handler();
+
+ private Fragment mRobotAutomationFragment;
+
+
+ /**
+ * A method to display exceptions on screen. This is very useful because our USB port is taken
+ * and we often need to debug without adb.
+ * Based on this article:
+ * https://trivedihardik.wordpress.com/2011/08/20/how-to-avoid-force-close-error-in-android/
+ */
+ public class LoggingExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler {
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ StringWriter stackTrace = new StringWriter();
+ ex.printStackTrace(new PrintWriter(stackTrace));
+ String msg = "WALT crashed with the following exception:\n" + stackTrace;
+
+ // Fire a new activity showing the stack trace
+ Intent intent = new Intent(MainActivity.this, CrashLogActivity.class);
+ intent.putExtra("crash_log", msg);
+ MainActivity.this.startActivity(intent);
+
+ // Terminate this process
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(10);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ final UsbDevice usbDevice;
+ Intent intent = getIntent();
+ if (intent != null && intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ setIntent(null); // done with the intent
+ usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ } else {
+ usbDevice = null;
+ }
+
+ // Connect and sync clocks, but a bit later as it takes time
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (usbDevice == null) {
+ waltDevice.connect();
+ } else {
+ waltDevice.connect(usbDevice);
+ }
+ }
+ }, 1000);
+
+ if (intent != null && AutoRunFragment.TEST_ACTION.equals(intent.getAction())) {
+ getSupportFragmentManager().popBackStack("Automated Test",
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ Fragment autoRunFragment = new AutoRunFragment();
+ autoRunFragment.setArguments(intent.getExtras());
+ switchScreen(autoRunFragment, "Automated Test");
+ }
+
+ // Handle robot automation originating from adb shell am
+ if (intent != null && Intent.ACTION_SEND.equals(intent.getAction())) {
+ Log.e(TAG, "Received Intent: " + intent.toString());
+ String test = intent.getStringExtra("StartTest");
+ if (test != null) {
+ Log.e(TAG, "Extras \"StartTest\" = " + test);
+ if ("TapLatencyTest".equals(test)) {
+ mRobotAutomationFragment = new TapLatencyFragment();
+ switchScreen(mRobotAutomationFragment, "Tap Latency");
+ } else if ("ScreenResponseTest".equals(test)) {
+ mRobotAutomationFragment = new ScreenResponseFragment();
+ switchScreen(mRobotAutomationFragment, "Screen Response");
+ } else if ("DragLatencyTest".equals(test)) {
+ mRobotAutomationFragment = new DragLatencyFragment();
+ switchScreen(mRobotAutomationFragment, "Drag Latency");
+ }
+ }
+
+ String robotEvent = intent.getStringExtra("RobotAutomationEvent");
+ if (robotEvent != null && mRobotAutomationFragment != null) {
+ Log.e(TAG, "Received robot automation event=\"" + robotEvent + "\", Fragment = " +
+ mRobotAutomationFragment);
+ // Writing and clearing the log is not fragment-specific, so handle them here.
+ if (robotEvent.equals(RobotAutomationListener.WRITE_LOG_EVENT)) {
+ attemptSaveLog();
+ } else if (robotEvent.equals(RobotAutomationListener.CLEAR_LOG_EVENT)) {
+ attemptClearLog();
+ } else {
+ // All other robot automation events are forwarded to the current fragment.
+ ((RobotAutomationListener) mRobotAutomationFragment)
+ .onRobotAutomationEvent(robotEvent);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Thread.setDefaultUncaughtExceptionHandler(new LoggingExceptionHandler());
+ setContentView(R.layout.activity_main);
+
+ // App bar
+ toolbar = (Toolbar) findViewById(R.id.toolbar_main);
+ setSupportActionBar(toolbar);
+ getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
+ @Override
+ public void onBackStackChanged() {
+ int stackTopIndex = getSupportFragmentManager().getBackStackEntryCount() - 1;
+ if (stackTopIndex >= 0) {
+ toolbar.setTitle(getSupportFragmentManager().getBackStackEntryAt(stackTopIndex).getName());
+ } else {
+ toolbar.setTitle(R.string.app_name);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ // Disable fullscreen mode
+ getSupportActionBar().show();
+ getWindow().getDecorView().setSystemUiVisibility(0);
+ }
+ }
+ });
+
+ waltDevice = WaltDevice.getInstance(this);
+
+ // Create front page fragment
+ FrontPageFragment frontPageFragment = new FrontPageFragment();
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.add(R.id.fragment_container, frontPageFragment);
+ transaction.commit();
+
+ logger = SimpleLogger.getInstance(this);
+ broadcastManager = LocalBroadcastManager.getInstance(this);
+
+ // Add basic version and device info to the log
+ logger.log(String.format("WALT v%s (versionCode=%d)",
+ BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
+ logger.log("WALT protocol version " + WaltDevice.PROTOCOL_VERSION);
+ logger.log("DEVICE INFO:");
+ logger.log(" " + Build.FINGERPRINT);
+ logger.log(" Build.SDK_INT=" + Build.VERSION.SDK_INT);
+ logger.log(" os.version=" + System.getProperty("os.version"));
+
+ // Set volume buttons to control media volume
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ requestSystraceWritePermission();
+ // Allow network operations on the main thread
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ this.menu = menu;
+ return true;
+ }
+
+ public void toast(String msg) {
+ logger.log(msg);
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ // Go back when the back or up button on toolbar is clicked
+ getSupportFragmentManager().popBackStack();
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+
+ Log.i(TAG, "Toolbar button: " + item.getTitle());
+
+ switch (item.getItemId()) {
+ case R.id.action_help:
+ return true;
+ case R.id.action_share:
+ attemptSaveAndShareLog();
+ return true;
+ case R.id.action_upload:
+ showUploadLogDialog();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // Handlers for main menu clicks
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private void switchScreen(Fragment newFragment, String title) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ toolbar.setTitle(title);
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.fragment_container, newFragment);
+ transaction.addToBackStack(title);
+ transaction.commit();
+ }
+
+ public void onClickClockSync(View view) {
+ DiagnosticsFragment diagnosticsFragment = new DiagnosticsFragment();
+ switchScreen(diagnosticsFragment, "Diagnostics");
+ }
+
+ public void onClickTapLatency(View view) {
+ TapLatencyFragment newFragment = new TapLatencyFragment();
+ requestSystraceWritePermission();
+ switchScreen(newFragment, "Tap Latency");
+ }
+
+ public void onClickScreenResponse(View view) {
+ ScreenResponseFragment newFragment = new ScreenResponseFragment();
+ requestSystraceWritePermission();
+ switchScreen(newFragment, "Screen Response");
+ }
+
+ public void onClickAudio(View view) {
+ AudioFragment newFragment = new AudioFragment();
+ switchScreen(newFragment, "Audio Latency");
+ }
+
+ public void onClickMIDI(View view) {
+ if (MidiFragment.hasMidi(this)) {
+ MidiFragment newFragment = new MidiFragment();
+ switchScreen(newFragment, "MIDI Latency");
+ } else {
+ toast("This device does not support MIDI");
+ }
+ }
+
+ public void onClickDragLatency(View view) {
+ DragLatencyFragment newFragment = new DragLatencyFragment();
+ switchScreen(newFragment, "Drag Latency");
+ }
+
+ public void onClickOpenLog(View view) {
+ LogFragment logFragment = new LogFragment();
+ // menu.findItem(R.id.action_help).setVisible(false);
+ switchScreen(logFragment, "Log");
+ }
+
+ public void onClickOpenAbout(View view) {
+ AboutFragment aboutFragment = new AboutFragment();
+ switchScreen(aboutFragment, "About");
+ }
+
+ public void onClickOpenSettings(View view) {
+ SettingsFragment settingsFragment = new SettingsFragment();
+ switchScreen(settingsFragment, "Settings");
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // Handlers for diagnostics menu clicks
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ public void onClickReconnect(View view) {
+ waltDevice.connect();
+ }
+
+ public void onClickPing(View view) {
+ long t1 = waltDevice.clock.micros();
+ try {
+ waltDevice.command(WaltDevice.CMD_PING);
+ long dt = waltDevice.clock.micros() - t1;
+ logger.log(String.format(Locale.US,
+ "Ping reply in %.1fms", dt / 1000.
+ ));
+ } catch (IOException e) {
+ logger.log("Error sending ping: " + e.getMessage());
+ }
+ }
+
+ public void onClickStartListener(View view) {
+ if (waltDevice.isListenerStopped()) {
+ try {
+ waltDevice.startListener();
+ } catch (IOException e) {
+ logger.log("Error starting USB listener: " + e.getMessage());
+ }
+ } else {
+ waltDevice.stopListener();
+ }
+ }
+
+ public void onClickSync(View view) {
+ try {
+ waltDevice.syncClock();
+ } catch (IOException e) {
+ logger.log("Error syncing clocks: " + e.getMessage());
+ }
+ }
+
+ public void onClickCheckDrift(View view) {
+ waltDevice.checkDrift();
+ }
+
+ public void onClickProgram(View view) {
+ if (waltDevice.isConnected()) {
+ // show dialog telling user to first press white button
+ final AlertDialog dialog = new AlertDialog.Builder(this)
+ .setTitle("Press white button")
+ .setMessage("Please press the white button on the WALT device.")
+ .setCancelable(false)
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {}
+ }).show();
+
+ waltDevice.setConnectionStateListener(new WaltConnection.ConnectionStateListener() {
+ @Override
+ public void onConnect() {}
+
+ @Override
+ public void onDisconnect() {
+ dialog.cancel();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ new Programmer(MainActivity.this).program();
+ }
+ }, 1000);
+ }
+ });
+ } else {
+ new Programmer(this).program();
+ }
+ }
+
+ private void attemptSaveAndShareLog() {
+ int currentPermission = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (currentPermission == PackageManager.PERMISSION_GRANTED) {
+ String filePath = saveLogToFile();
+ shareLogFile(filePath);
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG);
+ }
+ }
+
+ private void attemptSaveLog() {
+ int currentPermission = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (currentPermission == PackageManager.PERMISSION_GRANTED) {
+ saveLogToFile();
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG);
+ }
+ }
+
+ private void attemptClearLog() {
+ int currentPermission = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (currentPermission == PackageManager.PERMISSION_GRANTED) {
+ clearLogFile();
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ final boolean isPermissionGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ if (!isPermissionGranted) {
+ logger.log("Could not get permission to write file to storage");
+ return;
+ }
+ switch (requestCode) {
+ case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG:
+ attemptSaveAndShareLog();
+ break;
+ case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG:
+ attemptSaveLog();
+ break;
+ case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG:
+ attemptClearLog();
+ break;
+ }
+ }
+
+ public String saveLogToFile() {
+
+ // Save to file to later fire an Intent.ACTION_SEND
+ // This allows to either send the file as email attachment
+ // or upload it to Drive.
+
+ // The permissions for attachments are a mess, writing world readable files
+ // is frowned upon, but deliberately giving permissions as part of the intent is
+ // way too cumbersome.
+
+ // A reasonable world readable location,on many phones it's /storage/emulated/Documents
+ // TODO: make this location configurable?
+ File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+ File file = null;
+ FileOutputStream outStream = null;
+
+ try {
+ if (!path.exists()) {
+ path.mkdirs();
+ }
+ file = new File(path, LOG_FILENAME);
+ logger.log("Saving log to: " + file + " at " + new Date());
+
+ outStream = new FileOutputStream(file);
+ outStream.write(logger.getLogText().getBytes());
+
+ outStream.close();
+ logger.log("Log saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.log("Failed to write log: " + e.getMessage());
+ }
+ return file.getPath();
+ }
+
+ public void clearLogFile() {
+ File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+ try {
+ File file = new File(path, LOG_FILENAME);
+ file.delete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.log("Failed to clear log: " + e.getMessage());
+ }
+ }
+
+ public void shareLogFile(String filepath) {
+ File file = new File(filepath);
+ logger.log("Firing Intent.ACTION_SEND for file:");
+ logger.log(file.getPath());
+
+ Intent i = new Intent(Intent.ACTION_SEND);
+ i.setType("text/plain");
+
+ i.putExtra(Intent.EXTRA_SUBJECT, "WALT log");
+ i.putExtra(Intent.EXTRA_TEXT, "Attaching log file " + file.getPath());
+ i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
+
+ try {
+ startActivity(Intent.createChooser(i, "Send mail..."));
+ } catch (android.content.ActivityNotFoundException ex) {
+ toast("There are no email clients installed.");
+ }
+ }
+
+ private static boolean startsWithHttp(String url) {
+ return url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://");
+ }
+
+ private void showUploadLogDialog() {
+ final AlertDialog dialog = new AlertDialog.Builder(this)
+ .setTitle("Upload log to URL")
+ .setView(R.layout.dialog_upload)
+ .setPositiveButton("Upload", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {}
+ })
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {}
+ })
+ .show();
+ final EditText editText = (EditText) dialog.findViewById(R.id.edit_text);
+ editText.setText(Utils.getStringPreference(
+ MainActivity.this, R.string.preference_log_url, ""));
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).
+ setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ View progress = dialog.findViewById(R.id.progress_bar);
+ String urlString = editText.getText().toString();
+ if (!startsWithHttp(urlString)) {
+ urlString = "http://" + urlString;
+ }
+ editText.setVisibility(View.GONE);
+ progress.setVisibility(View.VISIBLE);
+ LogUploader uploader = new LogUploader(MainActivity.this, urlString);
+ final String finalUrlString = urlString;
+ uploader.registerListener(1, new Loader.OnLoadCompleteListener<Integer>() {
+ @Override
+ public void onLoadComplete(Loader<Integer> loader, Integer data) {
+ dialog.cancel();
+ if (data == -1) {
+ Toast.makeText(MainActivity.this,
+ "Failed to upload log", Toast.LENGTH_SHORT).show();
+ return;
+ } else if (data / 100 == 2) {
+ Toast.makeText(MainActivity.this,
+ "Log successfully uploaded", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(MainActivity.this,
+ "Failed to upload log. Server returned status code " + data,
+ Toast.LENGTH_SHORT).show();
+ }
+ SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(MainActivity.this);
+ preferences.edit().putString(
+ getString(R.string.preference_log_url), finalUrlString).apply();
+ }
+ });
+ uploader.startUpload();
+ }
+ });
+ }
+
+ private void requestSystraceWritePermission() {
+ if (getBooleanPreference(this, R.string.preference_systrace, true)) {
+ int currentPermission = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (currentPermission != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SYSTRACE);
+ }
+ }
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/MidiFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/MidiFragment.java
new file mode 100644
index 0000000..c6f1118
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/MidiFragment.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+public class MidiFragment extends Fragment
+ implements View.OnClickListener, BaseTest.TestStateListener {
+
+ private SimpleLogger logger;
+ private TextView textView;
+ private View startMidiInButton;
+ private View startMidiOutButton;
+ private HistogramChart latencyChart;
+ private MidiTest midiTest;
+
+ public MidiFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ logger = SimpleLogger.getInstance(getContext());
+ midiTest = new MidiTest(getActivity());
+ midiTest.setTestStateListener(this);
+
+ final View view = inflater.inflate(R.layout.fragment_midi, container, false);
+ textView = (TextView) view.findViewById(R.id.txt_box_midi);
+ startMidiInButton = view.findViewById(R.id.button_start_midi_in);
+ startMidiOutButton = view.findViewById(R.id.button_start_midi_out);
+ latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
+ textView.setMovementMethod(new ScrollingMovementMethod());
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Register this fragment class as the listener for some button clicks
+ startMidiInButton.setOnClickListener(this);
+ startMidiOutButton.setOnClickListener(this);
+
+ textView.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.button_start_midi_in:
+ disableButtons();
+ latencyChart.setVisibility(View.VISIBLE);
+ latencyChart.clearData();
+ latencyChart.setLegendEnabled(false);
+ latencyChart.getBarChart().getDescription().setText("MIDI Input Latency [ms]");
+ midiTest.testMidiIn();
+ break;
+ case R.id.button_start_midi_out:
+ disableButtons();
+ latencyChart.setVisibility(View.VISIBLE);
+ latencyChart.clearData();
+ latencyChart.setLegendEnabled(false);
+ latencyChart.getBarChart().getDescription().setText("MIDI Output Latency [ms]");
+ midiTest.testMidiOut();
+ break;
+ }
+ }
+
+ private void disableButtons() {
+ startMidiInButton.setEnabled(false);
+ startMidiOutButton.setEnabled(false);
+ }
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ textView.append(msg + "\n");
+ }
+ };
+
+ @Override
+ public void onTestStopped() {
+ if (!midiTest.deltasOutputTotal.isEmpty()) {
+ latencyChart.setLegendEnabled(true);
+ latencyChart.setLabel(String.format(
+ Locale.US, "Median=%.1f ms", Utils.median(midiTest.deltasOutputTotal)));
+ } else if (!midiTest.deltasInputTotal.isEmpty()) {
+ latencyChart.setLegendEnabled(true);
+ latencyChart.setLabel(String.format(
+ Locale.US, "Median=%.1f ms", Utils.median(midiTest.deltasInputTotal)));
+ }
+ LogUploader.uploadIfAutoEnabled(getContext());
+ startMidiInButton.setEnabled(true);
+ startMidiOutButton.setEnabled(true);
+ }
+
+ @Override
+ public void onTestStoppedWithError() {
+ onTestStopped();
+ latencyChart.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onTestPartialResult(double value) {
+ latencyChart.addEntry(value);
+ }
+
+ public static boolean hasMidi(Context context) {
+ return context.getPackageManager().
+ hasSystemFeature("android.software.midi");
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/MidiTest.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/MidiTest.java
new file mode 100644
index 0000000..27df929
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/MidiTest.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiReceiver;
+import android.os.Handler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import static org.chromium.latency.walt.Utils.getIntPreference;
+
+@TargetApi(23)
+class MidiTest extends BaseTest {
+
+ private Handler handler = new Handler();
+
+ private static final String TEENSY_MIDI_NAME = "Teensyduino Teensy MIDI";
+ private static final byte[] noteMsg = {(byte) 0x90, (byte) 99, (byte) 0};
+
+ private MidiManager midiManager;
+ private MidiDevice midiDevice;
+ // Output and Input here are with respect to the MIDI device, not the Android device.
+ private MidiOutputPort midiOutputPort;
+ private MidiInputPort midiInputPort;
+ private boolean isConnecting = false;
+ private long last_tWalt = 0;
+ private long last_tSys = 0;
+ private long last_tJava = 0;
+ private int inputSyncAfterRepetitions = 100;
+ private int outputSyncAfterRepetitions = 20; // TODO: implement periodic clock sync for output
+ private int inputRepetitions;
+ private int outputRepetitions;
+ private int repetitionsDone;
+ private ArrayList<Double> deltasToSys = new ArrayList<>();
+ ArrayList<Double> deltasInputTotal = new ArrayList<>();
+ ArrayList<Double> deltasOutputTotal = new ArrayList<>();
+
+ private static final int noteDelay = 300;
+ private static final int timeout = 1000;
+
+ MidiTest(Context context) {
+ super(context);
+ inputRepetitions = getIntPreference(context, R.string.preference_midi_in_reps, 100);
+ outputRepetitions = getIntPreference(context, R.string.preference_midi_out_reps, 10);
+ midiManager = (MidiManager) context.getSystemService(Context.MIDI_SERVICE);
+ findMidiDevice();
+ }
+
+ MidiTest(Context context, AutoRunFragment.ResultHandler resultHandler) {
+ this(context);
+ this.resultHandler = resultHandler;
+ }
+
+ void setInputRepetitions(int repetitions) {
+ inputRepetitions = repetitions;
+ }
+
+ void setOutputRepetitions(int repetitions) {
+ outputRepetitions = repetitions;
+ }
+
+ void testMidiOut() {
+ if (midiDevice == null) {
+ if (isConnecting) {
+ logger.log("Still connecting...");
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ testMidiOut();
+ }
+ });
+ } else {
+ logger.log("MIDI device is not open!");
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ }
+ return;
+ }
+ try {
+ setupMidiOut();
+ } catch (IOException e) {
+ logger.log("Error setting up test: " + e.getMessage());
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ return;
+ }
+ handler.postDelayed(cancelMidiOutRunnable, noteDelay * inputRepetitions + timeout);
+ }
+
+ void testMidiIn() {
+ if (midiDevice == null) {
+ if (isConnecting) {
+ logger.log("Still connecting...");
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ testMidiIn();
+ }
+ });
+ } else {
+ logger.log("MIDI device is not open!");
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ }
+ return;
+ }
+ try {
+ setupMidiIn();
+ } catch (IOException e) {
+ logger.log("Error setting up test: " + e.getMessage());
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ return;
+ }
+ handler.postDelayed(requestNoteRunnable, noteDelay);
+ }
+
+ private void setupMidiOut() throws IOException {
+ repetitionsDone = 0;
+ deltasInputTotal.clear();
+ deltasOutputTotal.clear();
+
+ midiInputPort = midiDevice.openInputPort(0);
+
+ waltDevice.syncClock();
+ waltDevice.command(WaltDevice.CMD_MIDI);
+ waltDevice.startListener();
+ waltDevice.setTriggerHandler(triggerHandler);
+
+ scheduleNotes();
+ }
+
+ private void findMidiDevice() {
+ MidiDeviceInfo[] infos = midiManager.getDevices();
+ for(MidiDeviceInfo info : infos) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ logger.log("Found MIDI device named " + name);
+ if(TEENSY_MIDI_NAME.equals(name)) {
+ logger.log("^^^ using this device ^^^");
+ isConnecting = true;
+ midiManager.openDevice(info, new MidiManager.OnDeviceOpenedListener() {
+ @Override
+ public void onDeviceOpened(MidiDevice device) {
+ if (device == null) {
+ logger.log("Error, unable to open MIDI device");
+ } else {
+ logger.log("Opened MIDI device successfully!");
+ midiDevice = device;
+ }
+ isConnecting = false;
+ }
+ }, null);
+ break;
+ }
+ }
+ }
+
+ private WaltDevice.TriggerHandler triggerHandler = new WaltDevice.TriggerHandler() {
+ @Override
+ public void onReceive(WaltDevice.TriggerMessage tmsg) {
+ last_tWalt = tmsg.t + waltDevice.clock.baseTime;
+ double dt = (last_tWalt - last_tSys) / 1000.;
+
+ deltasOutputTotal.add(dt);
+ logger.log(String.format(Locale.US, "Note detected: latency of %.3f ms", dt));
+ if (testStateListener != null) testStateListener.onTestPartialResult(dt);
+ if (traceLogger != null) {
+ traceLogger.log(last_tSys, last_tWalt, "MIDI Output",
+ "Bar starts when system sends audio and ends when WALT receives note");
+ }
+
+ last_tSys += noteDelay * 1000;
+ repetitionsDone++;
+
+ if (repetitionsDone < outputRepetitions) {
+ try {
+ waltDevice.command(WaltDevice.CMD_MIDI);
+ } catch (IOException e) {
+ logger.log("Failed to send command CMD_MIDI: " + e.getMessage());
+ }
+ } else {
+ finishMidiOut();
+ }
+ }
+ };
+
+ private void scheduleNotes() {
+ if(midiInputPort == null) {
+ logger.log("midiInputPort is not open");
+ return;
+ }
+ long t = System.nanoTime() + ((long) noteDelay) * 1000000L;
+ try {
+ // TODO: only schedule some, then sync clock
+ for (int i = 0; i < outputRepetitions; i++) {
+ midiInputPort.send(noteMsg, 0, noteMsg.length, t + ((long) noteDelay) * 1000000L * i);
+ }
+ } catch(IOException e) {
+ logger.log("Unable to schedule note: " + e.getMessage());
+ return;
+ }
+ last_tSys = t / 1000;
+ }
+
+ private void finishMidiOut() {
+ logger.log("All notes detected");
+ logger.log(String.format(
+ Locale.US, "Median total output latency %.1f ms", Utils.median(deltasOutputTotal)));
+
+ handler.removeCallbacks(cancelMidiOutRunnable);
+
+ if (resultHandler != null) {
+ resultHandler.onResult(deltasOutputTotal);
+ }
+ if (testStateListener != null) testStateListener.onTestStopped();
+ if (traceLogger != null) traceLogger.flush(context);
+ teardownMidiOut();
+ }
+
+ private Runnable cancelMidiOutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ logger.log("Timed out waiting for notes to be detected by WALT");
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ teardownMidiOut();
+ }
+ };
+
+ private void teardownMidiOut() {
+ try {
+ midiInputPort.close();
+ } catch(IOException e) {
+ logger.log("Error, failed to close input port: " + e.getMessage());
+ }
+
+ waltDevice.stopListener();
+ waltDevice.clearTriggerHandler();
+ waltDevice.checkDrift();
+ }
+
+ private Runnable requestNoteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ logger.log("Requesting note from WALT...");
+ String s;
+ try {
+ s = waltDevice.command(WaltDevice.CMD_NOTE);
+ } catch (IOException e) {
+ logger.log("Error sending NOTE command: " + e.getMessage());
+ if (testStateListener != null) testStateListener.onTestStoppedWithError();
+ return;
+ }
+ last_tWalt = Integer.parseInt(s);
+ handler.postDelayed(finishMidiInRunnable, timeout);
+ }
+ };
+
+ private Runnable finishMidiInRunnable = new Runnable() {
+ @Override
+ public void run() {
+ waltDevice.checkDrift();
+
+ logger.log("deltas: " + deltasToSys.toString());
+ logger.log("MIDI Input Test Results:");
+ logger.log(String.format(Locale.US,
+ "Median MIDI subsystem latency %.1f ms\nMedian total latency %.1f ms",
+ Utils.median(deltasToSys), Utils.median(deltasInputTotal)
+ ));
+
+ if (resultHandler != null) {
+ resultHandler.onResult(deltasToSys, deltasInputTotal);
+ }
+ if (testStateListener != null) testStateListener.onTestStopped();
+ if (traceLogger != null) traceLogger.flush(context);
+ teardownMidiIn();
+ }
+ };
+
+ private class WaltReceiver extends MidiReceiver {
+ public void onSend(byte[] data, int offset,
+ int count, long timestamp) throws IOException {
+ if(count > 0 && data[offset] == (byte) 0x90) { // NoteOn message on channel 1
+ handler.removeCallbacks(finishMidiInRunnable);
+ last_tJava = waltDevice.clock.micros();
+ last_tSys = timestamp / 1000 - waltDevice.clock.baseTime;
+
+ final double d1 = (last_tSys - last_tWalt) / 1000.;
+ final double d2 = (last_tJava - last_tSys) / 1000.;
+ final double dt = (last_tJava - last_tWalt) / 1000.;
+ logger.log(String.format(Locale.US,
+ "Result: Time to MIDI subsystem = %.3f ms, Time to Java = %.3f ms, " +
+ "Total = %.3f ms",
+ d1, d2, dt));
+ deltasToSys.add(d1);
+ deltasInputTotal.add(dt);
+ if (testStateListener != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ testStateListener.onTestPartialResult(dt);
+ }
+ });
+ }
+ if (traceLogger != null) {
+ traceLogger.log(last_tWalt + waltDevice.clock.baseTime,
+ last_tSys + waltDevice.clock.baseTime, "MIDI Input Subsystem",
+ "Bar starts when WALT sends note and ends when received by MIDI subsystem");
+ traceLogger.log(last_tSys + waltDevice.clock.baseTime,
+ last_tJava + waltDevice.clock.baseTime, "MIDI Input Java",
+ "Bar starts when note received by MIDI subsystem and ends when received by app");
+ }
+
+ repetitionsDone++;
+ if (repetitionsDone % inputSyncAfterRepetitions == 0) {
+ try {
+ waltDevice.syncClock();
+ } catch (IOException e) {
+ logger.log("Error syncing clocks: " + e.getMessage());
+ handler.post(finishMidiInRunnable);
+ return;
+ }
+ }
+ if (repetitionsDone < inputRepetitions) {
+ handler.post(requestNoteRunnable);
+ } else {
+ handler.post(finishMidiInRunnable);
+ }
+ } else {
+ logger.log(String.format(Locale.US, "Expected 0x90, got 0x%x and count was %d",
+ data[offset], count));
+ }
+ }
+ }
+
+ private void setupMidiIn() throws IOException {
+ repetitionsDone = 0;
+ deltasInputTotal.clear();
+ deltasOutputTotal.clear();
+ midiOutputPort = midiDevice.openOutputPort(0);
+ midiOutputPort.connect(new WaltReceiver());
+ waltDevice.syncClock();
+ }
+
+ private void teardownMidiIn() {
+ handler.removeCallbacks(requestNoteRunnable);
+ handler.removeCallbacks(finishMidiInRunnable);
+ try {
+ midiOutputPort.close();
+ } catch (IOException e) {
+ logger.log("Error, failed to close output port: " + e.getMessage());
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/NumberPickerPreference.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/NumberPickerPreference.java
new file mode 100644
index 0000000..9d71d42
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/NumberPickerPreference.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.preference.DialogPreference;
+import android.support.v7.preference.PreferenceDialogFragmentCompat;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class NumberPickerPreference extends DialogPreference {
+ private int currentValue;
+ private int maxValue;
+ private int minValue;
+
+ private static final int DEFAULT_value = 0;
+ private static final int DEFAULT_maxValue = 0;
+ private static final int DEFAULT_minValue = 0;
+
+ private final String defaultSummary;
+
+ public NumberPickerPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ defaultSummary = getSummary().toString();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference);
+
+ try {
+ maxValue = a.getInt(R.styleable.NumberPickerPreference_maxValue, DEFAULT_maxValue);
+ minValue = a.getInt(R.styleable.NumberPickerPreference_minValue, DEFAULT_minValue);
+ } finally {
+ a.recycle();
+ }
+
+ setDialogLayoutResource(R.layout.numberpicker_dialog);
+ setPositiveButtonText(android.R.string.ok);
+ setNegativeButtonText(android.R.string.cancel);
+
+ setDialogIcon(null);
+
+ }
+
+ public int getValue() {
+ return currentValue;
+ }
+
+ public void setValue(int value) {
+ currentValue = value;
+ persistInt(currentValue);
+ setSummary(String.format(defaultSummary, getValue()));
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, DEFAULT_value);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+ setValue(restorePersistedValue ? getPersistedInt(currentValue) : (Integer) defaultValue);
+ }
+
+ public static class NumberPickerPreferenceDialogFragmentCompat
+ extends PreferenceDialogFragmentCompat {
+ private static final String SAVE_STATE_VALUE = "NumberPickerPreferenceDialogFragment.value";
+ private CustomNumberPicker picker;
+ private int currentValue = 1;
+
+ public NumberPickerPreferenceDialogFragmentCompat() {
+ }
+
+ public static NumberPickerPreferenceDialogFragmentCompat newInstance(String key) {
+ NumberPickerPreferenceDialogFragmentCompat fragment =
+ new NumberPickerPreferenceDialogFragmentCompat();
+ Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ currentValue = getNumberPickerPreference().getValue();
+ } else {
+ currentValue = savedInstanceState.getInt(SAVE_STATE_VALUE);
+ }
+ }
+
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putInt(SAVE_STATE_VALUE, currentValue);
+ }
+
+ private NumberPickerPreference getNumberPickerPreference() {
+ return (NumberPickerPreference) this.getPreference();
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ picker = (CustomNumberPicker) view.findViewById(R.id.numpicker_pref);
+ picker.setMaxValue(getNumberPickerPreference().maxValue);
+ picker.setMinValue(getNumberPickerPreference().minValue);
+ picker.setValue(currentValue);
+ }
+
+ @Override
+ public void onDialogClosed(boolean b) {
+ if (b) {
+ int value = picker.getValue();
+ if(getPreference().callChangeListener(value)) {
+ getNumberPickerPreference().setValue(value);
+ }
+ }
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java
new file mode 100644
index 0000000..4db9358
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+/**
+ * Representation of our best knowledge of the remote clock.
+ * All time variables here are stored in microseconds.
+ *
+ * Which time reporting function is used locally on Android:
+ * This app uses SystemClock.uptimeMillis() for keeping local time which, up to
+ * units, is the same time reported by System.nanoTime() and by
+ * clock_gettime(CLOCK_MONOTONIC, &ts) from time.h and is, roughly, the time
+ * elapsed since last boot, excluding sleep time.
+ *
+ * base_time is the local monotonic clock time when remote clock was zeroed.
+ *
+ * micros() is our best available approximation of the current reading of the remote clock.
+ *
+ * Immediately after synchronization, minLag is set to zero and the remote clock is guaranteed to
+ * lag behind what micros() reports by at most maxLag.
+ *
+ * Immediately after synchronization or an update of the bounds (minLag, maxLag) the following holds
+ * t_remote + minLag < micros() < t_remote + maxLag
+ *
+ * For more details about clock synchronization refer to
+ * https://github.com/google/walt/blob/master/android/WALT/app/src/main/jni/README.md
+ * and sync_clock.c
+ */
+
+public class RemoteClockInfo {
+ public int minLag;
+ public int maxLag;
+ public long baseTime;
+
+ public static long microTime() {
+ return System.nanoTime() / 1000;
+ }
+
+ public long micros() {
+ return microTime() - baseTime;
+ }
+
+ public int getMeanLag() {
+ return (minLag + maxLag) / 2;
+ }
+
+ public String toString() {
+ return "Remote clock [us]: current time = " + micros() + " baseTime = " + baseTime +
+ " lagBounds = (" + minLag + ", " + maxLag + ")";
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java
new file mode 100644
index 0000000..47a21ba
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+public interface RobotAutomationListener {
+ public static final String START_EVENT = "START_EVENT";
+ public static final String RESTART_EVENT = "RESTART_EVENT";
+ public static final String FINISH_EVENT = "FINISH_EVENT";
+ public static final String WRITE_LOG_EVENT = "WRITE_LOG_EVENT";
+ public static final String CLEAR_LOG_EVENT = "CLEAR_LOG_EVENT";
+
+ public void onRobotAutomationEvent(String event);
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java
new file mode 100644
index 0000000..629ed7d
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.text.method.ScrollingMovementMethod;
+import android.view.Choreographer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.Description;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static org.chromium.latency.walt.Utils.getBooleanPreference;
+import static org.chromium.latency.walt.Utils.getIntPreference;
+
+/**
+ * Measurement of screen response time when switching between black and white.
+ */
+public class ScreenResponseFragment extends Fragment
+ implements View.OnClickListener, RobotAutomationListener {
+
+ private static final int CURVE_TIMEOUT = 1000; // milliseconds
+ private static final int CURVE_BLINK_TIME = 250; // milliseconds
+ private static final int W2B_INDEX = 0;
+ private static final int B2W_INDEX = 1;
+ private SimpleLogger logger;
+ private TraceLogger traceLogger = null;
+ private WaltDevice waltDevice;
+ private Handler handler = new Handler();
+ private TextView blackBox;
+ private View startButton;
+ private View stopButton;
+ private Spinner spinner;
+ private LineChart brightnessChart;
+ private HistogramChart latencyChart;
+ private View brightnessChartLayout;
+ private View buttonBarView;
+ private FastPathSurfaceView fastSurfaceView;
+ private int timesToBlink;
+ private boolean shouldShowLatencyChart = false;
+ private boolean isTestRunning = false;
+ private boolean enableFullScreen = false;
+ private boolean isFastPathGraphics = false;
+ int initiatedBlinks = 0;
+ int detectedBlinks = 0;
+ boolean isBoxWhite = false;
+ long lastFrameStartTime;
+ long lastFrameCallbackTime;
+ long lastSetBackgroundTime;
+ ArrayList<Double> deltas_w2b = new ArrayList<>();
+ ArrayList<Double> deltas_b2w = new ArrayList<>();
+ ArrayList<Double> deltas = new ArrayList<>();
+ private static final int color_gray = Color.argb(0xFF, 0xBB, 0xBB, 0xBB);
+ private StringBuilder brightnessCurveData;
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!isTestRunning) {
+ String msg = intent.getStringExtra("message");
+ blackBox.append(msg + "\n");
+ }
+ }
+ };
+
+ public ScreenResponseFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ timesToBlink = getIntPreference(getContext(), R.string.preference_screen_blinks, 20);
+ shouldShowLatencyChart = getBooleanPreference(getContext(), R.string.preference_show_blink_histogram, true);
+ enableFullScreen = getBooleanPreference(getContext(), R.string.preference_screen_fullscreen, true);
+ if (getBooleanPreference(getContext(), R.string.preference_systrace, true)) {
+ traceLogger = TraceLogger.getInstance();
+ }
+ waltDevice = WaltDevice.getInstance(getContext());
+ logger = SimpleLogger.getInstance(getContext());
+
+ // Inflate the layout for this fragment
+ final View view = inflater.inflate(R.layout.fragment_screen_response, container, false);
+ stopButton = view.findViewById(R.id.button_stop_screen_response);
+ startButton = view.findViewById(R.id.button_start_screen_response);
+ blackBox = (TextView) view.findViewById(R.id.txt_black_box_screen);
+ fastSurfaceView = (FastPathSurfaceView) view.findViewById(R.id.fast_path_surface);
+ spinner = (Spinner) view.findViewById(R.id.spinner_screen_response);
+ buttonBarView = view.findViewById(R.id.button_bar);
+ ArrayAdapter<CharSequence> modeAdapter = ArrayAdapter.createFromResource(getContext(),
+ R.array.screen_response_mode_array, android.R.layout.simple_spinner_item);
+ modeAdapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item);
+ spinner.setAdapter(modeAdapter);
+ stopButton.setEnabled(false);
+ blackBox.setMovementMethod(new ScrollingMovementMethod());
+ brightnessChartLayout = view.findViewById(R.id.brightness_chart_layout);
+ view.findViewById(R.id.button_close_chart).setOnClickListener(this);
+ brightnessChart = (LineChart) view.findViewById(R.id.chart);
+ latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
+
+ if (getBooleanPreference(getContext(), R.string.preference_auto_increase_brightness, true)) {
+ increaseScreenBrightness();
+ }
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ logger.registerReceiver(logReceiver);
+ // Register this fragment class as the listener for some button clicks
+ startButton.setOnClickListener(this);
+ stopButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+ void startBlinkLatency() {
+ setFullScreen(enableFullScreen);
+ deltas.clear();
+ deltas_b2w.clear();
+ deltas_w2b.clear();
+ if (shouldShowLatencyChart) {
+ latencyChart.clearData();
+ latencyChart.setVisibility(View.VISIBLE);
+ latencyChart.setLabel(W2B_INDEX, "White-to-black");
+ latencyChart.setLabel(B2W_INDEX, "Black-to-white");
+ }
+ initiatedBlinks = 0;
+ detectedBlinks = 0;
+ if (isFastPathGraphics) {
+ blackBox.setVisibility(View.GONE);
+ fastSurfaceView.setVisibility(View.VISIBLE);
+ fastSurfaceView.setRectColor(Color.WHITE);
+ } else {
+ blackBox.setText("");
+ blackBox.setBackgroundColor(Color.WHITE);
+ }
+ isBoxWhite = true;
+
+ handler.postDelayed(startBlinking, enableFullScreen ? 800 : 300);
+ }
+
+ Runnable startBlinking = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Check for PWM
+ WaltDevice.TriggerMessage tmsg = waltDevice.readTriggerMessage(WaltDevice.CMD_SEND_LAST_SCREEN);
+ logger.log("Blink count was: " + tmsg.count);
+
+ waltDevice.softReset();
+ waltDevice.syncClock(); // Note, sync also sends CMD_RESET (but not simpleSync).
+ waltDevice.command(WaltDevice.CMD_AUTO_SCREEN_ON);
+ waltDevice.startListener();
+ } catch (IOException e) {
+ logger.log("Error: " + e.getMessage());
+ }
+
+ // Register a callback for triggers
+ waltDevice.setTriggerHandler(triggerHandler);
+
+ // post doBlink runnable
+ handler.postDelayed(doBlinkRunnable, 100);
+ }
+ };
+
+ Runnable doBlinkRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!isTestRunning) return;
+ logger.log("======\ndoBlink.run(), initiatedBlinks = " + initiatedBlinks + " detectedBlinks = " + detectedBlinks);
+ // Check if we saw some transitions without blinking, this would usually mean
+ // the screen has PWM enabled, warn and ask the user to turn it off.
+ if (initiatedBlinks == 0 && detectedBlinks > 1) {
+ logger.log("Unexpected blinks detected, probably PWM, turn it off");
+ isTestRunning = false;
+ stopButton.setEnabled(false);
+ startButton.setEnabled(true);
+ showPwmDialog();
+ return;
+ }
+
+ if (initiatedBlinks >= timesToBlink) {
+ isTestRunning = false;
+ finishAndShowStats();
+ return;
+ }
+
+ // * 2 flip the screen, save time as last flip time (last flip direction?)
+
+ isBoxWhite = !isBoxWhite;
+ int nextColor = isBoxWhite ? Color.WHITE : Color.BLACK;
+ initiatedBlinks++;
+ if (traceLogger != null) {
+ traceLogger.log(RemoteClockInfo.microTime(), RemoteClockInfo.microTime() + 1000,
+ "Request-to-" + (isBoxWhite ? "white" : "black"),
+ "Application has called setBackgroundColor at start of bar");
+ }
+ if (isFastPathGraphics) {
+ fastSurfaceView.setRectColor(nextColor);
+ } else {
+ blackBox.setBackgroundColor(nextColor);
+ }
+ lastSetBackgroundTime = waltDevice.clock.micros();
+
+ // Set up a callback to run on next frame render to collect the timestamp
+ Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ // frameTimeNanos is the time in nanoseconds when the frame started being
+ // rendered, in the nanoTime() timebase.
+ lastFrameStartTime = frameTimeNanos / 1000 - waltDevice.clock.baseTime;
+ lastFrameCallbackTime = System.nanoTime() / 1000 - waltDevice.clock.baseTime;
+ }
+ });
+
+ // Repost doBlink to some far away time to blink again even if nothing arrives from
+ // Teensy. This callback will almost always get cancelled by onIncomingTimestamp()
+ handler.postDelayed(doBlinkRunnable, 550 + (long) (Math.random()*100));
+ }
+ };
+
+ private void showPwmDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage("Detected extra blinks, please set your brightness to max")
+ .setTitle("Unexpected Blinks")
+ .setPositiveButton("OK", null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private WaltDevice.TriggerHandler triggerHandler = new WaltDevice.TriggerHandler() {
+ @Override
+ public void onReceive(WaltDevice.TriggerMessage tmsg) {
+ // Remove the far away doBlink callback
+ handler.removeCallbacks(doBlinkRunnable);
+
+ detectedBlinks++;
+ logger.log("blink counts " + initiatedBlinks + " " + detectedBlinks);
+ if (initiatedBlinks == 0) {
+ if (detectedBlinks < 5) {
+ logger.log("got incoming but initiatedBlinks = 0");
+ return;
+ } else {
+ logger.log("Looks like PWM is used for this screen, turn auto brightness off and set it to max brightness");
+ showPwmDialog();
+ return;
+ }
+ }
+
+ if (traceLogger != null) {
+ final long startTimeMicros = lastFrameStartTime + waltDevice.clock.baseTime;
+ final long finishTimeMicros = tmsg.t + waltDevice.clock.baseTime;
+ traceLogger.log(startTimeMicros, finishTimeMicros,
+ isBoxWhite ? "Black-to-white" : "White-to-black",
+ "Bar starts at beginning of frame and ends when photosensor detects blink");
+ }
+
+ double dt = (tmsg.t - lastFrameStartTime) / 1000.;
+ deltas.add(dt);
+ if (isBoxWhite) { // Current color is the color we transitioned to
+ deltas_b2w.add(dt);
+ } else {
+ deltas_w2b.add(dt);
+ }
+ if (shouldShowLatencyChart) latencyChart.addEntry(isBoxWhite ? B2W_INDEX : W2B_INDEX, dt);
+
+ // Other times can be important, logging them to allow more detailed analysis
+ logger.log(String.format(Locale.US,
+ "Times [ms]: setBG:%.3f callback:%.3f physical:%.3f black2white:%d",
+ (lastSetBackgroundTime - lastFrameStartTime) / 1000.0,
+ (lastFrameCallbackTime - lastFrameStartTime) / 1000.0,
+ dt,
+ isBoxWhite ? 1 : 0
+ ));
+ if (traceLogger != null) {
+ traceLogger.log(lastFrameCallbackTime + waltDevice.clock.baseTime,
+ lastFrameCallbackTime + waltDevice.clock.baseTime + 1000,
+ isBoxWhite ? "FrameCallback Black-to-white" : "FrameCallback White-to-black",
+ "FrameCallback was called at start of bar");
+ }
+ // Schedule another blink soon-ish
+ handler.postDelayed(doBlinkRunnable, 40 + (long) (Math.random()*20));
+ }
+ };
+
+
+ void finishAndShowStats() {
+ setFullScreen(false);
+ // Stop the USB listener
+ waltDevice.stopListener();
+
+ // Unregister trigger handler
+ waltDevice.clearTriggerHandler();
+
+ waltDevice.sendAndFlush(WaltDevice.CMD_AUTO_SCREEN_OFF);
+
+ waltDevice.checkDrift();
+
+ // Show deltas and the median
+ /* // Debug printouts
+ logger.log("deltas = array(" + deltas.toString() + ")");
+ logger.log("deltas_w2b = array(" + deltas_w2b.toString() + ")");
+ logger.log("deltas_b2w = array(" + deltas_b2w.toString() + ")");
+ */
+
+ double median_b2w = Utils.median(deltas_b2w);
+ double median_w2b = Utils.median(deltas_w2b);
+ logger.log(String.format(Locale.US,
+ "\n-------------------------------\n" +
+ "Median screen response latencies (N=%d):\n" +
+ "Black to white: %.1f ms (N=%d)\n" +
+ "White to black: %.1f ms (N=%d)\n" +
+ "Average: %.1f ms\n" +
+ "-------------------------------\n",
+ deltas.size(),
+ median_b2w, deltas_b2w.size(),
+ median_w2b, deltas_w2b.size(),
+ (median_b2w + median_w2b) / 2
+ ));
+
+ if (traceLogger != null) traceLogger.flush(getContext());
+ fastSurfaceView.setVisibility(View.GONE);
+ blackBox.setVisibility(View.VISIBLE);
+ blackBox.setText(logger.getLogText());
+ blackBox.setMovementMethod(new ScrollingMovementMethod());
+ blackBox.setBackgroundColor(color_gray);
+ stopButton.setEnabled(false);
+ startButton.setEnabled(true);
+ if (shouldShowLatencyChart) {
+ latencyChart.setLabel(W2B_INDEX, String.format(Locale.US, "White-to-black m=%.1f ms", median_w2b));
+ latencyChart.setLabel(B2W_INDEX, String.format(Locale.US, "Black-to-white m=%.1f ms", median_b2w));
+ }
+ LogUploader.uploadIfAutoEnabled(getContext());
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.button_stop_screen_response) {
+ isTestRunning = false;
+ handler.removeCallbacks(doBlinkRunnable);
+ handler.removeCallbacks(startBlinking);
+ finishAndShowStats();
+ return;
+ }
+
+ if (v.getId() == R.id.button_start_screen_response) {
+ brightnessChartLayout.setVisibility(View.GONE);
+ latencyChart.setVisibility(View.GONE);
+ if (!waltDevice.isConnected()) {
+ logger.log("Error starting test: WALT is not connected");
+ return;
+ }
+
+ isTestRunning = true;
+ startButton.setEnabled(false);
+ blackBox.setBackgroundColor(Color.BLACK);
+ blackBox.setText("");
+ isFastPathGraphics = false;
+ final int spinnerPosition = spinner.getSelectedItemPosition();
+ if (spinnerPosition == 0) {
+ logger.log("Starting screen response measurement");
+ stopButton.setEnabled(true);
+ startBlinkLatency();
+ } else if (spinnerPosition == 1) {
+ logger.log("Starting screen brightness curve measurement");
+ startBrightnessCurve();
+ } else if (spinnerPosition == 2) {
+ logger.log("Starting fast-path screen response measurement");
+ isFastPathGraphics = true;
+ startBlinkLatency();
+ } else {
+ logger.log("ERROR: Spinner position is out of range");
+ }
+ return;
+ }
+
+ if (v.getId() == R.id.button_close_chart) {
+ brightnessChartLayout.setVisibility(View.GONE);
+ return;
+ }
+ }
+
+ public void onRobotAutomationEvent(String event) {
+ // Never show the latency chart during automated runs.
+ shouldShowLatencyChart = false;
+ // Disable full-screen mode to prevent modal user dialog.
+ enableFullScreen = false;
+ if (event.equals(RobotAutomationListener.RESTART_EVENT)) {
+ onClick(stopButton);
+ } else if (event.equals(RobotAutomationListener.START_EVENT)) {
+ onClick(startButton);
+ }
+ }
+
+ private WaltDevice.TriggerHandler brightnessTriggerHandler = new WaltDevice.TriggerHandler() {
+ @Override
+ public void onReceive(WaltDevice.TriggerMessage tmsg) {
+ logger.log("ERROR: Brightness curve trigger got a trigger message, " +
+ "this should never happen."
+ );
+ }
+
+ @Override
+ public void onReceiveRaw(String s) {
+ brightnessCurveData.append(s);
+ if (s.trim().equals("end")) {
+ // Remove the delayed callback and run it now
+ handler.removeCallbacks(finishBrightnessCurve);
+ handler.post(finishBrightnessCurve);
+ }
+ }
+ };
+
+ void startBrightnessCurve() {
+ try {
+ brightnessCurveData = new StringBuilder();
+ waltDevice.syncClock();
+ waltDevice.startListener();
+ } catch (IOException e) {
+ logger.log("Error starting test: " + e.getMessage());
+ isTestRunning = false;
+ startButton.setEnabled(true);
+ return;
+ }
+ setFullScreen(enableFullScreen);
+ blackBox.setText("");
+ blackBox.setBackgroundColor(Color.BLACK);
+ handler.postDelayed(startBrightness, enableFullScreen ? 1000 : CURVE_BLINK_TIME);
+ }
+
+ Runnable startBrightness = new Runnable() {
+ @Override
+ public void run() {
+ waltDevice.setTriggerHandler(brightnessTriggerHandler);
+ long tStart = waltDevice.clock.micros();
+
+ try {
+ waltDevice.command(WaltDevice.CMD_BRIGHTNESS_CURVE);
+ } catch (IOException e) {
+ logger.log("Error sending command CMD_BRIGHTNESS_CURVE: " + e.getMessage());
+ isTestRunning = false;
+ startButton.setEnabled(true);
+ return;
+ }
+
+ blackBox.setBackgroundColor(Color.WHITE);
+
+ logger.log("=== Screen brightness curve: ===\nt_start: " + tStart);
+
+ handler.postDelayed(finishBrightnessCurve, CURVE_TIMEOUT);
+
+ // Schedule the screen to flip back to black in CURVE_BLINK_TIME ms
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ long tBack = waltDevice.clock.micros();
+ blackBox.setBackgroundColor(Color.BLACK);
+ logger.log("t_back: " + tBack);
+
+ }
+ }, CURVE_BLINK_TIME);
+ }
+ };
+
+ Runnable finishBrightnessCurve = new Runnable() {
+ @Override
+ public void run() {
+ waltDevice.stopListener();
+ waltDevice.clearTriggerHandler();
+
+ // TODO: Add option to save this data into a separate file rather than the main log.
+ logger.log(brightnessCurveData.toString());
+ logger.log("=== End of screen brightness data ===");
+
+ blackBox.setText(logger.getLogText());
+ blackBox.setMovementMethod(new ScrollingMovementMethod());
+ blackBox.setBackgroundColor(color_gray);
+ isTestRunning = false;
+ startButton.setEnabled(true);
+ setFullScreen(false);
+ drawBrightnessChart();
+ LogUploader.uploadIfAutoEnabled(getContext());
+ }
+ };
+
+ private void drawBrightnessChart() {
+ final String brightnessCurveString = brightnessCurveData.toString();
+ List<Entry> entries = new ArrayList<>();
+
+ // "u" marks the start of the brightness curve data
+ int startIndex = brightnessCurveString.indexOf("u") + 1;
+ int endIndex = brightnessCurveString.indexOf("end");
+ if (endIndex == -1) endIndex = brightnessCurveString.length();
+
+ String[] brightnessStrings =
+ brightnessCurveString.substring(startIndex, endIndex).trim().split("\n");
+ for (String str : brightnessStrings) {
+ String[] arr = str.split(" ");
+ final float timestampMs = Integer.parseInt(arr[0]) / 1000f;
+ final float brightness = Integer.parseInt(arr[1]);
+ entries.add(new Entry(timestampMs, brightness));
+ }
+ LineDataSet dataSet = new LineDataSet(entries, "Brightness");
+ dataSet.setColor(Color.BLACK);
+ dataSet.setValueTextColor(Color.BLACK);
+ dataSet.setCircleColor(Color.BLACK);
+ dataSet.setCircleRadius(1.5f);
+ dataSet.setCircleColorHole(Color.DKGRAY);
+ LineData lineData = new LineData(dataSet);
+ brightnessChart.setData(lineData);
+ final Description desc = new Description();
+ desc.setText("Screen Brightness [digital level 0-1023] vs. Time [ms]");
+ desc.setTextSize(12f);
+ brightnessChart.setDescription(desc);
+ brightnessChart.getLegend().setEnabled(false);
+ brightnessChart.invalidate();
+ brightnessChartLayout.setVisibility(View.VISIBLE);
+ }
+
+ private void increaseScreenBrightness() {
+ final WindowManager.LayoutParams layoutParams = getActivity().getWindow().getAttributes();
+ layoutParams.screenBrightness = 1f;
+ getActivity().getWindow().setAttributes(layoutParams);
+ }
+
+ private void setFullScreen(boolean enable) {
+ final AppCompatActivity activity = (AppCompatActivity) getActivity();
+ final ActionBar actionBar = activity != null ? activity.getSupportActionBar() : null;
+ int newVisibility = 0;
+ if (enable) {
+ if (actionBar != null) actionBar.hide();
+ buttonBarView.setVisibility(View.GONE);
+ newVisibility |= View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ } else {
+ if (actionBar != null) actionBar.show();
+ buttonBarView.setVisibility(View.VISIBLE);
+ }
+ if (activity != null) activity.getWindow().getDecorView().setSystemUiVisibility(newVisibility);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/SettingsFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/SettingsFragment.java
new file mode 100644
index 0000000..4f74fc4
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/SettingsFragment.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceFragmentCompat;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+
+
+public class SettingsFragment extends PreferenceFragmentCompat implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+
+ private Toolbar toolbar;
+
+ public SettingsFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ // Load the preferences from an XML resource
+ setPreferencesFromResource(R.xml.preferences, rootKey);
+
+ PreferenceScreen prefMidiScreen =
+ (PreferenceScreen) getPreferenceScreen().findPreference("pref_midi_screen");
+ if (prefMidiScreen != null) {
+ boolean hasMidi =
+ getContext().getPackageManager().hasSystemFeature("android.software.midi");
+ prefMidiScreen.setVisible(hasMidi);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar_main);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.ColorBackground));
+ }
+
+ @Override
+ public void onDisplayPreferenceDialog(Preference preference) {
+ if (preference instanceof NumberPickerPreference) {
+ DialogFragment fragment = NumberPickerPreference.
+ NumberPickerPreferenceDialogFragmentCompat.newInstance(preference.getKey());
+ fragment.setTargetFragment(this, 0);
+ fragment.show(getFragmentManager(),
+ "android.support.v7.preference.PreferenceFragment.DIALOG");
+ } else {
+ super.onDisplayPreferenceDialog(preference);
+ }
+ }
+
+ @Override
+ public Fragment getCallbackFragment() {
+ return this;
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
+ PreferenceScreen preferenceScreen) {
+ SettingsFragment fragment = new SettingsFragment();
+ Bundle args = new Bundle();
+ args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
+ fragment.setArguments(args);
+
+ FragmentTransaction ft = preferenceFragmentCompat.getFragmentManager().beginTransaction();
+ ft.add(R.id.fragment_container, fragment, preferenceScreen.getKey());
+ ft.addToBackStack(preferenceScreen.getTitle().toString());
+ ft.commit();
+
+ toolbar.setTitle(preferenceScreen.getTitle());
+ return true;
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/SimpleLogger.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/SimpleLogger.java
new file mode 100644
index 0000000..6059e0f
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/SimpleLogger.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+/**
+ * A very simple logger that keeps its data in a StringBuilder. We need on screen log because the
+ * USB port is often taken and we don't have easy access to adb log.
+ */
+public class SimpleLogger {
+ private static final String LOG_INTENT = "log-message";
+ public static final String TAG = "WaltLogger";
+
+ private static final Object LOCK = new Object();
+ private static SimpleLogger instance;
+
+ private StringBuilder sb = new StringBuilder();
+ private LocalBroadcastManager broadcastManager;
+
+ public static SimpleLogger getInstance(Context context) {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new SimpleLogger(context.getApplicationContext());
+ }
+ return instance;
+ }
+ }
+
+ private SimpleLogger(Context context) {
+ broadcastManager = LocalBroadcastManager.getInstance(context);
+ }
+
+ public synchronized void log(String msg) {
+ Log.i(TAG, msg);
+ sb.append(msg);
+ sb.append('\n');
+ if (broadcastManager != null) {
+ Intent intent = new Intent(LOG_INTENT);
+ intent.putExtra("message", msg);
+ broadcastManager.sendBroadcast(intent);
+ }
+ }
+
+ public void registerReceiver(BroadcastReceiver broadcastReceiver) {
+ broadcastManager.registerReceiver(broadcastReceiver, new IntentFilter(LOG_INTENT));
+ }
+
+ public void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
+ broadcastManager.unregisterReceiver(broadcastReceiver);
+ }
+
+ public String getLogText() {
+ return sb.toString();
+ }
+
+ public void clear() {
+ sb = new StringBuilder();
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java
new file mode 100644
index 0000000..e26a328
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import static org.chromium.latency.walt.Utils.getBooleanPreference;
+
+public class TapLatencyFragment extends Fragment
+ implements View.OnClickListener, RobotAutomationListener {
+
+ private static final int ACTION_DOWN_INDEX = 0;
+ private static final int ACTION_UP_INDEX = 1;
+ private SimpleLogger logger;
+ private TraceLogger traceLogger;
+ private WaltDevice waltDevice;
+ private TextView logTextView;
+ private TextView tapCatcherView;
+ private TextView tapCountsView;
+ private TextView moveCountsView;
+ private ImageButton finishButton;
+ private ImageButton restartButton;
+ private HistogramChart latencyChart;
+ private int moveCount = 0;
+ private int allDownCount = 0;
+ private int allUpCount = 0;
+ private int okDownCount = 0;
+ private int okUpCount = 0;
+ private boolean shouldShowLatencyChart = false;
+
+ ArrayList<UsMotionEvent> eventList = new ArrayList<>();
+ ArrayList<Double> p2kDown = new ArrayList<>();
+ ArrayList<Double> p2kUp = new ArrayList<>();
+ ArrayList<Double> k2cDown = new ArrayList<>();
+ ArrayList<Double> k2cUp = new ArrayList<>();
+
+ private BroadcastReceiver logReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String msg = intent.getStringExtra("message");
+ TapLatencyFragment.this.appendLogText(msg);
+ }
+ };
+
+ private View.OnTouchListener touchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ UsMotionEvent tapEvent = new UsMotionEvent(event, waltDevice.clock.baseTime);
+
+ if(tapEvent.action != MotionEvent.ACTION_UP && tapEvent.action != MotionEvent.ACTION_DOWN) {
+ moveCount++;
+ updateCountsDisplay();
+ return true;
+ }
+
+ // Debug: logger.log("\n"+ action + " event received: " + tapEvent.toStringLong());
+ tapEvent.physicalTime = waltDevice.readLastShockTime();
+
+ tapEvent.isOk = checkTapSanity(tapEvent);
+ // Save it in any case so we can do stats on bad events later
+ eventList.add(tapEvent);
+
+ final double physicalToKernelTime = (tapEvent.kernelTime - tapEvent.physicalTime) / 1000.;
+ final double kernelToCallbackTime = (tapEvent.createTime - tapEvent.kernelTime) / 1000.;
+ if (tapEvent.action == MotionEvent.ACTION_DOWN) {
+ allDownCount++;
+ if (tapEvent.isOk) {
+ okDownCount++;
+ p2kDown.add(physicalToKernelTime);
+ k2cDown.add(kernelToCallbackTime);
+ if (shouldShowLatencyChart) latencyChart.addEntry(ACTION_DOWN_INDEX, physicalToKernelTime);
+ logger.log(String.format(Locale.US,
+ "ACTION_DOWN:\ntouch2kernel: %.1f ms\nkernel2java: %.1f ms",
+ physicalToKernelTime, kernelToCallbackTime));
+ }
+ } else if (tapEvent.action == MotionEvent.ACTION_UP) {
+ allUpCount++;
+ if (tapEvent.isOk) {
+ okUpCount++;
+ p2kUp.add(physicalToKernelTime);
+ k2cUp.add(kernelToCallbackTime);
+ if (shouldShowLatencyChart) latencyChart.addEntry(ACTION_UP_INDEX, physicalToKernelTime);
+ logger.log(String.format(Locale.US,
+ "ACTION_UP:\ntouch2kernel: %.1f ms\nkernel2java: %.1f ms",
+ physicalToKernelTime, kernelToCallbackTime));
+ }
+ }
+ traceLogEvent(tapEvent);
+
+ updateCountsDisplay();
+ return true;
+ }
+ };
+
+ private void traceLogEvent(UsMotionEvent tapEvent) {
+ if (!tapEvent.isOk) return;
+ if (traceLogger == null) return;
+ if (tapEvent.action != MotionEvent.ACTION_DOWN && tapEvent.action != MotionEvent.ACTION_UP) return;
+ final String title = tapEvent.action == MotionEvent.ACTION_UP ? "Tap-Up" : "Tap-Down";
+ traceLogger.log(tapEvent.physicalTime + waltDevice.clock.baseTime,
+ tapEvent.kernelTime + waltDevice.clock.baseTime, title + " Physical",
+ "Bar starts at accelerometer shock and ends at kernel time of tap event");
+ traceLogger.log(tapEvent.kernelTime + waltDevice.clock.baseTime,
+ tapEvent.createTime + waltDevice.clock.baseTime, title + " App Callback",
+ "Bar starts at kernel time of tap event and ends at app callback time");
+ }
+
+ public TapLatencyFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ shouldShowLatencyChart = getBooleanPreference(getContext(), R.string.preference_show_tap_histogram, true);
+ if (getBooleanPreference(getContext(), R.string.preference_systrace, true)) {
+ traceLogger = TraceLogger.getInstance();
+ }
+ waltDevice = WaltDevice.getInstance(getContext());
+ logger = SimpleLogger.getInstance(getContext());
+ // Inflate the layout for this fragment
+ final View view = inflater.inflate(R.layout.fragment_tap_latency, container, false);
+ restartButton = (ImageButton) view.findViewById(R.id.button_restart_tap);
+ finishButton = (ImageButton) view.findViewById(R.id.button_finish_tap);
+ tapCatcherView = (TextView) view.findViewById(R.id.tap_catcher);
+ logTextView = (TextView) view.findViewById(R.id.txt_log_tap_latency);
+ tapCountsView = (TextView) view.findViewById(R.id.txt_tap_counts);
+ moveCountsView = (TextView) view.findViewById(R.id.txt_move_count);
+ latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
+ logTextView.setMovementMethod(new ScrollingMovementMethod());
+ finishButton.setEnabled(false);
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ logTextView.setText(logger.getLogText());
+ logger.registerReceiver(logReceiver);
+
+ // Register this fragment class as the listener for some button clicks
+ restartButton.setOnClickListener(this);
+ finishButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ logger.unregisterReceiver(logReceiver);
+ super.onPause();
+ }
+
+ public void appendLogText(String msg) {
+ logTextView.append(msg + "\n");
+ }
+
+ public boolean checkTapSanity(UsMotionEvent e) {
+ String action = e.getActionString();
+ double dt = (e.kernelTime - e.physicalTime) / 1000.0;
+
+ if (e.physicalTime == 0) {
+ logger.log(action + " no shock found");
+ return false;
+ }
+
+ if (dt < 0 || dt > 200) {
+ logger.log(action + " bogus kernelTime=" + e.kernelTime + ", ignored, dt=" + dt);
+ return false;
+ }
+ return true;
+ }
+
+ void updateCountsDisplay() {
+ String tpl = "N ↓%d (%d) ↑%d (%d)";
+ tapCountsView.setText(String.format(Locale.US,
+ tpl,
+ okDownCount,
+ allDownCount,
+ okUpCount,
+ allUpCount
+ ));
+
+ moveCountsView.setText(String.format(Locale.US, "⇄ %d", moveCount));
+ }
+
+ void restartMeasurement() {
+ logger.log("\n## Restarting tap latency measurement. Re-sync clocks ...");
+ try {
+ waltDevice.softReset();
+ waltDevice.syncClock();
+ } catch (IOException e) {
+ logger.log("Error syncing clocks: " + e.getMessage());
+ restartButton.setImageResource(R.drawable.ic_play_arrow_black_24dp);
+ finishButton.setEnabled(false);
+ latencyChart.setVisibility(View.GONE);
+ return;
+ }
+
+ eventList.clear();
+ p2kDown.clear();
+ p2kUp.clear();
+ k2cDown.clear();
+ k2cUp.clear();
+
+ moveCount = 0;
+ allDownCount = 0;
+ allUpCount = 0;
+ okDownCount = 0;
+ okUpCount = 0;
+
+ updateCountsDisplay();
+ tapCatcherView.setOnTouchListener(touchListener);
+ }
+
+ void finishAndShowStats() {
+ tapCatcherView.setOnTouchListener(null);
+ waltDevice.checkDrift();
+ logger.log("\n-------------------------------");
+ logger.log(String.format(Locale.US,
+ "Tap latency results:\n" +
+ "Number of events recorded:\n" +
+ " ACTION_DOWN %d (bad %d)\n" +
+ " ACTION_UP %d (bad %d)\n" +
+ " ACTION_MOVE %d",
+ okDownCount,
+ allDownCount - okDownCount,
+ okUpCount,
+ allUpCount - okUpCount,
+ moveCount
+ ));
+
+ logger.log("ACTION_DOWN median times:");
+ logger.log(String.format(Locale.US,
+ " Touch to kernel: %.1f ms\n Kernel to Java: %.1f ms",
+ Utils.median(p2kDown),
+ Utils.median(k2cDown)
+ ));
+ logger.log("ACTION_UP median times:");
+ logger.log(String.format(Locale.US,
+ " Touch to kernel: %.1f ms\n Kernel to Java: %.1f ms",
+ Utils.median(p2kUp),
+ Utils.median(k2cUp)
+ ));
+ logger.log("-------------------------------");
+ if (traceLogger != null) traceLogger.flush(getContext());
+
+ if (shouldShowLatencyChart) {
+ latencyChart.setLabel(ACTION_DOWN_INDEX, String.format(Locale.US, "ACTION_DOWN median=%.1f ms", Utils.median(p2kDown)));
+ latencyChart.setLabel(ACTION_UP_INDEX, String.format(Locale.US, "ACTION_UP median=%.1f ms", Utils.median(p2kUp)));
+ }
+ LogUploader.uploadIfAutoEnabled(getContext());
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.button_restart_tap) {
+ restartButton.setImageResource(R.drawable.ic_refresh_black_24dp);
+ finishButton.setEnabled(true);
+ if (shouldShowLatencyChart) {
+ latencyChart.setVisibility(View.VISIBLE);
+ latencyChart.clearData();
+ latencyChart.setLabel(ACTION_DOWN_INDEX, "ACTION_DOWN");
+ latencyChart.setLabel(ACTION_UP_INDEX, "ACTION_UP");
+ }
+ restartMeasurement();
+ return;
+ }
+
+ if (v.getId() == R.id.button_finish_tap) {
+ finishButton.setEnabled(false);
+ finishAndShowStats();
+ restartButton.setImageResource(R.drawable.ic_play_arrow_black_24dp);
+ return;
+ }
+
+ }
+
+ public void onRobotAutomationEvent(String event) {
+ // Never show the latency chart during automated runs.
+ shouldShowLatencyChart = false;
+ if (event.equals(RobotAutomationListener.RESTART_EVENT) ||
+ event.equals(RobotAutomationListener.START_EVENT)) {
+ restartMeasurement();
+ } else if (event.equals(RobotAutomationListener.FINISH_EVENT)) {
+ finishAndShowStats();
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/TouchCatcherView.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/TouchCatcherView.java
new file mode 100644
index 0000000..9e04056
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/TouchCatcherView.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+
+
+public class TouchCatcherView extends View {
+
+ private Paint linePaint = new Paint();
+ private WaltDevice waltDevice;
+ private boolean isAnimated = false;
+
+ private double animationAmplitude = 0.4; // Fraction of view height
+ private double lineLength = 0.6; // Fraction of view width
+ public final int animationPeriod_us = 1000000;
+
+ public void startAnimation() {
+ isAnimated = true;
+ invalidate();
+ }
+
+ public void stopAnimation() {
+ isAnimated = false;
+ invalidate();
+ }
+
+ public TouchCatcherView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ waltDevice = WaltDevice.getInstance(context);
+ initialisePaint();
+ }
+
+ private void initialisePaint() {
+ float density = getResources().getDisplayMetrics().density;
+ float lineWidth = 10f * density;
+ linePaint.setColor(Color.GREEN);
+ linePaint.setStrokeWidth(lineWidth);
+ }
+
+ public static double markerPosition(long t_us, int period_us) {
+ // Normalized time within a period, goes from 0 to 1
+ double t = (t_us % period_us) / (double) period_us;
+
+ // Triangular wave with unit amplitude
+ // 1| * *
+ // | * * *
+ // 0-----*-------*---|---*-----> t
+ // | * * 1 *
+ // -1| * *
+ double y_tri = -1 + 4 * Math.abs(t - 0.5);
+
+ // Apply some smoothing to get a feeling of deceleration and acceleration at the edges.
+ // f(y) = y / {1 + exp(b(|y|-1))/(b-1)}
+ // This is inspired by Fermi function and adjusted to have continuous derivative at extrema.
+ // b = beta is a dimensionless smoothing parameter, value selected by experimentation.
+ // Higher value gives less smoothing = closer to original triangular wave.
+ double beta = 4;
+ double y_smooth = y_tri / (1 + Math.exp(beta*(Math.abs(y_tri)-1))/(beta - 1));
+ return y_smooth;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (!isAnimated) return;
+
+ int h = getHeight();
+ double normPos = markerPosition(waltDevice.clock.micros(), animationPeriod_us);
+ int pos = (int) (h * (0.5 + animationAmplitude * normPos));
+ // Log.i("AnimatedView", "Pos is " + pos);
+ int w = getWidth();
+
+ int lineStart = (int) (w * (1 - lineLength) / 2);
+ int lineEnd = (int) (w * (1 + lineLength) / 2);
+ canvas.drawLine(lineStart, pos, lineEnd, pos, linePaint);
+
+ // Run every frame
+ invalidate();
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/TraceLogger.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/TraceLogger.java
new file mode 100644
index 0000000..6fdb8d9
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/TraceLogger.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.os.Environment;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+
+/**
+ * Used to log events for Android systrace
+ */
+class TraceLogger {
+
+ private static final Object LOCK = new Object();
+ private static TraceLogger instance;
+
+ private ArrayList<TraceEvent> traceEvents;
+
+ public static TraceLogger getInstance() {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new TraceLogger();
+ }
+ return instance;
+ }
+ }
+
+ private TraceLogger() {
+ traceEvents = new ArrayList<>();
+ }
+
+ public synchronized void log(long startTimeMicros, long finishTimeMicros, String title, String description) {
+ traceEvents.add(new TraceEvent(startTimeMicros, finishTimeMicros, title, description));
+ }
+
+ public String getLogText() {
+ DecimalFormat df = new DecimalFormat(".000000");
+ StringBuilder sb = new StringBuilder();
+ int pid = android.os.Process.myPid();
+ for (TraceEvent e : traceEvents) {
+ sb.append(String.format(
+ "WALTThread-1234 (%d) [000] ...1 %s: tracing_mark_write: B|%d|%s|description=%s|WALT\n",
+ pid, df.format(e.startTimeMicros / 1e6), pid, e.title, e.description));
+ sb.append(String.format(
+ "WALTThread-1234 (%d) [000] ...1 %s: tracing_mark_write: E|%d|%s||WALT\n",
+ pid, df.format(e.finishTimeMicros / 1e6), pid, e.title));
+ }
+ return sb.toString();
+ }
+
+ void flush(Context context) {
+ SimpleLogger logger = SimpleLogger.getInstance(context);
+ if (!isExternalStorageWritable()) {
+ logger.log("ERROR: could not write systrace logs to file");
+ return;
+ }
+ writeSystraceLogs(context);
+ traceEvents.clear();
+ }
+
+ private void writeSystraceLogs(Context context) {
+ File file = new File(context.getExternalFilesDir(null), "trace.txt");
+ SimpleLogger logger = SimpleLogger.getInstance(context);
+ try {
+ OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file, true));
+ writer.write(getLogText());
+ writer.close();
+ logger.log(String.format("TraceLogger wrote %d events to %s",
+ traceEvents.size(), file.getAbsolutePath()));
+ } catch (IOException e) {
+ logger.log("ERROR: IOException writing to trace.txt");
+ e.printStackTrace();
+ }
+ }
+
+ private boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ private class TraceEvent {
+ long startTimeMicros;
+ long finishTimeMicros;
+ String title;
+ String description;
+ TraceEvent(long startTimeMicros, long finishTimeMicros, String title, String description) {
+ this.startTimeMicros = startTimeMicros;
+ this.finishTimeMicros = finishTimeMicros;
+ this.title = title;
+ this.description = description;
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/UsMotionEvent.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/UsMotionEvent.java
new file mode 100644
index 0000000..e961949
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/UsMotionEvent.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.util.Log;
+import android.view.MotionEvent;
+
+import java.lang.reflect.Method;
+
+/**
+ * A convenient representation of MotionEvent events
+ * - microsecond accuracy
+ * - no bundling of ACTION_MOVE events
+ */
+
+public class UsMotionEvent {
+
+ public long physicalTime, kernelTime, createTime;
+ public float x, y;
+ public int slot;
+ public int action;
+ public int num;
+ public String metadata;
+ public long baseTime;
+
+ public boolean isOk = false;
+
+ /**
+ *
+ * @param event - MotionEvent as received by the handler.
+ * @param baseTime - base time of the last clock sync.
+ */
+ public UsMotionEvent(MotionEvent event, long baseTime) {
+ createTime = RemoteClockInfo.microTime() - baseTime;
+ this.baseTime = baseTime;
+ slot = -1;
+ kernelTime = getEventTimeMicro(event) - baseTime;
+ x = event.getX();
+ y = event.getY();
+ action = event.getAction();
+ }
+
+ public UsMotionEvent(MotionEvent event, long baseTime, int pos) {
+ createTime = RemoteClockInfo.microTime() - baseTime;
+ this.baseTime = baseTime;
+ slot = pos;
+ action = MotionEvent.ACTION_MOVE; // Only MOVE events get bundled with history
+
+ kernelTime = getHistoricalEventTimeMicro(event, pos) - baseTime;
+ x = event.getHistoricalX(pos);
+ y = event.getHistoricalY(pos);
+ }
+
+ public String getActionString() {
+ return actionToString(action);
+ }
+
+
+ public String toString() {
+ return String.format("%d %f %f",
+ kernelTime, x, y);
+
+ }
+
+ public String toStringLong() {
+ return String.format("Event: t=%d x=%.1f y=%.1f slot=%d num=%d %s",
+ kernelTime, x, y, slot, num, actionToString(action));
+
+ }
+
+ // The MotionEvent.actionToString is not present before API 19
+ public static String actionToString(int action) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ return "ACTION_DOWN";
+ case MotionEvent.ACTION_UP:
+ return "ACTION_UP";
+ case MotionEvent.ACTION_CANCEL:
+ return "ACTION_CANCEL";
+ case MotionEvent.ACTION_OUTSIDE:
+ return "ACTION_OUTSIDE";
+ case MotionEvent.ACTION_MOVE:
+ return "ACTION_MOVE";
+ case MotionEvent.ACTION_HOVER_MOVE:
+ return "ACTION_HOVER_MOVE";
+ case MotionEvent.ACTION_SCROLL:
+ return "ACTION_SCROLL";
+ case MotionEvent.ACTION_HOVER_ENTER:
+ return "ACTION_HOVER_ENTER";
+ case MotionEvent.ACTION_HOVER_EXIT:
+ return "ACTION_HOVER_EXIT";
+ }
+ return "UNKNOWN_ACTION";
+ }
+
+ /**
+ MotionEvent.getEventTime() function only provides millisecond resolution.
+ There is a MotionEvent.getEventTimeNano() function but for some reason it
+ is hidden by @hide which means it can't be called directly.
+ Calling is via reflection.
+
+ See:
+ http://stackoverflow.com/questions/17035271/what-does-hide-mean-in-the-android-source-code
+ */
+ private long getEventTimeMicro(MotionEvent event) {
+ long t_nanos = -1;
+ try {
+ Class cls = Class.forName("android.view.MotionEvent");
+ Method myTimeGetter = cls.getMethod("getEventTimeNano");
+ t_nanos = (long) myTimeGetter.invoke(event);
+ } catch (Exception e) {
+ Log.i("WALT.MsMotionEvent", e.getMessage());
+ }
+
+ return t_nanos / 1000;
+ }
+
+ private long getHistoricalEventTimeMicro(MotionEvent event, int pos) {
+ long t_nanos = -1;
+ try {
+ Class cls = Class.forName("android.view.MotionEvent");
+ Method myTimeGetter = cls.getMethod("getHistoricalEventTimeNano", new Class[] {int.class});
+ t_nanos = (long) myTimeGetter.invoke(event, new Object[]{pos});
+ } catch (Exception e) {
+ Log.i("WALT.MsMotionEvent", e.getMessage());
+ }
+
+ return t_nanos / 1000;
+ }
+
+}
+
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/Utils.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/Utils.java
new file mode 100644
index 0000000..19c7488
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/Utils.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.annotation.StringRes;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Kitchen sink for small utility functions
+ */
+public class Utils {
+ public static double median(ArrayList<Double> arrList) {
+ ArrayList<Double> lst = new ArrayList<>(arrList);
+ Collections.sort(lst);
+ int len = lst.size();
+ if (len == 0) {
+ return Double.NaN;
+ }
+
+ if (len % 2 == 1) {
+ return lst.get(len / 2);
+ } else {
+ return 0.5 * (lst.get(len / 2) + lst.get(len / 2 - 1));
+ }
+ }
+
+ public static double mean(double[] x) {
+ double s = 0;
+ for (double v: x) s += v;
+ return s / x.length;
+ }
+
+ /**
+ * Linear interpolation styled after numpy.interp()
+ * returns values at points x interpolated using xp, yp data points
+ * Both x and xp must be monotonically increasing.
+ */
+ public static double[] interp(double[] x, double[] xp, double[] yp) {
+ // assuming that x and xp are already sorted.
+ // go over x and xp as if we are merging them
+ double[] y = new double[x.length];
+ int i = 0;
+ int ip = 0;
+
+ // skip x points that are outside the data
+ while (i < x.length && x[i] < xp[0]) i++;
+
+ while (ip < xp.length && i < x.length) {
+ // skip until we see an xp >= current x
+ while (ip < xp.length && xp[ip] < x[i]) ip++;
+ if (ip >= xp.length) break;
+ if (xp[ip] == x[i]) {
+ y[i] = yp[ip];
+ } else {
+ double dy = yp[ip] - yp[ip-1];
+ double dx = xp[ip] - xp[ip-1];
+ y[i] = yp[ip-1] + dy/dx * (x[i] - xp[ip-1]);
+ }
+ i++;
+ }
+ return y;
+ }
+
+ public static double stdev(double[] a) {
+ double m = mean(a);
+ double sumsq = 0;
+ for (double v : a) sumsq += (v-m)*(v-m);
+ return Math.sqrt(sumsq / a.length);
+ }
+
+ /**
+ * Similar to numpy.extract()
+ * returns a shorter array with values taken from x at indices where indicator == value
+ */
+ public static double[] extract(int[] indicator, int value, double[] arr) {
+ if (arr.length != indicator.length) {
+ throw new IllegalArgumentException("Length of arr and indicator must be the same.");
+ }
+ int newLen = 0;
+ for (int v: indicator) if (v == value) newLen++;
+ double[] newx = new double[newLen];
+
+ int j = 0;
+ for (int i=0; i<arr.length; i++) {
+ if (indicator[i] == value) {
+ newx[j] = arr[i];
+ j++;
+ }
+ }
+ return newx;
+ }
+
+ public static String array2string(double[] a, String format) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("array([");
+ for (double x: a) {
+ sb.append(String.format(format, x));
+ sb.append(", ");
+ }
+ sb.append("])");
+ return sb.toString();
+ }
+
+
+ public static int argmin(double[] a) {
+ int imin = 0;
+ for (int i=1; i<a.length; i++) if (a[i] < a[imin]) imin = i;
+ return imin;
+ }
+
+ private static double getShiftError(double[] laserT, double[] touchT, double[] touchY, double shift) {
+ double[] T = new double[laserT.length];
+ for (int j=0; j<T.length; j++) {
+ T[j] = laserT[j] + shift;
+ }
+ double [] laserY = Utils.interp(T, touchT, touchY);
+ // TODO: Think about throwing away a percentile of most distanced points for noise reduction
+ return Utils.stdev(laserY);
+ }
+
+ /**
+ * Simplified Java re-implementation or py/qslog/minimization.py.
+ * This is very specific to the drag latency algorithm.
+ *
+ * tl;dr: Shift laser events by some time delta and see how well they fit on a horizontal line.
+ * Delta that results in the best looking straight line is the latency.
+ */
+ public static double findBestShift(double[] laserT, double[] touchT, double[] touchY) {
+ int steps = 1500;
+ double[] shiftSteps = new double[]{0.1, 0.01}; // milliseconds
+ double[] stddevs = new double[steps];
+ double bestShift = shiftSteps[0]*steps/2;
+ for (final double shiftStep : shiftSteps) {
+ for (int i = 0; i < steps; i++) {
+ stddevs[i] = getShiftError(laserT, touchT, touchY, bestShift + shiftStep * i - shiftStep * steps / 2);
+ }
+ bestShift = argmin(stddevs) * shiftStep + bestShift - shiftStep * steps / 2;
+ }
+ return bestShift;
+ }
+
+ static byte[] char2byte(char c) {
+ return new byte[]{(byte) c};
+ }
+
+ static int getIntPreference(Context context, @StringRes int keyId, int defaultValue) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getInt(context.getString(keyId), defaultValue);
+ }
+
+ static boolean getBooleanPreference(Context context, @StringRes int keyId, boolean defaultValue) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getBoolean(context.getString(keyId), defaultValue);
+ }
+
+ static String getStringPreference(Context context, @StringRes int keyId, String defaultValue) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getString(context.getString(keyId), defaultValue);
+ }
+
+ public enum ListenerState {
+ RUNNING,
+ STARTING,
+ STOPPED,
+ STOPPING
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltConnection.java
new file mode 100644
index 0000000..98835af
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltConnection.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+
+import java.io.IOException;
+
+
+public interface WaltConnection {
+
+ void connect();
+
+ boolean isConnected();
+
+ void sendByte(char c) throws IOException;
+
+ int blockingRead(byte[] buffer);
+
+ RemoteClockInfo syncClock() throws IOException;
+
+ void updateLag();
+
+ void setConnectionStateListener(ConnectionStateListener connectionStateListener);
+
+ interface ConnectionStateListener {
+ void onConnect();
+ void onDisconnect();
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java
new file mode 100644
index 0000000..8ec2cb4
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.usb.UsbDevice;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * A singleton used as an interface for the physical WALT device.
+ */
+public class WaltDevice implements WaltConnection.ConnectionStateListener {
+
+ private static final int DEFAULT_DRIFT_LIMIT_US = 1500;
+ private static final String TAG = "WaltDevice";
+ public static final String PROTOCOL_VERSION = "5";
+
+ // Teensy side commands. Each command is a single char
+ // Based on #defines section in walt.ino
+ static final char CMD_PING_DELAYED = 'D'; // Ping with a delay
+ static final char CMD_RESET = 'F'; // Reset all vars
+ static final char CMD_SYNC_SEND = 'I'; // Send some digits for clock sync
+ static final char CMD_PING = 'P'; // Ping with a single byte
+ static final char CMD_VERSION = 'V'; // Determine WALT's firmware version
+ static final char CMD_SYNC_READOUT = 'R'; // Read out sync times
+ static final char CMD_GSHOCK = 'G'; // Send last shock time and watch for another shock.
+ static final char CMD_TIME_NOW = 'T'; // Current time
+ static final char CMD_SYNC_ZERO = 'Z'; // Initial zero
+ static final char CMD_AUTO_SCREEN_ON = 'C'; // Send a message on screen color change
+ static final char CMD_AUTO_SCREEN_OFF = 'c';
+ static final char CMD_SEND_LAST_SCREEN = 'E'; // Send info about last screen color change
+ static final char CMD_BRIGHTNESS_CURVE = 'U'; // Probe screen for brightness vs time curve
+ static final char CMD_AUTO_LASER_ON = 'L'; // Send messages on state change of the laser
+ static final char CMD_AUTO_LASER_OFF = 'l';
+ static final char CMD_SEND_LAST_LASER = 'J';
+ static final char CMD_AUDIO = 'A'; // Start watching for signal on audio out line
+ static final char CMD_BEEP = 'B'; // Generate a tone into the mic and send timestamp
+ static final char CMD_BEEP_STOP = 'S'; // Stop generating tone
+ static final char CMD_MIDI = 'M'; // Start listening for a MIDI message
+ static final char CMD_NOTE = 'N'; // Generate a MIDI NoteOn message
+
+ private static final int BYTE_BUFFER_SIZE = 1024 * 4;
+ private byte[] buffer = new byte[BYTE_BUFFER_SIZE];
+
+ private Context context;
+ protected SimpleLogger logger;
+ private WaltConnection connection;
+ public RemoteClockInfo clock;
+ private WaltConnection.ConnectionStateListener connectionStateListener;
+
+ private static final Object LOCK = new Object();
+ private static WaltDevice instance;
+
+ public static WaltDevice getInstance(Context context) {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new WaltDevice(context.getApplicationContext());
+ }
+ return instance;
+ }
+ }
+
+ private WaltDevice(Context context) {
+ this.context = context;
+ triggerListener = new TriggerListener();
+ logger = SimpleLogger.getInstance(context);
+ }
+
+ public void onConnect() {
+ try {
+ // TODO: restore
+ softReset();
+ checkVersion();
+ syncClock();
+ } catch (IOException e) {
+ logger.log("Unable to communicate with WALT: " + e.getMessage());
+ }
+
+ if (connectionStateListener != null) {
+ connectionStateListener.onConnect();
+ }
+ }
+
+ // Called when disconnecting from WALT
+ // TODO: restore this, not called from anywhere
+ public void onDisconnect() {
+ if (!isListenerStopped()) {
+ stopListener();
+ }
+
+ if (connectionStateListener != null) {
+ connectionStateListener.onDisconnect();
+ }
+ }
+
+ public void connect() {
+ if (WaltTcpConnection.probe()) {
+ logger.log("Using TCP bridge for ChromeOS");
+ connection = WaltTcpConnection.getInstance(context);
+ } else {
+ // USB connection
+ logger.log("No TCP bridge detected, using direct USB connection");
+ connection = WaltUsbConnection.getInstance(context);
+ }
+ connection.setConnectionStateListener(this);
+ connection.connect();
+ }
+
+ public void connect(UsbDevice usbDevice) {
+ // This happens when apps starts as a result of plugging WALT into USB. In this case we
+ // receive an intent with a usbDevice
+ WaltUsbConnection usbConnection = WaltUsbConnection.getInstance(context);
+ connection = usbConnection;
+ connection.setConnectionStateListener(this);
+ usbConnection.connect(usbDevice);
+ }
+
+ public boolean isConnected() {
+ return connection.isConnected();
+ }
+
+
+ public String readOne() throws IOException {
+ if (!isListenerStopped()) {
+ throw new IOException("Can't do blocking read while listener is running");
+ }
+
+ byte[] buff = new byte[64];
+ int ret = connection.blockingRead(buff);
+
+ if (ret < 0) {
+ throw new IOException("Timed out reading from WALT");
+ }
+ String s = new String(buff, 0, ret);
+ Log.i(TAG, "readOne() received data: " + s);
+ return s;
+ }
+
+
+ private String sendReceive(char c) throws IOException {
+ synchronized (connection) {
+ connection.sendByte(c);
+ return readOne();
+ }
+ }
+
+ public void sendAndFlush(char c) {
+
+ try {
+ synchronized (connection) {
+ connection.sendByte(c);
+ while (connection.blockingRead(buffer) > 0) {
+ // flushing all incoming data
+ }
+ }
+ } catch (Exception e) {
+ logger.log("Exception in sendAndFlush: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ public void softReset() {
+ sendAndFlush(CMD_RESET);
+ }
+
+ String command(char cmd, char ack) throws IOException {
+ if (!isListenerStopped()) {
+ connection.sendByte(cmd); // TODO: check response even if the listener is running
+ return "";
+ }
+ String response = sendReceive(cmd);
+ if (!response.startsWith(String.valueOf(ack))) {
+ throw new IOException("Unexpected response from WALT. Expected \"" + ack
+ + "\", got \"" + response + "\"");
+ }
+ // Trim out the ack
+ return response.substring(1).trim();
+ }
+
+ String command(char cmd) throws IOException {
+ return command(cmd, flipCase(cmd));
+ }
+
+ private char flipCase(char c) {
+ if (Character.isUpperCase(c)) {
+ return Character.toLowerCase(c);
+ } else if (Character.isLowerCase(c)) {
+ return Character.toUpperCase(c);
+ } else {
+ return c;
+ }
+ }
+
+ public void checkVersion() throws IOException {
+ if (!isConnected()) throw new IOException("Not connected to WALT");
+ if (!isListenerStopped()) throw new IOException("Listener is running");
+
+ String s = command(CMD_VERSION);
+ if (!PROTOCOL_VERSION.equals(s)) {
+ Resources res = context.getResources();
+ throw new IOException(String.format(res.getString(R.string.protocol_version_mismatch),
+ s, PROTOCOL_VERSION));
+ }
+ }
+
+ public void syncClock() throws IOException {
+ clock = connection.syncClock();
+ }
+
+ // Simple way of syncing clocks. Used for diagnostics. Accuracy of several ms.
+ public void simpleSyncClock() throws IOException {
+ byte[] buffer = new byte[1024];
+ clock = new RemoteClockInfo();
+ clock.baseTime = RemoteClockInfo.microTime();
+ String reply = sendReceive(CMD_SYNC_ZERO);
+ logger.log("Simple sync reply: " + reply);
+ clock.maxLag = (int) clock.micros();
+ logger.log("Synced clocks, the simple way:\n" + clock);
+ }
+
+ public void checkDrift() {
+ if (! isConnected()) {
+ logger.log("ERROR: Not connected, aborting checkDrift()");
+ return;
+ }
+ connection.updateLag();
+ if (clock == null) {
+ // updateLag() will have logged a message if we get here
+ return;
+ }
+ int drift = Math.abs(clock.getMeanLag());
+ String msg = String.format("Remote clock delayed between %d and %d us",
+ clock.minLag, clock.maxLag);
+ // TODO: Convert the limit to user editable preference
+ if (drift > DEFAULT_DRIFT_LIMIT_US) {
+ msg = "WARNING: High clock drift. " + msg;
+ }
+ logger.log(msg);
+ }
+
+ public long readLastShockTime_mock() {
+ return clock.micros() - 15000;
+ }
+
+ public long readLastShockTime() {
+ String s;
+ try {
+ s = sendReceive(CMD_GSHOCK);
+ } catch (IOException e) {
+ logger.log("Error sending GSHOCK command: " + e.getMessage());
+ return -1;
+ }
+ Log.i(TAG, "Received S reply: " + s);
+ long t = 0;
+ try {
+ t = Integer.parseInt(s.trim());
+ } catch (NumberFormatException e) {
+ logger.log("Bad reply for shock time: " + e.getMessage());
+ }
+
+ return t;
+ }
+
+ static class TriggerMessage {
+ public char tag;
+ public long t;
+ public int value;
+ public int count;
+ // TODO: verify the format of the message while parsing it
+ TriggerMessage(String s) {
+ String[] parts = s.trim().split("\\s+");
+ tag = parts[0].charAt(0);
+ t = Integer.parseInt(parts[1]);
+ value = Integer.parseInt(parts[2]);
+ count = Integer.parseInt(parts[3]);
+ }
+
+ static boolean isTriggerString(String s) {
+ return s.trim().matches("G\\s+[A-Z]\\s+\\d+\\s+\\d+.*");
+ }
+ }
+
+ TriggerMessage readTriggerMessage(char cmd) throws IOException {
+ String response = command(cmd, 'G');
+ return new TriggerMessage(response);
+ }
+
+
+ /***********************************************************************************************
+ Trigger Listener
+ A thread that constantly polls the interface for incoming triggers and passes them to the handler
+
+ */
+
+ private TriggerListener triggerListener;
+ private Thread triggerListenerThread;
+
+ abstract static class TriggerHandler {
+ private Handler handler;
+
+ TriggerHandler() {
+ handler = new Handler();
+ }
+
+ private void go(final String s) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ onReceiveRaw(s);
+ }
+ });
+ }
+
+ void onReceiveRaw(String s) {
+ for (String trigger : s.split("\n")) {
+ if (TriggerMessage.isTriggerString(trigger)) {
+ TriggerMessage tmsg = new TriggerMessage(trigger.substring(1).trim());
+ onReceive(tmsg);
+ } else {
+ Log.i(TAG, "Malformed trigger data: " + s);
+ }
+ }
+ }
+
+ abstract void onReceive(TriggerMessage tmsg);
+ }
+
+ private TriggerHandler triggerHandler;
+
+ void setTriggerHandler(TriggerHandler triggerHandler) {
+ this.triggerHandler = triggerHandler;
+ }
+
+ void clearTriggerHandler() {
+ triggerHandler = null;
+ }
+
+ private class TriggerListener implements Runnable {
+ static final int BUFF_SIZE = 1024 * 4;
+ public Utils.ListenerState state = Utils.ListenerState.STOPPED;
+ private byte[] buffer = new byte[BUFF_SIZE];
+
+ @Override
+ public void run() {
+ state = Utils.ListenerState.RUNNING;
+ while(isRunning()) {
+ int ret = connection.blockingRead(buffer);
+ if (ret > 0 && triggerHandler != null) {
+ String s = new String(buffer, 0, ret);
+ Log.i(TAG, "Listener received data: " + s);
+ if (s.length() > 0) {
+ triggerHandler.go(s);
+ }
+ }
+ }
+ state = Utils.ListenerState.STOPPED;
+ }
+
+ public synchronized boolean isRunning() {
+ return state == Utils.ListenerState.RUNNING;
+ }
+
+ public synchronized boolean isStopped() {
+ return state == Utils.ListenerState.STOPPED;
+ }
+
+ public synchronized void stop() {
+ state = Utils.ListenerState.STOPPING;
+ }
+ }
+
+ public boolean isListenerStopped() {
+ return triggerListener.isStopped();
+ }
+
+ public void startListener() throws IOException {
+ if (!isConnected()) {
+ throw new IOException("Not connected to WALT");
+ }
+ triggerListenerThread = new Thread(triggerListener);
+ logger.log("Starting Listener");
+ triggerListener.state = Utils.ListenerState.STARTING;
+ triggerListenerThread.start();
+ }
+
+ public void stopListener() {
+ // If the trigger listener is already stopped, then it is possible the listener thread is
+ // null. In that case, calling stop() followed by join() will result in a listener object
+ // that is stuck in the STOPPING state.
+ if (triggerListener.isStopped()) {
+ return;
+ }
+ logger.log("Stopping Listener");
+ triggerListener.stop();
+ try {
+ triggerListenerThread.join();
+ } catch (Exception e) {
+ logger.log("Error while stopping Listener: " + e.getMessage());
+ }
+ logger.log("Listener stopped");
+ }
+
+ public void setConnectionStateListener(WaltConnection.ConnectionStateListener connectionStateListener) {
+ this.connectionStateListener = connectionStateListener;
+ if (isConnected()) {
+ this.connectionStateListener.onConnect();
+ }
+ }
+
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java
new file mode 100644
index 0000000..e63f4dc
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+
+
+public class WaltTcpConnection implements WaltConnection {
+
+ // Use a "reverse" port over adb. The server is running on the host to which we're attached.
+ private static final String SERVER_IP = "127.0.0.1";
+ private static final int SERVER_PORT = 50007;
+ private static final int TCP_READ_TIMEOUT_MS = 200;
+
+ private final SimpleLogger logger;
+ private HandlerThread networkThread;
+ private Handler networkHandler;
+ private final Object readLock = new Object();
+ private boolean messageReceived = false;
+ private Utils.ListenerState connectionState = Utils.ListenerState.STOPPED;
+ private int lastRetVal;
+ static final int BUFF_SIZE = 1024 * 4;
+ private byte[] buffer = new byte[BUFF_SIZE];
+
+ private final Handler mainHandler = new Handler();
+ private RemoteClockInfo remoteClock = new RemoteClockInfo();
+
+ private Socket socket;
+ private OutputStream outputStream = null;
+ private InputStream inputStream = null;
+
+ private WaltConnection.ConnectionStateListener connectionStateListener;
+
+ // Singleton stuff
+ private static WaltTcpConnection instance;
+ private static final Object LOCK = new Object();
+
+ public static WaltTcpConnection getInstance(Context context) {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new WaltTcpConnection(context.getApplicationContext());
+ }
+ return instance;
+ }
+ }
+
+ private WaltTcpConnection(Context context) {
+ logger = SimpleLogger.getInstance(context);
+ }
+
+ public void connect() {
+ // If the singleton is already connected, do not kill the connection.
+ if (isConnected()) {
+ return;
+ }
+ connectionState = Utils.ListenerState.STARTING;
+ networkThread = new HandlerThread("NetworkThread");
+ networkThread.start();
+ networkHandler = new Handler(networkThread.getLooper());
+ logger.log("Started network thread for TCP bridge");
+ networkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
+ socket = new Socket(serverAddr, SERVER_PORT);
+ socket.setKeepAlive(true);
+ socket.setSoTimeout(TCP_READ_TIMEOUT_MS);
+ outputStream = socket.getOutputStream();
+ inputStream = socket.getInputStream();
+ logger.log("TCP connection established");
+ connectionState = Utils.ListenerState.RUNNING;
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.log("Can't connect to TCP bridge: " + e.getMessage());
+ connectionState = Utils.ListenerState.STOPPED;
+ return;
+ }
+
+ // Run the onConnect callback, but on main thread.
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ WaltTcpConnection.this.onConnect();
+ }
+ });
+ }
+ });
+
+ }
+
+ public void onConnect() {
+ if (connectionStateListener != null) {
+ connectionStateListener.onConnect();
+ }
+ }
+
+ public synchronized boolean isConnected() {
+ return connectionState == Utils.ListenerState.RUNNING;
+ }
+
+ public void sendByte(final char c) throws IOException {
+ // All network accesses must occur on a separate thread.
+ networkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ outputStream.write(Utils.char2byte(c));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public void sendString(final String s) throws IOException {
+ // All network accesses must occur on a separate thread.
+ networkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ outputStream.write(s.getBytes("UTF-8"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public synchronized int blockingRead(byte[] buff) {
+
+ messageReceived = false;
+
+ // All network accesses must occur on a separate thread.
+ networkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ lastRetVal = -1;
+ try {
+ synchronized (readLock) {
+ lastRetVal = inputStream.read(buffer);
+ messageReceived = true;
+ readLock.notifyAll();
+ }
+ } catch (SocketTimeoutException e) {
+ messageReceived = true;
+ lastRetVal = -2;
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ messageReceived = true;
+ lastRetVal = -1;
+ // TODO: better messaging / error handling here
+ }
+ }
+ });
+
+ // TODO: make sure length is ok
+ // This blocks on readLock which is taken by the blocking read operation
+ try {
+ synchronized (readLock) {
+ while (!messageReceived) readLock.wait(TCP_READ_TIMEOUT_MS);
+ }
+ } catch (InterruptedException e) {
+ return -1;
+ }
+
+ if (lastRetVal > 0) {
+ System.arraycopy(buffer, 0, buff, 0, lastRetVal);
+ }
+
+ return lastRetVal;
+ }
+
+ private synchronized void updateClock(String cmd) throws IOException {
+ sendString(cmd);
+ int retval = blockingRead(buffer);
+ if (retval <= 0) {
+ throw new IOException("WaltTcpConnection, can't sync clocks");
+ }
+ String s = new String(buffer, 0, retval);
+ String[] parts = s.trim().split("\\s+");
+ // TODO: make sure reply starts with "clock"
+ // The bridge sends the time difference between when it sent the reply and when it zeroed
+ // the WALT's clock. We assume here that the reply transit time is negligible.
+ remoteClock.baseTime = RemoteClockInfo.microTime() - Long.parseLong(parts[1]);
+ remoteClock.minLag = Integer.parseInt(parts[2]);
+ remoteClock.maxLag = Integer.parseInt(parts[3]);
+ }
+
+ public RemoteClockInfo syncClock() throws IOException {
+ updateClock("bridge sync");
+ logger.log("Synced clocks via TCP bridge:\n" + remoteClock);
+ return remoteClock;
+ }
+
+ public void updateLag() {
+ try {
+ updateClock("bridge update");
+ } catch (IOException e) {
+ logger.log("Failed to update clock lag: " + e.getMessage());
+ }
+ }
+
+ public void setConnectionStateListener(ConnectionStateListener connectionStateListener) {
+ this.connectionStateListener = connectionStateListener;
+ }
+
+ // A way to test if there is a TCP bridge to decide whether to use it.
+ // Some thread dancing to get around the Android strict policy for no network on main thread.
+ public static boolean probe() {
+ ProbeThread probeThread = new ProbeThread();
+ probeThread.start();
+ try {
+ probeThread.join();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return probeThread.isReachable;
+ }
+
+ private static class ProbeThread extends Thread {
+ public boolean isReachable = false;
+ private final String TAG = "ProbeThread";
+
+ @Override
+ public void run() {
+ Socket socket = new Socket();
+ try {
+ InetSocketAddress remoteAddr = new InetSocketAddress(SERVER_IP, SERVER_PORT);
+ socket.connect(remoteAddr, 50 /* timeout in milliseconds */);
+ isReachable = true;
+ socket.close();
+ } catch (Exception e) {
+ Log.i(TAG, "Probing TCP connection failed: " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltUsbConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltUsbConnection.java
new file mode 100644
index 0000000..f118ae2
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltUsbConnection.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * A singleton used as an interface for the physical WALT device.
+ */
+public class WaltUsbConnection extends BaseUsbConnection implements WaltConnection {
+
+ private static final int TEENSY_VID = 0x16c0;
+ // TODO: refactor to demystify PID. See BaseUsbConnection.isCompatibleUsbDevice()
+ private static final int TEENSY_PID = 0;
+ private static final int HALFKAY_PID = 0x0478;
+ private static final int USB_READ_TIMEOUT_MS = 200;
+ private static final String TAG = "WaltUsbConnection";
+
+ private UsbEndpoint endpointIn = null;
+ private UsbEndpoint endpointOut = null;
+
+ private RemoteClockInfo remoteClock = new RemoteClockInfo();
+
+ private static final Object LOCK = new Object();
+
+ private static WaltUsbConnection instance;
+
+ private WaltUsbConnection(Context context) {
+ super(context);
+ }
+
+ public static WaltUsbConnection getInstance(Context context) {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new WaltUsbConnection(context.getApplicationContext());
+ }
+ return instance;
+ }
+ }
+
+ @Override
+ public int getPid() {
+ return TEENSY_PID;
+ }
+
+ @Override
+ public int getVid() {
+ return TEENSY_VID;
+ }
+
+ @Override
+ protected boolean isCompatibleUsbDevice(UsbDevice usbDevice) {
+ // Allow any Teensy, but not in HalfKay bootloader mode
+ // Teensy PID depends on mode (e.g: Serail + MIDI) and also changed in TeensyDuino 1.31
+ return ((usbDevice.getProductId() != HALFKAY_PID) &&
+ (usbDevice.getVendorId() == TEENSY_VID));
+ }
+
+
+ // Called when WALT is physically unplugged from USB
+ @Override
+ public void onDisconnect() {
+ endpointIn = null;
+ endpointOut = null;
+ super.onDisconnect();
+ }
+
+
+ // Called when WALT is physically plugged into USB
+ @Override
+ public void onConnect() {
+ // Serial mode only
+ // TODO: find the interface and endpoint indexes no matter what mode it is
+ int ifIdx = 1;
+ int epInIdx = 1;
+ int epOutIdx = 0;
+
+ UsbInterface iface = usbDevice.getInterface(ifIdx);
+
+ if (usbConnection.claimInterface(iface, true)) {
+ logger.log("Interface claimed successfully\n");
+ } else {
+ logger.log("ERROR - can't claim interface\n");
+ return;
+ }
+
+ endpointIn = iface.getEndpoint(epInIdx);
+ endpointOut = iface.getEndpoint(epOutIdx);
+
+ super.onConnect();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return super.isConnected() && (endpointIn != null) && (endpointOut != null);
+ }
+
+
+ @Override
+ public void sendByte(char c) throws IOException {
+ if (!isConnected()) {
+ throw new IOException("Not connected to WALT");
+ }
+ // logger.log("Sending char " + c);
+ usbConnection.bulkTransfer(endpointOut, Utils.char2byte(c), 1, 100);
+ }
+
+ @Override
+ public int blockingRead(byte[] buffer) {
+ return usbConnection.bulkTransfer(endpointIn, buffer, buffer.length, USB_READ_TIMEOUT_MS);
+ }
+
+
+ @Override
+ public RemoteClockInfo syncClock() throws IOException {
+ if (!isConnected()) {
+ throw new IOException("Not connected to WALT");
+ }
+
+ try {
+ int fd = usbConnection.getFileDescriptor();
+ int ep_out = endpointOut.getAddress();
+ int ep_in = endpointIn.getAddress();
+
+ remoteClock.baseTime = syncClock(fd, ep_out, ep_in);
+ remoteClock.minLag = 0;
+ remoteClock.maxLag = getMaxE();
+ } catch (Exception e) {
+ logger.log("Exception while syncing clocks: " + e.getStackTrace());
+ }
+ logger.log("Synced clocks, maxE=" + remoteClock.maxLag + "us");
+ Log.i(TAG, remoteClock.toString());
+ return remoteClock;
+ }
+
+ @Override
+ public void updateLag() {
+ if (! isConnected()) {
+ logger.log("ERROR: Not connected, aborting checkDrift()");
+ return;
+ }
+ updateBounds();
+ remoteClock.minLag = getMinE();
+ remoteClock.maxLag = getMaxE();
+ }
+
+
+
+ // NDK / JNI stuff
+ // TODO: add guards to avoid calls to updateBounds and getter when listener is running.
+ private native long syncClock(int fd, int endpoint_out, int endpoint_in);
+
+ private native void updateBounds();
+
+ private native int getMinE();
+
+ private native int getMaxE();
+
+ static {
+ System.loadLibrary("sync_clock_jni");
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/BootloaderConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/BootloaderConnection.java
new file mode 100644
index 0000000..0f2e802
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/BootloaderConnection.java
@@ -0,0 +1,80 @@
+package org.chromium.latency.walt.programmer;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+
+import org.chromium.latency.walt.BaseUsbConnection;
+
+class BootloaderConnection extends BaseUsbConnection {
+ private static final int HALFKAY_VID = 0x16C0;
+ private static final int HALFKAY_PID = 0x0478;
+
+ private static final Object LOCK = new Object();
+ private static BootloaderConnection instance;
+
+ public static BootloaderConnection getInstance(Context context) {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new BootloaderConnection(context.getApplicationContext());
+ }
+ return instance;
+ }
+ }
+
+ @Override
+ public int getPid() {
+ return HALFKAY_PID;
+ }
+
+ @Override
+ public int getVid() {
+ return HALFKAY_VID;
+ }
+
+ @Override
+ protected boolean isCompatibleUsbDevice(UsbDevice usbDevice) {
+ return ((usbDevice.getProductId() == HALFKAY_PID) &&
+ (usbDevice.getVendorId() == HALFKAY_VID));
+ }
+
+ @Override
+ public void onConnect() {
+ int ifIdx = 0;
+
+ UsbInterface iface = usbDevice.getInterface(ifIdx);
+
+ if (usbConnection.claimInterface(iface, true)) {
+ logger.log("Interface claimed successfully\n");
+ } else {
+ logger.log("ERROR - can't claim interface\n");
+ }
+
+ super.onConnect();
+ }
+
+ public void write(byte[] buf, int timeout) {
+ write(buf, 0, buf.length, timeout);
+ }
+
+ public void write(byte[] buf, int index, int len, int timeout) {
+ if (!isConnected()) return;
+
+ while (timeout > 0) {
+ // USB HID Set_Report message
+ int result = usbConnection.controlTransfer(0x21, 9, 0x0200, index, buf, len, timeout);
+
+ if (result >= 0) break;
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ timeout -= 10;
+ }
+ }
+
+ private BootloaderConnection(Context context) {
+ super(context);
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/DeviceConstants.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/DeviceConstants.java
new file mode 100644
index 0000000..b1618c7
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/DeviceConstants.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt.programmer;
+
+class DeviceConstants {
+ static final int FIRMWARE_SIZE = 0x10000; // 64k
+ static final int BLOCK_SIZE = 512; // how many bytes to send at once
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/FirmwareImage.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/FirmwareImage.java
new file mode 100644
index 0000000..d2feb01
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/FirmwareImage.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt.programmer;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.util.Arrays;
+
+class FirmwareImage {
+ private static final String TAG = "FirmwareImage";
+
+ private boolean atEOF = false;
+ private byte[] image = new byte[DeviceConstants.FIRMWARE_SIZE];
+ private boolean[] mask = new boolean[DeviceConstants.FIRMWARE_SIZE];
+
+ boolean shouldWrite(int addr, int len) {
+ if (addr < 0 || addr + len > DeviceConstants.FIRMWARE_SIZE) return false;
+ for (int i = 0; i < len; i++) {
+ if (mask[addr + i]) return true;
+ }
+ return false;
+ }
+
+ void getData(byte[] dest, int index, int addr, int count) {
+ System.arraycopy(image, addr, dest, index, count);
+ }
+
+ void parseHex(InputStream stream) throws ParseException {
+ Arrays.fill(image, (byte) 0xFF);
+ Arrays.fill(mask, false);
+ BufferedReader in = new BufferedReader(new InputStreamReader(stream));
+ try {
+ String line;
+ while ((line = in.readLine()) != null) {
+ parseLine(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Reading input file: " + e);
+ }
+
+ if (!atEOF) throw new ParseException("No EOF marker", -1);
+ Log.d(TAG, "Done parsing file");
+ }
+
+ private void parseLine(String line) throws ParseException {
+ if (atEOF) throw new ParseException("Line after EOF marker", -1);
+ int cur = 0;
+ final int length = line.length();
+ if (length < 1 || line.charAt(cur) != ':') {
+ throw new ParseException("Expected ':', got '" + line.charAt(cur), cur);
+ }
+ cur++;
+
+ int count = parseByte(line, cur);
+ cur += 2;
+ int addr = parseInt(line, cur);
+ cur += 4;
+ byte code = parseByte(line, cur);
+ cur += 2;
+
+ switch (code) {
+ case 0x00: {
+ parseData(line, cur, count, image, mask, addr);
+ // TODO: verify checksum
+ break;
+ }
+ case 0x01: {
+ Log.d(TAG, "Got EOF marker");
+ atEOF = true;
+ return;
+ }
+ default: {
+ throw new ParseException(String.format("Unknown code '%x'", code), cur);
+ }
+ }
+ }
+
+ private static byte parseByte(String line, int pos) throws ParseException {
+ if (line.length() < pos + 2) throw new ParseException("Unexpected EOL", pos);
+ try {
+ return (byte) Integer.parseInt(line.substring(pos, pos + 2), 16);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Malformed file: " + e.getMessage(), pos);
+ }
+ }
+
+ private static int parseInt(String line, int pos) throws ParseException {
+ if (line.length() < pos + 4) throw new ParseException("Unexpected EOL", pos);
+ try {
+ return Integer.parseInt(line.substring(pos, pos + 4), 16);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Malformed file: " + e.getMessage(), pos);
+ }
+ }
+
+ private static void parseData(String line, int pos, int count,
+ byte[] dest, boolean[] mask, int addr) throws ParseException {
+ for (int i = 0; i < count; i++) {
+ try {
+ dest[addr + i] = parseByte(line, pos + i * 2);
+ mask[addr + i] = true;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new ParseException(String.format("Address '%x' out of range", addr + i),
+ pos + i * 2);
+ }
+ }
+ }
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/Programmer.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/Programmer.java
new file mode 100644
index 0000000..5deef42
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/programmer/Programmer.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt.programmer;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import org.chromium.latency.walt.R;
+import org.chromium.latency.walt.SimpleLogger;
+import org.chromium.latency.walt.WaltConnection;
+
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.Arrays;
+
+public class Programmer {
+ private static final String TAG = "Programmer";
+ private SimpleLogger logger;
+
+ private FirmwareImage image;
+ private BootloaderConnection conn;
+
+ private Context context;
+ private Handler handler = new Handler();
+
+ public Programmer(Context context) {
+ this.context = context;
+ }
+
+ public void program() {
+ logger = SimpleLogger.getInstance(context);
+ InputStream in = context.getResources().openRawResource(R.raw.walt);
+ image = new FirmwareImage();
+ try {
+ image.parseHex(in);
+ } catch (ParseException e) {
+ Log.e(TAG, "Parsing input file: ", e);
+ }
+
+ conn = BootloaderConnection.getInstance(context);
+ // TODO: automatically reboot into the bootloader
+ logger.log("\nRemember to press the button on the Teensy first\n");
+ conn.setConnectionStateListener(new WaltConnection.ConnectionStateListener() {
+ @Override
+ public void onConnect() {
+ handler.post(programRunnable);
+ }
+
+ @Override
+ public void onDisconnect() {}
+ });
+ if (!conn.isConnected()) {
+ conn.connect();
+ }
+ }
+
+ private Runnable programRunnable = new Runnable() {
+ @Override
+ public void run() {
+ logger.log("Programming...");
+
+ // The logic for this is ported from
+ // https://github.com/PaulStoffregen/teensy_loader_cli
+ byte[] buf = new byte[DeviceConstants.BLOCK_SIZE + 64];
+ for (int addr = 0; addr < DeviceConstants.FIRMWARE_SIZE;
+ addr += DeviceConstants.BLOCK_SIZE) {
+ if (!image.shouldWrite(addr, DeviceConstants.BLOCK_SIZE) && addr != 0)
+ continue; // don't need to flash this block
+
+ buf[0] = (byte) (addr & 255);
+ buf[1] = (byte) ((addr >>> 8) & 255);
+ buf[2] = (byte) ((addr >>> 16) & 255);
+ Arrays.fill(buf, 3, 64, (byte) 0);
+ image.getData(buf, 64, addr, DeviceConstants.BLOCK_SIZE);
+
+ conn.write(buf, (addr == 0) ? 3000 : 250);
+ }
+
+ logger.log("Programming complete. Rebooting.");
+
+ // reboot the device
+ buf[0] = (byte) 0xFF;
+ buf[1] = (byte) 0xFF;
+ buf[2] = (byte) 0xFF;
+ Arrays.fill(buf, 3, DeviceConstants.BLOCK_SIZE + 64, (byte) 0);
+ conn.write(buf, 250);
+ }
+ };
+}
diff --git a/android/WALT/app/src/main/jni/Android.mk b/android/WALT/app/src/main/jni/Android.mk
new file mode 100644
index 0000000..3307036
--- /dev/null
+++ b/android/WALT/app/src/main/jni/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := sync_clock_jni
+LOCAL_SRC_FILES := sync_clock_jni.c sync_clock.c player.c
+
+LOCAL_CFLAGS := -g -DUSE_LIBLOG -Werror
+
+# needed for logcat
+LOCAL_SHARED_LIBRARIES := libcutils
+
+LOCAL_LDLIBS := -lOpenSLES -llog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/android/WALT/app/src/main/jni/Application.mk b/android/WALT/app/src/main/jni/Application.mk
new file mode 100644
index 0000000..7c01d06
--- /dev/null
+++ b/android/WALT/app/src/main/jni/Application.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+APP_ABI := all
+APP_PLATFORM := android-9
diff --git a/android/WALT/app/src/main/jni/Makefile b/android/WALT/app/src/main/jni/Makefile
new file mode 100644
index 0000000..4c54f56
--- /dev/null
+++ b/android/WALT/app/src/main/jni/Makefile
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+all:
+ gcc -o ut sync_clock_linux.c sync_clock.c
\ No newline at end of file
diff --git a/android/WALT/app/src/main/jni/README.md b/android/WALT/app/src/main/jni/README.md
new file mode 100644
index 0000000..a1316db
--- /dev/null
+++ b/android/WALT/app/src/main/jni/README.md
@@ -0,0 +1,112 @@
+# Clock Synchronization
+
+How it works
+
+## Step 1 - rough sync
+
+ T0 = current_time()
+ Tell the remote to zero clock.
+ Wait for confirmation from remote
+ maxE = current_time() - T0
+ All further local time is measured from T0
+
+
+After this step we are sure that the remote clock lags behind the local clock by
+some value E. And we know that E >= 0 because remote was zeroed *after* we
+zeroed the local time (recored T0). And also E<= maxE. So 0 = minE < E < maxE.
+
+
+## Step 2 - find better lower bound - `minE`
+
+Send some messages from local to remote, note the time right before sending the
+message (denote it as `t_local`) and have the remote reply with his timestamps
+of when it received the messages according to his clock that lags by unknown
+positive value `E` behind the local clock, denote it by `t_remote`.
+
+
+ t_remote = t_local - E + travel_time
+ E = t_local - t_remote + travel_time > t_local - t_remote
+ since travel_time > 0
+ E > t_local - t_remote
+
+ set minE to max(minE, t_local - t_remote)
+ Repeat
+
+We need to first send a bunch of messages with some random small delays, and
+only after that get the remote timestamps for all of them. This helps deal with
+unwanted buffering and delay added by the kernel of hardware in the outgoing
+direction.
+
+## Step 3 - find better upper bound `maxE`
+
+Same idea, but in the opposite direction. Remote device sends us messages and
+then the timestamps according to his clock of when they were sent. We record the
+local timestamps when we receive them.
+
+ t_local = t_remote + E + travel_time
+ E = t_local - t_remote - travel time < t_local - t_remote
+ set maxE = min(maxE, t_local - t_remote)
+ Repeat
+
+## Comparison with NTP
+
+NTP measures the mean travel_time (latency) and assumes it to be symmetric - the
+same in both directions. If the symmetry assumption is broken, there is no way
+to detect this. Both, systematic asymmetry in latency and clock difference would
+result in exactly the same observations -
+[explanation here](http://cs.stackexchange.com/questions/103/clock-synchronization-in-a-network-with-asymmetric-delays).
+
+In our case the latency can be relatively small compared to network, but is
+likely to be asymmetric due to the asymmetric nature of USB. The algorithm
+described above guarantees that the clock difference is within the measured
+bounds `minE < E < maxE`, though the resulting interval `deltaE = maxE - minE`
+can be fairly large compared to synchronization accuracy of NTP on a network
+with good latency symmetry.
+
+Observed values for `deltaE`
+ - Linux desktop machine (HP Z420), USB2 port: ~100us
+ - Same Linux machine, USB3 port: ~800us
+ - Nexus 5 ~100us
+ - Nexus 7 (both the old and the newer model) ~300us
+ - Samsung Galaxy S3 ~150us
+
+
+
+## Implementation notes
+
+General
+ - All times in this C code are recored in microseconds, unless otherwise
+ specified.
+ - The timestamped messages are all single byte.
+
+USB communication
+ - USB hierarchy recap: USB device has several interfaces. Each interface has
+ several endpoints. Endpoints are directional IN = data going into the host,
+ OUT = data going out of the host. To get data from the device via an IN
+ endpoint, we must query it.
+ - There are two types of endpoints - BULK and INTERRUPT. For our case it's not
+ really important. Currently all the comms are done via a BULK interface
+ exposed when you compile Teensy code in "Serial".
+ - All the comms are done using the Linux API declared in linux/usbdevice_fs.h
+ - The C code can be compiled for both Android JNI and Linux.
+ - The C code is partially based on the code of libusbhost from the Android OS
+ core code, but does not use that library because it's an overkill for our
+ needs.
+
+## There are two ways of communicating via usbdevice_fs
+
+ // Async way
+ ioctl(fd, USBDEVFS_SUBMITURB, urb);
+ // followed by
+ ioctl(fd, USBDEVFS_REAPURB, &urb); // Blocks until there is a URB to read.
+
+ // Sync way
+ struct usbdevfs_bulktransfer ctrl;
+ ctrl.ep = endpoint;
+ ctrl.len = length;
+ ctrl.data = buffer;
+ ctrl.timeout = timeout; // [milliseconds] Will timeout if there is nothing to read
+ int ret = ioctl(fd, USBDEVFS_BULK, &ctrl);
+
+
+
diff --git a/android/WALT/app/src/main/jni/findteensy.py b/android/WALT/app/src/main/jni/findteensy.py
new file mode 100755
index 0000000..820bc14
--- /dev/null
+++ b/android/WALT/app/src/main/jni/findteensy.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Find and print the device path for TeensyUSB
+
+Usage:
+ sudo ./ut `./findteensy.py`
+"""
+
+import subprocess
+line = subprocess.check_output("lsusb | grep eensy", shell=True)
+parts = line.split()
+bus = parts[1]
+dev = parts[3].strip(':')
+print "/dev/bus/usb/%s/%s" % (bus, dev)
\ No newline at end of file
diff --git a/android/WALT/app/src/main/jni/player.c b/android/WALT/app/src/main/jni/player.c
new file mode 100644
index 0000000..f532dbe
--- /dev/null
+++ b/android/WALT/app/src/main/jni/player.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <assert.h>
+#include <jni.h>
+#include <malloc.h>
+#include <math.h>
+#include <sys/types.h>
+
+// for native audio
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <SLES/OpenSLES_AndroidConfiguration.h>
+
+#include "sync_clock.h"
+
+// logging
+#define APPNAME "WALT"
+
+// engine interfaces
+static SLObjectItf engineObject = NULL;
+static SLEngineItf engineEngine = NULL;
+
+// output mix interfaces
+static SLObjectItf outputMixObject = NULL;
+
+// buffer queue player interfaces
+static SLObjectItf bqPlayerObject = NULL;
+static SLPlayItf bqPlayerPlay = NULL;
+static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
+
+// recorder interfaces
+static SLObjectItf recorderObject = NULL;
+static SLRecordItf recorderRecord;
+static SLAndroidSimpleBufferQueueItf recorderBufferQueue;
+static volatile int bqPlayerRecorderBusy = 0;
+
+static unsigned int recorder_frames;
+static short* recorderBuffer;
+static unsigned recorderSize = 0;
+
+static unsigned int framesPerBuffer;
+
+#define CHANNELS 1 // 1 for mono, 2 for stereo
+
+// Each short represents a 16-bit audio sample
+static short* beepBuffer = NULL;
+static short* silenceBuffer = NULL;
+static unsigned int bufferSizeInBytes = 0;
+
+#define MAXIMUM_AMPLITUDE_VALUE 32767
+
+// how many times to play the wave table (so we can actually hear it)
+#define BUFFERS_TO_PLAY 10
+
+static unsigned buffersRemaining = 0;
+static short warmedUp = 0;
+
+
+// Timestamps
+// te - enqueue time
+// tc - callback time
+int64_t te_play = 0, te_rec = 0, tc_rec = 0;
+
+/**
+ * Create wave tables for audio out.
+ */
+void createWaveTables(){
+ bufferSizeInBytes = framesPerBuffer * sizeof(*beepBuffer);
+ silenceBuffer = malloc(bufferSizeInBytes);
+ beepBuffer = malloc(bufferSizeInBytes);
+
+
+ __android_log_print(ANDROID_LOG_VERBOSE,
+ APPNAME,
+ "Creating wave tables, 1 channel. Frames: %i Buffer size (bytes): %i",
+ framesPerBuffer,
+ bufferSizeInBytes);
+
+ unsigned int i;
+ for (i = 0; i < framesPerBuffer; i++) {
+ silenceBuffer[i] = 0;
+ beepBuffer[i] = (i & 2 - 1) * MAXIMUM_AMPLITUDE_VALUE;
+ // This fills a buffer that looks like [min, min, max, max, min, min...]
+ // which is a square wave at 1/4 frequency of the sampling rate
+ // for 48kHz sampling this is 12kHz pitch, still well audible.
+ }
+}
+
+// this callback handler is called every time a buffer finishes playing
+void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, __attribute__((unused)) void *context)
+{
+ if (bq == NULL) {
+ __android_log_print(ANDROID_LOG_ERROR, APPNAME, "buffer queue is null");
+ }
+ assert(bq == bqPlayerBufferQueue);
+ assert(NULL == context);
+
+ if (buffersRemaining > 0) { // continue playing tone
+ if(buffersRemaining == BUFFERS_TO_PLAY && warmedUp) {
+ // Enqueue the first non-silent buffer, save the timestamp
+ // For cold test Enqueue happens in playTone rather than here.
+ te_play = uptimeMicros();
+ }
+ buffersRemaining--;
+
+ SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, beepBuffer,
+ bufferSizeInBytes);
+ (void)result;
+ assert(SL_RESULT_SUCCESS == result);
+ } else if (warmedUp) { // stop tone but keep playing silence
+ SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, silenceBuffer,
+ bufferSizeInBytes);
+ assert(SL_RESULT_SUCCESS == result);
+ (void) result;
+ } else { // stop playing completely
+ SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Done playing tone");
+ }
+}
+
+jlong Java_org_chromium_latency_walt_AudioTest_playTone(__attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jclass clazz){
+
+ int64_t t_start = uptimeMicros();
+ te_play = 0;
+
+ SLresult result;
+
+ if (!warmedUp) {
+ result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // Enqueue first buffer
+ te_play = uptimeMicros();
+ result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, beepBuffer,
+ bufferSizeInBytes);
+ assert(SL_RESULT_SUCCESS == result);
+ (void) result;
+
+ result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
+ assert(SL_RESULT_SUCCESS == result);
+ (void) result;
+
+ int dt_state = uptimeMicros() - t_start;
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "playTone() changed state to playing dt=%d us", dt_state);
+ // TODO: this block takes lots of time (~13ms on Nexus 7) research this and decide how to measure.
+ }
+
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Playing tone");
+ buffersRemaining = BUFFERS_TO_PLAY;
+
+ return (jlong) t_start;
+}
+
+
+// create the engine and output mix objects
+void Java_org_chromium_latency_walt_AudioTest_createEngine(__attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jclass clazz)
+{
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio engine");
+
+ SLresult result;
+
+ // create engine
+ result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // realize the engine
+ result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // get the engine interface, which is needed in order to create other objects
+ result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // create output mix,
+ result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // realize the output mix
+ result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+}
+
+void Java_org_chromium_latency_walt_AudioTest_destroyEngine(__attribute__((unused)) JNIEnv *env,
+ __attribute__((unused)) jclass clazz)
+{
+ if (bqPlayerObject != NULL) {
+ (*bqPlayerObject)->Destroy(bqPlayerObject);
+ bqPlayerObject = NULL;
+ }
+
+ if (outputMixObject != NULL) {
+ (*outputMixObject)->Destroy(outputMixObject);
+ outputMixObject = NULL;
+ }
+
+ if (engineObject != NULL) {
+ (*engineObject)->Destroy(engineObject);
+ engineObject = NULL;
+ }
+}
+
+// create buffer queue audio player
+void Java_org_chromium_latency_walt_AudioTest_createBufferQueueAudioPlayer(
+ __attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jclass clazz,
+ jint optimalFrameRate,
+ jint optimalFramesPerBuffer)
+{
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio player with frame rate %d and frames per buffer %d",
+ optimalFrameRate, optimalFramesPerBuffer);
+
+ framesPerBuffer = optimalFramesPerBuffer;
+ createWaveTables();
+
+ SLresult result;
+
+ // configure the audio source (supply data through a buffer queue in PCM format)
+ SLDataLocator_AndroidSimpleBufferQueue locator_bufferqueue_source;
+ SLDataFormat_PCM format_pcm;
+ SLDataSource audio_source;
+
+ // source location
+ locator_bufferqueue_source.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ locator_bufferqueue_source.numBuffers = 1;
+
+ // source format
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = 1;
+
+ // Note: this shouldn't be called samplesPerSec it should be called *framesPerSec*
+ // because when channels = 2 then there are 2 samples per frame.
+ format_pcm.samplesPerSec = (SLuint32) optimalFrameRate * 1000;
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.containerSize = 16;
+ format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
+ format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+
+ audio_source.pLocator = &locator_bufferqueue_source;
+ audio_source.pFormat = &format_pcm;
+
+ // configure the output: An output mix sink
+ SLDataLocator_OutputMix locator_output_mix;
+ SLDataSink audio_sink;
+
+ locator_output_mix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ locator_output_mix.outputMix = outputMixObject;
+
+ audio_sink.pLocator = &locator_output_mix;
+ audio_sink.pFormat = NULL;
+
+ // create audio player
+ // Note: Adding other output interfaces here will result in your audio being routed using the
+ // normal path NOT the fast path
+ const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
+ const SLboolean interfaces_required[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
+
+ result = (*engineEngine)->CreateAudioPlayer(
+ engineEngine,
+ &bqPlayerObject,
+ &audio_source,
+ &audio_sink,
+ 2, // Number of interfaces
+ interface_ids,
+ interfaces_required
+ );
+
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // realize the player
+ result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // get the play interface
+ result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // get the buffer queue interface
+ result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
+ &bqPlayerBufferQueue);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // register callback on the buffer queue
+ result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+}
+
+void Java_org_chromium_latency_walt_AudioTest_startWarmTest(__attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jclass clazz) {
+ SLresult result;
+
+ result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // enqueue some silence
+ result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, silenceBuffer, bufferSizeInBytes);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // set the player's state to playing
+ result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ warmedUp = 1;
+}
+
+void Java_org_chromium_latency_walt_AudioTest_stopTests(__attribute__((unused)) JNIEnv *env,
+ __attribute__((unused)) jclass clazz) {
+ SLresult result;
+
+ result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ warmedUp = 0;
+}
+
+// this callback handler is called every time a buffer finishes recording
+void bqRecorderCallback(__attribute__((unused)) SLAndroidSimpleBufferQueueItf bq,
+ __attribute__((unused)) void *context)
+{
+ tc_rec = uptimeMicros();
+ assert(bq == recorderBufferQueue);
+ assert(NULL == context);
+
+ // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill
+ // but instead, this is a one-time buffer so we stop recording
+ SLresult result;
+ result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
+ if (SL_RESULT_SUCCESS == result) {
+ recorderSize = recorder_frames * sizeof(short);
+ }
+ bqPlayerRecorderBusy = 0;
+
+ //// TODO: Use small buffers and re-enqueue each time
+ // result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer,
+ // recorder_frames * sizeof(short));
+ // assert(SL_RESULT_SUCCESS == result);
+}
+
+// create audio recorder
+jboolean Java_org_chromium_latency_walt_AudioTest_createAudioRecorder(
+ __attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jclass clazz,
+ jint optimalFrameRate,
+ jint framesToRecord)
+{
+ SLresult result;
+
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio recorder with frame rate %d and frames to record %d",
+ optimalFrameRate, framesToRecord);
+ // Allocate buffer
+ recorder_frames = framesToRecord;
+ recorderBuffer = malloc(sizeof(*recorderBuffer) * recorder_frames);
+
+ // configure audio source
+ SLDataLocator_IODevice loc_dev = {
+ SL_DATALOCATOR_IODEVICE,
+ SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT,
+ NULL
+ };
+ SLDataSource audioSrc = {&loc_dev, NULL};
+
+ // configure audio sink
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq;
+ loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ loc_bq.numBuffers = 2;
+
+
+ // source format
+ SLDataFormat_PCM format_pcm;
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = CHANNELS;
+ // Note: this shouldn't be called samplesPerSec it should be called *framesPerSec*
+ // because when channels = 2 then there are 2 samples per frame.
+ format_pcm.samplesPerSec = (SLuint32) optimalFrameRate * 1000;
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.containerSize = 16;
+ format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
+ format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+
+
+ SLDataSink audioSnk = {&loc_bq, &format_pcm};
+
+ // create audio recorder
+ // (requires the RECORD_AUDIO permission)
+ const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ SL_IID_ANDROIDCONFIGURATION };
+ const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+ result = (*engineEngine)->CreateAudioRecorder(engineEngine,
+ &recorderObject,
+ &audioSrc,
+ &audioSnk,
+ sizeof(id)/sizeof(id[0]),
+ id, req);
+
+ // Configure the voice recognition preset which has no
+ // signal processing for lower latency.
+ SLAndroidConfigurationItf inputConfig;
+ result = (*recorderObject)->GetInterface(recorderObject,
+ SL_IID_ANDROIDCONFIGURATION,
+ &inputConfig);
+ if (SL_RESULT_SUCCESS == result) {
+ SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
+ (*inputConfig)->SetConfiguration(inputConfig,
+ SL_ANDROID_KEY_RECORDING_PRESET,
+ &presetValue,
+ sizeof(SLuint32));
+ }
+
+ // realize the audio recorder
+ result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) {
+ return JNI_FALSE;
+ }
+
+ // get the record interface
+ result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // get the buffer queue interface
+ result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &recorderBufferQueue);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // register callback on the buffer queue
+ result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback,
+ NULL);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Audio recorder created, buffer size: %d frames",
+ recorder_frames);
+
+ return JNI_TRUE;
+}
+
+
+// set the recording state for the audio recorder
+void Java_org_chromium_latency_walt_AudioTest_startRecording(__attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jclass clazz)
+{
+ SLresult result;
+
+ if( bqPlayerRecorderBusy) {
+ return;
+ }
+ // in case already recording, stop recording and clear buffer queue
+ result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+ result = (*recorderBufferQueue)->Clear(recorderBufferQueue);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // the buffer is not valid for playback yet
+ recorderSize = 0;
+
+ // enqueue an empty buffer to be filled by the recorder
+ // (for streaming recording, we would enqueue at least 2 empty buffers to start things off)
+ te_rec = uptimeMicros(); // TODO: investigate if it's better to time after SetRecordState
+ tc_rec = 0;
+ result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer,
+ recorder_frames * sizeof(short));
+ // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
+ // which for this code example would indicate a programming error
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+
+ // start recording
+ result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
+ assert(SL_RESULT_SUCCESS == result);
+ (void)result;
+ bqPlayerRecorderBusy = 1;
+}
+
+jshortArray Java_org_chromium_latency_walt_AudioTest_getRecordedWave(
+ JNIEnv *env,
+ __attribute__((unused)) jclass cls)
+{
+ jshortArray result;
+ result = (*env)->NewShortArray(env, recorder_frames);
+ if (result == NULL) {
+ return NULL; /* out of memory error thrown */
+ }
+ (*env)->SetShortArrayRegion(env, result, 0, recorder_frames, recorderBuffer);
+ return result;
+}
+
+jlong Java_org_chromium_latency_walt_AudioTest_getTcRec(__attribute__((unused)) JNIEnv *env,
+ __attribute__((unused)) jclass cls) {
+ return (jlong) tc_rec;
+}
+
+jlong Java_org_chromium_latency_walt_AudioTest_getTeRec(__attribute__((unused)) JNIEnv *env,
+ __attribute__((unused)) jclass cls) {
+ return (jlong) te_rec;
+}
+
+jlong Java_org_chromium_latency_walt_AudioTest_getTePlay(__attribute__((unused)) JNIEnv *env,
+ __attribute__((unused)) jclass cls) {
+ return (jlong) te_play;
+}
diff --git a/android/WALT/app/src/main/jni/sync_clock.c b/android/WALT/app/src/main/jni/sync_clock.c
new file mode 100644
index 0000000..e16b759
--- /dev/null
+++ b/android/WALT/app/src/main/jni/sync_clock.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sync_clock.h"
+
+#include <asm/byteorder.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/usbdevice_fs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#ifdef __ANDROID__
+ #include <android/log.h>
+ #define LOGD(...) __android_log_print(ANDROID_LOG_VERBOSE, "ClockSyncNative", __VA_ARGS__)
+#else
+ #define LOGD(...) printf(__VA_ARGS__)
+#endif
+
+
+// How many times to repeat the 1..9 digit sequence it's a tradeoff between
+// precision and how long it takes.
+// TODO: investigate better combination of constants for repeats and wait times
+const int kSyncRepeats = 7;
+const int kMillion = 1000000;
+
+
+/**
+uptimeMicros() - returns microseconds elapsed since boot.
+Same time as Android's SystemClock.uptimeMillis() but in microseconds.
+
+Adapted from Android:
+platform/system/core/libutils/Timers.cpp
+platform/system/core/include/utils/Timers.h
+
+See:
+http://developer.android.com/reference/android/os/SystemClock.html
+https://android.googlesource.com/platform/system/core/+/master/libutils/Timers.cpp
+*/
+int64_t uptimeMicros() {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ((int64_t)ts.tv_sec) * kMillion + ts.tv_nsec / 1000;
+}
+
+
+// Sleeps us microseconds
+int microsleep(int us) {
+ struct timespec ts;
+ ts.tv_sec = us / kMillion;
+ us %= kMillion;
+ ts.tv_nsec = us*1000;
+ return nanosleep(&ts, NULL);
+}
+
+
+// *********************** Generic USB functions *******************************
+
+static int send_char_async(int fd, int endpoint, char msg, char * label) {
+ // TODO: Do we really need a buffer longer than 1 char here?
+ char buffer[256] = {0};
+ buffer[0] = msg;
+ int length = 1;
+
+ // TODO: free() the memory used for URBs.
+ // Circular buffer of URBs? Cleanup at the end of clock sync?
+ // Several may be used simultaneously, no signal when done.
+ struct usbdevfs_urb *urb = calloc(1, sizeof(struct usbdevfs_urb));
+ memset(urb, 0, sizeof(struct usbdevfs_urb));
+
+ int res;
+ urb->status = -1;
+ urb->buffer = buffer;
+ urb->buffer_length = length;
+ urb->endpoint = endpoint;
+ urb->type = USBDEVFS_URB_TYPE_BULK;
+ urb->usercontext = label; // This is hackish
+ do {
+ res = ioctl(fd, USBDEVFS_SUBMITURB, urb);
+ } while((res < 0) && (errno == EINTR));
+ return res;
+}
+
+
+// Send or read using USBDEVFS_BULK. Allows to set a timeout.
+static int bulk_talk(int fd, int endpoint, char * buffer, int length) {
+ // Set some reasonable timeout. 20ms is plenty time for most transfers but
+ // short enough to fail quickly if all transfers and retries fail with
+ // timeout.
+ const int kTimeoutMs = 20;
+ struct usbdevfs_bulktransfer ctrl;
+ // TODO: need to limit request size to avoid EINVAL
+
+ ctrl.ep = endpoint;
+ ctrl.len = length;
+ ctrl.data = buffer;
+ ctrl.timeout = kTimeoutMs;
+ int ret = ioctl(fd, USBDEVFS_BULK, &ctrl);
+ return ret;
+}
+
+
+/*******************************************************************************
+* Clock sync specific stuff below.
+* Most data is stored in the clock_connection struct variable.
+*/
+
+// Send a single character to the remote in a blocking mode
+int send_cmd(struct clock_connection *clk, char cmd) {
+ return bulk_talk(clk->fd, clk->endpoint_out, &cmd, 1);
+}
+
+// Schedule a single character to be sent to the remote - async.
+int send_async(struct clock_connection *clk, char cmd) {
+ return send_char_async(clk->fd, clk->endpoint_out, cmd, NULL);
+}
+
+
+int bulk_read(struct clock_connection *clk) {
+ memset(clk->buffer, 0, sizeof(clk->buffer));
+ int ret = bulk_talk(clk->fd, clk->endpoint_in, clk->buffer, sizeof(clk->buffer));
+ return ret;
+}
+
+// microseconds elapsed since clk->t_base
+int micros(struct clock_connection *clk) {
+ return uptimeMicros() - clk->t_base;
+}
+
+// Clear all incoming data that's already waiting somewhere in kernel buffers
+// and discard it.
+void flush_incoming(struct clock_connection *clk) {
+ // When bulk_read times out errno = ETIMEDOUT=110, retval =-1
+ // should we check for this?
+
+ while(bulk_read(clk) >= 0) {
+ // TODO: fail nicely if waiting too long to avoid hangs
+ }
+}
+
+// Ask the remote to send its timestamps
+// for the digits previously sent to it.
+void read_remote_timestamps(struct clock_connection *clk, int * times_remote) {
+ int i;
+ int t_remote;
+ // Go over the digits [1, 2 ... 9]
+ for (i = 0; i < 9; i++) {
+ char digit = i + '1';
+ send_cmd(clk, CMD_SYNC_READOUT);
+ bulk_read(clk);
+ if (clk->buffer[0] != digit) {
+ LOGD("Error, bad reply for R%d: %s", i+1, clk->buffer);
+ }
+ // The reply string looks like digit + space + timestamp
+ // Offset by 2 to ignore the digit and the space
+ t_remote = atoi(clk->buffer + 2);
+ times_remote[i] = t_remote;
+ }
+}
+
+
+// Preliminary rough sync with a single message - CMD_SYNC_ZERO = 'Z'.
+// This is not strictly necessary but greatly simplifies debugging
+// by removing the need to look at very long numbers.
+void zero_remote(struct clock_connection *clk) {
+ flush_incoming(clk);
+ clk->t_base = uptimeMicros();
+ send_cmd(clk, CMD_SYNC_ZERO);
+ bulk_read(clk); // TODO, make sure we got 'z'
+ clk->maxE = micros(clk);
+ clk->minE = 0;
+
+ LOGD("Sent a 'Z', reply '%c' in %d us\n", clk->buffer[0], clk->maxE);
+}
+
+
+
+void improve_minE(struct clock_connection *clk) {
+ int times_local_sent[9] = {0};
+ int times_remote_received[9] = {0};
+
+ // Set sleep time as 1/kSleepTimeDivider of the current bounds interval,
+ // but never less or more than k(Min/Max)SleepUs. All pretty random
+ // numbers that could use some tuning and may behave differently on
+ // different devices.
+ const int kMaxSleepUs = 700;
+ const int kMinSleepUs = 70;
+ const int kSleepTimeDivider = 10;
+ int minE = clk->minE;
+ int sleep_time = (clk->maxE - minE) / kSleepTimeDivider;
+ if(sleep_time > kMaxSleepUs) sleep_time = kMaxSleepUs;
+ if(sleep_time < kMinSleepUs) sleep_time = kMinSleepUs;
+
+ flush_incoming(clk);
+ // Send digits to remote side
+ int i;
+ for (i = 0; i < 9; i++) {
+ char c = i + '1';
+ times_local_sent[i] = micros(clk);
+ send_async(clk, c);
+ microsleep(sleep_time);
+ }
+
+ // Read out receive times from the other side
+ read_remote_timestamps(clk, times_remote_received);
+
+ // Do stats
+ for (i = 0; i < 9; i++) {
+ int tls = times_local_sent[i];
+ int trr = times_remote_received[i];
+
+ int dt;
+
+ // Look at outgoing digits
+ dt = tls - trr;
+ if (tls != 0 && trr != 0 && dt > minE) {
+ minE = dt;
+ }
+
+ }
+
+ clk->minE = minE;
+
+ LOGD("E is between %d and %d us, sleep_time=%d\n", clk->minE, clk->maxE, sleep_time);
+}
+
+void improve_maxE(struct clock_connection *clk) {
+ int times_remote_sent[9] = {0};
+ int times_local_received[9] = {0};
+
+ // Tell the remote to send us digits with delays
+ // TODO: try tuning / configuring the delay time on remote side
+ send_async(clk, CMD_SYNC_SEND);
+
+ // Read and timestamp the incoming digits, they may arrive out of order.
+ // TODO: Try he same with USBDEVFS_REAPURB, it might be faster
+ int i;
+ for (i = 0; i < 9; ++i) {
+ int retval = bulk_read(clk);
+ // TODO: deal with retval = (bytes returned) > 1. shouldn't happen.
+ // Can it happen on some devices?
+ int t_local = micros(clk);
+ int digit = atoi(clk->buffer);
+ if (digit <=0 || digit > 9) {
+ LOGD("Error, bad incoming digit: %s\n", clk->buffer);
+ }
+ times_local_received[digit-1] = t_local;
+ }
+
+ // Flush whatever came after the digits. As of this writing, it's usually
+ // a single linefeed character.
+ flush_incoming(clk);
+ // Read out the remote timestamps of when the digits were sent
+ read_remote_timestamps(clk, times_remote_sent);
+
+ // Do stats
+ int maxE = clk->maxE;
+ for (i = 0; i < 9; i++) {
+ int trs = times_remote_sent[i];
+ int tlr = times_local_received[i];
+ int dt = tlr - trs;
+ if (tlr != 0 && trs != 0 && dt < maxE) {
+ maxE = dt;
+ }
+ }
+
+ clk->maxE = maxE;
+
+ LOGD("E is between %d and %d us\n", clk->minE, clk->maxE);
+}
+
+
+void improve_bounds(struct clock_connection *clk) {
+ improve_minE(clk);
+ improve_maxE(clk);
+}
+
+// get minE and maxE again after some time to check for clock drift
+void update_bounds(struct clock_connection *clk) {
+ // Reset the bounds to some unrealistically large numbers
+ int i;
+ clk->minE = -1e7;
+ clk->maxE = 1e7;
+ // Talk to remote to get bounds on minE and maxE
+ for (i=0; i < kSyncRepeats; i++) {
+ improve_bounds(clk);
+ }
+}
+
+void sync_clocks(struct clock_connection *clk) {
+ // Send CMD_SYNC_ZERO to remote for rough initial sync
+ zero_remote(clk);
+
+ int rep;
+ for (rep=0; rep < kSyncRepeats; rep++) {
+ improve_bounds(clk);
+ }
+
+ // Shift the base time to set minE = 0
+ clk->t_base += clk->minE;
+ clk->maxE -= clk->minE;
+ clk->minE = 0;
+ LOGD("Base time shifted for zero minE\n");
+}
+
+
diff --git a/android/WALT/app/src/main/jni/sync_clock.h b/android/WALT/app/src/main/jni/sync_clock.h
new file mode 100644
index 0000000..862bb7d
--- /dev/null
+++ b/android/WALT/app/src/main/jni/sync_clock.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define CLOCK_BUFFER_LENGTH 512
+
+// Commands, original definitions in TensyUSB side code.
+#define CMD_RESET 'F' // Reset all vars
+#define CMD_SYNC_SEND 'I' // Send some digits for clock sync
+#define CMD_SYNC_READOUT 'R' // Read out sync times
+#define CMD_SYNC_ZERO 'Z' // Initial zero
+
+
+struct clock_connection {
+ int fd;
+ int endpoint_in;
+ int endpoint_out;
+ int64_t t_base;
+ char buffer[CLOCK_BUFFER_LENGTH];
+ int minE;
+ int maxE;
+};
+
+
+// Returns microseconds elapsed since boot
+int64_t uptimeMicros();
+
+// Returns microseconds elapsed since last clock sync
+int micros(struct clock_connection *clk);
+
+// Runs clock synchronization logic
+void sync_clocks(struct clock_connection *clk);
+
+// Run the sync logic without changing clocks, used for estimating clock drift
+void update_bounds(struct clock_connection *clk);
+
diff --git a/android/WALT/app/src/main/jni/sync_clock_jni.c b/android/WALT/app/src/main/jni/sync_clock_jni.c
new file mode 100644
index 0000000..22753fe
--- /dev/null
+++ b/android/WALT/app/src/main/jni/sync_clock_jni.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sync_clock.h"
+
+#include <android/log.h>
+#include <jni.h>
+
+
+#define APPNAME "ClockSyncJNI"
+
+// This is global so that we don't have to pass it aroundbetween Java and here.
+// TODO: come up with some more elegant solution.
+struct clock_connection clk;
+
+jlong
+Java_org_chromium_latency_walt_WaltUsbConnection_syncClock__III(
+ __attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jobject thiz,
+ jint fd,
+ jint endpoint_out,
+ jint endpoint_in
+){
+ clk.fd = (int)fd;
+ clk.endpoint_in = (int)endpoint_in;
+ clk.endpoint_out = (int)endpoint_out;
+ clk.t_base = 0;
+ sync_clocks(&clk);
+ // __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Returned from sync_clocks\n");
+
+ int64_t t_base = clk.t_base;
+ return (jlong) t_base;
+}
+
+void
+Java_org_chromium_latency_walt_WaltUsbConnection_updateBounds() {
+ update_bounds(&clk);
+}
+
+jint
+Java_org_chromium_latency_walt_WaltUsbConnection_getMinE() {
+ return clk.minE;
+}
+
+
+jint
+Java_org_chromium_latency_walt_WaltUsbConnection_getMaxE() {
+ return clk.maxE;
+}
diff --git a/android/WALT/app/src/main/jni/sync_clock_linux.c b/android/WALT/app/src/main/jni/sync_clock_linux.c
new file mode 100644
index 0000000..1287b76
--- /dev/null
+++ b/android/WALT/app/src/main/jni/sync_clock_linux.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sync_clock.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/usbdevice_fs.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+int main(int argc, char ** argv) {
+ if(argc < 2) {
+ printf("Usage %s <device_path>\n"
+ "Try `lsusb | grep eensy` and use /dev/bus/usb/<Bus>/<Device>\n",
+ argv[0]);
+ return 1;
+ }
+
+ printf("Opening %s\n", argv[1]);
+ int fd = open(argv[1], O_RDWR);
+ printf("open() fd=%d, errno=%d, %s\n", fd, errno, strerror(errno));
+
+ // The interface and endpoint numbers are defined by the TeensyUSB. They may
+ // be different depending on the mode (Serial vs HID) the Teensy code is
+ // compiled in. A real app would employ some discovery logic here. To list
+ // the interfaces and endpoints use `lsusb --verbose` or an app like USB
+ // Host Viewer on Android. Look for a "CDC Data" interface (class 0x0a).
+ int interface = 1;
+ int ep_out = 0x03;
+ int ep_in = 0x84;
+
+ int ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface);
+ printf("Interface claimed retval=%d, errno=%d, %s\n", ret, errno, strerror(errno));
+ if (errno == EBUSY) {
+ printf("You may need to run 'sudo rmmod cdc_acm' to release the "
+ "interface claimed by the kernel serial driver.");
+ return 1;
+ }
+
+ struct clock_connection clk;
+ clk.fd = fd;
+ clk.endpoint_in = ep_in;
+ clk.endpoint_out = ep_out;
+
+ sync_clocks(&clk);
+
+ printf("===========================\n"
+ "sync_clocks base_t=%lld, minE=%d, maxE=%d\n",
+ (long long int)clk.t_base, clk.minE, clk.maxE);
+
+ // Check for clock drift. Try sleeping here to let it actually drift away.
+ update_bounds(&clk);
+
+ printf("*** UPDATE ****************\n"
+ "Update_bounds base_t=%lld, minE=%d, maxE=%d\n",
+ (long long int)(clk.t_base), clk.minE, clk.maxE
+ );
+
+
+ close(fd);
+ return 0;
+}
\ No newline at end of file
diff --git a/android/WALT/app/src/main/res/color/button_tint.xml b/android/WALT/app/src/main/res/color/button_tint.xml
new file mode 100644
index 0000000..ec76860
--- /dev/null
+++ b/android/WALT/app/src/main/res/color/button_tint.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="#FFABABAB"/>
+ <item android:state_enabled="true" android:color="#FF000000"/>
+</selector>
diff --git a/android/WALT/app/src/main/res/drawable/border.xml b/android/WALT/app/src/main/res/drawable/border.xml
new file mode 100644
index 0000000..5a318d9
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/border.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="2dp"/>
+ <stroke android:width="1dp" android:color="#000000"/>
+</shape>
diff --git a/android/WALT/app/src/main/res/drawable/ic_brightness_7_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_brightness_7_black_24dp.xml
new file mode 100644
index 0000000..e504de1
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_brightness_7_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zm0,-10c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_brightness_medium_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_brightness_medium_black_24dp.xml
new file mode 100644
index 0000000..2f9cd1e
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_brightness_medium_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18V6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_check_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_check_black_24dp.xml
new file mode 100644
index 0000000..3c728c5
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_check_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml
new file mode 100644
index 0000000..e6bb3ca
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_equalizer_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_equalizer_black_24dp.xml
new file mode 100644
index 0000000..6322102
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_equalizer_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,20h4V4h-4v16zm-6,0h4v-8H4v8zM16,9v11h4V9h-4z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_file_upload_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_file_upload_black_24dp.xml
new file mode 100644
index 0000000..0f6fe19
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_file_upload_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zm-4,2h14v2H5z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_help_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_help_black_24dp.xml
new file mode 100644
index 0000000..a3936eb
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_help_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,17h-2v-2h2v2zm2.07,-7.75l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2H8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_help_outline_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_help_outline_black_24dp.xml
new file mode 100644
index 0000000..3e5f481
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_help_outline_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,18h2v-2h-2v2zm1,-16C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zm0,-14c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_import_export_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_import_export_black_24dp.xml
new file mode 100644
index 0000000..cac4f1f
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_import_export_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,3L5,6.99h3V14h2V6.99h3L9,3zm7,14.01V10h-2v7.01h-3L15,21l4,-3.99h-3z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_input_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_input_black_24dp.xml
new file mode 100644
index 0000000..e418310
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_input_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24.0"
+android:viewportHeight="24.0">
+<path
+ android:fillColor="#FF000000"
+ android:pathData="m 21,3.01 -12,0 c -1.1,0 -2,0.9 -2,2 L 7,9 9,9 9,4.99 l 12,0 0,14.03 -12,0 0,-4.02 -2,0 0,4.01 c 0,1.1 0.9,1.98 2,1.98 l 12,0 c 1.1,0 2,-0.88 2,-1.98 l 0,-14 c 0,-1.11 -0.9,-2 -2,-2 z M 11,16 l 4,-4 -4,-4 0,3 -10,0 0,2 10,0 z"/>
+</vector>
\ No newline at end of file
diff --git a/android/WALT/app/src/main/res/drawable/ic_mic_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_mic_black_24dp.xml
new file mode 100644
index 0000000..15356bb
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_mic_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zm5.3,-3c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_music_note_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_music_note_black_24dp.xml
new file mode 100644
index 0000000..2e5b3cc
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_music_note_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24.0"
+android:viewportHeight="24.0">
+<path
+ android:fillColor="#FF000000"
+ android:pathData="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
+</vector>
\ No newline at end of file
diff --git a/android/WALT/app/src/main/res/drawable/ic_output_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_output_black_24dp.xml
new file mode 100644
index 0000000..c2fd495
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_output_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24.0"
+android:viewportHeight="24.0">
+<path
+ android:fillColor="#FF000000"
+ android:pathData="m1,19.01c0,1.1,0.9,1.98,2,1.98h12c1.1,0,1.509-0.99564,2-1.98v-4.01h-2v4.02h-12v-14.03h12v4.01h2v-3.99c0-1.11-0.9-2-2-2h-12c-1.1,0-2,0.9-2,2m18,10.99l4-4-4-4v3h-10v2h10z"/>
+</vector>
\ No newline at end of file
diff --git a/android/WALT/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
new file mode 100644
index 0000000..bf9b895
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml
new file mode 100644
index 0000000..657397f
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zm0,-5C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_receipt_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_receipt_black_24dp.xml
new file mode 100644
index 0000000..ab17085
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_receipt_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,17H6v-2h12v2zm0,-4H6v-2h12v2zm0,-4H6V7h12v2zM3,22l1.5,-1.5L6,22l1.5,-1.5L9,22l1.5,-1.5L12,22l1.5,-1.5L15,22l1.5,-1.5L18,22l1.5,-1.5L21,22V2l-1.5,1.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2 7.5,3.5 6,2 4.5,3.5 3,2v20z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_refresh_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_refresh_black_24dp.xml
new file mode 100644
index 0000000..8229a9a
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_refresh_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_schedule_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_schedule_black_24dp.xml
new file mode 100644
index 0000000..e020fde
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_schedule_black_24dp.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
+ android:fillAlpha=".9"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_search_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_search_black_24dp.xml
new file mode 100644
index 0000000..3180fac
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_search_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zm-6,0C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_settings_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_settings_black_24dp.xml
new file mode 100644
index 0000000..ace746c
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_settings_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_share_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_share_black_24dp.xml
new file mode 100644
index 0000000..e3fe874
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_share_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_stop_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_stop_black_24dp.xml
new file mode 100644
index 0000000..c428d72
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_stop_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,6h12v12H6z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_swap_horiz_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_swap_horiz_black_24dp.xml
new file mode 100644
index 0000000..d571dc3
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_swap_horiz_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_swap_vert_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_swap_vert_black_24dp.xml
new file mode 100644
index 0000000..1c9a30b
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_swap_vert_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16,17.01V10h-2v7.01h-3L15,21l4,-3.99h-3zM9,3L5,6.99h3V14h2V6.99h3L9,3z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_system_update_alt_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_system_update_alt_black_24dp.xml
new file mode 100644
index 0000000..ec96a71
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_system_update_alt_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12 16.5l4-4h-3v-9h-2v9H8l4 4zm9-13h-6v1.99h6v14.03H3V5.49h6V3.5H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-14c0-1.1-.9-2-2-2z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_timelapse_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_timelapse_black_24dp.xml
new file mode 100644
index 0000000..887aeaf
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_timelapse_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.24,7.76C15.07,6.59 13.54,6 12,6v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_usb_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_usb_black_24dp.xml
new file mode 100644
index 0000000..2e7e0dc
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_usb_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15,7v4h1v2h-3V5h2l-3,-4 -3,4h2v8H8v-2.07c0.7,-0.37 1.2,-1.08 1.2,-1.93 0,-1.21 -0.99,-2.2 -2.2,-2.2 -1.21,0 -2.2,0.99 -2.2,2.2 0,0.85 0.5,1.56 1.2,1.93V13c0,1.11 0.89,2 2,2h3v3.05c-0.71,0.37 -1.2,1.1 -1.2,1.95 0,1.22 0.99,2.2 2.2,2.2 1.21,0 2.2,-0.98 2.2,-2.2 0,-0.85 -0.49,-1.58 -1.2,-1.95V15h3c1.11,0 2,-0.89 2,-2v-2h1V7h-4z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/drawable/ic_volume_up_black_24dp.xml b/android/WALT/app/src/main/res/drawable/ic_volume_up_black_24dp.xml
new file mode 100644
index 0000000..755e467
--- /dev/null
+++ b/android/WALT/app/src/main/res/drawable/ic_volume_up_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,9v6h4l5,5V4L7,9H3zm13.5,3c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
+</vector>
diff --git a/android/WALT/app/src/main/res/layout/activity_crash_log.xml b/android/WALT/app/src/main/res/layout/activity_crash_log.xml
new file mode 100644
index 0000000..0f34aa5
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/activity_crash_log.xml
@@ -0,0 +1,11 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="vertical"
+ android:id="@+id/txt_crash_log"/>
+
+</RelativeLayout>
diff --git a/android/WALT/app/src/main/res/layout/activity_main.xml b/android/WALT/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..57e7679
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+
+ <include
+ android:id="@+id/toolbar_main"
+ layout="@layout/toolbar" />
+
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/android/WALT/app/src/main/res/layout/dialog_upload.xml b/android/WALT/app/src/main/res/layout/dialog_upload.xml
new file mode 100644
index 0000000..be990a2
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/dialog_upload.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp">
+
+ <EditText
+ android:id="@+id/edit_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="Enter URL"
+ android:inputType="textUri" />
+
+ <ProgressBar
+ android:id="@+id/progress_bar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_centerInParent="true" />
+
+</RelativeLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_about.xml b/android/WALT/app/src/main/res/layout/fragment_about.xml
new file mode 100644
index 0000000..8baa80e
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_about.xml
@@ -0,0 +1,57 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context="org.chromium.latency.walt.AboutFragment">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="all"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="32dp"
+ android:scrollbars="vertical"
+ android:text="@string/disclaimer"
+ android:textStyle="bold" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="20dp"
+ android:scrollbars="vertical"
+ android:text="@string/about_description" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="web"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="20dp"
+ android:scrollbars="vertical"
+ android:text="@string/more_info" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="web"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="20dp"
+ android:scrollbars="vertical"
+ android:text="@string/privacy_policy" />
+
+ <TextView
+ android:id="@+id/txt_build_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="20dp"
+ android:scrollbars="vertical" />
+
+</LinearLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_audio.xml b/android/WALT/app/src/main/res/layout/fragment_audio.xml
new file mode 100644
index 0000000..e11f157
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_audio.xml
@@ -0,0 +1,68 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:walt="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.TapLatencyFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/button_stop_audio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_stop_black_24dp" />
+
+ <ImageButton
+ android:id="@+id/button_start_audio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_play_arrow_black_24dp" />
+
+ <Spinner
+ android:id="@+id/spinner_audio_mode"
+ android:layout_height="45dp"
+ android:layout_width="fill_parent"
+ android:layout_toRightOf="@id/button_stop_audio"
+ android:layout_toLeftOf="@id/button_start_audio"
+ android:prompt="@string/audio_mode"/>
+
+ </RelativeLayout>
+
+ <include
+ android:id="@+id/chart_layout"
+ layout="@layout/line_chart" />
+
+ <org.chromium.latency.walt.HistogramChart
+ android:id="@+id/latency_chart"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="gone"
+ walt:description="Latency [ms]"
+ walt:binWidth="0.1" />
+
+ <!-- For now the results are just listed in this textView -->
+ <TextView
+ android:id="@+id/txt_box_audio"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:background="#aaaaaa"
+ android:gravity="bottom"
+ android:scrollbars="vertical" />
+
+ </LinearLayout>
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_auto_run.xml b/android/WALT/app/src/main/res/layout/fragment_auto_run.xml
new file mode 100644
index 0000000..7e8ca4a
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_auto_run.xml
@@ -0,0 +1,15 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.AutoRunFragment"
+ android:id="@+id/fragment_auto_run">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/txt_log_auto_run"
+ android:scrollbars = "vertical"
+ android:gravity="bottom" />
+
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_diagnostics.xml b/android/WALT/app/src/main/res/layout/fragment_diagnostics.xml
new file mode 100644
index 0000000..82a3cae
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_diagnostics.xml
@@ -0,0 +1,206 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context="org.chromium.latency.walt.DiagnosticsFragment">
+
+ <!-- The whole list of options -->
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="3">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:orientation="vertical">
+
+ <!-- Reconnect -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickReconnect">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_usb_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Reconnect" />
+
+ <TextView
+ android:visibility="gone"
+ style="@style/MenuTextBottom"
+ android:text="TBD: Conn status" />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Reconnect -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Ping -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickPing">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_swap_horiz_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Ping" />
+
+ <TextView
+ style="@style/MenuTextBottom"
+ android:text="Ping over USB with 1 byte" />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Ping -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- ReSync -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickSync">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_schedule_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Re-sync clocks" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of ReSync -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- CheckDrift -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickCheckDrift">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_timelapse_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Check clock drift" />
+
+ <TextView
+ style="@style/MenuTextBottom"
+ android:text="Check how much clocks diverged" />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of CheckDrift -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Program -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickProgram">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_system_update_alt_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Update WALT Firmware" />
+
+ <TextView
+ style="@style/MenuTextBottom"
+ android:text="Please press the button on the Teensy first" />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- Program -->
+
+ <!--<View style="@style/MenuDivider" />-->
+
+ <!-- Send T TODO: replace with send any char, it says nothing on the log, broadcast? -->
+ <!--
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickSendT">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_swap_horiz_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Send 'T'" />
+
+ <TextView
+ style="@style/MenuTextBottom"
+ android:text="..." />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ -->
+ <!-- End of Send T -->
+ </LinearLayout>
+ </ScrollView>
+
+ <TextView
+ android:id="@+id/txt_log_diag"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:background="#BBBBBB"
+ android:gravity="bottom"
+ android:scrollbars="vertical" />
+
+</LinearLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_drag_latency.xml b/android/WALT/app/src/main/res/layout/fragment_drag_latency.xml
new file mode 100644
index 0000000..f9b65d0
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_drag_latency.xml
@@ -0,0 +1,123 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.TapLatencyFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/button_restart_drag"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_refresh_black_24dp" />
+
+ <ImageButton
+ android:id="@+id/button_start_drag"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_play_arrow_black_24dp" />
+
+ <ImageButton
+ android:id="@+id/button_finish_drag"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_check_black_24dp" />
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#000000"
+ android:gravity="right"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/txt_cross_counts"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#000000"
+ android:padding="4dp"
+ android:text=""
+
+ android:textColor="#00ff00" />
+
+ <TextView
+ android:id="@+id/txt_drag_counts"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#000000"
+ android:padding="4dp"
+ android:text=""
+
+ android:textColor="#ff0000" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:id="@+id/latency_chart_layout"
+ android:layout_width="match_parent"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_margin="5dp"
+ android:background="@drawable/border">
+
+ <com.github.mikephil.charting.charts.ScatterChart
+ android:id="@+id/latency_chart"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="5dp" />
+
+ <Button
+ android:id="@+id/button_close_chart"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:tint="@color/button_tint"
+ android:layout_margin="5dp"
+ android:text="Close" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/txt_log_drag_latency"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:background="#000000"
+ android:gravity="bottom"
+ android:textColor="#ffffff"
+ android:scrollbars="vertical" />
+
+ </LinearLayout>
+
+ <!-- Overlay semi-transparent view that catches the touch events -->
+ <org.chromium.latency.walt.TouchCatcherView
+ android:id="@+id/tap_catcher"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#01000000" />
+
+ </FrameLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_front_page.xml b/android/WALT/app/src/main/res/layout/fragment_front_page.xml
new file mode 100644
index 0000000..ec3a95a
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_front_page.xml
@@ -0,0 +1,248 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ tools:context="org.chromium.latency.walt.FrontPageFragment">
+ <!-- The whole list of options -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- Diagnostics -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickClockSync">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_search_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Diagnostics" />
+
+ <TextView
+ style="@style/MenuTextBottom"
+ android:text="TBD: Connection/sync status"
+ android:visibility="gone" />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Diagnostics -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Tap latency -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickTapLatency">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_radio_button_checked_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Tap latency" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Tap latency -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Drag latency -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickDragLatency">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_swap_vert_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Drag latency" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End drag latency -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Screen response -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickScreenResponse">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_brightness_medium_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Screen response" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Screen response -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Audio -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickAudio">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_volume_up_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Audio latency" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Audio -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- MIDI -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickMIDI" >
+
+ <ImageView
+ android:id="@+id/midi_image"
+ style="@style/MenuIconStyle"
+ android:tint="@color/ColorDisabled"
+ android:src="@drawable/ic_music_note_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/midi_text"
+ style="@style/MenuTextTop"
+ android:textColor="@color/ColorDisabled"
+ android:text="MIDI latency" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of MIDI -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Log -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickOpenLog">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_receipt_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="View log" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Log -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- Settings -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickOpenSettings">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_settings_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="Settings" />
+
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of Settings -->
+
+ <View style="@style/MenuDivider" />
+
+ <!-- About / Help -->
+ <LinearLayout
+ style="@style/MenuItemStyle"
+ android:onClick="onClickOpenAbout">
+
+ <ImageView
+ style="@style/MenuIconStyle"
+ android:src="@drawable/ic_help_outline_black_24dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/MenuTextTop"
+ android:text="About" />
+
+ </LinearLayout>
+ </LinearLayout>
+ <!-- End of About -->
+
+
+ </LinearLayout>
+</ScrollView>
diff --git a/android/WALT/app/src/main/res/layout/fragment_log.xml b/android/WALT/app/src/main/res/layout/fragment_log.xml
new file mode 100644
index 0000000..0d953d9
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_log.xml
@@ -0,0 +1,17 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.LogFragment">
+
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/txt_log"
+ android:scrollbars = "vertical"
+ android:gravity="bottom"
+
+ />
+
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_midi.xml b/android/WALT/app/src/main/res/layout/fragment_midi.xml
new file mode 100644
index 0000000..70f9be5
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_midi.xml
@@ -0,0 +1,54 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:walt="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.TapLatencyFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/button_start_midi_in"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_input_black_24dp" />
+
+ <ImageButton
+ android:id="@+id/button_start_midi_out"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_output_black_24dp" />
+ </RelativeLayout>
+
+ <org.chromium.latency.walt.HistogramChart
+ android:id="@+id/latency_chart"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="gone"
+ walt:binWidth="0.5" />
+
+ <!-- For now the results are just listed in this textView -->
+ <TextView
+ android:id="@+id/txt_box_midi"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:background="#aaaaaa"
+ android:gravity="bottom"
+ android:scrollbars="vertical" />
+
+ </LinearLayout>
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_screen_response.xml b/android/WALT/app/src/main/res/layout/fragment_screen_response.xml
new file mode 100644
index 0000000..b789579
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_screen_response.xml
@@ -0,0 +1,76 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:walt="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.TapLatencyFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:id="@+id/button_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/button_stop_screen_response"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_stop_black_24dp" />
+
+ <ImageButton
+ android:id="@+id/button_start_screen_response"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_play_arrow_black_24dp" />
+
+ <Spinner
+ android:id="@+id/spinner_screen_response"
+ android:layout_height="45dp"
+ android:layout_width="fill_parent"
+ android:layout_toRightOf="@id/button_stop_screen_response"
+ android:layout_toLeftOf="@id/button_start_screen_response"
+ android:prompt="@string/screen_response_mode"/>
+ </RelativeLayout>
+
+ <include
+ android:id="@+id/brightness_chart_layout"
+ layout="@layout/line_chart" />
+
+ <org.chromium.latency.walt.HistogramChart
+ android:id="@+id/latency_chart"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="gone"
+ walt:description="Blink Latency [ms]"
+ walt:numDataSets="2"
+ walt:binWidth="5" />
+
+ <!-- The big box that flickers between black and white -->
+ <TextView
+ android:id="@+id/txt_black_box_screen"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:background="#aaaaaa"
+ android:gravity="bottom"
+ android:scrollbars="vertical" />
+
+ <org.chromium.latency.walt.FastPathSurfaceView
+ android:id="@+id/fast_path_surface"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:visibility="gone" />
+
+ </LinearLayout>
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/fragment_tap_latency.xml b/android/WALT/app/src/main/res/layout/fragment_tap_latency.xml
new file mode 100644
index 0000000..2c701d2
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/fragment_tap_latency.xml
@@ -0,0 +1,99 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:walt="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="org.chromium.latency.walt.TapLatencyFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ImageButton
+ android:id="@+id/button_finish_tap"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_check_black_24dp" />
+
+ <ImageButton
+ android:id="@+id/button_restart_tap"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:tint="@color/button_tint"
+ android:src="@drawable/ic_play_arrow_black_24dp" />
+ </RelativeLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#000000"
+ android:gravity="right"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/txt_tap_counts"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#000000"
+ android:padding="4dp"
+ android:text=""
+ android:textColor="#00ff00" />
+
+ <TextView
+ android:id="@+id/txt_move_count"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#000000"
+ android:padding="4dp"
+ android:text=""
+ android:textColor="#ff0000" />
+ </LinearLayout>
+
+ <org.chromium.latency.walt.HistogramChart
+ android:id="@+id/latency_chart"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="gone"
+ walt:description="Tap Latency [ms]"
+ walt:binWidth="5"
+ walt:numDataSets="2" />
+
+ <TextView
+ android:id="@+id/txt_log_tap_latency"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:background="#000000"
+ android:gravity="bottom"
+ android:textColor="#ffffff"
+ android:scrollbars="vertical" />
+ </LinearLayout>
+
+ <!-- Overlay semi-transparent view that catches the touch events -->
+ <TextView
+ android:id="@+id/tap_catcher"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#01000000" />
+
+ </FrameLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/android/WALT/app/src/main/res/layout/histogram.xml b/android/WALT/app/src/main/res/layout/histogram.xml
new file mode 100644
index 0000000..ca6dc1e
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/histogram.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/latency_chart_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="5dp"
+ android:background="@drawable/border">
+ <com.github.mikephil.charting.charts.BarChart
+ android:id="@+id/bar_chart"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="10dp"
+ android:gravity="bottom" />
+ <Button
+ android:id="@+id/button_close_bar_chart"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:tint="@color/button_tint"
+ android:layout_margin="5dp"
+ android:text="Close" />
+</RelativeLayout>
diff --git a/android/WALT/app/src/main/res/layout/line_chart.xml b/android/WALT/app/src/main/res/layout/line_chart.xml
new file mode 100644
index 0000000..7de4cfb
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/line_chart.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="5dp"
+ android:background="@drawable/border"
+ android:visibility="gone">
+ <com.github.mikephil.charting.charts.LineChart
+ android:id="@+id/chart"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="10dp"
+ android:gravity="bottom" />
+ <Button
+ android:id="@+id/button_close_chart"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:tint="@color/button_tint"
+ android:layout_margin="5dp"
+ android:text="Close" />
+</RelativeLayout>
diff --git a/android/WALT/app/src/main/res/layout/numberpicker_dialog.xml b/android/WALT/app/src/main/res/layout/numberpicker_dialog.xml
new file mode 100644
index 0000000..05c9e99
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/numberpicker_dialog.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="4dp"
+ android:paddingStart="6dp"
+ android:paddingEnd="6dp" >
+ <TextView
+ android:id="@android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+ <org.chromium.latency.walt.CustomNumberPicker
+ android:id="@+id/numpicker_pref"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+</LinearLayout>
diff --git a/android/WALT/app/src/main/res/layout/toolbar.xml b/android/WALT/app/src/main/res/layout/toolbar.xml
new file mode 100644
index 0000000..d02028d
--- /dev/null
+++ b/android/WALT/app/src/main/res/layout/toolbar.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/ColorPrimary"
+ android:elevation="4dp">
+
+</android.support.v7.widget.Toolbar>
\ No newline at end of file
diff --git a/android/WALT/app/src/main/res/menu/menu_main.xml b/android/WALT/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..6333e6d
--- /dev/null
+++ b/android/WALT/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,28 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+
+
+
+ <item
+ android:id="@+id/action_upload"
+ android:orderInCategory="180"
+ android:title="Upload"
+ android:icon="@drawable/ic_file_upload_black_24dp"
+ app:showAsAction="ifRoom" />
+
+ <item
+ android:id="@+id/action_share"
+ android:orderInCategory="190"
+ android:title="Share"
+ android:icon="@drawable/ic_share_black_24dp"
+ app:showAsAction="ifRoom" />
+
+ <item
+ android:id="@+id/action_help"
+ android:orderInCategory="200"
+ android:title="Help"
+ android:icon="@drawable/ic_help_outline_black_24dp"
+ app:showAsAction="ifRoom" />
+
+</menu>
diff --git a/android/WALT/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/WALT/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b7c8868
--- /dev/null
+++ b/android/WALT/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/android/WALT/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/WALT/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..3f22278
--- /dev/null
+++ b/android/WALT/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/android/WALT/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/WALT/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..86877de
--- /dev/null
+++ b/android/WALT/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/android/WALT/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/WALT/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6ff2e63
--- /dev/null
+++ b/android/WALT/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/android/WALT/app/src/main/res/raw/walt.hex b/android/WALT/app/src/main/res/raw/walt.hex
new file mode 100644
index 0000000..21fccf6
--- /dev/null
+++ b/android/WALT/app/src/main/res/raw/walt.hex
@@ -0,0 +1,1397 @@
+:1000000000180020C100000069250000352500000F
+:100010003525000035250000352500003525000078
+:100020003525000035250000352500006925000034
+:100030006925000035250000692500002525000000
+:100040006925000069250000692500006925000078
+:100050006925000069250000692500006925000068
+:100060006925000069250000692500006925000058
+:10007000A92B000049300000E1350000692500008F
+:100080006925000069250000692500006925000038
+:100090006925000069250000013B0000692500007A
+:1000A0004113000069250000692500006925000052
+:1000B0006925000069250000692500006925000008
+:1000C00038B502F055FA454A454B1A60454A464B49
+:1000D0001A60464A464B1A60464B0822197811426C
+:1000E00002D019780A431A70434B2A221A7000234F
+:1000F000424A43499A188A4204D24249C9580433B1
+:100100001160F5E7404B41491A1F181D00238A4230
+:1001100002D21360031CF6E73D493E4A5958101CB1
+:1001200099500433C02BF7D100239A083A49920022
+:10013000521803211940C9001568FF248C40A543BB
+:100140002C1C80258D40291C214301331160202B5C
+:10015000EBD1324B8A221860314B1A70314B24227A
+:100160005A70A0221A709A799107FCD59A79D5060F
+:10017000FCD498790C212B4A01400829F9D103219C
+:100180001171402151719A799106FCD59A795506E1
+:10019000FCD52549254A1160224A20211170997900
+:1001A0000C220A400C2AFAD1214A224B1A60224A18
+:1001B000224B1A60224B00221A60224B07221A603F
+:1001C000214A224B1A6062B600F092FF04F044FB11
+:1001D00002F0D4F902F04CF9FEE7C046300004F01A
+:1001E00034800440823F0000388004400100000F4A
+:1001F0003C80044002D0074000E00740700200202D
+:10020000600400205055000064040020D80800203D
+:100210000000000000F9FF1F00E400E008ED00E02E
+:100220000050064000400640000001104480044099
+:10023000C0000505048004407FBB000014E000E01E
+:1002400018E000E010E000E00000202020ED00E0D9
+:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE
+:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E
+:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E
+:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E
+:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E
+:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E
+:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E
+:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E
+:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E
+:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E
+:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E
+:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD
+:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED
+:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD
+:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD
+:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD
+:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD
+:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D
+:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D
+:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D
+:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D
+:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D
+:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D
+:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D
+:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D
+:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D
+:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D
+:10040000FFFFFFFFFFFFFFFFFFFFFFFFDEF9FFFF23
+:1004100010B5064C2378002B07D1054B002B02D0DA
+:10042000044800E000BF0123237010BD60040020D9
+:10043000000000005055000008B5084B002B03D009
+:100440000748084900E000BF07480368002B03D0B5
+:10045000064B002B00D0984708BDC04600000000A6
+:1004600050550000640400206004002000000000DB
+:1004700010B5041C00F0FEFD2368C01A10BDFFFF7C
+:1004800010B50948FFF7F4FF084C20600E2000F07B
+:100490009BFDA36841424141C9B201330C20616018
+:1004A000A36000F071FD10BD100500209C04002029
+:1004B00008B5011C014801F0CFFF08BDEC04002085
+:1004C00008B5011C014801F0C0FF08BDEC04002084
+:1004D00000B58DB0021C084904A804F0D1FA04A9A3
+:1004E000684601F033FF6846FFF7EAFF684601F00F
+:1004F000E9FE0DB000BDC0460C51000000B58DB046
+:10050000021C084904A804F0BBFA04A9684601F0DB
+:100510001DFF6846FFF7D4FF684601F0D3FE0DB01B
+:1005200000BDC0461051000030B5134B85B01D789A
+:10053000124C002D0AD02068A16801F0EFFA01F0FA
+:100540001FFB201CF02101F03BFF12E00C48A268C9
+:10055000036821685B68984701F042FD291C6846E2
+:1005600001F0F4FE201C694601F022FF684601F00C
+:10057000A9FE05B030BDC0460C050020EC040020EB
+:100580009806002008B5054B0A201B78002B00D0E8
+:10059000F720FFF78DFFFFF7C7FF08BD0C05002010
+:1005A00010B5141CCAB01A0CD2B2031C00910194ED
+:1005B0000AA8084904F064FA0AA906A801F0C6FED0
+:1005C00006A8FFF77DFF06A801F07CFEFFF7DAFF23
+:1005D0004AB010BD14510000031C413B192B01D837
+:1005E000203004E0031C613B192B01D82038C0B235
+:1005F000704708B5FFF7F0FFFFF75AFFFFF7C2FF9C
+:1006000008BDFFFF10B5054C0021201C2C2204F072
+:1006100075F901235B42636010BDC046BC04002035
+:1006200010B572B6FFF7EEFF00240D4B0021E05825
+:100630001022043404F062F9142CF6D1094B4C2238
+:100640009A73094B53229A73084B47229A73084BAB
+:1006500041229A73074B4D229A7362B610BDC04671
+:10066000740200209C04002014050020FC040020DB
+:10067000AC0400207C04002010B50B20012100F008
+:10068000B5FC0C20012100F0B1FC0D20012100F08F
+:10069000ADFC1420002100F0A9FC0F20002100F087
+:1006A000A5FC0E20022100F0A1FC0E200C49042222
+:1006B00000F02CFC0B4B00221A700124FFF7B0FF56
+:1006C000094B211C0D201C7000F05EFC211C0B202E
+:1006D00000F05AFC0C20211C00F056FC10BDC04656
+:1006E000810400000C050020E804002030B50B2038
+:1006F00085B0012100F048FCFA24A400142000F089
+:1007000057FB051C1248FFF7B3FEFFF7E1FE202060
+:10071000FFF7CEFE28B2FFF7DBFEFFF733FFE12342
+:100720005B01013BFDD1013C002CE7D1211C0B20DA
+:1007300000F02AFC0749684601F008FE6846FFF70A
+:10074000BFFE684601F0BEFDFFF71CFF05B030BDDF
+:1007500010050020235100007FB5041E5A2C16D12D
+:1007600072B600F087FCB14B1860FFF74BFFB04B3F
+:1007700001221A7062B60025AE4B291C0B201D7099
+:1007800000F002FC0C20291C00F0FEFBECE05428D9
+:100790000FD1A949684601F0D9FD6846FFF790FEE0
+:1007A000684601F08FFDA148FFF762FEFFF7A6FE45
+:1007B00039E1502800D1D7E0442803D10A2000F0C5
+:1007C0007DFCD1E0031C313B082B0AD89748FFF78A
+:1007D0004FFE2F3C964B0122A4005242E0505A603B
+:1007E00023E1522811D1924A00245368591C5160C8
+:1007F000082902DCD91C89008C58981CFFF768FE78
+:100800003A20FFF755FE201CFDE0492819D1884BFE
+:10081000012252425A6000241D1C884B013BFDD12D
+:100820008248FFF725FEA300EB189860201C3130AA
+:10083000C0B20134FFF73CFEFFF7A4FE092CECD157
+:10084000F3E0462802D1FFF7EBFE8DE0562811D1E8
+:10085000FFF7C2FEFFF72CFE2020FFF729FE7849A4
+:10086000684601F073FD6846FFF72AFE684601F00E
+:1008700029FDD8E047280BD1724C2068FFF728FEED
+:10088000FFF780FE00232360A36001232373CCE0E5
+:10089000412801D16C4B1DE0422812D16348FFF77B
+:1008A000E7FD6A490022051C172001F049FC201CC5
+:1008B000FFF792FEFFF7FCFD2020FFF7F9FD281C53
+:1008C000A1E0532803D1172001F0CEFC4CE04D28C5
+:1008D00006D15F4B00221A609A6001221A7342E02F
+:1008E0004E2820D1514E5B4A316868468B180093E0
+:1008F000FFF7BEFD051C201CFFF76EFEFFF7D8FDBD
+:100900002020FFF7D5FD281CFFF7F8FDFFF73AFE82
+:10091000301CFFF7ADFDA842FAD34F4801F0B4F800
+:1009200001F02EF981E043280ED1142000F040FAA6
+:100930004A4C0123191C221C6E2800DC0023636032
+:1009400051731173432017E0632804D1434B0022F5
+:100950005A731A7308E0452801D1404C12E04C2824
+:1009600004D13F4B01225A73201C05E06C2806D1AC
+:100970003B4B00225A736C20FFF73BFE55E04A28A0
+:1009800009D1374CE36820686168A268FFF708FE68
+:100990000023A36049E0552804D1FFF72AFEFFF7A2
+:1009A000A5FE42E0512831D1FFF716FEFFF780FD8A
+:1009B0002C49684601F0CAFC6846FFF781FD68468D
+:1009C00001F080FC0F2000F0F3F9FFF781FD2649CC
+:1009D000684601F0BBFC6846FFF772FD684601F00F
+:1009E00071FC142000F0E4F9FFF772FD1F4968461E
+:1009F00001F0ACFC6846FFF763FD684601F062FC5D
+:100A00000E2000F0D5F9FFF763FD0CE018496846A9
+:100A100001F09CFC6846FFF753FD684601F052FC6C
+:100A2000201CFFF745FDFFF7ADFD7FBD1005002041
+:100A3000BC040020E804002027510000102E000014
+:100A40002A510000FC040020AC04002088130000A0
+:100A50007C040020F0D8FFFF09903C6314050020BF
+:100A60009C0400202C510000305100003C5100003B
+:100A700047510000F0B5624C87B021780D2000F09E
+:100A800083FA604D2B7B002B13D00F2000F090F9E0
+:100A9000E1239B0098420CDD5B48FFF7E9FCAB6863
+:100AA00001220133AB6000232B732378286053406D
+:100AB0002370564D2B7B002B11D0162000F078F9B7
+:100AC00014280CDD5048FFF7D3FCAB68012201333A
+:100AD000AB6000232B7323782860534023700120E0
+:100AE00001F068F8061C701E8641F6B20196002ED1
+:100AF00007D0474D2B7B002B03D0464B1F78002F90
+:100B00006ED0454D2B7B002B19D0142000F050F9EE
+:100B10006968002902D16E2802DC10E059280EDC39
+:100B20003948FFF7A5FCAB6801220133AB6023789D
+:100B30006E68534023707342734128606B600024D9
+:100B400072B6364B0021E3585A7B8A420BD09F681D
+:100B50008F4208DD324A1D1C101CC4CDC4C02D6854
+:100B600005609960012162B6002906D02C4B1868F7
+:100B700059689A68DB68FFF713FD0434142CDFD141
+:100B8000019E002E22D0234B1B78042B18D1254B1D
+:100B900002A8197801394B425941234BC9B2197047
+:100BA000002903D0F02101F015FC01E001F0CEFB9B
+:100BB00002A91E4801F0FCFB02A801F083FB05E03E
+:100BC000052B03D1174B1878FFF7C6FD01F066F926
+:100BD000002812D001F01AF9C0B2FFF7BDFD0CE0F9
+:100BE0000948FFF745FCAB6801220133AB6023786D
+:100BF000286053402F73237083E707B0F0BDC046D1
+:100C0000E8040020FC04002010050020AC040020B3
+:100C10007C040020860800201405002074020020B7
+:100C20008C040020880800200C050020EC04002023
+:100C300010B500F01FFA064B064C18600021201C6E
+:100C400001F084FB201C0449044A03F0EDFD10BDB3
+:100C500010050020EC040020C522000070020020D6
+:100C6000F0B51E4B1A6880231A40FAD172B61C4B9D
+:100C70001978002930D01B490C681B490F681B49A3
+:100C80003C190E681A49A4190D681A4964190868B4
+:100C900019492418096819486418A4B21849640845
+:100CA0000C43A4B20460174C174800682568174C21
+:100CB000AC462768164C60442668164CC019256857
+:100CC000154C801924684019001980B2400801436E
+:100CD000124889B201601A7062B6F0BD24B00340B8
+:100CE0002505002038B003403CB0034040B003402D
+:100CF00044B0034048B003404CB003402CB0034024
+:100D00000080FFFF5CB0034058B0034060B0034078
+:100D100064B0034068B003406CB0034030B003409F
+:100D20001B4B1C4A19781C4B082901D1002102E0F9
+:100D30000A2903D138211160132206E00C2901D1C0
+:100D4000342100E03C21116012221A60134B1A7802
+:100D5000134B002A01D0002200E001221A60114B3F
+:100D60001A78114B012A01D880220CE0042A01D8FC
+:100D7000842208E0082A01D8852204E0102A01D83C
+:100D8000862200E087221A60084B01221A70704701
+:100D90008802002008B003400CB003402705002063
+:100DA00020B003408902002024B003402505002024
+:100DB000031C70B50020272B32D81A4AD45CFF2CB4
+:100DC0002ED0194B1B78834201D0FFF749FF72B632
+:100DD000164B1022610605D51968914319603F230F
+:100DE0001C4002E019680A431A60114B114D0122A0
+:100DF0001C602A7062B61E1C72B633681A0608D5CB
+:100E00000D4B186800232B7062B60C4B1B781841F1
+:100E100006E02B78002BDBD062B601F05FFBEBE73E
+:100E200070BDC04659510000250500200CB003409C
+:100E300000B003402605002010B003402405002028
+:100E40007047FFFF38B5074B1C681C60064BDA681B
+:100E50001D1CA30700D590472B69620700D5984752
+:100E600038BDC046A09004408C02002038B5244B09
+:100E7000244C1D681D60636A2A0700D59847A36A41
+:100E8000EA0600D59847E36A6A0600D59847236BBF
+:100E90002A0600D59847636BAA0600D59847E36BEE
+:100EA000EA0700D59847A36DAA0700D59847E36DD8
+:100EB0006A0700D59847144B1D681D60A368EA07B0
+:100EC00000D5984763692A0600D59847A369EA06C2
+:100ED00000D59847E3696A0700D59847236A2A072F
+:100EE00000D59847A36BAA0700D59847236DAA069B
+:100EF00000D59847636D6A0600D5984738BDC0464F
+:100F0000A0B004408C020020A0C0044070B5041EB4
+:100F10001A2C2CD8042A2AD8101C02F0BDFE07096E
+:100F2000050B03000B2306E00A2304E0082302E07C
+:100F30000C2300E009230C2262430D480D4E821859
+:100F40000D485268051CB8352E600C4DBC3005604C
+:100F500072B60B481568A40028400A4D1060615114
+:100F60008021490408431B041843106062B670BD19
+:100F700084510000450E000000F9FF1F6D0E0000B7
+:100F8000FFFFF0FF8C02002010B51A2818D80C23A0
+:100F900058430C4A835810181C7D027A224205D00F
+:100FA000002901D01A710BE01A7209E043681A682F
+:100FB000002902D003210A4301E002218A431A607A
+:100FC00010BDC0468451000000231A280AD80C2303
+:100FD0005843054A81581018027A0B7C13405A1E58
+:100FE0009341DBB2181C704784510000F0B51A28F9
+:100FF0003DD80C273D1C45431D4A54196368161CF7
+:10100000012901D0042912D1784332583018147DB7
+:10101000007A20431075A22252001A60202204296F
+:1010200001D1196814E0196891430A1C1EE0AA58FE
+:10103000247A107DA0431075002902D08A1E012A4F
+:1010400012D8802252001A60022903D11A680321A3
+:101050000A430BE003290AD11A6802210A431A60E5
+:101060001A6801218A4301E00422FF321A60F0BDB0
+:101070008451000072B60C4B0C491A680C4B1B686B
+:10108000096862B6480104D532219142894149423A
+:101090005B18FA20800043430648821A0648424300
+:1010A000920D98187047C04618E000E004ED00E08B
+:1010B000280500207FBB00005555010038B5041CF1
+:1010C000FFF7D8FF051C002C0FD0FFF7D3FF074B0D
+:1010D000401B984206D9013C002C06D0FA239B0005
+:1010E000ED18F2E701F0FAF9EFE738BDE703000089
+:1010F0001D4B8022D20510B51A60802212061A609C
+:101100001A4B002018601A491A4B1B4A19602823F1
+:1011100013601A4A1A4C13601A4A13601A4A136071
+:101120001A4A13601A4A136009222260194C20607F
+:10113000194C2160194C2360194C2360194C226012
+:10114000194C20601948016019490B6019490B605E
+:10115000194B1A60FFF7E4FDC8204000FFF7AEFF0F
+:1011600000F0D8FB10BDC04600E100E00480034061
+:10117000FFBF0000088003400C8003401480034040
+:10118000008003401C800340248003402C800340E7
+:101190003480034004900340089003400C900340C7
+:1011A000149003400090034004A0034008A00340B3
+:1011B0000CA0034014A0034000A00340F0B50B4F67
+:1011C00002223B780A4E1A43D2000A4DB4186060DE
+:1011D00028788824002800D0C82409040C43B4507F
+:1011E00001225040534028703B70F0BDA005002004
+:1011F00000F8FF1F20060020431E10B5042B0FD857
+:1012000072B6084A99008858002807D044685B00E5
+:101210008C50054A01889C5A611A995262B600E0C6
+:10122000002010BD8C0500207C080020431E0020FB
+:10123000042B09D872B6054A9B009B58002B02D09C
+:1012400001305B68FAE762B67047C046E4050020EB
+:1012500038B572B60123164A9A18013A1278110766
+:101260001CD5144A5C01A5589900002D05D113190D
+:1012700008305860104BA3500AE001242143C900F4
+:101280008C58002C0AD15318083058600B4B8B50E7
+:101290000B4B1A78013A1A7062B608E00133062B3C
+:1012A000D9D162B6064B00221A7000F0A7FB38BDF8
+:1012B000F052000000F8FF1F88004000C800400006
+:1012C00016060020421E10B5042A30D802238000E2
+:1012D0001843174BC0001B1872B6164CA05C0328AD
+:1012E00009D802F0D9FC140216050833022011E0D7
+:1012F000083304200EE01048920014580F4B002CC5
+:1013000001D1115001E09858416099500EE003203E
+:1013100000E00520A0540A1C08325A6088221807F1
+:1013200000D5C822098809040A431A6062B610BDB4
+:1013300000F8FF1F44050020E4050020F805002008
+:10134000F7B5B6490C780E1CE4B262071FD5B44B52
+:101350001B78002B18D0B34A1378DBB2002B05D0D2
+:10136000013BDBB21370002B00D100BEAE4A1378F4
+:10137000DBB2002B06D0013BDBB21370002B01D196
+:1013800000F05CFE00F0FCFBA44D04232B7008254C
+:10139000221C2A4000D154E2A44BA54C1B78DBB29E
+:1013A0001E099A08002E00D0DDE1D50065192F68CE
+:1013B0006A68B806000F01380C2800D9D0E102F0A5
+:1013C00075FC93019301CF01CF01CF01CF01CF0174
+:1013D000CF01AC01CF01CF01CF010D0095491368BA
+:1013E000954F526895482960954D964900263B6077
+:1013F0007A609BB201222E602661A66102708B4248
+:1014000018D8D021C9008B4200D3E5E0812191405A
+:101410008B4200D1C2E006D8802B00D1A9E0822BFC
+:1014200000D1ABE0F5E0884A934200D1C5E0A022AC
+:10143000D20017E085498B4200D152E10DD88821B6
+:1014400009018B4200D18EE090221201934200D01C
+:10145000DFE0BA78724B1A7009E07D4A934200D1FE
+:10146000CFE07C4A0026934200D115E1D1E0A319D8
+:10147000196A090603D5586A083800F0BFFA083619
+:10148000A02EF4D100252E1C734AA858002805D0A0
+:101490004368019300F0B2FA0198F7E76E496F4B89
+:1014A0006F4A4851E850A858002805D04368019376
+:1014B00000F0A4FA0198F7E76A4B6B4AE850730012
+:1014C000D0526749694B4851F05C0238032807D86D
+:1014D00002F0E2FB02040204002200E001229A551D
+:1014E00001360435052ECFD1614A00231370012542
+:1014F000161C604BEA18604B9200EB18013B1B78FE
+:101500001370190720D500F05BFA6B01002805D095
+:10151000E21808305060594AE25003E0E050337856
+:101520000133337000F04CFA0122AB001343DB00AF
+:10153000002805D0E218083050603E4AE25003E02F
+:10154000E050337801333370AB0002221A43D200EB
+:101550000021A1500322134393400135E150062D91
+:10156000C7D10E1C98E02E4B454C1B78161C2370DF
+:1015700093E0434C2670667002268EE0BB88052BF4
+:1015800047D83F4C3F4D9B00267066705B191B7817
+:101590000226334200D180E022707EE0BA887F23A9
+:1015A0001340052B35DC7E88002E32D135499B0057
+:1015B0005B181A7802218A4329E0BA887F231340F6
+:1015C000052B26DC7E88002E23D12E4A9B009B18FB
+:1015D0001A7802210A431AE07A88B9882A4B5C6893
+:1015E000002C16D01888904209D15888884206D11C
+:1015F000120A032A01D126784FE01E894DE00C33F0
+:10160000EDE7224B1A68224B1A60BA78214B1A7008
+:1016100042E01C4B0F221A7063E0C0468020074056
+:101620001F060020A10500205C0600209020074036
+:1016300000F8FF1FC80040000C0600202006002014
+:101640002C0500208106000002030000212000007C
+:1016500021220000212300008C05002030050020FD
+:10166000E4050020F80500207C0800204405002047
+:101670001606002030C80110F0520000880040001B
+:1016800017060020C0200740F8520000280500205F
+:10169000D00800206C060020341CFB889E4200D934
+:1016A0001E1C371E402F00D94027201C391CFFF775
+:1016B00085FDE419F61B03D1351C402F04D010E042
+:1016C0004025B54200D9351C201C291CFFF776FDAA
+:1016D0006419761B01D1402D03D1754D754B2C60DB
+:1016E0001E800122744B3AE0744B1988744B994266
+:1016F00010D10023D05C7349C8540133072BF9D1B2
+:101700000B68862B02D1704B0F221A700020011C2F
+:10171000FFF754FD6D4B2B6022E0654F3D68002DB7
+:1017200013D064490E88341E402C00D94024281C54
+:10173000211CFFF743FD361B5E4AB6B21680002E11
+:1017400001D1402C00D12E193E605C4BA02219889B
+:10175000D200914204D100225A709A785C4B1A70E0
+:101760000122554B61E0D200A4186068013E0838A0
+:10177000F6B22B4036D000F041F95649B00042583D
+:10178000554B002A1BD055684550111C985D0831F7
+:10179000616003280BD802F07FFA020406080321D7
+:1017A00004E0022102E0052100E00421995588238C
+:1017B000270700D5C8231288120413433CE0985D24
+:1017C000032808D802F068FA39390204002200E040
+:1017D00001229A5531E02C40624262410324A41A4E
+:1017E0009C552AE062880280002A20D03B4D43802D
+:1017F0004360B3005F593A49002F01D1585101E0CD
+:10180000CD586860C850374B7600F15A5218F252E2
+:1018100000F0D6F8002804D00830606021070AD50F
+:1018200007E0314B20601A7801321A7005E02C4233
+:1018300001D0264B00E02D4B23602D4A0823137066
+:101840007FE5012004421ED01B4902230B70294B67
+:10185000264C1A70284B294D1C609C60284C1A613C
+:10186000DC609A615D60274B0D241C70264CFF23C1
+:101870002370174C33702270244A1370244B9F221C
+:101880001A70087013E063B2002B04DA1D4B0D22AE
+:101890001A708023337002231C4204D01A4A117834
+:1018A000C9B21170337010231C4200D03370F7BDE1
+:1018B0002C05002014060020942007400C06002070
+:1018C00021200000C8080020A1050020C800400019
+:1018D00098200740E4050020440500208C050020E6
+:1018E000300500207C0800201606002088004000FB
+:1018F00080200740A005002000F8FF1F4C050020B5
+:10190000A4050020C0200740882007408C20074005
+:101910008420074010B501F0D9FE00221A4B0021A7
+:10192000D150981808324160A82AF7D1174A802070
+:101930001468C00220431060180A154AC0B2107023
+:10194000180C144AC0B21070134A1B0E1370134ABD
+:10195000FF231370124A13481370134A13700122A5
+:1019600002701248017012490A70124A11689943B4
+:101970000B1C70210B4313600F4B802252041A6022
+:101980000E4B10221A7010BD00F8FF1F3480044067
+:101990009C200740B0200740B4200740802007402B
+:1019A000882007409420074010200740002107406E
+:1019B0008420074018E400E000E100E0082107402F
+:1019C00038B572B60B4C2568281C02F003FA1D28A6
+:1019D00002D962B600200CE080231B06C3409D4361
+:1019E000256062B648235843034B18180023036050
+:1019F000436038BDF802002000FAFF1F10B5041C38
+:101A00000E484821201A02F05BF91D2815D80C4B0E
+:101A10001B78002B07D00B4B1B78002B03D0201C0E
+:101A2000FFF716FC09E072B68022074B1206C2408F
+:101A3000101C1A681043186062B610BD00FAFF1F30
+:101A4000160600201F060020F802002038B50B4CB7
+:101A5000051C21783C2908D1094B1B68002B04D0B8
+:101A60000848002298470023237023783B2B03D893
+:101A70005A1C2270034AD55438BDC046870800203E
+:101A80004C0600208B080020F8B51F4D1F4C012389
+:101A90002B702368061C002B1DD11D4F1D4B1B787E
+:101AA000002B2FD00420FFF7C1FB052804D9013FEC
+:101AB000194B002F06D108E0FFF782FF20600028B5
+:101AC000F5D008E01A78002A02D001221A7019E035
+:101AD00000F004FDE2E7104B002221681A704A88EA
+:101AE000531C92008A1896600F2B01D84B8007E098
+:101AF00040230B800420FFF7E5FBFFF761FF206028
+:101B000000232B70F8BDC0463C0600204406002090
+:101B1000E14200001F0600204806002038B5041CE2
+:101B20000D1C032D0ED92078637800021B04184386
+:101B300004231843A378033D1B061843FFF7A4FFB3
+:101B40000334EEE7032D09D12078637800021B04EB
+:101B5000184307231843A3781B060DE0022D06D176
+:101B60002078637800021B041843062304E0012D4B
+:101B700005D12078052300021843FFF785FF38BD03
+:101B800010B50A4B1B78002B0ED1094C2168002997
+:101B90000AD04B88002B07D09B000B800420FFF756
+:101BA00091FBFFF70DFF206010BDC0463C060020F2
+:101BB00044060020F8B5834C071C2368251C002B25
+:101BC00012D1814B1B78002B00D1E2E00520FFF7FA
+:101BD00013FB2060002800D1DBE00688002E03D133
+:101BE000FFF70CFF2660D4E0286843889A0892002B
+:101BF0008218946802880433934201D2438005E03E
+:101C0000FFF7FCFE0520FFF7F7FA28600F23251CDD
+:101C10001D40200A061C2A1C1E40083A0136062ACE
+:101C200000D980E0002F02D0B74200D0B1E0220BF3
+:101C30001340082D0AD1082B00D0AAE0634B0022E4
+:101C40001A70634B1B68934232D163E0092D1AD19D
+:101C5000092B00D09DE0220E5C4B210C002A09D0FC
+:101C6000012018705B4B1B68002B53D0F0B2C9B237
+:101C7000D2B206E01A70564B1B68002B4AD0F0B265
+:101C8000C9B2984746E00A2D07D10A2B00D080E060
+:101C90004E4B02221A70504B07E00B2D0DD10B2B2F
+:101CA00077D14A4B03221A704C4B1B68002B31D062
+:101CB000210CF0B2C9B2220EE3E70C2D06D10C2B99
+:101CC00067D1424B04221A70454B07E00D2D0DD110
+:101CD0000D2B5ED13D4B05221A70424B1B68002B29
+:101CE00018D0210CF0B2C9B2984713E00E2D50D194
+:101CF0000E2B4ED1354B06221A703B4B1B68002B26
+:101D000008D0FE21620C890111406202520EF0B22D
+:101D100011439847354B220C1E70354B240E1A7018
+:101D2000344B1C704CE0042D08D1C0B2FFF78EFE7E
+:101D3000200CC0B2FFF78AFE200E28E06B1F022B9A
+:101D40001ED8C0B2FFF782FE052D08D0200CC0B20D
+:101D5000FFF77CFE072D02D1200EFFF777FE264B02
+:101D6000234A1978117000221A70184B07221A7032
+:101D7000224B1B68002B23D02148012298471FE0EB
+:101D80000F2D12D11C4B1B78002B04D0C0B2FFF7D3
+:101D90005DFE002015E00D4B08221A70194B1B68E0
+:101DA000002BB7D0C0B29847B4E7022DF1D1074B52
+:101DB00009221A70144B1B68002B01D0200C984785
+:101DC0000120F8BD500600201F06002086080020D4
+:101DD0002806002058060020340600205406002063
+:101DE0002C06002038060020300600208A0800203B
+:101DF0008808002089080020870800204C06002061
+:101E00008B080020400600202406002038B5114C25
+:101E10002368002B0CD020684288531C8218157A46
+:101E20000288934212D3FFF7E9FD002323600EE0FE
+:101E3000094B1B78002B02D10120404208E0022010
+:101E4000FFF7DAF920600028E5D1F5E74380281C88
+:101E500038BDC046680600201F06002010B50D4C96
+:101E60002368002B03D02368002B0ED103E00A4B1C
+:101E70001B78002B02D10120404209E00220FFF72D
+:101E8000BBF920600028EED1F5E75A889B18187A34
+:101E900010BDC046680600201F060020054B58886C
+:101EA000054B1B68002B03D01A885B88D31AC01817
+:101EB0007047C0467C0800206806002010B50A4B19
+:101EC0001B78002B0FD0094C2068002803D0FFF7A7
+:101ED00095FD002323600220FFF78EF9002802D031
+:101EE000FFF78CFDF7E710BD1F06002068060020F5
+:101EF000F7B5304D0123071C0C1C2B70002C56D05D
+:101F00002D4E3068002827D12C4A01922C4B1B788B
+:101F1000DBB2002B03D12B700120404249E00320AB
+:101F2000FFF784F9072808D801232B70FFF748FD35
+:101F3000214B186000280FD128700198214B0138DF
+:101F40000190002802D01A78002A02D001221A70CB
+:101F5000E2E700F0C3FAD9E71A4B002231681A70A1
+:101F60004B884020C01A0094844200D90090181C6D
+:101F7000009A08309B1808184B800190A41A00237F
+:101F80000098834204D0F85C019AD0540133F7E7FB
+:101F9000FF184B883F2B06D940230B800320FFF707
+:101FA00091F900233360084B05221A70A6E72C70C4
+:101FB000201CFEBD5D0600206006002009750000A3
+:101FC0001F060020640600205C06002007B56B4653
+:101FD000D8710733181C0121FFF78AFF0EBDFFFFE0
+:101FE000F8B5124B1B78002B1ED0114F114B3C68DB
+:101FF00001261E70104D002C09D000262E7063881B
+:1020000003202380211CFFF75DF93E6009E0FFF704
+:10201000D7FC011E04D003202C70FFF753F900E019
+:102020002E70044A00231370F8BDC0461F0600201E
+:10203000600600205D0600205C06002038B50E4BCF
+:102040001D78EDB2002D15D10C4C2168002906D069
+:102050004B8803200B80FFF735F925600AE0FFF776
+:10206000AFFC011E03D00320FFF72CF902E0044B64
+:1020700001221A7038BDC0465D06002060060020AF
+:102080005C06002010B5041C01F0FCFD201C10BDF6
+:10209000214B70B518681E1C2049214A002809D020
+:1020A000127809680A71421E511D01D10322524261
+:1020B0001A6030E00B6812781A4D1A702B68002BEA
+:1020C00023D0194B00211C68201C01F065FE00285C
+:1020D00016D1201C154901F069FE002810D11449C1
+:1020E000201C02F091F8FC21890502F0B7F901F0FB
+:1020F0008FFE021E272A03D90E480F4901F0AAFDC0
+:102100002B68336000232B6005E00A4801F0BAFD1C
+:102110000A4BFF221A7070BD900600208406002032
+:102120008C0600208806002094060020ABAA2A4DC9
+:102130000000C0417006002091200000FC02002039
+:10214000F8B5051C0C1C1A2874D8002A08D0101CDD
+:102150004843FA21890001F0B3FD4600013601E051
+:1021600003267642201C02F00FFB011C324801F0CE
+:1021700067FE0C236B43314A041CD3185F6872B6A8
+:102180002F4B19788D4215D12E4D296801F0F4FDA1
+:102190002D4B002806D0186801220240013A9619FA
+:1021A0001E6046E0294A1968166001220A402C6028
+:1021B0001A603EE0264B1A2903D826491868097888
+:1021C00001701F490D700C214D4351595519081CC0
+:1021D000083018601F4A287A1070087272B61B689F
+:1021E0001278197B0A431A7362B6A2235B003B6024
+:1021F000154B201C1E60134B00211C6001F0CCFD10
+:10220000002816D1201C144901F0D0FD002810D15F
+:102210001249201C01F0F8FFFC21890502F01EF98B
+:1022200001F0F6FD021E272A03D90D480D4901F0E1
+:1022300011FD62B6F8BDC0460024F4488451000088
+:10224000FC02002094060020900600208806002052
+:10225000840600208C060020ABAA2A4D0000C04155
+:10226000700600209120000010B51A280FD872B611
+:10227000074C2378834209D1064801F003FD064B41
+:10228000064A1B6812781A70FF23237062B610BDCD
+:10229000FC02002070060020840600208C0600202E
+:1022A00008B505480023037004498023044A43739A
+:1022B00002F0BAFA08BDC046700600208520000072
+:1022C0007002002010B5041C006802F0EBFA201C1C
+:1022D00010BD38B5041C0D1C0068013102F0B2FBC2
+:1022E000002801D021C4012038BD436810B5041C6A
+:1022F0008B4208D2FFF7EDFF002805D0A368002B22
+:1023000001D122681370012010BDF8B5041C0F1C08
+:10231000151E05D10368002B00D01A7000230DE0B4
+:10232000111CFFF7E2FF061C2068002E08D10028D0
+:1023300002D002F0B7FA266000236360A36003E0D6
+:10234000A560391C02F0BEFB201CF8BD38B5002387
+:10235000041C0D1C0360436083600373994207D023
+:10236000081C02F0B7FB291C021C201CFFF7CDFF44
+:10237000201C38BD38B5051C00680C1C00280CD08A
+:102380006A688B689A4206D3096802F09BFBA268D0
+:102390000023AA600AE002F085FA236862682B60D5
+:1023A000A3686A60AB60002323606360A36038BDEC
+:1023B00010B5041E8C4201D0FFF7DCFF201C10BDBD
+:1023C00007B5002201AB19705A70191C0122FFF7E2
+:1023D0009CFF0EBD10B50023041C036043608360A6
+:1023E0000373FFF7EDFF201C10BDF7B50368171C42
+:1023F0000022041C0D1C86680092994206D39A198B
+:10240000914203D2CB1A019301230093002F1AD0DB
+:10241000BE19201C311CFFF768FF002813D0009B59
+:10242000A0682168002B09D0019A081889183A1C65
+:1024300002F042FA22680023935503E00818291C91
+:1024400002F040FBA660201CFEBD08B50B1C9A687C
+:102450000968FFF7CAFF08BD13B5002201AB041CD1
+:1024600019705A70191C0122FFF7BFFF201C16BDFE
+:1024700008B5FEF701F9FEF7FDFA00F02FF8FAE7CC
+:1024800008B5FFF70BFD08BD08B5FFF7BFFC08BD99
+:1024900008B5FFF7E3FC08BD08B5FFF7A1FD08BDCF
+:1024A00008B5FFF70BFD08BD08B5081CFFF78EFD4A
+:1024B00008BD08B5081C111CFFF71AFD08BD7047C0
+:1024C000044B00221A711A73FA21034A8900996099
+:1024D0001A60704798060020D052000010B5104CCA
+:1024E0002378002B1BD101232370FFF7D7FC002892
+:1024F00001D0FFF7E4FF00F0E5FA002801D001F079
+:10250000AFF900F02FFD002801D001F049FA00F0EA
+:10251000F5FF002801D001F0E3FA0023237010BD7D
+:10252000A8060020024B1A6801321A607047C046A4
+:102530002805002010B50B4C23685A0301D5FEF77F
+:10254000FFFE23685A0501D500F02EFB23681A050B
+:1025500001D500F079FD2368DA04ECD501F040F8EC
+:10256000E9E7C0463480044008B5FFF7E3FFFFFF0A
+:10257000014B00221A607047008104407047FFFF42
+:10258000024A136818181060181C704700030020D6
+:10259000EFF31380002821D1EFF31082002A1FD11E
+:1025A000EFF30583002B0FD0101C0F2B0ED91A1C34
+:1025B000103A92080B4903209200034052188340BE
+:1025C0001068D840C0B201E080204000EFF31182D3
+:1025D000002A05D0824203D2101C01E001204042B3
+:1025E0007047C04600E400E0F8B5061C0D1C171C3F
+:1025F0000024301C391C01F0A7FB092901D8303117
+:1026000000E03731C9B22955301C391C01F058FBA4
+:10261000061E01D00134ECE72B1958702B1C5A1BF5
+:10262000A24206DA1A78295D19702A550133013C55
+:10263000F5E7281CF8BDFFFF264B80221968D20061
+:102640000A431A60244A00231370244A1370244A50
+:102650001370244A1370244A1370244B1B78032BE5
+:1026600009D0152B04D0002B08D1214A214B04E0BE
+:102670001F4A214B01E0214A214B1A60214B1B7854
+:10268000042B0BD0052B05D0012B0BD1D1229200AE
+:102690001D4B06E0D12292001C4B02E091221C4B04
+:1026A00092001A601B4BC204D20E1A70C0B20022F4
+:1026B00058709A702C22DA70174BFF2219689143D8
+:1026C0000A1C40210A431A60144B802252011A60EE
+:1026D0007047C0463480044035070020B106002012
+:1026E0003C07002036070020B0060020F20600203C
+:1026F0001303000040A0044018C00440130200006F
+:10270000049004400403002044A004401CC0044082
+:102710000890044000A006400CE400E000E100E066
+:1027200010B5174B132299789143032202400A43B4
+:10273000410701D510210A439A700F221049024027
+:10274000042A03D18A79402422438A715A791021BC
+:102750008A43084200D00A435A7199791022914362
+:10276000840600D51143064A9971C10506D55378F0
+:1027700011782020DBB201431170537010BDC046A8
+:1027800000A0064008B5124B1B685A051ED5114B18
+:102790001B78DBB2002B02D0FFF7A0FEF7E70E4A52
+:1027A0008021490111600D4A0D49D3700422FF3286
+:1027B0000A600C490A600C4A13700C4A13700C4BE7
+:1027C0001B68002B02D00B4A12781A7108BDC04654
+:1027D00034800440B006002080E100E000A0064004
+:1027E00040A0044044A0044035070020B10600206A
+:1027F000AC060020F306002038B5041C0B4B1D78F6
+:10280000EDB2002DFAD1201C0121FEF7EFFB291CAF
+:10281000201CFEF7B9FB0C225443054B054AE15836
+:102820001C191160044B227A1A7038BDB0060020C2
+:1028300084510000380700203407002030B50029FB
+:1028400001D080231843224A1378141C98423ED0AA
+:10285000204A1268550539D57F221340042B0FD02A
+:1028600004D8012B0FD100221B4B0BE0052B04D009
+:10287000182B08D10022194B04E00022184B01E06C
+:10288000184B00221A604423002900D060237F22C5
+:102890000240042A15D006D8012A17D1C02292007E
+:1028A00013430D4A11E0052A06D0182A0ED18022C2
+:1028B000D2001343094A08E0C02292001343084A99
+:1028C00003E0802292001343064A1360207030BD5B
+:1028D000040300203480044044A0044050D004404D
+:1028E0001CC004400890044010B51A4A137898425E
+:1028F0002ED0194909684C0529D5032B0FD004D8CF
+:10290000002B0FD115490B600CE0152B04D0192BAF
+:1029100008D10021124B04E00021124B01E0124BC0
+:102920000021196003280FD004D800280FD10F49C7
+:102930000A4B0BE0152804D0192808D10C49084B84
+:1029400004E00A49074B01E00A49074B196010707F
+:1029500010BDC046F20600203480044040A0044070
+:1029600054D0044018C004400490044013030000F5
+:10297000130400001302000038B50E4B1A68002340
+:10298000510514D50C4C1A2810D80C2343430B4A7C
+:102990000B4D9958D3181B7A216001212B70FEF73B
+:1029A00025FB23682A781A72012300E02360181C93
+:1029B00038BDC04634800440AC060020845100007D
+:1029C000F306002000207047F8B5214B071C1B6858
+:1029D0005A053BD51F4B1B68002B02D01E4A1278AC
+:1029E0001A711E4E002334783F220134A2425B410B
+:1029F0005B421C401A4D2B78A3421CD1FFF7C8FD47
+:102A0000402813DC174A13795BB2002BF2DA2B78DB
+:102A1000002101333F209842494149420B4012496D
+:102A2000C95CDBB2C9B2D1712B70E3E7FF28E1DDED
+:102A3000FFF754FDDEE70C4BFFB21F550B4B012295
+:102A40001A70084BE4B2AC223470DA70F8BDC0469C
+:102A50003480044038070020340700203C07002061
+:102A60003607002000A00640B2060020B006002075
+:102A700038B5041C4518AC4204D02078FFF7A4FFF9
+:102A80000134F8E738BDFFFF08B5044B1B78002B75
+:102A900002D0FFF723FDF8E708BDC046B0060020CE
+:102AA000064B074A1B781078DBB2C0B2834201D3D1
+:102AB0003F3000E00138C01A7047C0463C07002094
+:102AC00036070020054B1878054BC0B21B78DBB2E7
+:102AD000984200D24030C01A7047C04635070020E7
+:102AE000B106002010B5124B12491A780B78D2B2F9
+:102AF000DBB29A4219D001333F2400209C4240416E
+:102B0000404203400C48DCB2C05C0C700B49C0B2C0
+:102B1000096800290BD09A4200D24032D31A1A2BEE
+:102B200005DC074B1B780B7201E00120404210BD11
+:102B300035070020B1060020F4060020AC06002076
+:102B4000F30600200A4B1A780A4B1B78DBB29A4234
+:102B50000AD0013300223F21994252415242134090
+:102B6000054AD05CC0B201E0012040427047C04637
+:102B700035070020B1060020F4060020064B1A7825
+:102B8000064BD2B21A70064B1B68002B02D0054AC6
+:102B900012781A727047C046B1060020350700202F
+:102BA000AC060020F306002030B5254A13799906BB
+:102BB00012D52449D4790B78002001333F259D425A
+:102BC0004041404203402048E4B20078834203D0B1
+:102BD0001E48C454DBB20B70D178194CC9B24BB249
+:102BE000002B1ADA23795BB2002B16DA184B19483E
+:102BF0001D780378DBB29D4202D16C23E3700CE0B8
+:102C0000013300243F259D42644164422340124C1D
+:102C1000E45CDBB2E4B2D4710370402319420ED0FD
+:102C2000117919420BD00D4B002119700C4B1B6808
+:102C30008B4202D00B49097819722C23D37030BD16
+:102C400000A0064035070020B1060020F406002051
+:102C50003C07002036070020B2060020B006002006
+:102C60003807002034070020164B80221968120113
+:102C70000A431A60144A00231370144A144913704B
+:102C8000144A1370144A1370144A1370144A1160D2
+:102C9000144AD12189001160134AC104C90EC0B27F
+:102CA0001170507093702C23D370104B104A196818
+:102CB0000A408021C9010A431A600E4B802292010A
+:102CC0001A60704734800440B00700204507002098
+:102CD00013030000B8070020B107002044070020BC
+:102CE0000CB0044010B0044000B006400CE400E01A
+:102CF000FF00FFFF00E100E010B5174B13229978A9
+:102D00009143032202400A43410701D510210A439F
+:102D10009A700F2210490240042A03D18A79402474
+:102D200022438A715A7910218A43084200D00A430B
+:102D30005A71997910229143840600D51143064AAD
+:102D40009971C10506D5537811782020DBB2014373
+:102D50001170537010BDC04600B0064008B5124B4C
+:102D60001B681A051ED5114B1B78DBB2002B02D055
+:102D7000FFF7B4FBF7E70E4A8021890111600D4A85
+:102D80000D49D3700422FF320A600C490A600C4AD4
+:102D900013700C4A13700C4B1B68002B02D00B4AAB
+:102DA00012781A7108BDC0463480044044070020E0
+:102DB00080E100E000B006400CB0044010B00440D8
+:102DC000B007002045070020400700206E070020C4
+:102DD00038B5041C0B4B1D78EDB2002DFAD1201C28
+:102DE0000121FEF703F9291C201CFEF7CDF80C2267
+:102DF0005443054B054AE1581C191160044B227AD3
+:102E00001A7038BD4407002084510000B407002028
+:102E1000AF0700207047704738B50E4B1A68002383
+:102E2000110514D50C4C1A2810D80C2343430B4A17
+:102E30000B4D9958D3181B7A216001212B70FEF796
+:102E4000D5F823682A781A72012300E02360181C41
+:102E500038BDC04634800440400700208451000043
+:102E60006E07002000207047F8B5214B071C1B6837
+:102E70001A053BD51F4B1B68002B02D01E4A127847
+:102E80001A711E4E0023347827220134A2425B417E
+:102E90005B421C401A4D2B78A3421CD1FFF778FBF4
+:102EA000402813DC174A13795BB2002BF2DA2B7837
+:102EB0000021013327209842494149420B401249E1
+:102EC000C95CDBB2C9B2D1712B70E3E7FF28E1DD49
+:102ED000FFF704FBDEE70C4BFFB21F550B4B012243
+:102EE0001A70084BE4B2AC223470DA70F8BDC046F8
+:102EF00034800440B4070020AF070020B80700204A
+:102F0000B107002000B0064046070020440700201B
+:102F100038B5041C4518AC4204D02078FFF7A4FF54
+:102F20000134F8E738BDFFFF08B5044B1B78002BD0
+:102F300002D0FFF7D3FAF8E708BDC04644070020E7
+:102F4000064B074A1B781078DBB2C0B2834201D32C
+:102F5000273000E00138C01A7047C046B80700208B
+:102F6000B1070020054B1878054BC0B21B78DBB2C7
+:102F7000984200D24030C01A7047C046B0070020C7
+:102F80004507002010B5124B12491A780B78D2B2BF
+:102F9000DBB29A4219D001333F2400209C424041C9
+:102FA000404203400C48DCB2C05C0C700B49C0B21C
+:102FB000096800290BD09A4200D24032D31A1A2B4A
+:102FC00005DC074B1B780B7201E00120404210BD6D
+:102FD000B0070020450700206F07002040070020B1
+:102FE0006E0700200A4B1A780A4B1B78DBB29A4214
+:102FF0000AD0013300223F219942524152421340EC
+:10300000054AD05CC0B201E0012040427047C04692
+:10301000B0070020450700206F070020064B1A78F4
+:10302000064BD2B21A70064B1B68002B02D0054A21
+:1030300012781A727047C04645070020B00700207A
+:10304000400700206E07002030B5254A1379990605
+:1030500012D52449D4790B78002001333F259D42B5
+:103060004041404203402048E4B20078834203D00C
+:103070001E48C454DBB20B70D178194CC9B24BB2A4
+:10308000002B1ADA23795BB2002B16DA184B194899
+:103090001D780378DBB29D4202D16C23E3700CE013
+:1030A0000133002427259D42644164422340124C91
+:1030B000E45CDBB2E4B2D4710370402319420ED059
+:1030C000117919420BD00D4B002119700C4B1B6864
+:1030D0008B4202D00B49097819722C23D37030BD72
+:1030E00000B00640B0070020450700206F07002011
+:1030F000B8070020B1070020460700204407002041
+:10310000B4070020AF070020204B8022196852012D
+:103110000A431A601E4A002313701E4A13701E4A87
+:1031200013701E4A13701E4A13701E4B1B78062B19
+:1031300004D0072B05D11C4A1C4B01E01A4A1C4B3A
+:103140001A601C4B1B78082B05D0142B07D1D122F9
+:103150009200194B02E0D122184B92001A60184BD2
+:10316000C204D20E1A70C0B2002258709A702C227B
+:10317000DA70144B144A19680A408021C9030A43C3
+:103180001A60124B8022D2011A60704734800440CA
+:103190002C080020C1070020340800202D08002042
+:1031A000C0070020050300201303000008C00440EE
+:1031B00010C004400603002014C004400CC00440AA
+:1031C00000C006400CE400E0FFFF00FF00E100E06B
+:1031D00010B5174B132299789143032202400A43FA
+:1031E000410701D510210A439A700F22104902406D
+:1031F000042A03D18A79402422438A715A79102102
+:103200008A43084200D00A435A71997910229143A7
+:10321000840600D51143064A9971C10506D5537835
+:1032200011782020DBB201431170537010BDC046ED
+:1032300000C0064008B5124B1B68DA041ED5114BBE
+:103240001B78DBB2002B02D0FFF748F9F7E70E4AF4
+:103250008021C90111600D4A0D49D3700422FF324B
+:103260000A600C490A600C4A13700C4A13700C4B2C
+:103270001B68002B02D00B4A12781A7108BDC04699
+:1032800034800440C007002080E100E000C0064018
+:1032900008C004400CC004402C080020C1070020D6
+:1032A000BC070020EA07002038B5041C0B4B1D7832
+:1032B000EDB2002DFAD1201C0121FDF797FE291C4B
+:1032C000201CFDF761FE0C225443054B054AE158D2
+:1032D0001C191160044B227A1A7038BDC0070020F7
+:1032E00084510000300800202B08002030B5002950
+:1032F00001D080231843154A1378141C984224D017
+:10330000134A1268D5041FD57F221340082B04D01E
+:10331000142B05D100220F4B01E00F4B00221A6045
+:103320004423002900D060237F220240082A06D0CF
+:10333000142A09D1C02292001343064A03E0C02296
+:1033400092001343044A1360207030BD060300202E
+:103350003480044014C004400CC0044010B50F4A2F
+:103360001378984218D00E490968CC0413D5062B5F
+:1033700004D0072B05D100210A4B01E00A4B0021A4
+:103380001960062804D0072805D10849054B01E03B
+:103390000649054B1960107010BDC046050300209A
+:1033A0003480044008C0044010C0044013030000EF
+:1033B00038B50E4B1A680023D10414D50C4C1A28CA
+:1033C00010D80C2343430B4A0B4D9958D3181B7A42
+:1033D000216001212B70FDF709FE23682A781A72FB
+:1033E000012300E02360181C38BDC046348004402F
+:1033F000BC07002084510000EA070020002070472D
+:10340000F8B5214B071C1B68DA043BD51F4B1B6822
+:10341000002B02D01E4A12781A711E4E00233478F7
+:1034200027220134A2425B415B421C401A4D2B789B
+:10343000A3421CD1FFF7ACF8402813DC174A1379DC
+:103440005BB2002BF2DA2B7800210133272098425F
+:10345000494149420B401249C95CDBB2C9B2D17142
+:103460002B70E3E7FF28E1DDFFF738F8DEE70C4BD0
+:10347000FFB21F550B4B01221A70084BE4B2AC226D
+:103480003470DA70F8BDC046348004403008002043
+:103490002B080020340800202D08002000C0064022
+:1034A000C2070020C007002038B5041C4518AC42F4
+:1034B00004D02078FFF7A4FF0134F8E738BDFFFF00
+:1034C00008B5044B1B78002B02D0FFF707F8F8E78C
+:1034D00008BDC046C0070020064B074A1B7810787D
+:1034E000DBB2C0B2834201D3273000E00138C01AFA
+:1034F0007047C046340800202D080020054B18787E
+:10350000054BC0B21B78DBB2984200D24030C01AE3
+:103510007047C0462C080020C107002010B5124B90
+:1035200012491A780B78D2B2DBB29A4219D0013321
+:103530003F2400209C424041404203400C48DCB202
+:10354000C05C0C700B49C0B2096800290BD09A42CC
+:1035500000D24032D31A1A2B05DC074B1B780B72B2
+:1035600001E00120404210BD2C080020C1070020CE
+:10357000EB070020BC070020EA0700200A4B1A785E
+:103580000A4B1B78DBB29A420AD0013300223F215A
+:103590009942524152421340054AD05CC0B201E008
+:1035A000012040427047C0462C080020C10700207F
+:1035B000EB070020064B1A78064BD2B21A70064B66
+:1035C0001B68002B02D0054A12781A727047C04659
+:1035D000C10700202C080020BC070020EA070020BB
+:1035E00030B52E4A1379990624D52D49D4790B7814
+:1035F000002001333F259D4240414042034029487D
+:10360000E4B20078834203D02748C454D8B208708B
+:103610002649086800280DD025490978C9B28B428F
+:1036200001D35B1A01E05B1A4033272B02DD214BEB
+:103630001B780371D178194CC9B24BB2002B1ADA3E
+:1036400023795BB2002B16DA1B4B19481D780378DF
+:10365000DBB29D4202D16C23E3700CE00133002405
+:1036600027259D42644164422340144CE45CDBB254
+:10367000E4B2D4710370402319420ED0117919427B
+:103680000BD00F4B002119700E4B1B688B4202D0E0
+:103690000D49097819722C23D37030BD00C0064043
+:1036A0002C080020C1070020EB070020BC070020E9
+:1036B0002D080020EA07002034080020C20700205F
+:1036C000C0070020300800202B0800201FB572B66C
+:1036D000154B70221A70154A41211170144A0F219E
+:1036E000117080221A701A7852B2002AFBDA114B3C
+:1036F000186862B6104B984201D80A23584301ACAF
+:10370000211C0A22FEF770FF0023E15C0B4A00290E
+:1037100005D058001018013341800A2BF5D1013330
+:103720005B0013701FBDC04600000240070002404E
+:1037300006000240080002407F969800E403002043
+:1037400008B50368C9B21B68984708BD08B5036887
+:10375000C9B21B68984708BD08B50368C9B21B68A1
+:10376000984708BD08B50368C9B21B68984708BDEB
+:1037700008B5044B4808C01800F0A2FAFEF75CFF39
+:1037800008BDC046C0C62D0010B5064B4808C0187D
+:10379000141C00F095FAFEF74FFF201CFEF7C0FF47
+:1037A00010BDC046C0C62D0008B5FEF7EBFF08BD32
+:1037B00008B5081CFFF720F808BD08B5081CFFF77E
+:1037C00093F808BD08B5081C111CFFF737F808BDB1
+:1037D00008B5081CFFF7D0F8431E9841C0B208BDD9
+:1037E00008B5081CFFF7EEF8431E9841C0B208BDAB
+:1037F00008B5FFF767F908BD08B5FFF7A3F908BDDD
+:1038000008B5FFF76FF908BD08B5FFF73DF908BD2A
+:1038100008B5FFF7B3F908BD08B5FFF741F908BDD2
+:1038200008B5081CFFF7D0F8012008BD08B5081C32
+:10383000FFF7CAF8012008BD10B5081C141C111CA4
+:10384000FFF716F9201C10BD38B5081C0D1C01F03F
+:1038500041F9041C211C281CFFF70AF9201C38BD63
+:103860007047FFFF044B00221A711A73FA21034AB2
+:10387000890099601A60704738080020585300008A
+:1038800008B50368C9B21B68984708BD08B5036846
+:10389000C9B21B68984708BD08B50368C9B21B6860
+:1038A000984708BD08B50368C9B21B68984708BDAA
+:1038B00008B5044B4808C01800F002FAFFF7D4F925
+:1038C00008BDC04660E3160010B5064B4808C01896
+:1038D000141C00F0F5F9FFF7C7F9201CFFF70CFAEC
+:1038E00010BDC04660E3160008B5FFF737FA08BD03
+:1038F00008B5081CFFF76CFA08BD08B5081CFFF7EF
+:103900008AFA08BD08B5081C111CFFF783FA08BD28
+:1039100008B5081CFFF780FA431E9841C0B208BDE5
+:1039200008B5081CFFF79EFA431E9841C0B208BDB7
+:1039300008B5FFF717FB08BD08B5FFF753FB08BD37
+:1039400008B5FFF71FFB08BD08B5FFF7EDFA08BD86
+:1039500008B5FFF763FB08BD08B5FFF7F1FA08BD2E
+:1039600008B5081CFFF780FA012008BD08B5081C3F
+:10397000FFF77AFA012008BD10B5081C141C111CB1
+:10398000FFF7C6FA201C10BD38B5081C0D1C01F04D
+:10399000A1F8041C211C281CFFF7BAFA201C38BD12
+:1039A0007047FFFF044B00221A711A73FA21034A71
+:1039B000890099601A60704748080020B8530000D9
+:1039C00008B50368C9B21B68984708BD08B5036805
+:1039D000C9B21B68984708BD08B50368C9B21B681F
+:1039E000984708BD08B50368C9B21B68984708BD69
+:1039F00008B5044B4808C01800F062F9FFF784FBD3
+:103A000008BDC04660E3160010B5064B4808C01854
+:103A1000141C00F055F9FFF777FB201CFFF7D8FBCB
+:103A200010BDC04660E3160008B5FFF703FC08BDF3
+:103A300008B5081CFFF738FC08BD08B5081CFFF7DF
+:103A40008DFC08BD08B5081C111CFFF74FFC08BD14
+:103A500008B5081CFFF7ACFC431E9841C0B208BD76
+:103A600008B5081CFFF7CAFC431E9841C0B208BD48
+:103A700008B5FFF743FD08BD08B5FFF77FFD08BD9A
+:103A800008B5FFF74BFD08BD08B5FFF719FD08BDE8
+:103A900008B5FFF78FFD08BD08B5FFF71DFD08BD90
+:103AA00008B5081CFFF7ACFC012008BD08B5081CD0
+:103AB000FFF7A6FC012008BD10B5081C141C111C42
+:103AC000FFF7F2FC201C10BD38B5081C0D1C01F0DE
+:103AD00001F8041C211C281CFFF7E6FC201C38BD43
+:103AE0007047FFFF044B00221A711A73FA21034A30
+:103AF000890099601A607047580800201854000027
+:103B000008B50B4B1A68002A04D001221A60094B31
+:103B10001B689847084B1B78002B08D0074B1A6886
+:103B2000002A04D001221A60024B5B68984708BD46
+:103B30000C7103406C0800206A0800201C710340CF
+:103B4000064B8022196812040A431A60044B0022B3
+:103B50001A60044B01221A707047C0463C80044032
+:103B6000007003406A080020054B01221A60054BD3
+:103B7000054A19680A401A60044B00221A707047FF
+:103B8000007003403C800440FFFF7FFF6A08002074
+:103B900070B544780E4D0F4E23015A199B190E4DE6
+:103BA0000669A40042606651002483601C601160B5
+:103BB00003221A60094B417B094A18680904024034
+:103BC0000A431A60074B8022D2031A6070BDC046B8
+:103BD00000710340087103406C08002014E400E009
+:103BE000FFFF00FF00E100E0F8B50C4B061C1B785E
+:103BF0000F1C002B01D1FFF7A3FF094D2C78002CDF
+:103C000004D06B780020834207D10124301C7470EB
+:103C1000391CFFF7BDFF01202855F8BD6A080020B8
+:103C20006808002008B5836800221A608021074BCD
+:103C3000C90319604178064B5A541A78002A04D1F6
+:103C40005B78002B01D1FFF78FFF08BD80E100E01A
+:103C50006808002070B50378041C0E1C151C002B8E
+:103C600003D0FFF7DFFF002323702661201C291CEF
+:103C7000FFF7BAFF002802D00123237000E0207074
+:103C8000207870BD037810B5041C002B01D0FFF71D
+:103C9000C9FF0023237010BD02B4714649084900D2
+:103CA000095C49008E4402BC7047C04603B47146AB
+:103CB000490840004900095A49008E4403BC704736
+:103CC000002934D00123002210B488422CD30124CF
+:103CD0002407A14204D2814202D209011B01F8E764
+:103CE000E400A14204D2814202D249005B00F8E71D
+:103CF000884201D3401A1A434C08A04202D3001B49
+:103D00005C0822438C08A04202D3001B9C0822437B
+:103D1000CC08A04202D3001BDC082243002803D0B9
+:103D20001B0901D00909E3E7101C10BC70470028EB
+:103D300001D00020C04307B4024802A140180290FD
+:103D400003BDC046190000000029F0D003B5FFF7FD
+:103D5000B9FF0EBC4243891A1847C0467047C04697
+:103D60008446081C6146FFE71FB500F001FA0028F1
+:103D700001D40021C8421FBD10B500F087F94042B0
+:103D8000013010BD10B500F0F3F9002801DB002070
+:103D900010BD012010BDC04610B500F0E9F90028A3
+:103DA00001DD002010BD012010BDC04610B500F09F
+:103DB00097F9002801DC002010BD012010BDC0468D
+:103DC00010B500F08DF9002801DA002010BD0120A7
+:103DD00010BDC0461C2101231B04984201D3000CD6
+:103DE00010391B0A984201D3000A08391B0998426E
+:103DF00001D30009043902A2105C40187047C04684
+:103E000004030202010101010000000000000000A3
+:103E10009E2110B5C905041CFFF7D2FF002803D16D
+:103E2000201C00F091FC10BD9E21C905201C00F053
+:103E300015FB00F089FC80231B06C018F3E7C04681
+:103E4000F0B55F4656464D464446F0B4460245003E
+:103E5000C00F85B00F1C760A2D0E804641D0FF2D75
+:103E600026D08024240400212643F6007F3D894685
+:103E70008B46F90F7C027800640A000E00918A4696
+:103E80003CD0FF2834D080231B041C430023E400D3
+:103E90007F380193009F4346019A7B4049469C46E8
+:103EA00011430F2900D971E0764F89007F58BF4632
+:103EB000002E3ED10822022391469B46D9E75A465E
+:103EC000341CC24601920199022937D0032900D13E
+:103ED000CFE0012900D0ABE053460B400022002682
+:103EE00032E0002E19D10421012289469346C0E711
+:103EF000221C531E9A4102320192CBE701270197FF
+:103F0000002CC7D0201CFFF765FF431F9C40762381
+:103F10005B420021181A0191BCE7301CFFF75AFFE1
+:103F20007625431F9E406D4200232D1A99469B46DD
+:103F30009FE70C23032199468B469AE7D446012339
+:103F400067463B40FF2200267602D205700ADB0757
+:103F50001043184305B03CBC90469946A246AB4678
+:103F6000F0BD80260023F603FF22EDE700220026A5
+:103F7000EAE78020C00306423BD0044239D1061C48
+:103F800026437602760A009BFF22DDE7281A03907B
+:103F900076016401A64239D3361B1A22012301207F
+:103FA000311C5B007600002901DBB44201D8361BCE
+:103FB0000343013A002AF3DC741EA641341C1C435F
+:103FC000039A7F32002A27DD630704D00F232340A2
+:103FD000042B00D00434270103D52B4B039A1C403B
+:103FE0008032FE2A0BDD012361460B40FF220026B2
+:103FF000AAE706437602760A4346FF22A4E7A40115
+:1040000001236746660AD2B23B409DE7039F1B220D
+:10401000013F03970023C2E77E23039F5B42DB1B24
+:104020001B2B07DD012361460B40002200268BE796
+:10403000D446C5E7221CDA40039B9E339C40231CD8
+:104040005C1EA34113435A0704D00F221A40042ACE
+:1040500000D004335F0105D5012361460B400122E6
+:10406000002671E79E01624601231340760A002272
+:104070006AE78026F60326437602760A5346FF2235
+:1040800062E7C04670540000FFFFFFF74A02430298
+:1040900070B55C0A550A43004A001B0EC60F120E8B
+:1040A000C90FFF2B05D0FF2A08D0012093420BD067
+:1040B00070BD0120002CFBD1FF2AF6D10120002D7C
+:1040C000F6D101209342F3D1AC42F1D18E4205D01A
+:1040D000002BEDD1201C441EA041E9E70020E7E7BA
+:1040E0004A024302F0B55C0A550A43004A001B0E1F
+:1040F000C60F120EC90FFF2B31D0FF2A34D0002B70
+:1041000016D1604260418446002A14D0002820D194
+:104110008E4217D1934215DC04DBAC4212D800204A
+:10412000AC4212D2704270414042012318430CE06D
+:10413000002AEDD194466F426F416046002805D1B8
+:10414000002FE5D0704201231843F0BD0020002F5E
+:10415000FBD148424841404201231843F5E7002C77
+:10416000CBD002204042F0E7002DC8D0F9E7C0468E
+:104170004A024302F0B55C0A550A43004A001B0E8E
+:10418000C60F120EC90FFF2B27D0FF2A29D0002BF4
+:1041900010D0002A15D194466F426F416046002826
+:1041A00015D00020002F04D148424841404201234D
+:1041B0001843F0BD604260418446002AECD00028DC
+:1041C000F2D18E4211D0704201231843F1E7002F43
+:1041D000F7D0704201231843EBE70220002CE8D10E
+:1041E000D3E70220002DE4D1D1E79342EBDC04DBDE
+:1041F000AC42E8D80020AC42DBD270427041404271
+:1042000001231843D5E7C046F0B55F4656464D46F4
+:104210004446F0B44402460083B00F1C640A360ED4
+:10422000C50F002E41D0FF2E22D080231B0400207A
+:104230001C43E4007F3E82468046391C4B007F02CF
+:10424000C90F7F0A1B0E8B463BD0FF2B34D0802238
+:1042500012041743FF007F3B00215A466A40019237
+:1042600052460A430F2A63D87A48920082589746EA
+:10427000002C3FD10822022392469846DDE70195A3
+:10428000404602282AD1019A01251540FF23002427
+:104290006402DB05600AED071843284303B03CBC09
+:1042A00090469946A246AB46F0BD002C27D104208B
+:1042B000012282469046C0E7391C4A1E91410231D4
+:1042C000CBE70121002FC8D0381CFFF783FD431F27
+:1042D0009F4076235B421B1A0021BEE7032800D1D2
+:1042E000AEE001284FD1019842461040C5B20023EC
+:1042F0000024CDE70C2303209A4680469DE7201C2E
+:10430000FFF768FD7626431F9C4076420023361A4D
+:104310009A46984691E780240025E403FF23B7E7F7
+:104320005B463C1C01938846AAE73C1C8846A7E7ED
+:10433000250C24043A0C240C3F04F6183F0C211CD5
+:10434000231C794353436F435543FB180A0C9B18B6
+:10435000B1469F4202D980225202AD1809041A04C4
+:10436000090C521894011B0C611E8C41920EED1821
+:104370001443AD012C43230105D50122630801201C
+:10438000144081441C434B467F33002B2DDD6007D6
+:1043900004D00F222240042A00D00434220103D585
+:1043A0002D4B1C404B468033FE2B17DD019B012516
+:1043B0001D400024FF236BE78020C003044208D087
+:1043C000074206D1041C3C436402640A5D46FF2395
+:1043D0005EE704436402640AFF2359E70198A401DD
+:1043E0000125640ADBB2054052E77E235B424A4660
+:1043F0009B1A1B2B05DD019B01251D40002400237A
+:1044000046E7221CDA404B469E339C40231C5C1E30
+:10441000A3411343580704D00F221A40042A00D0A6
+:1044200004335A0105D5019B01251D4000240123B9
+:104430002EE701989C010125640A0540002327E727
+:104440008027FF03019B3C43640201251D40640A51
+:10445000FF231DE7B0540000FFFFFFF7F8B5C20FC0
+:10446000430244004D024800240E161C9B09000E16
+:10447000C90FAD09FF2800D183E0012779408A42A6
+:104480005CD0221A002A00DC8EE000281ED1002D0C
+:1044900000D07AE0580704D00F221A40042A00D036
+:1044A00004338021C90401221940324000293AD046
+:1044B0000134FF2C00D183E09B015B0A5B02E4B274
+:1044C000E405580AD20720431043F8BDFF2CE1D081
+:1044D0008021C9040D431B2A00DD31E1291C202065
+:1044E000D140821A95406A1E95410D435B1B5801CD
+:1044F000D0D59B019F09381CFFF76CFC421F9740E9
+:1045000094425FDC141B1F231B1B3A1C9F40611C41
+:104510003B1CCA405F1EBB4113430024BAE7131E75
+:10452000B8D100230022DB08FF2C04D1002B47D098
+:104530008020C00303435B025B0ABFE7211A002906
+:1045400044DD002827D0FF2CA4D08020C0040543E0
+:104550001B2900DDF2E0281C2027C840791A8D4075
+:10456000691E8D4105435B19590100D492E701345E
+:10457000FF2C59D0734901221A400B405B081343AA
+:1045800088E7002D00D07AE777E7013A002AADD01E
+:10459000FF2CA0D17EE7002D00D17BE70139002957
+:1045A000E1D0FF2CD4D175E7002A1BD1621CD2B216
+:1045B000012A4BDD5F1B7A0123D5EF1A0E1C9AE707
+:1045C00000237BE75F4BA41A3B4063E7002946D1F9
+:1045D000611CC8B2012829DDFF2924D0EB185B0833
+:1045E0000C1C57E7002C13D0FF2818D08024E404BB
+:1045F000524223431B2A4DDD0123EB1A041C0E1CDF
+:1046000075E7002F00D076E70023002200248AE718
+:10461000002B3BD0D243002AEFD0FF28EAD12B1C3D
+:10462000FF240E1C36E7FF2400237CE7002C5CD11E
+:10463000002B00D180E0002D00D12BE75B19580141
+:1046400000D427E73F4A0124134023E7002C15D16B
+:10465000002B40D1002D63D02B1C0E1C1AE7002C20
+:1046600021D1002B54D0C943002904D0FF284CD0BD
+:104670001B2958DD01235B19041C75E7002B19D198
+:10468000002D48D02B1C0E1CFF2403E72B1C041C00
+:104690000E1CFFE61C1C2026D440B21A93405A1E62
+:1046A00093412343A9E7FF282FD08024E404494203
+:1046B0002343DDE7FF24002D00D1EBE68022DB0859
+:1046C000D203134204D0ED08154201D12B1C0E1C5D
+:1046D000DB00FF24DEE6002D00D1DBE65A1B500193
+:1046E00000D41CE7EB1A0E1CD4E6002B0DD0FF24DF
+:1046F000002D00D1CEE68022DB08D2031342E7D0A2
+:10470000ED081542E4D12B1CE2E72B1CFF24C1E687
+:104710002B1C041CBEE6802300229B04FF2402E71E
+:10472000231C0022FFE61C1C2027CC40791A8B405A
+:10473000591E8B4123439EE72B1CABE6012512E754
+:104740000125D3E6FFFFFFFB4302590A4300C20FD6
+:104750001B0E00207E2B0DDD9D2B0CDC8020000429
+:104760000143952B0ADC9620C31AD9404842002AFF
+:1047700000D1081C7047034BD018FBE7963B9940CB
+:10478000F4E7C046FFFFFF7F10B5041E33D0FFF7EC
+:1047900021FB9E231B1A962B09DC083884406402F7
+:1047A000640ADBB26402DB05600A184310BD992B72
+:1047B0000ADD0522121A211CD1400A1C011C1B31E2
+:1047C0008C40611E8C411443052801DD421F94403A
+:1047D000144A2240610704D00F210C40042C00D061
+:1047E000043251010AD59F23181AFF2816D09401CC
+:1047F000640AC3B2D6E700230024D3E7D208FF2B14
+:1048000003D05402640ADBB2CCE7002A06D080242D
+:10481000E40314436402640AFF23C3E7FF23002474
+:10482000C0E7C046FFFFFFFB08B5031C081C191CAE
+:1048300000F002F808BDFFFF38B5051C05480C1C48
+:10484000131C002804D00220291C221C00E000BFF9
+:1048500038BDC0460000000070B50E4B0E4D002460
+:10486000ED1AAD101E1CAC4204D0A300F3589847BB
+:104870000134F8E700F058FE084B094D0024ED1A0A
+:10488000AD101E1CAC4204D0A300F358984701346D
+:10489000F8E770BD34550000345500003455000071
+:1048A0005055000008B5034B011C186800F02EF8A5
+:1048B00008BDC0465C04002010B50023934203D01D
+:1048C000CC5CC4540133F9E710BD70B5814201D30B
+:1048D00000230CE08C18A042FAD28518131C013B6F
+:1048E0000BD351426618F65C6918CE54F7E7934231
+:1048F00003D0CC5CC4540133F9E770BD031C8218AB
+:10490000934202D019700133FAE7704730B500299D
+:1049100040D004390B68002B00DAC9181E4A13680E
+:10492000141C002B02D14B60116033E099420FD26E
+:1049300008680A189A4205D113685268C0180860BE
+:104940004A6000E04B60216024E08A4203D8131CD7
+:104950005A68002AF9D11D685C198C420BD109688C
+:10496000691858181960904214D1146852680919CE
+:1049700019605A600EE08C4202D90C23036009E0F2
+:1049800008680C18944203D1146852680019086032
+:104990004A60596030BDC0467808002070B50323D6
+:1049A000CD1C9D430835061C0C2D01D20C2501E0C1
+:1049B000002D3FDB8D423DD3204B1C681A1C211C6F
+:1049C000002913D00868431B0DD40B2B02D90B60B0
+:1049D000CC181EE08C4202D1636813601AE048686C
+:1049E00060600C1C16E00C1C4968E9E7144C206858
+:1049F000002803D1301C00F031F82060301C291C45
+:104A000000F02CF8431C15D0C41C03239C438442A3
+:104A10000AD12560201C0B300722231D9043C31AA6
+:104A20000BD05A42E25008E0211A301C00F016F870
+:104A30000130EED10C233360002070BD78080020D7
+:104A40007408002008B50A1C0349031C0868191CD7
+:104A500000F02DFB08BDC0465C04002038B5074CB3
+:104A60000023051C081C2360FDF78AFD431C03D1AD
+:104A70002368002B00D02B6038BDC046D40800202E
+:104A80000EB400B59CB01DAB04CB822202A99200EB
+:104A90008A810A4A02908A604A6101225242CA818E
+:104AA000074A086110681D9A019300F07BF8029A8A
+:104AB000002313701CB008BC03B01847FFFFFF7F32
+:104AC0005C040020031C0A7801311A700133002AAB
+:104AD000F9D170470023C25C0133002AFBD1581E74
+:104AE0007047FFFFF0B58D6885B0071C0C1C039262
+:104AF0000193AB4245D390228B89D20013423DD023
+:104B000062690326564309692068F20F401A961914
+:104B10000290021C0198013212187610964200D2BF
+:104B2000161C381C5A050FD5311CFFF737FF051E20
+:104B300013D0029A2169FFF7BFFEA289184B1340D8
+:104B400080221343A38111E0321C00F0B0FA051E4D
+:104B50000CD1381C2169FFF7D9FE0C233B60A389D7
+:104B6000402213430120A381404217E0029B2561AC
+:104B7000ED1825606661019DF61AA6600198A842AD
+:104B800000D2019D2A1C20680399FFF79EFEA268AF
+:104B90000020531BA36023685D19256005B0F0BD9C
+:104BA0007FFBFFFFF0B59FB0039005938B890E1C30
+:104BB000171C19060FD53269002A0CD14021FFF7C6
+:104BC000EDFE30603061002803D103990C230B60A7
+:104BD000C9E04023736106AD00236B6120236B762F
+:104BE0003023AB763C1C2378002B03D1E21B0292CE
+:104BF00011D003E0252BF9D00134F4E70398311CE0
+:104C00003A1C029BFFF76EFF013000D1A6E06969F4
+:104C1000029A8B186B612378002B00D19EE0012251
+:104C200052426A606A4600235B3201342B60EB60BB
+:104C3000AB601370AB654E4F2178381C052200F035
+:104C40002BFA002807D0C71B2B680120B840184357
+:104C500028600134EFE72B68D90603D56A46202186
+:104C60005B3211701A0703D56A462B215B32117033
+:104C700022782A2A01D0099B0EE0059A111D12689C
+:104C80000591002A01DB099204E05242EA60022207
+:104C900013432B60013409E02278303A092A04D802
+:104CA0000A214B4301349B18F6E7099323782E2BF6
+:104CB00018D163782A2B09D1059B02341A1D1B6871
+:104CC0000592002B0DDA01235B420AE00134002338
+:104CD0002278303A092A04D80A214B4301349B1820
+:104CE000F6E70793234F2178381C032200F0D4F90C
+:104CF000002806D0C71B2B684020B8401843286006
+:104D0000013421781C480622671C297600F0C4F97A
+:104D1000002812D0194B002B06D1059B0722073320
+:104D200093430833059314E005AB00930398291CC3
+:104D3000321C134B00E000BF07E005AB0093039863
+:104D4000291C321C0E4B00F091F80490049901319B
+:104D500004D06A69049953186B6143E7B3895A0612
+:104D600001D40B9801E0012040421FB0F0BDC046C5
+:104D7000F2540000F8540000FC5400000000000051
+:104D8000E54A0000F7B5151C01930A698B68061CFB
+:104D90000C1C934200DA131C221C2B604332127845
+:104DA000002A01D001332B602068800602D52B68D1
+:104DB00002332B60216806270F401FD0231C43338A
+:104DC0001B785A1E9341226892061FD5E118403184
+:104DD0003020C870211C5A1C45310978A218403275
+:104DE0000233D17012E0221C301C019919320123C8
+:104DF000089FB847013011D0009F01370097E06845
+:104E00002968009F431A9F42EDDBD7E7221C301C24
+:104E100001994332089FB847013002D10120404236
+:104E200023E0206806212B68E26801400025042960
+:104E300003D1D51AEB43DB171D40A26823699A42C0
+:104E400001DDD31AED1800270097009FAF420BDA5F
+:104E5000221C301C01991A320123089FB8470130E7
+:104E6000DCD0009F0137EFE70020FEBDF0B50D1C40
+:104E70008BB0433506920590079304950B7E0C1C6E
+:104E8000109A6E2B00D1A7E011D8632B22D009D83D
+:104E9000002B00D1B0E0582B00D0C0E045310B70A2
+:104EA0007B4D4EE0642B1CD0692B1AD0B7E0732BDE
+:104EB00000D1A5E009D86F2B29D0702B00D0AEE02F
+:104EC0000E68202333430B6036E0752B1FD0782B00
+:104ED00032D0A4E013680D1C191D423511601B6807
+:104EE0009FE0216813680E0603D5191D11601E6826
+:104EF00005E04806F9D5191D116000215E5E644B7E
+:104F0000002E3BDA049D2D2276422A7036E021687D
+:104F100013680E0603D5191D11601E6804E04806CB
+:104F2000F9D5191D1E881160594B227E039308275D
+:104F30006F2A1ED00A271CE0231C78214533554DCB
+:104F4000197011682368081D039510601E0601D5AD
+:104F50000E6802E05806FBD50E88D90702D520223C
+:104F6000134323601027002E03D1226820239A4385
+:104F70002260231C002243331A7001E003930A27A6
+:104F80006368A360002B03DB25680422954325603A
+:104F9000002E02D1049D002B0ED0049D301C391C24
+:104FA000FEF7D2FE0398013D435C301C2B70391C88
+:104FB000FEF786FE061EF1D1082F09D12168C90728
+:104FC00006D5626823699A4202DC013D30232B70CA
+:104FD000049E731B23612AE008681368496905066B
+:104FE00004D5181D10601B68196005E04606F8D549
+:104FF000181D10601B68198000232361049D16E0B2
+:105000001368191D11601D68281CFFF763FD636894
+:105010002061984200D923612069606004E0251C6A
+:1050200042352B7001232361049E00233370079EB9
+:1050300005980096211C09AA069BFFF7A3FE0130E4
+:1050400002D10120404221E02A1C059806992369DB
+:10505000079DA8470130F4D02668B60705D4099B00
+:10506000E068984212DA181C10E00025E0680999FF
+:10507000431A9D42F3DA221C05980699193201233E
+:10508000079EB0470130DCD00135EFE70BB0F0BD33
+:105090000355000014550000C9B28218904204D094
+:1050A00003788B4202D00130F8E700207047F8B552
+:1050B000061C0C1C151C002904D1111CFFF76EFCEA
+:1050C000041C18E0002A03D1FFF720FC2C1C12E07E
+:1050D00000F013F8A8420ED2301C291CFFF75EFC2A
+:1050E000071E07D0211C2A1CFFF7E6FB301C211CE1
+:1050F000FFF70CFC3C1C201CF8BD04390B68181F82
+:10510000002B02DAC8581B18181F7047256C640062
+:10511000256C75004720256320256C6420256420BC
+:10512000256400656E6400742000350020473A0055
+:105130002050445F73637265656E3A002050445F8F
+:105140006C617365723A00556E6B6E6F776E20639B
+:105150006F6D6D616E643A2000050E08090D0C0636
+:10516000070F0B004417FF050E08090D0C06070F6B
+:105170000B004417FFFFFFFFFFFFFFFFFFFFFF1ABA
+:105180001BFFFFFF420000F840A0044001000000A8
+:10519000420000F844A0044002000000C00000F8F3
+:1051A00000C0044001000000000000F8049004402A
+:1051B00002000000000000F8089004400400000015
+:1051C000C00000F81CC0044080000000C00000F8CF
+:1051D00010C0044010000000C00000F808C00440E7
+:1051E00004000000C00000F80CC0044008000000EB
+:1051F000800000F80CB0044008000000800000F8B7
+:1052000010B0044010000000800000F818B0044006
+:1052100040000000800000F81CB004408000000046
+:10522000800000F814B0044020000000C00000F826
+:1052300004C0044002000000800000F800B00440F8
+:1052400001000000400000F800A004400100000040
+:10525000400000F804A0044002000000400000F8F4
+:105260000CA0044008000000400000F808A0044022
+:1052700004000000C00000F814C00440200000003A
+:10528000C00000F818C0044040000000800000F892
+:1052900004B0044002000000800000F808B00440A0
+:1052A00004000000020100F850D00440100000008B
+:1052B000020100F854D0044020000000030100F86F
+:1052C00078D0044040000000000000000000000012
+:1052D000A9240000B32400008124000089240000D8
+:1052E0009124000099240000A12400000000000087
+:1052F0001519151519FFFFFF000100000703002015
+:10530000120000000002000032030020950000009F
+:1053100000030000E0030020000000000103090476
+:105320001A0300200000000002030904C803002043
+:105330000000000003030904E40300200000000053
+:10534000000000000000000000000000FFFFFFFF61
+:105350000000000000000000213800003938000083
+:10536000F137000001380000F9370000093800006B
+:105370007137000089370000A9370000B1370000FD
+:10538000BB370000C5370000D1370000E13700000F
+:105390001138000019380000413700004D37000077
+:1053A0005937000065370000493800002D380000EB
+:1053B00000000000000000006139000079390000A1
+:1053C0003139000041390000393900004939000005
+:1053D000B1380000C9380000E9380000F138000099
+:1053E000FB380000053900001139000021390000A8
+:1053F0005139000059390000813800008D38000013
+:1054000099380000A5380000893900006D39000086
+:105410000000000000000000A13A0000B93A0000BE
+:10542000713A0000813A0000793A0000893A0000A0
+:10543000F1390000093A0000293A0000313A000031
+:105440003B3A0000453A0000513A0000613A000042
+:10545000913A0000993A0000C1390000CD390000AE
+:10546000D9390000E5390000C93A0000AD3A000022
+:105470008C3F00003E3F00006C3F0000C63E000035
+:105480006C3F0000623F00006C3F0000C63E000021
+:105490003E3F00003E3F0000623F0000C63E00006D
+:1054A000BE3E0000BE3E0000BE3E0000723F000057
+:1054B000304300002A4300002A430000204300003C
+:1054C000804200008042000016430000204300009C
+:1054D000804200001643000080420000204300008C
+:1054E0007E4200007E4200007E420000B843000081
+:1054F0004300232D302B2000686C4C006566674507
+:10550000464700303132333435363738394142433B
+:1055100044454600303132333435363738396162EC
+:105520006364656600FFFFFFF8B5C046F8BC08BCC1
+:105530009E46704739040000310C0000A122000093
+:10554000C124000065380000A5390000E53A0000DC
+:10555000000000009C04002014050020AC04002082
+:105560007C040020FC0400200A040000410E00001E
+:10557000410E0000410E0000410E0000410E0000EF
+:10558000410E0000410E0000410E0000410E0000DF
+:10559000410E0000410E0000410E0000410E0000CF
+:1055A000410E0000410E0000410E0000410E0000BF
+:1055B000410E0000410E0000410E0000410E0000AF
+:1055C000410E0000410E0000410E0000410E00009F
+:1055D000410E0000410E0000FFFFFFFFFF00000032
+:1055E000D80800200107081201010100000040C096
+:1055F000168904000201020301001803540065002B
+:1056000065006E00730079006400750069006E002B
+:105610006F0009029500030100C032080B0002026E
+:105620000201040904000001020201000524001027
+:1056300001052401010104240206052406000107D6
+:1056400005810310004009040100020A0000000760
+:105650000502024000000705830240000009040221
+:10566000000201030000072401000141000624029A
+:1056700001010006240202020009240301030102C1
+:10568000010009240302040101010009050502408B
+:105690000000000005250101010905840240000009
+:1056A0000000052501010300180354006500650092
+:1056B0006E007300790020004D004900440049004D
+:1056C000040309040C0300000000000000000000B7
+:1056D00000000000000000000000000000000000CA
+:1056E00000000000000000000000000000000000BA
+:1056F000000000000000000000000000F054000066
+:105700000000000000000000000000000000000099
+:105710000000000000000000000000000000000089
+:105720000000000000000000000000000000000079
+:10573000000000000000000000000000FC0300204A
+:00000001FF
diff --git a/android/WALT/app/src/main/res/values-w820dp/dimens.xml b/android/WALT/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/android/WALT/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/android/WALT/app/src/main/res/values/attrs.xml b/android/WALT/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..9aad7b9
--- /dev/null
+++ b/android/WALT/app/src/main/res/values/attrs.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <declare-styleable name="NumberPickerPreference">
+ <attr name="minValue" format="integer" />
+ <attr name="maxValue" format="integer" />
+ </declare-styleable>
+ <declare-styleable name="HistogramChart">
+ <attr name="description" format="string" />
+ <attr name="numDataSets" format="integer" />
+ <attr name="binWidth" format="float" />
+ </declare-styleable>
+</resources>
diff --git a/android/WALT/app/src/main/res/values/color.xml b/android/WALT/app/src/main/res/values/color.xml
new file mode 100644
index 0000000..8519775
--- /dev/null
+++ b/android/WALT/app/src/main/res/values/color.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="ColorPrimary">#F5F5F5</color>
+ <color name="ColorPrimaryDark">#757575</color>
+ <color name="ColorAccent">#757575</color>
+ <color name="ColorBackground">#FFFFFF</color>
+ <color name="DarkGreen">#026402</color>
+ <color name="ColorDisabled">#aaaaaa</color>
+</resources>
diff --git a/android/WALT/app/src/main/res/values/dimens.xml b/android/WALT/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/android/WALT/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/android/WALT/app/src/main/res/values/strings.xml b/android/WALT/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7204eaa
--- /dev/null
+++ b/android/WALT/app/src/main/res/values/strings.xml
@@ -0,0 +1,45 @@
+<resources>
+ <string name="app_name">WALT</string>
+
+ <string name="action_settings">Settings</string>
+ <string name="title_activity_crash_log">Crash Log</string>
+ <string name="protocol_version_mismatch">WALT reports protocol version %1$s, which is not
+ supported. Please program WALT to a firmware with protocol version %2$s. To do this
+ from the app, choose "Update WALT Firmware" from the "Diagnostics" menu.</string>
+ <string name="audio_mode">Audio Testing Mode</string>
+ <string name="screen_response_mode">Screen Testing Mode</string>
+ <string name="about_description">WALT is designed to measure the latency of physical sensors
+ and outputs on phones and computers. It can currently perform the following measurements:
+ tap latency, drag latency (scroll), screen draw latency, audio output/microphone
+ latencies, and MIDI input/output latencies.
+ </string>
+ <string name="disclaimer">DISCLAIMER: This is not an official Google product.</string>
+ <string name="more_info">A WALT device is required to run the latency tests. For more information, visit github.com/google/walt</string>
+ <string name="privacy_policy">Privacy policy:\ngithub.com/google/walt/blob/master/docs/PrivacyPolicy.md</string>
+ <string name="preference_screen_blinks" translatable="false">pref_screen_blinks</string>
+ <string name="preference_audio_in_reps" translatable="false">pref_audio_in_reps</string>
+ <string name="preference_audio_in_threshold" translatable="false">pref_audio_in_threshold</string>
+ <string name="preference_audio_out_reps" translatable="false">pref_audio_out_reps</string>
+ <string name="preference_midi_in_reps" translatable="false">pref_midi_in_reps</string>
+ <string name="preference_midi_out_reps" translatable="false">pref_midi_out_reps</string>
+ <string name="preference_auto_increase_brightness">auto_increase_brightness</string>
+ <string name="preference_show_tap_histogram">pref_show_tap_histogram</string>
+ <string name="preference_show_blink_histogram">pref_show_blink_histogram</string>
+ <string name="preference_systrace">pref_systrace</string>
+ <string name="preference_screen_fullscreen">pref_screen_fullscreen</string>
+ <string name="preference_log_url">pref_log_url</string>
+ <string name="preference_auto_upload_log">pref_auto_upload_log</string>
+ <string-array name="audio_mode_array">
+ <item>Continuous Playback Latency</item>
+ <item>Continuous Recording Latency</item>
+ <item>Cold Playback Latency</item>
+ <item>Cold Recording Latency</item>
+ <item>Display Recorded Waveform</item>
+ </string-array>
+ <string-array name="screen_response_mode_array">
+ <item>Blink Latency</item>
+ <item>Brightness Curve</item>
+ <item>Fast Path Graphics</item>
+ </string-array>
+
+</resources>
diff --git a/android/WALT/app/src/main/res/values/styles.xml b/android/WALT/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..02095e7
--- /dev/null
+++ b/android/WALT/app/src/main/res/values/styles.xml
@@ -0,0 +1,61 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+ <item name="colorPrimary">@color/ColorPrimary</item>
+ <item name="colorPrimaryDark">@color/ColorPrimaryDark</item>
+ <item name="colorAccent">@color/ColorAccent</item>
+ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
+ <item name="android:imageButtonStyle">@style/ImageButtonStyle</item>
+ <item name="imageButtonStyle">@style/ImageButtonStyle</item>
+ <!-- the homeAsUpIndicator doesn't work with either png or xml icons -->
+ <!--<item name="android:homeAsUpIndicator">@drawable/ic_chevron_left_black_24dp</item> -->
+
+ </style>
+
+ <style name="MenuDivider">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">1dp</item>
+ <item name="android:background">?android:attr/listDivider</item>
+ <item name="android:layout_marginLeft">72dp</item>
+ </style>
+
+ <style name="MenuTextTop">
+ <!--<item name="android:layout_marginTop">16dp</item>-->
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textColor">@android:color/black</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textSize">22sp</item>
+ </style>
+
+ <style name="MenuTextBottom">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:paddingLeft">2dp</item>
+ </style>
+
+ <style name="MenuIconStyle">
+ <item name="android:layout_width">40dp</item>
+ <item name="android:layout_height">40dp</item>
+ <item name="android:layout_marginLeft">@dimen/activity_horizontal_margin</item>
+ <item name="android:layout_marginRight">@dimen/activity_horizontal_margin</item>
+ <!--<item name="android:layout_gravity">center_vertical</item>-->
+ </style>
+
+ <style name="MenuItemStyle">
+ <item name="android:orientation">horizontal</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:layout_height">72dp</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:clickable">true</item>
+ </style>
+
+ <style name="ImageButtonStyle" parent="Widget.AppCompat.ImageButton">
+ <item name="android:padding">14dp</item>
+ </style>
+
+
+</resources>
diff --git a/android/WALT/app/src/main/res/xml/device_filter.xml b/android/WALT/app/src/main/res/xml/device_filter.xml
new file mode 100644
index 0000000..f4f4a19
--- /dev/null
+++ b/android/WALT/app/src/main/res/xml/device_filter.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Teensy in Serial mode, hex 16C0:0483 -->
+ <usb-device vendor-id="5824" product-id="1155" />
+
+ <!-- Teensy in Serial + keyboard +... mode. Hex 16C0:0483 -->
+ <usb-device vendor-id="5824" product-id="1159" />
+
+ <!-- Teensy in Serial + MIDI mode. Hex 16C0:0485 -->
+ <usb-device vendor-id="5824" product-id="1157" />
+
+ <!-- Teensy in Serial + MIDI mode with Teensyduion v1.31+. Hex 16C0:0489 -->
+ <usb-device vendor-id="5824" product-id="1161" />
+</resources>
diff --git a/android/WALT/app/src/main/res/xml/preferences.xml b/android/WALT/app/src/main/res/xml/preferences.xml
new file mode 100644
index 0000000..6482571
--- /dev/null
+++ b/android/WALT/app/src/main/res/xml/preferences.xml
@@ -0,0 +1,135 @@
+<android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:walt="http://schemas.android.com/apk/res/org.chromium.latency.walt">
+
+ <android.support.v7.preference.PreferenceScreen
+ android:key="pref_general_screen"
+ android:persistent="false"
+ android:title="General">
+
+ <SwitchPreference
+ android:key="@string/preference_systrace"
+ android:title="Enable systrace logging"
+ android:defaultValue="true" />
+
+ <PreferenceCategory android:title="Log Uploading">
+
+ <EditTextPreference
+ android:key="@string/preference_log_url"
+ android:title="URL to upload logs"
+ android:dialogTitle="Enter URL to upload logs"
+ android:defaultValue=""
+ android:inputType="textUri" />
+
+ <SwitchPreference
+ android:key="@string/preference_auto_upload_log"
+ android:title="Auto-upload logs"
+ android:summary="Will upload logs to URL after each test"
+ android:defaultValue="false" />
+
+ </PreferenceCategory>
+
+ </android.support.v7.preference.PreferenceScreen>
+
+ <android.support.v7.preference.PreferenceScreen
+ android:key="pref_tap_screen"
+ android:persistent="false"
+ android:title="Tap latency">
+
+ <SwitchPreference
+ android:key="@string/preference_show_tap_histogram"
+ android:title="Show live histogram for tap test"
+ android:defaultValue="true" />
+
+ </android.support.v7.preference.PreferenceScreen>
+
+ <android.support.v7.preference.PreferenceScreen
+ android:key="pref_screen_response_screen"
+ android:persistent="false"
+ android:title="Screen response">
+
+ <org.chromium.latency.walt.NumberPickerPreference
+ android:defaultValue="20"
+ android:dialogTitle="Number of blinks for screen latency measurement"
+ android:key="@string/preference_screen_blinks"
+ android:summary="%s blinks per test"
+ android:title="Blink latency test length"
+ walt:maxValue="1000"
+ walt:minValue="1" />
+
+ <SwitchPreference
+ android:key="@string/preference_auto_increase_brightness"
+ android:title="Automatically increase brightness for test"
+ android:defaultValue="true" />
+
+ <SwitchPreference
+ android:key="@string/preference_show_blink_histogram"
+ android:title="Show live histogram for blink latency test"
+ android:defaultValue="true" />
+
+ <SwitchPreference
+ android:key="@string/preference_screen_fullscreen"
+ android:title="Test in fullscreen mode"
+ android:defaultValue="true" />
+
+ </android.support.v7.preference.PreferenceScreen>
+
+ <android.support.v7.preference.PreferenceScreen
+ android:key="pref_audio_screen"
+ android:persistent="false"
+ android:title="Audio">
+
+ <org.chromium.latency.walt.NumberPickerPreference
+ android:defaultValue="5"
+ android:dialogTitle="Number of repetitions for audio input latency"
+ android:key="@string/preference_audio_in_reps"
+ android:summary="%s repetitions per test"
+ android:title="Audio input test length"
+ walt:maxValue="1000"
+ walt:minValue="1" />
+
+ <org.chromium.latency.walt.NumberPickerPreference
+ android:defaultValue="10"
+ android:dialogTitle="Number of repetitions for audio output latency"
+ android:key="@string/preference_audio_out_reps"
+ android:summary="%s repetitions per test"
+ android:title="Audio output test length"
+ walt:maxValue="1000"
+ walt:minValue="1" />
+
+ <org.chromium.latency.walt.NumberPickerPreference
+ android:defaultValue="5000"
+ android:dialogTitle="Threshold for audio recording test"
+ android:key="@string/preference_audio_in_threshold"
+ android:summary="%s"
+ android:title="Threshold for audio recording test"
+ walt:maxValue="100000"
+ walt:minValue="1" />
+
+ </android.support.v7.preference.PreferenceScreen>
+
+ <android.support.v7.preference.PreferenceScreen
+ android:key="pref_midi_screen"
+ android:persistent="false"
+ android:title="MIDI">
+
+ <org.chromium.latency.walt.NumberPickerPreference
+ android:defaultValue="100"
+ android:dialogTitle="Number of repetitions for MIDI input measurement"
+ android:key="@string/preference_midi_in_reps"
+ android:summary="%s repetitions per test"
+ android:title="MIDI input test length"
+ walt:maxValue="1000"
+ walt:minValue="1" />
+
+ <org.chromium.latency.walt.NumberPickerPreference
+ android:defaultValue="10"
+ android:dialogTitle="Number of repetitions for MIDI output measurement"
+ android:key="@string/preference_midi_out_reps"
+ android:summary="%s repetitions per test"
+ android:title="MIDI output test length"
+ walt:maxValue="1000"
+ walt:minValue="1" />
+
+ </android.support.v7.preference.PreferenceScreen>
+
+</android.support.v7.preference.PreferenceScreen>
diff --git a/android/WALT/app/src/test/java/org/chromium/latency/walt/HistogramChartTest.java b/android/WALT/app/src/test/java/org/chromium/latency/walt/HistogramChartTest.java
new file mode 100644
index 0000000..8365719
--- /dev/null
+++ b/android/WALT/app/src/test/java/org/chromium/latency/walt/HistogramChartTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.ArrayList;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(android.graphics.Color.class)
+public class HistogramChartTest {
+
+ private BarData barData;
+ private HistogramChart.HistogramData data;
+
+ @Before
+ public void setUp() {
+ mockStatic(android.graphics.Color.class);
+ when(android.graphics.Color.rgb(anyInt(), anyInt(), anyInt())).thenReturn(0);
+ barData = new BarData();
+ barData.setBarWidth((1f - HistogramChart.GROUP_SPACE)/1);
+ barData.addDataSet(new BarDataSet(new ArrayList<BarEntry>(), "SomeLabel"));
+ data = new HistogramChart.HistogramData(1, 5f);
+ data.addEntry(barData, 0, 12);
+ data.addEntry(barData, 0, 14);
+ data.addEntry(barData, 0, 16);
+ data.addEntry(barData, 0, 21);
+ }
+
+ @Test
+ public void testBinHeights() {
+ final IBarDataSet barDataSet = barData.getDataSetByIndex(0);
+ assertEquals(3, barDataSet.getEntryCount());
+ assertEquals(2d, barDataSet.getEntryForIndex(0).getY(), 0.000001);
+ assertEquals(1d, barDataSet.getEntryForIndex(1).getY(), 0.000001);
+ assertEquals(1d, barDataSet.getEntryForIndex(2).getY(), 0.000001);
+ }
+
+ @Test
+ public void testBinXPositions() {
+ final IBarDataSet barDataSet = barData.getDataSetByIndex(0);
+ assertEquals(3, barDataSet.getEntryCount());
+ assertEquals(0d + 0.05d + 0.45d, barDataSet.getEntryForIndex(0).getX(), 0.000001);
+ assertEquals(1d + 0.05d + 0.45d, barDataSet.getEntryForIndex(1).getX(), 0.000001);
+ assertEquals(2d + 0.05d + 0.45d, barDataSet.getEntryForIndex(2).getX(), 0.000001);
+ }
+
+ @Test
+ public void testDisplayValue() {
+ assertEquals(10d, data.getMinBin(), 0.000001);
+ assertEquals(15d, data.getDisplayValue(1), 0.000001);
+ }
+}
diff --git a/android/WALT/app/src/test/java/org/chromium/latency/walt/TraceLoggerTest.java b/android/WALT/app/src/test/java/org/chromium/latency/walt/TraceLoggerTest.java
new file mode 100644
index 0000000..ed617cb
--- /dev/null
+++ b/android/WALT/app/src/test/java/org/chromium/latency/walt/TraceLoggerTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(android.os.Process.class)
+public class TraceLoggerTest {
+
+ @Test
+ public void testLogText() {
+ final TraceLogger traceLogger = TraceLogger.getInstance();
+ traceLogger.log(30012345, 30045678, "SomeTitle", "Some description here");
+ traceLogger.log(40012345, 40045678, "AnotherTitle", "Another description here");
+ mockStatic(android.os.Process.class);
+ when(android.os.Process.myPid()).thenReturn(42);
+ String expected =
+ "WALTThread-[0-9]+ \\(42\\) \\[[0-9]+] .{4} 30\\.012345: tracing_mark_write: B\\|42\\|SomeTitle\\|description=Some description here\\|WALT\n" +
+ "WALTThread-[0-9]+ \\(42\\) \\[[0-9]+] .{4} 30\\.045678: tracing_mark_write: E\\|42\\|SomeTitle\\|\\|WALT\n" +
+ "WALTThread-[0-9]+ \\(42\\) \\[[0-9]+] .{4} 40\\.012345: tracing_mark_write: B\\|42\\|AnotherTitle\\|description=Another description here\\|WALT\n" +
+ "WALTThread-[0-9]+ \\(42\\) \\[[0-9]+] .{4} 40\\.045678: tracing_mark_write: E\\|42\\|AnotherTitle\\|\\|WALT\n";
+ assertTrue(traceLogger.getLogText().matches(expected));
+ }
+}
diff --git a/android/WALT/app/src/test/java/org/chromium/latency/walt/UtilsTest.java b/android/WALT/app/src/test/java/org/chromium/latency/walt/UtilsTest.java
new file mode 100644
index 0000000..bf77e05
--- /dev/null
+++ b/android/WALT/app/src/test/java/org/chromium/latency/walt/UtilsTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import static java.lang.Double.NaN;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class UtilsTest {
+
+ @Test
+ public void testMedian_singleNumber() {
+ ArrayList<Double> arr = new ArrayList<>();
+ arr.add(0d);
+ assertThat(Utils.median(arr), is(0d));
+ }
+
+ @Test
+ public void testMedian_evenSize() {
+ ArrayList<Double> arr = new ArrayList<>();
+ arr.add(1d); arr.add(2d); arr.add(3d); arr.add(4d);
+ assertThat(Utils.median(arr), is(2.5d));
+ }
+
+ @Test
+ public void testMedian_oddSize() {
+ ArrayList<Double> arr = new ArrayList<>();
+ arr.add(1d); arr.add(2d); arr.add(3d); arr.add(4d); arr.add(5d);
+ assertThat(Utils.median(arr), is(3d));
+ }
+
+ @Test
+ public void testMean() {
+ assertThat(Utils.mean(new double[]{-1,1,2,3}), is(1.25d));
+ }
+
+ @Test
+ public void testMean_singleNumber() {
+ assertThat(Utils.mean(new double[]{0}), is(0d));
+ }
+
+ @Test
+ public void testMean_empty() {
+ assertThat(Utils.mean(new double[]{}), is(NaN));
+ }
+
+ @Test
+ public void testMean_repeatedNumbers() {
+ assertThat(Utils.mean(new double[]{5,5,5,5}), is(5d));
+ }
+
+ @Test
+ public void testInterp() {
+ assertThat(Utils.interp(new double[]{5,6,16,17}, new double[]{0, 10, 12, 18},
+ new double[]{35, 50, 75, 93}), is(new double[]{42.5, 44, 87, 90}));
+ }
+
+ @Test
+ public void testInterp_singleNumber() {
+ assertThat(Utils.interp(new double[]{5}, new double[]{0, 10},
+ new double[]{35, 50}), is(new double[]{42.5}));
+ }
+
+ @Test
+ public void testInterp_twoNumbers() {
+ assertThat(Utils.interp(new double[]{0}, new double[]{0, 10},
+ new double[]{35, 50}), is(new double[]{35}));
+ }
+
+ @Test
+ public void testInterp_numberContained() {
+ assertThat(Utils.interp(new double[]{5, 10}, new double[]{0, 5, 10},
+ new double[]{35, 19, 50}), is(new double[]{19, 50}));
+ }
+
+ @Test
+ public void testStdev() {
+ assertThat(Utils.stdev(new double[]{10,12,14,18}), is(Math.sqrt(8.75)));
+ }
+
+ @Test
+ public void testStdev_empty() {
+ assertThat(Utils.stdev(new double[]{}), is(NaN));
+ }
+
+ @Test
+ public void testStdev_singleNumber() {
+ assertThat(Utils.stdev(new double[]{42}), is(0d));
+ }
+
+ @Test
+ public void testStdev_manyNumbers() {
+ assertThat(Utils.stdev(new double[]{-1,0,1}), is(Math.sqrt(2d/3d)));
+ }
+
+ @Test
+ public void testExtract() {
+ assertThat(Utils.extract(new int[]{1, 2, 2, 1, 2, 2, 1, 2, 2}, 1,
+ new double[]{1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}),
+ is(new double[]{1.5, 4.5, 7.5}));
+ }
+
+ @Test
+ public void testExtract_empty() {
+ assertThat(Utils.extract(new int[]{}, 1, new double[]{}), is(new double[]{}));
+ }
+
+ @Test
+ public void testArgmin() {
+ assertThat(Utils.argmin(new double[]{5, 2, 1, -10, -20, 5, 19, 100}), is(4));
+ }
+
+ @Test
+ public void testArgmin_empty() {
+ assertThat(Utils.argmin(new double[]{}), is(0));
+ }
+
+ @Test
+ public void testFindBestShift() {
+ Random rand = new Random(42);
+ double latency = 12.34;
+ double[] touchTimes = new double[4000];
+ for (int i = 0; i < touchTimes.length; i++) {
+ // touch events every millisecond with some jitter
+ touchTimes[i] = i + rand.nextDouble()*0.2 - 0.1;
+ }
+ double[] touchY = new double[touchTimes.length];
+ for (int i = 0; i < touchY.length; i++) {
+ // sine wave will oscillate 1 time
+ touchY[i] = 1000*Math.cos((touchTimes[i] - latency) * Math.PI/500) + rand.nextDouble()*0.02 - 0.01;
+ }
+ double[] laserTimes = new double[4];
+ int i = 0;
+ for (int root = 0; root < 1000; root+=1000) {
+ laserTimes[i++] = root + 250 - 10;
+ laserTimes[i++] = root + 250 + 10;
+ laserTimes[i++] = root + 750 - 10;
+ laserTimes[i++] = root + 750 + 10;
+ }
+ assertEquals(latency, Utils.findBestShift(laserTimes, touchTimes, touchY), 1e-6);
+ }
+}
diff --git a/android/WALT/build.gradle b/android/WALT/build.gradle
new file mode 100644
index 0000000..aaedccb
--- /dev/null
+++ b/android/WALT/build.gradle
@@ -0,0 +1,20 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle-experimental:0.8.3'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven { url "https://jitpack.io" }
+ }
+}
diff --git a/android/WALT/gradle.properties b/android/WALT/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/android/WALT/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/android/WALT/gradle/wrapper/gradle-wrapper.jar b/android/WALT/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..05ef575
--- /dev/null
+++ b/android/WALT/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/android/WALT/gradle/wrapper/gradle-wrapper.properties b/android/WALT/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..aa3841f
--- /dev/null
+++ b/android/WALT/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Nov 29 15:45:52 EST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/android/WALT/gradlew b/android/WALT/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/android/WALT/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/android/WALT/gradlew.bat b/android/WALT/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/android/WALT/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/WALT/settings.gradle b/android/WALT/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/android/WALT/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/arduino/.gitignore b/arduino/.gitignore
new file mode 100644
index 0000000..32992f5
--- /dev/null
+++ b/arduino/.gitignore
@@ -0,0 +1,3 @@
+build
+walt.elf
+walt.hex
\ No newline at end of file
diff --git a/arduino/README.md b/arduino/README.md
new file mode 100644
index 0000000..c8fc5b6
--- /dev/null
+++ b/arduino/README.md
@@ -0,0 +1,14 @@
+### TeensyUSB code ###
+
+Refer to [Teensyduino instructions](https://www.pjrc.com/teensy/teensyduino.html)
+to set up the development environment.
+
+When compiling using Arduino IDE, remember to set the mode to **"Serial + MIDI"**.
+
+Note: the "Serial + MIDI" mode was
+[added recently](https://github.com/PaulStoffregen/cores/commit/a480cd28da49406c297d241a3cbb535e83bec7eb)
+and as of this writing is only available in the
+[beta version of Teensyduiono](https://forum.pjrc.com/threads/34472-Teensyduino-1-29-Beta-1-Available).
+
+Also check out [this template](https://github.com/apmorton/teensy-template)
+for using make and a text editor of your choice for comiling TeensyUSB code.
diff --git a/arduino/walt/walt.ino b/arduino/walt/walt.ino
new file mode 100644
index 0000000..ddb7c95
--- /dev/null
+++ b/arduino/walt/walt.ino
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define VERSION "5"
+
+// Commands
+// Digits 1 to 9 reserved for clock sync
+#define CMD_PING_DELAYED 'D' // Ping/Pong with a delay
+#define CMD_RESET 'F' // Reset all vars
+#define CMD_SYNC_SEND 'I' // Send some digits for clock sync
+#define CMD_PING 'P' // Ping/Pong with a single byte
+#define CMD_VERSION 'V' // Determine which version is running
+#define CMD_SYNC_READOUT 'R' // Read out sync times
+#define CMD_GSHOCK 'G' // Send last shock time and watch for another shock.
+#define CMD_TIME_NOW 'T' // Current time
+#define CMD_SYNC_ZERO 'Z' // Initial zero
+
+#define CMD_AUTO_SCREEN_ON 'C'
+#define CMD_AUTO_SCREEN_OFF 'c'
+#define CMD_SEND_LAST_SCREEN 'E'
+#define CMD_BRIGHTNESS_CURVE 'U'
+
+#define CMD_AUTO_LASER_ON 'L'
+#define CMD_AUTO_LASER_OFF 'l'
+#define CMD_SEND_LAST_LASER 'J'
+
+#define CMD_AUDIO 'A'
+#define CMD_BEEP 'B'
+#define CMD_BEEP_STOP 'S'
+
+#define CMD_SAMPLE_ALL 'Q'
+
+#define CMD_MIDI 'M'
+#define CMD_NOTE 'N'
+
+#define NOTE_DELAY 10000 // 10 ms
+
+// Message types for MIDI encapsulation
+#define MIDI_MODE_TYPE 4 // Program Change
+#define MIDI_COMMAND_TYPE 5 // Channel Pressure
+
+#define MIDI_SYSEX_BEGIN '\xF0'
+#define MIDI_SYSEX_END '\xF7'
+
+// LEDs
+#define LED_PIN_INT 13 // Built-in LED
+#define DEBUG_LED1 11 // On r0.7 PCB: D4 - Red
+#define DEBUG_LED2 12 // On r0.7 PCB: D3 - Green
+
+// WALT sensors
+#define PD_LASER_PIN 14
+#define PD_SCREEN_PIN 20 // Same as A6
+#define G_PIN 15 // Same as A1
+#define AUDIO_PIN 22 // Same as A8
+#define MIC_PIN 23 // Same as A9
+
+// Threshold and hysteresis for screen on/off reading
+#define SCREEN_THRESH_HIGH 800
+#define SCREEN_THRESH_LOW 300
+
+// Shock threshold
+#define GSHOCK_THRESHOLD 500
+
+elapsedMicros time_us;
+
+boolean led_state;
+char tmp_str[256];
+
+boolean serial_over_midi;
+String send_buffer;
+
+struct trigger {
+ long t; // time of latest occurrence in microseconds
+ int value; // value at latest occurrence
+ int count; // occurrences since last readout
+ boolean probe; // whether currently probing
+ boolean autosend; // whether sending over serial each time
+ char tag;
+};
+
+#define TRIGGER_COUNT 5
+struct trigger laser, screen, sound, midi, gshock, copy_trigger;
+struct trigger * triggers[TRIGGER_COUNT] = {&laser, &screen, &sound, &midi, &gshock};
+
+#define CLOCK_SYNC_N 9
+struct clock_sync {
+ boolean is_synced;
+ int last_sent;
+ unsigned long sync_times[CLOCK_SYNC_N];
+};
+
+struct clock_sync clock;
+
+// Interrupt handler for laser photodiode
+void irq_laser(void) {
+ laser.t = time_us;
+ // May need to remove the 'not' if not using internal pullup resistor
+ laser.value = !digitalRead(PD_LASER_PIN);
+ laser.count++;
+
+ digitalWrite(DEBUG_LED2, laser.value);
+ // led_state = !led_state;
+}
+
+void send(char c) { send_buffer += c; }
+void send(String s) { send_buffer += s; }
+
+void send(long l) {
+ char s[32];
+ sprintf(s, "%ld", l);
+ send(s);
+}
+
+void send(unsigned long l) {
+ char s[32];
+ sprintf(s, "%lu", l);
+ send(s);
+}
+
+void send(short i) { send((long)i); }
+void send(int i) { send((long)i); }
+void send(unsigned short i) { send ((unsigned long)i); }
+void send(unsigned int i) { send ((unsigned int)i); }
+
+void send_now() {
+ if (serial_over_midi) {
+ usbMIDI.sendSysEx(send_buffer.length(), (const uint8_t *)send_buffer.c_str());
+ usbMIDI.send_now();
+ send_buffer = MIDI_SYSEX_BEGIN;
+ } else {
+ Serial.write(send_buffer.c_str(), send_buffer.length());
+ Serial.send_now();
+ send_buffer = String();
+ }
+}
+
+void send_line() {
+ if (!serial_over_midi) {
+ send('\n');
+ } else {
+ send(MIDI_SYSEX_END);
+ }
+ send_now();
+}
+
+void send_trigger(struct trigger t) {
+ char s[256];
+ sprintf(s, "G %c %ld %d %d", t.tag, t.t, t.value, t.count);
+ send(s);
+ send_line();
+}
+
+// flips case for a give char. Unchanged if not in [A-Za-z].
+char flip_case(char c) {
+ if (c >= 'A' && c <= 'Z') {
+ return c + 32;
+ }
+ if (c >= 'a' && c <= 'z') {
+ return c - 32;
+ }
+ return c;
+}
+
+// Print the same char as the cmd but with flipped case
+void send_ack(char cmd) {
+ send(flip_case(cmd));
+ send_line();
+}
+
+void init_clock() {
+ memset(&clock, 0, sizeof(struct clock_sync));
+ clock.last_sent = -1;
+}
+
+void init_vars() {
+ noInterrupts();
+ init_clock();
+
+ for (int i = 0; i < TRIGGER_COUNT; i++) {
+ memset(triggers[i], 0, sizeof(struct trigger));
+ }
+
+ laser.tag = 'L';
+ screen.tag = 'S';
+ gshock.tag = 'G';
+ sound.tag = 'A'; // for Audio
+ midi.tag = 'M';
+
+ interrupts();
+}
+
+void setup() {
+ // LEDs
+ pinMode(DEBUG_LED1, OUTPUT);
+ pinMode(DEBUG_LED2, OUTPUT);
+ pinMode(LED_PIN_INT, OUTPUT);
+
+ // Sensors
+ pinMode(PD_SCREEN_PIN, INPUT);
+ pinMode(G_PIN, INPUT);
+ pinMode(PD_LASER_PIN, INPUT_PULLUP);
+ attachInterrupt(PD_LASER_PIN, irq_laser, CHANGE);
+
+ Serial.begin(115200);
+ serial_over_midi = false;
+ init_vars();
+
+ led_state = HIGH; // Turn on all LEDs on startup
+ digitalWrite(LED_PIN_INT, led_state);
+ digitalWrite(DEBUG_LED1, HIGH);
+ digitalWrite(DEBUG_LED2, HIGH);
+}
+
+
+void run_brightness_curve() {
+ int i;
+ long t;
+ short v;
+ digitalWrite(DEBUG_LED1, HIGH);
+ for (i = 0; i < 1000; i++) {
+ v = analogRead(PD_SCREEN_PIN);
+ t = time_us;
+ send(t);
+ send(' ');
+ send(v);
+ send_line();
+ delayMicroseconds(450);
+ }
+ digitalWrite(DEBUG_LED1, LOW);
+ send("end");
+ send_line();
+}
+
+void process_command(char cmd) {
+ int i;
+ if (cmd == CMD_SYNC_ZERO) {
+ noInterrupts();
+ time_us = 0;
+ init_clock();
+ clock.is_synced = true;
+ interrupts();
+ led_state = LOW;
+ digitalWrite(DEBUG_LED1, LOW);
+ digitalWrite(DEBUG_LED2, LOW);
+ send_ack(CMD_SYNC_ZERO);
+ } else if (cmd == CMD_TIME_NOW) {
+ send("t ");
+ send(time_us);
+ send_line();
+ } else if (cmd == CMD_PING) {
+ send_ack(CMD_PING);
+ } else if (cmd == CMD_PING_DELAYED) {
+ delay(10);
+ send_ack(CMD_PING_DELAYED);
+ } else if (cmd >= '1' && cmd <= '9') {
+ clock.sync_times[cmd - '1'] = time_us;
+ clock.last_sent = -1;
+ } else if (cmd == CMD_SYNC_READOUT) {
+ clock.last_sent++;
+ int t = 0;
+ if (clock.last_sent < CLOCK_SYNC_N) {
+ t = clock.sync_times[clock.last_sent];
+ }
+ send(clock.last_sent + 1);
+ send(':');
+ send(t);
+ send_line();
+ } else if (cmd == CMD_SYNC_SEND) {
+ clock.last_sent = -1;
+ // Send CLOCK_SYNC_N times
+ for (i = 0; i < CLOCK_SYNC_N; ++i) {
+ delayMicroseconds(737); // TODO: change to some congifurable random
+ char c = '1' + i;
+ clock.sync_times[i] = time_us;
+ send(c);
+ send_line();
+ }
+ } else if (cmd == CMD_RESET) {
+ init_vars();
+ send_ack(CMD_RESET);
+ } else if (cmd == CMD_VERSION) {
+ send(flip_case(cmd));
+ send(' ');
+ send(VERSION);
+ send_line();
+ } else if (cmd == CMD_GSHOCK) {
+ send(gshock.t); // TODO: Serialize trigger
+ send_line();
+ gshock.t = 0;
+ gshock.count = 0;
+ gshock.probe = true;
+ } else if (cmd == CMD_AUDIO) {
+ sound.t = 0;
+ sound.count = 0;
+ sound.probe = true;
+ sound.autosend = true;
+ send_ack(CMD_AUDIO);
+ } else if (cmd == CMD_BEEP) {
+ long beep_time = time_us;
+ tone(MIC_PIN, 5000 /* Hz */);
+ send(flip_case(cmd));
+ send(' ');
+ send(beep_time);
+ send_line();
+ } else if (cmd == CMD_BEEP_STOP) {
+ noTone(MIC_PIN);
+ send_ack(CMD_BEEP_STOP);
+ } else if (cmd == CMD_MIDI) {
+ midi.t = 0;
+ midi.count = 0;
+ midi.probe = true;
+ midi.autosend = true;
+ send_ack(CMD_MIDI);
+ } else if (cmd == CMD_NOTE) {
+ unsigned long note_time = time_us + NOTE_DELAY;
+ send(flip_case(cmd));
+ send(' ');
+ send(note_time);
+ send_line();
+ while (time_us < note_time);
+ usbMIDI.sendNoteOn(60, 99, 1);
+ usbMIDI.send_now();
+ } else if (cmd == CMD_AUTO_SCREEN_ON) {
+ screen.value = analogRead(PD_SCREEN_PIN) > SCREEN_THRESH_HIGH;
+ screen.autosend = true;
+ screen.probe = true;
+ send_ack(CMD_AUTO_SCREEN_ON);
+ } else if (cmd == CMD_AUTO_SCREEN_OFF) {
+ screen.autosend = false;
+ screen.probe = false;
+ send_ack(CMD_AUTO_SCREEN_OFF);
+ } else if (cmd == CMD_SEND_LAST_SCREEN) {
+ send_trigger(screen);
+ screen.count = 0;
+ } else if (cmd == CMD_AUTO_LASER_ON) {
+ laser.autosend = true;
+ laser.count = 0;
+ send_ack(CMD_AUTO_LASER_ON);
+ } else if (cmd == CMD_AUTO_LASER_OFF) {
+ laser.autosend = false;
+ send_ack(CMD_AUTO_LASER_OFF);
+ } else if (cmd == CMD_SEND_LAST_LASER) {
+ send_trigger(laser);
+ laser.count = 0;
+ } else if (cmd == CMD_BRIGHTNESS_CURVE) {
+ send_ack(CMD_BRIGHTNESS_CURVE);
+ // This blocks all other execution for about 1 second
+ run_brightness_curve();
+ } else if (cmd == CMD_SAMPLE_ALL) {
+ send(flip_case(cmd));
+ send(" G:");
+ send(analogRead(G_PIN));
+ send(" PD_screen:");
+ send(analogRead(PD_SCREEN_PIN));
+ send(" PD_laser:");
+ send(analogRead(PD_LASER_PIN));
+ send_line();
+ } else {
+ send("Unknown command: ");
+ send(cmd);
+ send_line();
+ }
+}
+
+void loop() {
+ digitalWrite(LED_PIN_INT, led_state);
+
+ // Probe the accelerometer
+ if (gshock.probe) {
+ int v = analogRead(G_PIN);
+ if (v > GSHOCK_THRESHOLD) {
+ gshock.t = time_us;
+ gshock.count++;
+ gshock.probe = false;
+ led_state = !led_state;
+ }
+ }
+
+ // Probe audio
+ if (sound.probe) {
+ int v = analogRead(AUDIO_PIN);
+ if (v > 20) {
+ sound.t = time_us;
+ sound.count++;
+ sound.probe = false;
+ led_state = !led_state;
+ }
+ }
+
+ // Probe MIDI
+ boolean has_midi = usbMIDI.read(1);
+ if(has_midi && midi.probe && usbMIDI.getType() == 0) { // Type 1: note on
+ midi.t = time_us;
+ midi.count++;
+ midi.probe = false;
+ led_state = !led_state;
+ }
+
+ // Probe screen
+ if (screen.probe) {
+ int v = analogRead(PD_SCREEN_PIN);
+ if ((screen.value == LOW && v > SCREEN_THRESH_HIGH) || (screen.value != LOW && v < SCREEN_THRESH_LOW)) {
+ screen.t = time_us;
+ screen.count++;
+ led_state = !led_state;
+ screen.value = !screen.value;
+ }
+ }
+
+ // Send out any triggers with autosend and pending data
+ for (int i = 0; i < TRIGGER_COUNT; i++) {
+ boolean should_send = false;
+
+ noInterrupts();
+ if (triggers[i]->autosend && triggers[i]->count > 0) {
+ should_send = true;
+ copy_trigger = *(triggers[i]);
+ triggers[i]->count = 0;
+ }
+ interrupts();
+
+ if (should_send) {
+ send_trigger(copy_trigger);
+ }
+ }
+
+ // Check if we got incoming commands from the host
+ if (has_midi) {
+ if (usbMIDI.getType() == MIDI_MODE_TYPE) {
+ short program = usbMIDI.getData1();
+ serial_over_midi = (program == 1);
+ send_buffer = (serial_over_midi ? MIDI_SYSEX_BEGIN : String());
+ } else if (usbMIDI.getType() == MIDI_COMMAND_TYPE) {
+ char cmd = usbMIDI.getData1();
+ process_command(cmd);
+ }
+ }
+ if (Serial.available()) {
+ char cmd = Serial.read();
+ process_command(cmd);
+ }
+}
+
diff --git a/docs/AudioLatency.md b/docs/AudioLatency.md
new file mode 100644
index 0000000..feac41e
--- /dev/null
+++ b/docs/AudioLatency.md
@@ -0,0 +1,67 @@
+## Audio Latency Measurement
+
+Audio output latency as measured by WALT is the time that passes from the moment an application
+decides to output a tone until it can be detected via the headphone jack. Microphone latency is
+defined similarly.
+
+Low latency audio IO on Android can be achieved via JNI C/C++ code.
+Documentation and sample code can be found on the
+ [High Performance Audio website](http://googlesamples.github.io/android-audio-high-performance/).
+
+
+### Reported values
+
+We are trying to stick to the following (overlapping) principles
+1. Timestamp events as close to hardware as possible. Most events up the stack can be easily timed with software alone.
+1. Measure time intervals that are likely to have low variability.
+
+##### Playback
+
+In order to avoid warm up latency during audio playback it is
+[recommended to constantly enqueue buffers containing silence](http://googlesamples.github.io/android-audio-high-performance/guides/audio-output-latency.html#avoid-warm-up-latency).
+WALT app follows this pattern.
+
+The audio data buffers are enqueued in the
+[player callback](https://github.com/google/walt/blob/v0.1.6/android/WALT/app/src/main/jni/player.c#L107)
+and the latency reported by WALT app is the time from the
+[Enqueue() call](https://github.com/google/walt/blob/v0.1.6/android/WALT/app/src/main/jni/player.c#L123)
+until there is a detectable signal on the wire. Note that this does not include the time between the moment the app decided to output a tone until the Enqueue() call. This is somewhat counterintuitive but this time is deliberately omitted. In case of the WALT app code this time is likely be uniformly distributed between 0 and the length of the buffer (5 ms in case of Nexus 5) and therefore would contribute considerable variance but little interesting information if included in the reported latency.
+
+##### Recording
+The reported latency is the time from the moment the last frame in a buffer was recorded until the
+[recorder callback](https://github.com/google/walt/blob/v0.1.6/android/WALT/app/src/main/jni/player.c#L345)
+receiving that buffer is executed.
+
+TODO: Is the round trip latency expected to be Recording latency + Playback latency + one buffer length?
+
+### Sample measurements
+
+| Device | OS version | Buffer | Playback [ms] | Recording* [ms] |
+| :--- | :--- | :--- | ---: | ---: |
+| Nexus 5 | M4B30Z (6.0.1) | 240 frames @ 48 kHz = 5 ms | 27.6 | 2.5 |
+| Nexus 5X | NRD91P (7.0) | 192 frames @ 48 kHz = 4 ms | 14.9 | 3.5 |
+| Nexus 7 | LMY47Q (5.1) | 240 frames @ 48 kHz = 5 ms | 32.1 | 16.3 |
+| Nexus 9 | MMB29K (6.0.1) | 128 frames @ 48 kHz = 2.6 ms | 9.8 | 1.0 |
+| Nexus 6P | MHC19I (6.0.1) | 192 frames @ 48 kHz = 4 ms | 15.3 | 1.6 |
+| Pixel | NDE63P (7.1) | 192 frames @ 48 kHz = 4 ms | 8.9 | 1.7 |
+| Pixel XL | NDE63H (7.1) | 192 frames @ 48 kHz = 4 ms | 9.1 | 1.6 |
+
+\* WALT clock synchronization accuracy is about 1 ms hence the relative error for recording latency can be fairly high.
+
+#### Published round trip measurements
+Superpowered Inc. maintains an open source app for measuring round trip audio latency -
+[Superpowered Latency App](https://github.com/superpoweredSDK/SuperpoweredLatency).
+
+* [Audio round trip measurements published by Android group](https://source.android.com/devices/audio/latency_measurements.html#measurements)
+* [Audio round trip measurements published by Superpowered Inc.](http://superpowered.com/latency)
+
+
+### Hardware
+
+Audio signal for measuring microphone latency is generated as a square wave using the Teensy tone()
+function ([currently at 5 kHz](https://github.com/google/walt/blob/v0.1.6/arduino/walt/walt.ino#L310)).
+The signal is attenuated by a simple circuit similar to the
+[ChromeOS/Android audio loopback dongle](https://source.android.com/devices/audio/loopback.html).
+
+Audio output signal from the phone is detected when audio line voltage crosses a predefined
+threshold (currently about 65 mV).
diff --git a/docs/ChromeOS.md b/docs/ChromeOS.md
new file mode 100644
index 0000000..6dbf680
--- /dev/null
+++ b/docs/ChromeOS.md
@@ -0,0 +1,44 @@
+## Using WALT on ChromeOS
+
+WALT can be used on ChromeOS in two different modes:
+ 1. Via the [pywalt/walt.py](/pywalt/walt.py) command line script - for this mode refer to [pywalt/README.md](/pywalt/README.md)
+ 1. Using the [WALT Android app](https://play.google.com/store/apps/details?id=org.kamrik.latency.walt) and walt.py script as a bridge
+
+
+For either mode you will need to use a ChromeOS test image -
+[some pointers on how to get it installed](https://www.chromium.org/chromium-os/testing/autotest-developer-faq/ssh-test-keys-setup)
+
+Copy the [pywalt/](/pywalt) directory from WALT repo to the Chromebook. One option is to download a repo tarball directly from GitHub:
+
+```
+wget https://github.com/google/walt/archive/master.tar.gz
+tar -xzf master.tar.gz
+cd walt-master/pywalt
+./walt.py --help
+```
+
+
+
+Connect WALT to Chromebook's USB port and test the setup by running: `$ ./walt.py -t sanity` on the Chromebook.
+This continuously displays readings from WALT's sensors (press Ctrl-C to stop):
+```
+Starting sanity test
+q G:480 PD_screen:3 PD_laser:910 min-max: 480-480 3-3 910-910
+q G:514 PD_screen:3 PD_laser:896 min-max: 480-514 3-3 896-910
+q G:486 PD_screen:4 PD_laser:894 min-max: 480-514 3-4 894-910
+q G:509 PD_screen:4 PD_laser:891 min-max: 480-514 3-4 891-910
+...
+```
+The first reading `G` is the accelerometer, it should change when WALT is rotated.
+`PD_screen` and `PD_laser` are the light sensors (photodiodes), shading them or exposing to light should change their readings.
+
+
+### Using WALT Android app
+
+If you intend to run the android app, run walt.py in TCP bridge mode. This is needed because Android container on ChromeOS has no access to USB.
+ - `iptables -A INPUT -p tcp --dport 50007 -j ACCEPT`
+ - `./walt.py -t bridge`
+
+The script will respond with `Listening on port 50007`. It can be stopped by pressing Ctrl-C. At this point you should be able to use the WALT Android app as if it's running on a regular Android device. If you reset or reconnect the WALT device, you'll need to re-run the script (no need to re-run the iptables command).
+
+If you need to deploy your own version of WALT Android app, follow instruction in [Development.md](Development.md)
diff --git a/docs/Development.md b/docs/Development.md
new file mode 100644
index 0000000..369bce3
--- /dev/null
+++ b/docs/Development.md
@@ -0,0 +1,44 @@
+## Setting up for WALT Android app development
+
+In general WALT Android app has no special requirements but since WALT usually takes up the only available USB connector on the phone, the typical development using ADB via USB becomes difficult. Below are some options to overcome this problem.
+
+
+#### Using a USB hub and a USB Ethernet adapter
+
+ADB can work over TCP connections. The [official documentation](https://developer.android.com/studio/command-line/adb.html#wireless) assumes that the TCP connection is established over WiFi, but a wired Ethernet connection can be used in the same way and is reported to work much better with ADB. Android will recognize and use most USB-Ethernet adapters out of the box.
+
+1. With the phone connected you computer via USB, run `adb tcpip 5555` (you can replace 5555 with any port)
+1. Disconnect the phone from USB
+1. Connect a USB hub (preferably powered) to the phone using a [USB-C to USB-A-female adapter](https://store.google.com/product/usb_type_c_to_usb_standard_a_adapter)
+ or a USB-OTG adapter.
+1. Use the hub to connect WALT, Ethernet adapter and whatever other USB peripherals you might want to test using WALT
+
+Note, this setup is sensitive to the order in which you connect the different components and adapters, experiments with it.
+
+
+#### Using a ChromeOS device with Android
+
+A ChromeOS device with Android (e.g. Asus Flip) is another convenient option.
+
+Since Android on ChromeOS has no access to USB, it requires a TCP bridge script implemented in [walt.py](/pywalt/walt.py). Detailed instructions in [Chromeos.md](Chromeos.md).
+
+In order to set up your for Android development, use either the
+[official guide for Android development on ChromeOS](https://developer.android.com/topic/arc/index.html#setup).
+Or the following short list:
+
+1. Log in to Chromebook via a non-guest account, also avoid restricted corporate/managed accounts
+1. Go to settings, scroll down to Google Play Store and click enable
+1. Click Manage your Android preferences and enable ADB debugging like on a regular Android device:
+ 1. Click About device
+ 1. Keep tapping the build number until it says “You are now a developer” (7 taps)
+ 1. Go back and click on Developer options
+ 1. Enable ADB debugging
+1. Get your Chromebook connected to a network so it would be accessible from you workstation
+1. Switch Chromebook to terminal via ctrl+alt+f2 and log in as root
+1. Run `ifconfig` to get the ip address of eth0 (or wlan0 if using wireless)
+1. On your workstation, run `ssh root@ip_addr` to verify that Chromebook is accessible
+1. Run `adb connect ip_addr:22`. From this point on Android studio and adb on your workstation should treat the Chromebook just like a regular Android device
+1. Run the WALT TCP bridge (more details [here](ChromeOS.md))
+ 1. scp the pywalt folder from your workstation and run the following on the Chromebook:
+ 1. `iptables -A INPUT -p tcp --dport 50007 -j ACCEPT`
+ 1. `python walt.py -t bridge`
diff --git a/docs/DragLatency.md b/docs/DragLatency.md
new file mode 100644
index 0000000..3797e4e
--- /dev/null
+++ b/docs/DragLatency.md
@@ -0,0 +1,41 @@
+## Drag / scroll latency
+
+For detailed instructions on how to perform the measurement refer to the [usage doc](usage/WALT_usage.md#dragscroll-latency).
+
+For drag (or scroll) latency WALT uses a laser that shines across the touch device and hits a detector on the
+other side. The microcontroller monitors the state of the laser detector and reports (over usb) when
+the laser beam is broken. A finger dragged back and forth on a touchpad or touch screen
+and interrupts a laser beam. Touch events from the pad and laser events are then processed together
+to deduce the delay.
+
+A [video](https://plus.google.com/+FrancoisBeaufort/posts/XctAif2nv4U) showing the measurement
+performed using a robotic stylus.
+
+
+![Drag/scroll latency measurement](usage/images/drag.png)
+
+Sample measurements
+
+| Device | OS version | Drag latency [ms]|
+| :--- | :--- | ---: |
+| Nexus 5 | M4B30Z (6.0.1) | 18.4 |
+| Nexus 5X | NRD91P (7.0) | 18.9 |
+| Nexus 7 | LMY47Q (5.1) | 35.8 |
+| Nexus 9 | MMB29K (6.0.1) | 10.2 |
+
+
+## Drag latency calculation
+
+The app collects all the motion events reported by Android with their coordinates and timestamps. The plot on the screenshot below show the vertical coordinate as a function of time as seen by the app _y_(_t_).
+
+The red 'x' marks are points where the finger was at times _t<sub>i</sub>_ when the finger went into or out of the laser beam. The y distance between the two lines of x marks is roughly the thickness of the finger. For now consider only one of the lines, e.g. the upper one.
+
+Now imagine that the clocks timestamping the touch and the laser events diverge a little (this divergence is the latency we want to measure). The red 'x' marks would move along the graph. But half of them would move up and half down (corresponding to direction of the finger movement in that point) and therefore they would diverge in opposite directions from the relatively straight horizontal line you see on the screenshot.
+
+The WALT app finds a shift _S_ such that the standard deviation of _y_( _t<sub>i</sub>_ + _S_) is minimal - that is the 'x' marks are as close as possible to a horizontal straight line on the graph below. The reported number is the average of the two time shifts calculated separately for the upper and the lower lines (corresponding to two sides of the finger).
+
+This calculation that only looks at the _y_ coordinate assumes the laser beam is parallel to the _x_ axis and prefers no motion of the finger in the _x_ direction.
+
+The Python script used for ChromeOS employs a 2D extension of this method (implemented in [minimization.py](/pywalt/minimization.py)). This has the opposite requirement of some considerable spread of the collected _x_ coordinates.
+
+![Drag/scroll latency measurement results](Drag_screen_N5X.png)
diff --git a/docs/Drag_screen_N5X.png b/docs/Drag_screen_N5X.png
new file mode 100644
index 0000000..d82c5a7
--- /dev/null
+++ b/docs/Drag_screen_N5X.png
Binary files differ
diff --git a/docs/Nexus5X_screen_transition.png b/docs/Nexus5X_screen_transition.png
new file mode 100644
index 0000000..2d61083
--- /dev/null
+++ b/docs/Nexus5X_screen_transition.png
Binary files differ
diff --git a/docs/PrivacyPolicy.md b/docs/PrivacyPolicy.md
new file mode 100644
index 0000000..3e96e0c
--- /dev/null
+++ b/docs/PrivacyPolicy.md
@@ -0,0 +1,13 @@
+## WALT Android App Privacy Policy
+
+This file is required in order to comply with Google Play requirements for apps using sensitive permissions:
+https://play.google.com/about/privacy-security/additional-requirements/
+
+WALT app processes all data locally on the Android device and does not transmit any information via the Internet,
+unless you explicitly opt-in to upload test logs and results to our server in the app settings
+(when such functionality becomes available).
+
+If you choose to opt-in to uploading the logs, please note that the logs can (and likely will) become publicly available to all.
+
+WALT app needs the Internet permission on ChromeOS devices where USB is not available to Android apps
+and a TCP bridge is used instead.
diff --git a/docs/ScreenLatency.md b/docs/ScreenLatency.md
new file mode 100644
index 0000000..8a7f1a5
--- /dev/null
+++ b/docs/ScreenLatency.md
@@ -0,0 +1,39 @@
+## Screen latency
+
+WALT can detect when screen changes color between white and black using a photodiode.
+
+![Photo of bottom side of WALT](WALT_bottom_view.png)
+
+Because the screen refreshes at predefined intervals (typically 60 Hz), most drawing commands do not take effect immediately but are processes during the next frame render event. See [Choreographer.FrameCallback](https://developer.android.com/reference/android/view/Choreographer.FrameCallback.html). An in-depth explanation about Android graphics can be found in [this video](https://www.youtube.com/watch?v=Q8m9sHdyXnE).
+
+WALT Android app uses View.setBackgroundColor to change the color and reports the time from the beginning of the next frame rendering until the screen changes physically.
+
+The physical transition between colors is not instantaneous and may look very different depending on the direction - from black to white or white to black. Moreover some sections of the screen may update several milliseconds later than others. These details vary considerably between screen technologies and device models.
+
+Example of screen brightness as a function of time during transition from black to white (blue line) and from white to black (green line) on Nexus 5X. The horizontal lines show the thresholds used by WALT for timing the transition (see #80).
+![Example plot of brightness as a function of time](Nexus5X_screen_transition.png)
+
+### Sample measurements
+
+Example of log output on Nexus 7:
+```
+-------------------------------
+Median screen response latencies (N=100):
+Black to white: 47.3 ms (N=50)
+White to black: 50.2 ms (N=50)
+Average: 48.8 ms
+-------------------------------
+```
+
+
+All measurements below were taken with the device set to max brightness and WALT positioned in the middle of the screen.
+
+| Device | OS version | Blk->white [ms]| White->blk [ms]| Average [ms] |
+| :--- | :--- | ---: | ---: | ---: |
+| Nexus 5 | M4B30Z (6.0.1) | 53.5 | 62.6 | 58.1 |
+| Nexus 5X | NRD91P (7.0) | 60.7 | 75.4 | 68.1 |
+| Nexus 7 | LMY47Q (5.1) | 47.3 | 50.2 | 48.8 |
+| Nexus 9 | MMB29K (6.0.1) | 49.0 | 53.8 | 51.4 |
+
+
+
diff --git a/docs/TapLatency.md b/docs/TapLatency.md
new file mode 100644
index 0000000..a3f84ae
--- /dev/null
+++ b/docs/TapLatency.md
@@ -0,0 +1,33 @@
+## Tap latency
+
+ * [Video demonstration of tap latency measurement](https://www.youtube.com/watch?v=1xAReF75Cts&list=PLd6Fi7WgXfcCEJg1FDqNCoQfpWo7W3J5a&index=2)
+ * [Detailed instructions on how to perform the measurement in usage doc](usage/WALT_usage.md#tap-latency).
+
+WALT uses a “stylus” equipped with an accelerometer. The finger is imitated by a flat metal
+tip that is grounded, pretty much any rigid tip can be used as long as it triggers the touch sensor.
+When the stylus “collides” with touch screen the accelerometer senses a shock (above 3g) which is
+timestamped by the Teensy. In order to generate a similar shock when picking the stylus up from the
+screen, the conductive surface and the accelerometer are mounted on a button of a retractable pen.
+On the way up, the spring keeps the button in contact with the touch screen for the first few mm of
+motion. This allows the hand holding the main part of the pen to gain some speed to which the button is
+then abruptly accelerated generating an easily detectable shock.
+
+Linux [Multi Touch (MT)](https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt)
+implementation timestamps touch events in the kernel as they arrive from the hardware. On Android
+the MT events are then exposed in Java as
+[MotionEvent](http://developer.android.com/reference/android/view/MotionEvent.html)
+and include the kernel timestamp. For tap, the relevant MotionEvent types are
+ACTION_DOWN and ACTION_UP.
+
+Sample measurements
+
+| Device | OS version | ACTION_DOWN [ms]| ACTION_UP [ms]| Kernel to Java [ms] |
+| :--- | :--- | ---: | ---: | ---: |
+| Nexus 5 | M4B30Z (6.0.1) | 26.9 | 15.9 | 3.3 |
+| Nexus 5X | NRD91P (7.0) | 25.0 | 22.5 | 2.4 |
+| Nexus 7 | LMY47Q (5.1) | 29.6 | 31.0 | 1.4 |
+| Nexus 9 | MMB29K (6.0.1) | 18.7 | 19.9 | 1.3 |
+
+
+![Tap measurement](usage/images/tap.png)
+![Tap screenshot](Tap_screen_N7.png)
diff --git a/docs/Tap_screen_N7.png b/docs/Tap_screen_N7.png
new file mode 100644
index 0000000..d78fbae
--- /dev/null
+++ b/docs/Tap_screen_N7.png
Binary files differ
diff --git a/docs/WALT_bottom_view.png b/docs/WALT_bottom_view.png
new file mode 100644
index 0000000..c062047
--- /dev/null
+++ b/docs/WALT_bottom_view.png
Binary files differ
diff --git a/docs/WALT_drag_latency.jpg b/docs/WALT_drag_latency.jpg
new file mode 100644
index 0000000..00c0e46
--- /dev/null
+++ b/docs/WALT_drag_latency.jpg
Binary files differ
diff --git a/docs/WALT_icon.svg b/docs/WALT_icon.svg
new file mode 100644
index 0000000..533afad
--- /dev/null
+++ b/docs/WALT_icon.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="512"
+ height="512"
+ viewBox="0 0 512.00001 512.00001"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r"
+ sodipodi:docname="WALT_icon.svg"
+ inkscape:export-filename="/usr/local/google/home/kamrik/WALT_icon_512.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.7"
+ inkscape:cx="373.97175"
+ inkscape:cy="112.99496"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ units="px"
+ inkscape:window-width="1920"
+ inkscape:window-height="1147"
+ inkscape:window-x="0"
+ inkscape:window-y="720"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-540.36217)">
+ <g
+ id="g4206"
+ transform="matrix(1.1152147,0,0,1.1152147,-28.288187,-98.698129)">
+ <text
+ sodipodi:linespacing="125%"
+ id="text4136"
+ y="1000.7413"
+ x="28.311005"
+ style="font-style:normal;font-weight:normal;font-size:557.60229492px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:557.60229492px;line-height:125%;font-family:MathJax_Math;-inkscape-font-specification:'MathJax_Math, Italic';text-align:start;writing-mode:lr-tb;text-anchor:start"
+ y="1000.7413"
+ x="28.311005"
+ id="tspan4138"
+ sodipodi:role="line">δ</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text4169"
+ y="997.63953"
+ x="253.93924"
+ style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:631.93328857px;line-height:125%;font-family:MathJax_Math;-inkscape-font-specification:'MathJax_Math, Italic';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ y="997.63953"
+ x="253.93924"
+ id="tspan4171"
+ sodipodi:role="line">t</tspan></text>
+ </g>
+ </g>
+</svg>
diff --git a/docs/WALT_photo.jpg b/docs/WALT_photo.jpg
new file mode 100644
index 0000000..c5c5687
--- /dev/null
+++ b/docs/WALT_photo.jpg
Binary files differ
diff --git a/docs/WALT_photo_audio.jpg b/docs/WALT_photo_audio.jpg
new file mode 100644
index 0000000..90420fe
--- /dev/null
+++ b/docs/WALT_photo_audio.jpg
Binary files differ
diff --git a/docs/WALT_photo_audio_r07.jpg b/docs/WALT_photo_audio_r07.jpg
new file mode 100644
index 0000000..98b4268
--- /dev/null
+++ b/docs/WALT_photo_audio_r07.jpg
Binary files differ
diff --git a/docs/WALT_r07_photo.jpg b/docs/WALT_r07_photo.jpg
new file mode 100644
index 0000000..c3177fc
--- /dev/null
+++ b/docs/WALT_r07_photo.jpg
Binary files differ
diff --git a/docs/WALT_stylus.jpg b/docs/WALT_stylus.jpg
new file mode 100644
index 0000000..dec90cc
--- /dev/null
+++ b/docs/WALT_stylus.jpg
Binary files differ
diff --git a/docs/usage/WALT_usage.md b/docs/usage/WALT_usage.md
new file mode 100644
index 0000000..24fd990
--- /dev/null
+++ b/docs/usage/WALT_usage.md
@@ -0,0 +1,261 @@
+# WALT Setup
+
+WALT (**W**hat an **A**wesome **L**atency **T**ester) is a tool for testing latency on Android devices.
+This document will show you how to set up and use your WALT.
+
+About WALT:
+
+ * Source code: https://github.com/google/walt
+ * [Youtube playlist](https://www.youtube.com/playlist?list=PLd6Fi7WgXfcCEJg1FDqNCoQfpWo7W3J5a)
+ * [Android Developers blog post](http://android-developers.blogspot.com/2016/04/a-new-method-to-measure-touch-and-audio.html)
+
+## Prerequisites
+
+WALT kit:
+ * Assembled WALT PCB and base, with DIP socket for Teensy LC
+ * [Teensy LC](http://www.pjrc.com/teensy/teensyLC.html) inserted into DIP socket
+ * 3.5mm male-to-male TRRS cable
+ ([like this](http://www.infinitecables.com/audio-and-pro-audio-cables/3.5mm-cables/3.5mm-male-to-3.5mm-male-4c/3.5mm-male-to-3.5mm-male-4c-black/2ft-3.5mm-4c-male-to-male-28awg-ft4-black/))
+ * Pen modified to replace the top button by a male TS
+ * Optional: laser pointer for measuring touch drag latency
+
+If anything is missing from the list above, please contact the person from whom you received your WALT.
+
+A test device and cables:
+ * Android device with USB micro-B female or type C female and a 3.5mm headset jack
+ * USB OTG (On-The-Go) adapter for your Android device (Examples: [A female to C male](https://store.google.com/product/usb_type_c_to_usb_standard_a_adapter), [A female to micro-B male](https://www.amazon.com/Cable-Matters-Micro-USB-Adapter-6-Inch/dp/B00GM0OZ4O))
+ * USB [Micro-B male to A male](http://www.staples.com/Staples-6-USB-to-Micro-B-Cable-18809-/product_837384) (the usual one we used for everything before type-C)
+ * Laser pointer, if not supplied in the kit
+
+![WALT setup](images/WALT_setup.png)
+
+
+## Setup
+
+ * Install and run the [WALT Android App](https://play.google.com/store/apps/details?id=org.kamrik.latency.walt&hl=en)
+ * Connect WALT to the Android headset using the 3.5mm male to male TRRS cable
+ * Connect WALT to the Android USB port using the cables above. (see photo at top of doc)
+ * You may be prompted to grant the WALT app permission to use the USB, tap "OK"
+ * All 3 LEDs will light up when WALT receives power (see photo below), they will be turned off once the app synchronizes clocks with WALT.
+
+
+## Programming the WALT firmware
+
+1. Launch the app, the main screen is pictured below.
+1. Verify you have the [latest version](https://github.com/google/walt/blob/master/android/WALT/app/build.gradle#L13) installed by tapping on "View log"
+1. Tap "Clock sync" on main menu.
+1. Press the Teensy reset button (see photo below)
+1. Tap "Reprogram WALT"
+1. At this stage you will see several USB permission dialogs. Confirm them all by pressing OK. This is due to WALT presenting itself as different USB devices during reprogramming and normal operation
+1. All 3 LEDs will light briefly (see photo below)
+1. Tap "Reconnect"
+1. Verify that the WALT device has been programmed correctly by running one of the tests below
+
+![WALT app screenshot](images/screenshot.png "WALT app main screen")
+![Teensy reset and reprogram](images/reprogram.jpg "Click Teensy reset button then reprogram WALT from the app")
+
+
+Below is an example of WALT app log when it starts and successfully connects
+to a WALT device. Note the first line and make sure that you use the latest
+version. The last line means that the WALT device clock is behind the Android
+clock but by no more than 161 microseconds. Values about about 1000 us are a
+sign of clock synchronization problems.
+
+
+```
+WALT v0.1.5 (versionCode=6)
+WALT protocol version 4
+DEVICE INFO:
+ google/volantis/flounder:6.0.1/MMB29K/2419427:user/release-keys
+ Build.SDK_INT=23
+ os.version=3.10.40-ga54a4f4
+Requesting permission for USB device.
+Interface claimed successfully
+
+Synced clocks, maxE=161us
+```
+
+# Running the latency tests
+
+This section describes how to run each test and interpret the results.
+
+
+## Audio input latency (microphone)
+
+The Teensy will generate a pulse of audio data which will be received at the 3.5mm headset jack. The time delta between the audio pulse being generated and being received by the WALT application is measured.
+
+ * Tap "Audio latency"
+ * Tap the microphone icon in the top left
+ * Wait for results
+
+
+Below is the output of a test on Nexus 9. The last two lines show the 6 individual measurements and their median. The time measured here is the time that passed from the recording of the _last_ frame in the buffer and until the software callback fired. The length of the optimal buffer in time units can be calculated using the first two lines: 128 frames divided by 48kHz frame rate is about 2.67 ms.
+
+
+```
+Optimal frame rate is: 48000
+Optimal frames per buffer is: 128
+Audio engine created
+Audio recorder created; starting test
+Synced clocks, maxE=154us
+Beeped, reply: 1039241
+Processed: L_cb = 1.167 ms, L_eq = 171.558 ms, noisy frame = 16799
+...
+Beeped, reply: 13621259
+Processed: L_cb = 0.907 ms, L_eq = 203.991 ms, noisy frame = 14465
+Remote clock delayed between -30 and 892 us
+deltas: [1.1671666666666278, 1.1871666666666278, 0.984, 0.9981666666666279, 1.7278333333333722, 0.9071666666666278]
+Median audio recording latency 1.1 ms
+```
+
+## Audio output latency
+
+The WALT app will generate a pulse of audio data which will be transmitted over the 3.5mm headset jack and received by the Teensy. The time delta between audio being transmitted by the app and received by the Teensy is measured
+
+ * Set volume to maximum in Settings -> Sound
+ * Tap "Audio latency"
+ * Tap the play button â–¶ in the top right
+ * Results are displayed in the log window, example below.
+
+```
+Beeping...
+Beeped, dtJ2N = 0.059 ms
+beep detected, total latency = 10.44, normal latency = 10.09, mInitiatedBeeps = 1, mDetectedBeeps = 1
+...
+Remote clock delayed between -53 and 349 us
+deltas: [10.439, 12.272, 11.708, 12.194, 12.919, 11.458, 12.985, 10.914, 10.935, 10.631]
+Median Java to native latency 0.006 ms
+Median total audio latency 11.6 ms
+Median callback to output time 9.9 ms
+```
+
+The callback to output time on the last line, 9.9 ms is the time from the execution of the
+[OpenSLES BufferQueue callback that enqueued the buffer](https://github.com/google/walt/blob/master/android/WALT/app/src/main/jni/player.c#L107)
+until the first frames of that buffer were detected by WALT device via the headphone jack.
+
+
+## Screen response latency
+
+ * Tap screen response
+ * Set screen brightness to maximum. On phones using PWM for screen backlight brightness control, yo man need to turn adaptive brightness off (settings -> Display -> Adaptive Brightness)
+ * Position WALT with the screen sensor looking at the screen (the light sensor recessed into the large plastic surface).
+ * Click start â–¶
+ * The screen will blink some 20 times. The orange LED on the Teensy will also blink every time the bottom light sensor detects a change.
+ * Results are displayed in the log window
+
+```
+Starting screen response measurement
+Synced clocks, maxE=162us
+Blink count was: 0
+Synced clocks, maxE=857us
+Starting Listener
+======
+doBlink.run(), mInitiatedBlinks = 0 mDetectedBlinks = 0
+blink counts 1 1
+======
+...
+======
+doBlink.run(), mInitiatedBlinks = 20 mDetectedBlinks = 20
+Stopping Listener
+Listener stopped
+Remote clock delayed between -16 and 628 us
+deltas: [69.908, 53.902, 54.715, 50.867, 70.73, 50.188, 71.344, 85.259, 40.691, 68.554, 72.016, 51.666, 71.826, 51.234, 71.896, 52.131, 68.943, 51.768, 71.919, 48.812]
+Median latency 61.6 ms
+```
+
+At the time of this writing (WALT v0.1.5) the number reported on the last line
+of the log above is the time from a call to View.setBackgroundColor() until
+the measured screen brightness crosses a threshold
+[hard coded in WALT firmware](https://github.com/google/walt/blob/master/arduino/walt/walt.ino#L70).
+This has significant drawbacks and will change in future versions.
+
+
+
+## Tap latency
+
+[Video demonstration](https://www.youtube.com/watch?v=1xAReF75Cts&list=PLd6Fi7WgXfcCEJg1FDqNCoQfpWo7W3J5a&index=2)
+
+ * Disconnect the audio cable
+ * Insert the supplied stylus (pen with audio jack) into the WALT's audio connector
+ * Click "Tap Latency"
+ * Tap the black part of the screen with WALT's copper tape tip while holding by the pen (photo below). Repeat some 20-30 times. Try to keep WALT pressed against the screen for a bit longer than you would usually keep the finger on the glass during a momentary tap.
+ * Click the checkmark button (top left of the screen), results will be calculated and displayed
+
+WALT has an accelerometer that is used to detect when the copper tip collides with the glass. The copper tip is grounded and triggers the touch sensor. The stylus springy action helps generate a collision shock detectable by accelerometer on the way back, away from the glass.
+
+![Tap latency measurement](images/tap.png)
+
+In the log below the following abbreviations are used:
+ - p2k - physical to kernel - in most cases this is the interesting time.
+ - k2c - kernel to (java) callback
+
+
+```
+## Restarting tap latency measurement. Re-sync clocks ...
+Synced clocks, maxE=158us
+
+...
+
+ACTION_DOWN event received: Event: t=10269422 x=871.7 y=1026.1 slot=-1 num=0 ACTION_DOWN
+Received S reply: 10247405
+
+ACTION_DOWN: dt_p2k = 22.0 ms
+Ignoring ACTION_MOVE 11
+Ignoring ACTION_MOVE 12
+Ignoring ACTION_MOVE 13
+Ignoring ACTION_MOVE 14
+Ignoring ACTION_MOVE 15
+
+ACTION_UP event received: Event: t=10365071 x=871.7 y=1026.1 slot=-1 num=0 ACTION_UP
+Received S reply: 10348526
+
+ACTION_UP: dt_p2k = 16.5 ms
+
+...
+
+## Processing tap latency data
+Remote clock delayed between -60 and 93 us
+Counts: ACTION_DOWN 20 (bad 1), ACTION_UP 21 (bad 0), ACTION_MOVE 162
+
+ACTION_DOWN:
+[26.515, 22.017, 26.952, 29.363, 29.63, 26.624, 31.581, 26.565, 31.335, 26.83, 31.786, 14.702, 19.796, 20.626, 16.688, 18.001, 19.044, 20.261, 17.66, 26.044]
+[1.774, 0.872, 0.888, 0.948, 1.749, 1.781, 1.698, 1.375, 1.709, 1.985, 2.522, 0.994, 1.008, 0.813, 0.746, 1.289, 1.158, 1.093, 0.891, 3.755]
+Medians, p2k & k2c [ms]: 26.3 1.2
+
+ACTION_UP:
+[16.572, 19.515, 16.545, 18.724, 15.188, 14.682, 18.156, 11.69, 20.367, 15.135, 20.601, 44.948, 17.517, 129.513, 24.681, 21.21, 21.979, 22.805, 133.306, 23.876, 17.579]
+[2.161, 2.02, 1.761, 2.222, 1.228, 1.24, 1.48, 1.42, 1.315, 4.034, 1.279, 1.957, 1.041, 2.058, 2.903, 1.725, 0.99, 2.124, 1.398, 3.018, 0.945]
+Medians, p2k & k2c [ms]: 19.5 1.7
+
+```
+
+## Drag/scroll latency
+
+Drag latency is measured by dragging a finger up and down along the screen. The finger interrupts a laser beam positioned across the screen. By comparing the timings of those beam interruptions with Android touch events the WALT app calculates the delay.
+
+
+ * Position a laser pointer so that the beam crosses the screen roughly in the middle. A convenient method for positioning the laser pointer is by using modeling clay (plasticine)
+ * Place WALT on the other side so that the beam hits the WALT optical sensor looking sideways. A green LED (marked D3) will light up whenever the beam hits the sensor
+ * Click the start button â–¶, a green animated line will start going up and down the screen
+ * Drag your finger on the screen, following the animated line
+ * The green counter in the top right corner counts how many times the laser sensor changed state. Each crossing of the beam add 2 to that counter. Continue until the counter shows at least 60
+ * Click the finish **✓**(check mark) button, results will be displayed in the black window
+
+![Drag/scroll latency measurement](images/drag.png)
+
+# Collecting Systrace logs
+
+WALT is able to log physical events as well as system events into systrace. This allows us to look at the system-level calls that occur between the application triggering a change and the WALT device receiving a physical response.
+
+ * In the WALT app go to Settings->General and check `Enable systrace logging`.
+ * On your workstation, follow the [instructions for systrace](https://developer.android.com/studio/profile/systrace-commandline.html) on the command line
+ * From the `android-sdk/platform-tools/systrace` directory, run `python systrace.py --walt`. If `--walt` is not recognized as an option, clone the latest version of [systrace repo](https://github.com/catapult-project/catapult) and from the `catapult/systrace/systrace` directory run `python run_systrace.py --walt`.
+ * Conduct a test through the WALT app. When the test is complete, you should see that TraceLogger wrote some events to a file.
+ * Press enter on your workstation to end the trace and pull the logs from the phone, and the traceview will be generated as HTML.
+
+
+# Troubleshooting
+
+ * Make sure your micro-USB cable is not power-only (can carry data). If it has a button to switch between data and no-data modes (e.g. the KAA cable), click the button.
+ * Make sure volume control on Android DUT is at maximum (update in settings -> sound while TRRS cable is inserted).
+ * Some devices use pulse width modulation (PWM) to control screen brightness. Disable adaptive screen brightness and set brightness manually to maximum.
diff --git a/docs/usage/images/WALT_setup.png b/docs/usage/images/WALT_setup.png
new file mode 100644
index 0000000..b253bf4
--- /dev/null
+++ b/docs/usage/images/WALT_setup.png
Binary files differ
diff --git a/docs/usage/images/drag.png b/docs/usage/images/drag.png
new file mode 100644
index 0000000..e7c81e4
--- /dev/null
+++ b/docs/usage/images/drag.png
Binary files differ
diff --git a/docs/usage/images/reprogram.jpg b/docs/usage/images/reprogram.jpg
new file mode 100644
index 0000000..97dcbbd
--- /dev/null
+++ b/docs/usage/images/reprogram.jpg
Binary files differ
diff --git a/docs/usage/images/screenshot.png b/docs/usage/images/screenshot.png
new file mode 100644
index 0000000..e53d9cd
--- /dev/null
+++ b/docs/usage/images/screenshot.png
Binary files differ
diff --git a/docs/usage/images/tap.png b/docs/usage/images/tap.png
new file mode 100644
index 0000000..0a988c8
--- /dev/null
+++ b/docs/usage/images/tap.png
Binary files differ
diff --git a/hardware/README.md b/hardware/README.md
new file mode 100644
index 0000000..52b9854
--- /dev/null
+++ b/hardware/README.md
@@ -0,0 +1,66 @@
+## Hardware assembly ##
+Depending on your specific needs, you may want to introduce some variations.
+In most cases it's possible to assemble a device with partial functionality (e.g. only for audio latency) on a solderless breadboard.
+
+### List of suggested parts ###
+
+ * USB OTG cable like
+ [this](http://www.amazon.com/Generic-Micro-Cable-Cellphone-Tablet/dp/B00AYPEL56)
+ (or a USB Type-C to A adapter)
+ for connecting WALT to your phone or tablet
+ * Microcontroller board - [Teensy LC](https://www.pjrc.com/teensy/teensyLC.html)
+ * Photodiodes - [BPW34](http://www.digikey.com/product-detail/en/osram-opto-semiconductors-inc/BPW34/475-1070-ND/607274) (3 units)
+ * Laser - any laser pointer will do, ~1 mW is just fine (5 mW is ok, but avoid stronger ones)
+ * Accelerometer board [Adafruit ADXL335](https://www.adafruit.com/product/163) **with filter capacitors removed**.
+ It's very important to remove the filter capacitors, otherwise they smooth out the abrupt shock we are looking for.
+ Alternatively use the ADXL335 chip directly without the breakout board, but it's rather difficult to solder manually.
+ * Some resistors and capacitors - see schematics
+ * TRRS connector or wire for audio measurements
+ * Clipboard, like [this one](https://upload.wikimedia.org/wikipedia/commons/c/c0/Wood-clipboard.jpg)
+
+### Schematic ###
+The current hardware version we use (r0.7) can be found as KiCAD project in this directory.
+ * [PDF](WALT_schematic.pdf)
+ * Blank PCBs can be ordered directly via [this shared project on OSH Park](https://oshpark.com/shared_projects/M5Z8fYCX)
+ * List of parts for version r0.7 - [BOM as a tsv file](WALT_bom_r07.tsv)
+
+ ![WALT r0.7 photo](../docs/WALT_r07_photo.jpg)
+
+
+### Microcontroller code ###
+
+Important pin numbers from the code listed below, defined in [walt.ino](../arduino/walt/walt.ino)
+
+ * PD_LASER_PIN 14 - Photodiode that looks at the laser
+ * G_PIN 15 // Same as A1 - Accelerometer for detecting when touch probe hits the screen
+ * PD_SCREEN_PIN 20 // Same as A6 - Photodiode that looks at the screen
+ * AUDIO_PIN 22 // Same as A8 - Detects audio signal from headphones output
+ * MIC_PIN 23 // Same as A9 - uses PWM to generate a tone for measuring microphone latency.
+
+### Schematic - older version ###
+An older version can be found
+[here on Upverter](https://upverter.com/kamrik/8af1f3b04e47ab78/WALT_w_audio/)
+
+![Slightly simplified WALT Schematic](WALT_schematic_simplified.png)
+
+
+### More electronics notes ###
+
+ * If using Teensy 3.1 instead of LC, it won’t be able to directly read
+ the screen photodiodes - a buffer opamp will be needed. The screen photodiodes
+ are bundled as a couple to produce more current, a single photodiode with 1.5M
+ resistor should provide the same result, but might be at the limit of what the
+ teensy can measure (input impedance), and again, an opamp will be needed.
+ * BPW34 photodiode has a small protrusion on the cathode pin (cathode marker).
+ Anode has a white dot near it.
+ * Note the different setup of the laser and screen photodiode. The laser one
+ uses internal pullup resistor (about 20k), enabled by pinMode(PD_LASER_PIN,
+ INPUT_PULLUP);
+
+## Mechanical ##
+
+Enclosure must be made from a non conductive and non transparent material.
+
+* The enclosure used for r0.7 production run is available in this repo as [STL](enclosure/WALT_recessed_enclosure.stl) and [STEP](enclosure/WALT_recessed_enclosure.step) files. It also used four M3x6 screws (McMaster-Carr PN 92000A116) and M3 hex nuts (McMaster-Carr PN 91828A211)
+* A slightly simpler enclosure suitable for printing on a desktop 3D printer is available as a [public OnShape project](https://cad.onshape.com/documents/6410a1ac7964cb29c4a5ba41/v/32bc78539f48ab7f514eeb31/e/fa513fcc84a825e97020c8b3) and [STL](enclosure/WALT_3DP_enclosure_v7.stl) file
+* A minimal enclosure suitable for 2D cutting from any 5 to 8 mm thick substrate is available as a [public OnShape project](https://cad.onshape.com/documents/5d7f21c8877ea8961bc499ef/v/84a66f3baf5f4a8f41be3bd7/e/e2a23559efbeb63be3916123)
diff --git a/hardware/WALT_bom_r07.tsv b/hardware/WALT_bom_r07.tsv
new file mode 100644
index 0000000..df558ac
--- /dev/null
+++ b/hardware/WALT_bom_r07.tsv
@@ -0,0 +1,14 @@
+Ref Qty Description Type Package Manufacturer Mfg Part #
+U3 1 Single Op Amp LM321 (TI, SOT-23) SMD SOT-23 Texas Instruments LM321MFX/NOPB
+U2 1 Small, Low Power, 3-Axis 3g Accelerometer SMD 16-Lead LFCSP_LQ Analog Devices ADXL335BCPZ
+D3 1 LED GREEN CLEAR 0603 SMD SMD 0603 (1608 metric), polarized Lite-On Inc. LTST-C191KGKT
+D4 1 LED RED CLEAR 0603 SMD SMD 0603 (1608 metric), polarized Lite-On Inc. LTST-C191KRKT
+C1 C2 2 Capacitor (0.1uF) SMD 0805 (2012 metric) Samsung Electro-Mechanics America, Inc. CL21F104ZBCNNNC
+R7,R5 2 Resistor (100) 100 Ohm SMD 0603 (1608 metric) Yageo RC0603FR-07100RL
+R4, R6 2 Resistor (3300) 3.3k SMD 0603 (1608 metric) Yageo RC0603JR-073K3L
+R3 1 Resistor (510000) 510k SMD 0603 (1608 metric) Yageo RC0603FR-07510KL
+R2,R1 2 Resistor (330R) 330 Ohm SMD 0603 (1608 metric) Yageo RC0603JR-07330RL
+D1, D2 2 Photodiode Thru-hole 2-DIP (5.4mm), polarized Vishay / OSRAM (either) BPW34
+J1 1 Audio jack 3.5 mm, TRRS 4 conductors Thru-hole Audio connector, non standard CUI Inc. SJ-43514
+HEADERS4U1 2 Female headers 2.54mm, 14 pins Thru-hole Generic Generic
+P1 1 Male straight headers 2.54mm, 6 pins Thru-hole Generic Generic
diff --git a/hardware/WALT_bom_r09.tsv b/hardware/WALT_bom_r09.tsv
new file mode 100644
index 0000000..dd7bded
--- /dev/null
+++ b/hardware/WALT_bom_r09.tsv
@@ -0,0 +1,14 @@
+Ref Qty Description Type Package Manufacturer Mfg Part #
+U3 1 Single Op Amp LM321 (TI, SOT-23) SMD SOT-23 Texas Instruments LM321MFX/NOPB
+U2 1 Small, Low Power, 3-Axis 3g Accelerometer SMD 16-Lead LFCSP_LQ Analog Devices ADXL335BCPZ
+D3 1 LED GREEN CLEAR 0603 SMD SMD 0603 (1608 metric), polarized Lite-On Inc. LTST-C191KGKT
+D4 1 LED RED CLEAR 0603 SMD SMD 0603 (1608 metric), polarized Lite-On Inc. LTST-C191KRKT
+C1 C2 2 Capacitor (0.1uF) SMD 0805 (2012 metric) Samsung Electro-Mechanics America, Inc. CL21F104ZBCNNNC
+R7,R5, R8 3 Resistor (100) 100 Ohm SMD 0603 (1608 metric) Yageo RC0603FR-07100RL
+R4, R6 2 Resistor (3300) 3.3k SMD 0603 (1608 metric) Yageo RC0603JR-073K3L
+R3 1 Resistor (510000) 510k SMD 0603 (1608 metric) Yageo RC0603FR-07510KL
+R2,R1 2 Resistor (330R) 330 Ohm SMD 0603 (1608 metric) Yageo RC0603JR-07330RL
+D1, D2 2 Photodiode Thru-hole 2-DIP (5.4mm), polarized Vishay / OSRAM (either) BPW34
+J1 1 Audio jack 3.5 mm, TRRS 4 conductors Thru-hole Audio connector, non standard CUI Inc. SJ-43514
+HEADERS4U1 2 Female headers 2.54mm, 14 pins Thru-hole Generic Generic
+P1 1 Male straight headers 2.54mm, 6 pins Thru-hole Generic Generic
diff --git a/hardware/WALT_schematic.pdf b/hardware/WALT_schematic.pdf
new file mode 100644
index 0000000..de1f247
--- /dev/null
+++ b/hardware/WALT_schematic.pdf
Binary files differ
diff --git a/hardware/WALT_schematic_simplified.png b/hardware/WALT_schematic_simplified.png
new file mode 100644
index 0000000..f584e29
--- /dev/null
+++ b/hardware/WALT_schematic_simplified.png
Binary files differ
diff --git a/hardware/enclosure/WALT_3DP_enclosure_v7.stl b/hardware/enclosure/WALT_3DP_enclosure_v7.stl
new file mode 100644
index 0000000..7da39b3
--- /dev/null
+++ b/hardware/enclosure/WALT_3DP_enclosure_v7.stl
Binary files differ
diff --git a/hardware/enclosure/WALT_recessed_enclosure.step b/hardware/enclosure/WALT_recessed_enclosure.step
new file mode 100644
index 0000000..6ad65cd
--- /dev/null
+++ b/hardware/enclosure/WALT_recessed_enclosure.step
@@ -0,0 +1,5245 @@
+ISO-10303-21;
+HEADER;
+FILE_DESCRIPTION (( 'STEP AP203' ),
+ '1' );
+FILE_NAME ('WALT_encl.STEP',
+ '2016-12-08T20:52:21',
+ ( '' ),
+ ( '' ),
+ 'SwSTEP 2.0',
+ 'SolidWorks 2016',
+ '' );
+FILE_SCHEMA (( 'CONFIG_CONTROL_DESIGN' ));
+ENDSEC;
+
+DATA;
+#1 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, -21.69999999999999900, 4.299999999999999800 ) ) ;
+#2 = PLANE ( 'NONE', #1044 ) ;
+#3 = ORIENTED_EDGE ( 'NONE', *, *, #4371, .F. ) ;
+#4 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#5 = EDGE_LOOP ( 'NONE', ( #432, #4474, #4472, #4320 ) ) ;
+#6 = FACE_OUTER_BOUND ( 'NONE', #413, .T. ) ;
+#7 = ORIENTED_EDGE ( 'NONE', *, *, #1786, .F. ) ;
+#8 = DIRECTION ( 'NONE', ( 0.7071067811865474600, 0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#9 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#10 = DIRECTION ( 'NONE', ( -0.4999999999999997200, -0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#11 = CARTESIAN_POINT ( 'NONE', ( -18.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#12 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#13 = EDGE_CURVE ( 'NONE', #434, #51, #3961, .T. ) ;
+#14 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#15 = ADVANCED_FACE ( 'NONE', ( #3475 ), #3692, .T. ) ;
+#16 = EDGE_CURVE ( 'NONE', #57, #2145, #751, .T. ) ;
+#17 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#18 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#19 = ADVANCED_FACE ( 'NONE', ( #1830 ), #850, .F. ) ;
+#20 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, -19.49289321881345000, 4.299999999999999800 ) ) ;
+#21 = DIRECTION ( 'NONE', ( -1.330566893520345700E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#22 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -2.368891848716884900, 6.000000000000000000 ) ) ;
+#23 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953611000E-015, 0.0000000000000000000 ) ) ;
+#24 = AXIS2_PLACEMENT_3D ( 'NONE', #5025, #2235, #5052 ) ;
+#25 = LINE ( 'NONE', #1533, #3109 ) ;
+#26 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#27 = EDGE_CURVE ( 'NONE', #4971, #4104, #3838, .T. ) ;
+#28 = LINE ( 'NONE', #548, #5130 ) ;
+#29 = EDGE_LOOP ( 'NONE', ( #4152, #3335, #566, #2634 ) ) ;
+#30 = VECTOR ( 'NONE', #267, 1000.000000000000000 ) ;
+#31 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 1.782507743686435200, 6.000000000000000000 ) ) ;
+#32 = CIRCLE ( 'NONE', #2035, 1.000000000000000900 ) ;
+#33 = AXIS2_PLACEMENT_3D ( 'NONE', #2716, #312, #3127 ) ;
+#34 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#35 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#36 = VECTOR ( 'NONE', #4432, 1000.000000000000000 ) ;
+#37 = CARTESIAN_POINT ( 'NONE', ( 7.925134423841990700E-016, -6.000000000000000000, 4.299999999999999800 ) ) ;
+#38 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#39 = CARTESIAN_POINT ( 'NONE', ( -1.076572875253784400, 20.27710678118655000, 4.299999999999999800 ) ) ;
+#40 = ORIENTED_EDGE ( 'NONE', *, *, #3694, .T. ) ;
+#41 = ADVANCED_FACE ( 'NONE', ( #5026 ), #1236, .F. ) ;
+#42 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, 2.000000000000000000 ) ) ;
+#43 = CIRCLE ( 'NONE', #4316, 0.9999999999999991100 ) ;
+#44 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, -18.71852980579590000, 4.299999999999999800 ) ) ;
+#45 = EDGE_CURVE ( 'NONE', #2677, #2948, #628, .T. ) ;
+#46 = LINE ( 'NONE', #2413, #998 ) ;
+#47 = ORIENTED_EDGE ( 'NONE', *, *, #1977, .F. ) ;
+#48 = ADVANCED_FACE ( 'NONE', ( #1707 ), #185, .T. ) ;
+#49 = EDGE_CURVE ( 'NONE', #3383, #1403, #2628, .T. ) ;
+#50 = MECHANICAL_CONTEXT ( 'NONE', #2062, 'mechanical' ) ;
+#51 = VERTEX_POINT ( 'NONE', #3986 ) ;
+#52 = FACE_OUTER_BOUND ( 'NONE', #2587, .T. ) ;
+#53 = ORIENTED_EDGE ( 'NONE', *, *, #5024, .F. ) ;
+#54 = VECTOR ( 'NONE', #3181, 1000.000000000000000 ) ;
+#55 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#56 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#57 = VERTEX_POINT ( 'NONE', #2043 ) ;
+#58 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#59 = AXIS2_PLACEMENT_3D ( 'NONE', #2889, #483, #4093 ) ;
+#60 = ORIENTED_EDGE ( 'NONE', *, *, #2488, .F. ) ;
+#61 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#62 = ORIENTED_EDGE ( 'NONE', *, *, #1204, .F. ) ;
+#63 = ORIENTED_EDGE ( 'NONE', *, *, #2234, .T. ) ;
+#64 = LINE ( 'NONE', #2051, #1014 ) ;
+#65 = ORIENTED_EDGE ( 'NONE', *, *, #4878, .T. ) ;
+#66 = AXIS2_PLACEMENT_3D ( 'NONE', #5097, #3094, #683 ) ;
+#67 = ORIENTED_EDGE ( 'NONE', *, *, #4436, .T. ) ;
+#68 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, -16.32049935181331100 ) ) ;
+#69 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#70 = EDGE_CURVE ( 'NONE', #4291, #4955, #3711, .T. ) ;
+#71 = ORIENTED_EDGE ( 'NONE', *, *, #4234, .F. ) ;
+#72 = ORIENTED_EDGE ( 'NONE', *, *, #1678, .F. ) ;
+#73 = LINE ( 'NONE', #737, #2212 ) ;
+#74 = ORIENTED_EDGE ( 'NONE', *, *, #5199, .T. ) ;
+#75 = CC_DESIGN_DATE_AND_TIME_ASSIGNMENT ( #2354, #2882, ( #4751 ) ) ;
+#76 = ORIENTED_EDGE ( 'NONE', *, *, #1860, .T. ) ;
+#77 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#78 = AXIS2_PLACEMENT_3D ( 'NONE', #3052, #655, #3461 ) ;
+#79 = PERSON_AND_ORGANIZATION_ROLE ( 'classification_officer' ) ;
+#80 = ORIENTED_EDGE ( 'NONE', *, *, #1041, .T. ) ;
+#81 = LINE ( 'NONE', #3717, #3977 ) ;
+#82 = ORIENTED_EDGE ( 'NONE', *, *, #2498, .F. ) ;
+#83 = ADVANCED_FACE ( 'NONE', ( #4652 ), #3504, .T. ) ;
+#84 = EDGE_CURVE ( 'NONE', #2393, #4595, #2519, .T. ) ;
+#85 = CARTESIAN_POINT ( 'NONE', ( -18.32398312239625700, 19.57000000000000000, 2.000000000000000000 ) ) ;
+#86 = PLANE ( 'NONE', #1736 ) ;
+#87 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#88 = EDGE_CURVE ( 'NONE', #167, #3719, #4776, .T. ) ;
+#89 = EDGE_CURVE ( 'NONE', #965, #5018, #3579, .T. ) ;
+#90 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#91 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#92 = EDGE_CURVE ( 'NONE', #1546, #3144, #4531, .T. ) ;
+#93 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.938893903907228400E-015, 0.0000000000000000000 ) ) ;
+#94 = VECTOR ( 'NONE', #1233, 1000.000000000000100 ) ;
+#95 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672604900, 19.69999999999999900, -0.6999999999999999600 ) ) ;
+#96 = APPROVAL_DATE_TIME ( #1169, #329 ) ;
+#97 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 0.0000000000000000000, -0.6999999999999999600 ) ) ;
+#98 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, 4.500000000000000000, 4.299999999999999800 ) ) ;
+#99 = FACE_OUTER_BOUND ( 'NONE', #694, .T. ) ;
+#100 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#101 = FACE_OUTER_BOUND ( 'NONE', #5107, .T. ) ;
+#102 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#103 = ADVANCED_FACE ( 'NONE', ( #1451 ), #5074, .T. ) ;
+#104 = LINE ( 'NONE', #5152, #4136 ) ;
+#105 = LINE ( 'NONE', #4167, #4139 ) ;
+#106 = CARTESIAN_POINT ( 'NONE', ( -23.19000000000000100, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#107 = CIRCLE ( 'NONE', #3198, 1.000000000000000000 ) ;
+#108 = VERTEX_POINT ( 'NONE', #5124 ) ;
+#109 = LINE ( 'NONE', #1479, #4264 ) ;
+#110 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#111 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#112 = EDGE_LOOP ( 'NONE', ( #60, #1635, #1511, #3645, #3173, #3085 ) ) ;
+#113 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#114 = ORIENTED_EDGE ( 'NONE', *, *, #2118, .T. ) ;
+#115 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#116 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 6.250000000000000000, 6.000000000000000000 ) ) ;
+#117 = VECTOR ( 'NONE', #1388, 1000.000000000000000 ) ;
+#118 = ORIENTED_EDGE ( 'NONE', *, *, #2367, .T. ) ;
+#119 = EDGE_CURVE ( 'NONE', #3619, #4033, #4404, .T. ) ;
+#120 = ORIENTED_EDGE ( 'NONE', *, *, #4174, .T. ) ;
+#121 = LINE ( 'NONE', #233, #242 ) ;
+#122 = ORIENTED_EDGE ( 'NONE', *, *, #4746, .T. ) ;
+#123 = EDGE_CURVE ( 'NONE', #4048, #2201, #3328, .T. ) ;
+#124 = ORIENTED_EDGE ( 'NONE', *, *, #3964, .T. ) ;
+#125 = LINE ( 'NONE', #4616, #3201 ) ;
+#126 = ORIENTED_EDGE ( 'NONE', *, *, #2247, .T. ) ;
+#127 = VERTEX_POINT ( 'NONE', #5059 ) ;
+#128 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, -19.19999999999999900, 2.000000000000000000 ) ) ;
+#129 = ORIENTED_EDGE ( 'NONE', *, *, #213, .T. ) ;
+#130 = ORIENTED_EDGE ( 'NONE', *, *, #722, .F. ) ;
+#131 = FACE_OUTER_BOUND ( 'NONE', #2802, .T. ) ;
+#132 = ORIENTED_EDGE ( 'NONE', *, *, #1750, .F. ) ;
+#133 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#134 = ORIENTED_EDGE ( 'NONE', *, *, #1163, .T. ) ;
+#135 = AXIS2_PLACEMENT_3D ( 'NONE', #602, #1421, #2206 ) ;
+#136 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#137 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#138 = AXIS2_PLACEMENT_3D ( 'NONE', #482, #3292, #897 ) ;
+#139 = VERTEX_POINT ( 'NONE', #4251 ) ;
+#140 = FACE_BOUND ( 'NONE', #2199, .T. ) ;
+#141 = ORIENTED_EDGE ( 'NONE', *, *, #4671, .F. ) ;
+#142 = EDGE_CURVE ( 'NONE', #1184, #1150, #4279, .T. ) ;
+#143 = ORIENTED_EDGE ( 'NONE', *, *, #2823, .F. ) ;
+#144 = ORIENTED_EDGE ( 'NONE', *, *, #1545, .F. ) ;
+#145 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#146 = EDGE_LOOP ( 'NONE', ( #1144, #656, #1521, #3525 ) ) ;
+#147 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, 21.19999999999999900, 4.299999999999999800 ) ) ;
+#148 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#149 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, -4.500000000000000000, 4.299999999999999800 ) ) ;
+#150 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#151 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, 21.69999999999999900, 4.299999999999999800 ) ) ;
+#152 = EDGE_CURVE ( 'NONE', #2951, #303, #121, .T. ) ;
+#153 = CARTESIAN_POINT ( 'NONE', ( 26.45000000000000300, -6.250000000000000000, 6.000000000000000000 ) ) ;
+#154 = LINE ( 'NONE', #301, #274 ) ;
+#155 = LINE ( 'NONE', #3344, #4317 ) ;
+#156 = AXIS2_PLACEMENT_3D ( 'NONE', #1595, #4403, #1988 ) ;
+#157 = CYLINDRICAL_SURFACE ( 'NONE', #2160, 0.9999999999999991100 ) ;
+#158 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#159 = ORIENTED_EDGE ( 'NONE', *, *, #2768, .T. ) ;
+#160 = VECTOR ( 'NONE', #2971, 1000.000000000000000 ) ;
+#161 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, 21.19999999999999900, -0.6999999999999999600 ) ) ;
+#162 = CARTESIAN_POINT ( 'NONE', ( -24.06714285714285500, -13.05655776574934800, 4.299999999999999800 ) ) ;
+#163 = VERTEX_POINT ( 'NONE', #1027 ) ;
+#164 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #4984 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #2047, #3663, #1262 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
+#165 = LINE ( 'NONE', #1617, #2307 ) ;
+#166 = AXIS2_PLACEMENT_3D ( 'NONE', #2065, #467, #2518 ) ;
+#167 = VERTEX_POINT ( 'NONE', #629 ) ;
+#168 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#169 = VECTOR ( 'NONE', #4822, 1000.000000000000000 ) ;
+#170 = FACE_OUTER_BOUND ( 'NONE', #2752, .T. ) ;
+#171 = ORIENTED_EDGE ( 'NONE', *, *, #1067, .T. ) ;
+#172 = EDGE_CURVE ( 'NONE', #1724, #5099, #2263, .T. ) ;
+#173 = DIRECTION ( 'NONE', ( -0.4999999999999997800, -0.8660254037844387100, 0.0000000000000000000 ) ) ;
+#174 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#175 = ORIENTED_EDGE ( 'NONE', *, *, #4138, .T. ) ;
+#176 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#177 = LINE ( 'NONE', #3968, #3261 ) ;
+#178 = EDGE_CURVE ( 'NONE', #5096, #2905, #2140, .T. ) ;
+#179 = VERTEX_POINT ( 'NONE', #3828 ) ;
+#180 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#181 = PLANE ( 'NONE', #5219 ) ;
+#182 = VECTOR ( 'NONE', #765, 1000.000000000000000 ) ;
+#183 = ORIENTED_EDGE ( 'NONE', *, *, #2667, .T. ) ;
+#184 = ORIENTED_EDGE ( 'NONE', *, *, #240, .T. ) ;
+#185 = PLANE ( 'NONE', #5049 ) ;
+#186 = ORIENTED_EDGE ( 'NONE', *, *, #1351, .T. ) ;
+#187 = CIRCLE ( 'NONE', #4523, 1.000000000000000000 ) ;
+#188 = EDGE_CURVE ( 'NONE', #4290, #3159, #5211, .T. ) ;
+#189 = VERTEX_POINT ( 'NONE', #5016 ) ;
+#190 = ORIENTED_EDGE ( 'NONE', *, *, #2408, .F. ) ;
+#191 = EDGE_CURVE ( 'NONE', #1639, #4291, #4025, .T. ) ;
+#192 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 20.19999999999999900, 2.000000000000000000 ) ) ;
+#193 = ORIENTED_EDGE ( 'NONE', *, *, #2940, .T. ) ;
+#194 = ORIENTED_EDGE ( 'NONE', *, *, #278, .F. ) ;
+#195 = LINE ( 'NONE', #3940, #1146 ) ;
+#196 = EDGE_LOOP ( 'NONE', ( #2390, #4545, #3465, #2579, #1275, #1891 ) ) ;
+#197 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#198 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#199 = ORIENTED_EDGE ( 'NONE', *, *, #2149, .F. ) ;
+#200 = PLANE ( 'NONE', #3105 ) ;
+#201 = CIRCLE ( 'NONE', #4813, 1.000000000000000000 ) ;
+#202 = ORIENTED_EDGE ( 'NONE', *, *, #4567, .T. ) ;
+#203 = PLANE ( 'NONE', #540 ) ;
+#204 = AXIS2_PLACEMENT_3D ( 'NONE', #833, #438, #3246 ) ;
+#205 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#206 = DIRECTION ( 'NONE', ( 1.000000000000000000, 3.469446951953614200E-015, 0.0000000000000000000 ) ) ;
+#207 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 1.543412575162920000, 6.000000000000000000 ) ) ;
+#208 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#209 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#210 = CARTESIAN_POINT ( 'NONE', ( -13.56499942082922100, -21.48999942082920000, 4.299999999999999800 ) ) ;
+#211 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 1.543412575162920000, 6.000000000000000000 ) ) ;
+#212 = LINE ( 'NONE', #1661, #3168 ) ;
+#213 = EDGE_CURVE ( 'NONE', #2621, #4525, #5089, .T. ) ;
+#214 = APPROVAL_PERSON_ORGANIZATION ( #1304, #329, #3290 ) ;
+#215 = CARTESIAN_POINT ( 'NONE', ( -1.567893218813450000, 21.90710678118655300, -0.6999999999999999600 ) ) ;
+#216 = DIRECTION ( 'NONE', ( 1.000000000000000000, 6.123233995736770200E-016, 0.0000000000000000000 ) ) ;
+#217 = EDGE_CURVE ( 'NONE', #5006, #4830, #3071, .T. ) ;
+#218 = VECTOR ( 'NONE', #4681, 1000.000000000000100 ) ;
+#219 = VECTOR ( 'NONE', #2772, 1000.000000000000000 ) ;
+#220 = VECTOR ( 'NONE', #713, 1000.000000000000000 ) ;
+#221 = DIRECTION ( 'NONE', ( 0.5000000000000008900, -0.8660254037844381500, 0.0000000000000000000 ) ) ;
+#222 = VECTOR ( 'NONE', #935, 1000.000000000000000 ) ;
+#223 = VECTOR ( 'NONE', #2938, 1000.000000000000000 ) ;
+#224 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#225 = AXIS2_PLACEMENT_3D ( 'NONE', #2875, #4105, #1691 ) ;
+#226 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 19.19999999999999900, 4.299999999999999800 ) ) ;
+#227 = ORIENTED_EDGE ( 'NONE', *, *, #2845, .F. ) ;
+#228 = DIRECTION ( 'NONE', ( 0.4999999999999979500, -0.8660254037844397100, 0.0000000000000000000 ) ) ;
+#229 = VECTOR ( 'NONE', #1061, 1000.000000000000000 ) ;
+#230 = EDGE_CURVE ( 'NONE', #5161, #2448, #1888, .T. ) ;
+#231 = ORIENTED_EDGE ( 'NONE', *, *, #1976, .F. ) ;
+#232 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#233 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#234 = VERTEX_POINT ( 'NONE', #1105 ) ;
+#235 = LINE ( 'NONE', #98, #1446 ) ;
+#236 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 20.19999999999999900, -0.6999999999999999600 ) ) ;
+#237 = VERTEX_POINT ( 'NONE', #1901 ) ;
+#238 = PLANE ( 'NONE', #78 ) ;
+#239 = EDGE_LOOP ( 'NONE', ( #4173, #193, #3265, #2321, #1470, #495 ) ) ;
+#240 = EDGE_CURVE ( 'NONE', #1201, #4347, #688, .T. ) ;
+#241 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#242 = VECTOR ( 'NONE', #1850, 1000.000000000000000 ) ;
+#243 = ORIENTED_EDGE ( 'NONE', *, *, #5139, .T. ) ;
+#244 = FACE_OUTER_BOUND ( 'NONE', #4219, .T. ) ;
+#245 = ORIENTED_EDGE ( 'NONE', *, *, #4511, .F. ) ;
+#246 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#247 = ORIENTED_EDGE ( 'NONE', *, *, #3249, .F. ) ;
+#248 = EDGE_CURVE ( 'NONE', #692, #1902, #3899, .T. ) ;
+#249 = EDGE_LOOP ( 'NONE', ( #3769, #3268, #3000, #2190 ) ) ;
+#250 = EDGE_CURVE ( 'NONE', #3774, #4706, #3768, .T. ) ;
+#251 = ORIENTED_EDGE ( 'NONE', *, *, #5181, .T. ) ;
+#252 = VECTOR ( 'NONE', #2059, 1000.000000000000000 ) ;
+#253 = ORIENTED_EDGE ( 'NONE', *, *, #1316, .T. ) ;
+#254 = ORIENTED_EDGE ( 'NONE', *, *, #2147, .T. ) ;
+#255 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#256 = ORIENTED_EDGE ( 'NONE', *, *, #327, .T. ) ;
+#257 = VECTOR ( 'NONE', #197, 1000.000000000000000 ) ;
+#258 = VERTEX_POINT ( 'NONE', #3487 ) ;
+#259 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, 16.50999999999999400, 2.500000000000000000 ) ) ;
+#260 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#261 = ORIENTED_EDGE ( 'NONE', *, *, #1239, .T. ) ;
+#262 = ORIENTED_EDGE ( 'NONE', *, *, #3964, .F. ) ;
+#263 = CARTESIAN_POINT ( 'NONE', ( 0.4178458973765375500, -4.500000000000000000, 4.299999999999999800 ) ) ;
+#264 = PLANE ( 'NONE', #4376 ) ;
+#265 = ADVANCED_FACE ( 'NONE', ( #812 ), #731, .T. ) ;
+#266 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#267 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#268 = EDGE_LOOP ( 'NONE', ( #3200, #1077, #1078, #4815, #2824, #2569 ) ) ;
+#269 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#270 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#271 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#272 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#273 = LINE ( 'NONE', #2533, #2295 ) ;
+#274 = VECTOR ( 'NONE', #1123, 1000.000000000000000 ) ;
+#275 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#276 = CARTESIAN_POINT ( 'NONE', ( -18.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#277 = CIRCLE ( 'NONE', #324, 1.000000000000000900 ) ;
+#278 = EDGE_CURVE ( 'NONE', #2471, #258, #4710, .T. ) ;
+#279 = CIRCLE ( 'NONE', #4277, 1.000000000000000000 ) ;
+#280 = FACE_OUTER_BOUND ( 'NONE', #2332, .T. ) ;
+#281 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672371800, 19.57000000000000000, 2.000000000000000000 ) ) ;
+#282 = EDGE_CURVE ( 'NONE', #5113, #1589, #1764, .T. ) ;
+#283 = ADVANCED_FACE ( 'NONE', ( #557 ), #3136, .F. ) ;
+#284 = EDGE_LOOP ( 'NONE', ( #2377, #3575, #3917, #7, #5201, #3657, #3660, #3627 ) ) ;
+#285 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, 21.19999999999999900, 4.299999999999999800 ) ) ;
+#286 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, 18.71852980579590000, 4.299999999999999800 ) ) ;
+#287 = LINE ( 'NONE', #4420, #3628 ) ;
+#288 = ORIENTED_EDGE ( 'NONE', *, *, #530, .T. ) ;
+#289 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#290 = LINE ( 'NONE', #2806, #169 ) ;
+#291 = CARTESIAN_POINT ( 'NONE', ( -13.06789321881344000, 20.99289321881345000, -0.6999999999999999600 ) ) ;
+#292 = LINE ( 'NONE', #3103, #4461 ) ;
+#293 = ORIENTED_EDGE ( 'NONE', *, *, #2498, .T. ) ;
+#294 = ORIENTED_EDGE ( 'NONE', *, *, #2046, .T. ) ;
+#295 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#296 = VECTOR ( 'NONE', #1000, 1000.000000000000000 ) ;
+#297 = ORIENTED_EDGE ( 'NONE', *, *, #1545, .T. ) ;
+#298 = LINE ( 'NONE', #4768, #4471 ) ;
+#299 = ORIENTED_EDGE ( 'NONE', *, *, #4114, .T. ) ;
+#300 = ORIENTED_EDGE ( 'NONE', *, *, #1810, .T. ) ;
+#301 = CARTESIAN_POINT ( 'NONE', ( 26.45000000000000300, 6.250000000000000000, 4.299999999999999800 ) ) ;
+#302 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#303 = VERTEX_POINT ( 'NONE', #270 ) ;
+#304 = ORIENTED_EDGE ( 'NONE', *, *, #3684, .F. ) ;
+#305 = ORIENTED_EDGE ( 'NONE', *, *, #4329, .F. ) ;
+#306 = VECTOR ( 'NONE', #2417, 1000.000000000000000 ) ;
+#307 = EDGE_LOOP ( 'NONE', ( #4345, #1766, #3647, #562 ) ) ;
+#308 = ORIENTED_EDGE ( 'NONE', *, *, #2643, .T. ) ;
+#309 = ADVANCED_FACE ( 'NONE', ( #431 ), #2719, .F. ) ;
+#310 = ORIENTED_EDGE ( 'NONE', *, *, #3384, .T. ) ;
+#311 = VERTEX_POINT ( 'NONE', #4741 ) ;
+#312 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#313 = LINE ( 'NONE', #1498, #2336 ) ;
+#314 = EDGE_CURVE ( 'NONE', #1322, #4276, #3641, .T. ) ;
+#315 = VERTEX_POINT ( 'NONE', #4172 ) ;
+#316 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#317 = EDGE_CURVE ( 'NONE', #2992, #2526, #1505, .T. ) ;
+#318 = CARTESIAN_POINT ( 'NONE', ( -19.99000000000000200, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#319 = ORIENTED_EDGE ( 'NONE', *, *, #1440, .T. ) ;
+#320 = LINE ( 'NONE', #4702, #1293 ) ;
+#321 = ORIENTED_EDGE ( 'NONE', *, *, #4266, .T. ) ;
+#322 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681198800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#323 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#324 = AXIS2_PLACEMENT_3D ( 'NONE', #4551, #2138, #4947 ) ;
+#325 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#326 = AXIS2_PLACEMENT_3D ( 'NONE', #285, #3106, #706 ) ;
+#327 = EDGE_CURVE ( 'NONE', #395, #3704, #1385, .T. ) ;
+#328 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.224646799147352000E-016, 0.0000000000000000000 ) ) ;
+#329 = APPROVAL ( #479, 'UNSPECIFIED' ) ;
+#330 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#331 = AXIS2_PLACEMENT_3D ( 'NONE', #4302, #1886, #4688 ) ;
+#332 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#333 = DIRECTION ( 'NONE', ( -0.1674394999967895000, -0.9858823529411738800, 0.0000000000000000000 ) ) ;
+#334 = ADVANCED_FACE ( 'NONE', ( #4587, #3514 ), #2360, .T. ) ;
+#335 = CIRCLE ( 'NONE', #3563, 1.000000000000000000 ) ;
+#336 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 6.250000000000000000, 4.299999999999999800 ) ) ;
+#337 = COORDINATED_UNIVERSAL_TIME_OFFSET ( 8, 0, .BEHIND. ) ;
+#338 = PLANE ( 'NONE', #3024 ) ;
+#339 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, -21.69999999999999900, 4.299999999999999800 ) ) ;
+#340 = EDGE_CURVE ( 'NONE', #3212, #3025, #1264, .T. ) ;
+#341 = LINE ( 'NONE', #3445, #4510 ) ;
+#342 = CIRCLE ( 'NONE', #5117, 1.000000000000000900 ) ;
+#343 = VECTOR ( 'NONE', #275, 1000.000000000000000 ) ;
+#344 = CARTESIAN_POINT ( 'NONE', ( -18.13655776574935200, 18.98714285714286000, 4.299999999999999800 ) ) ;
+#345 = EDGE_CURVE ( 'NONE', #4276, #2992, #4469, .T. ) ;
+#346 = EDGE_CURVE ( 'NONE', #5078, #1150, #177, .T. ) ;
+#347 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 5.000000000000000000, 6.000000000000000000 ) ) ;
+#348 = FACE_OUTER_BOUND ( 'NONE', #4483, .T. ) ;
+#349 = EDGE_CURVE ( 'NONE', #3521, #4103, #4338, .T. ) ;
+#350 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#351 = VERTEX_POINT ( 'NONE', #373 ) ;
+#352 = CARTESIAN_POINT ( 'NONE', ( -20.02500000000000200, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#353 = AXIS2_PLACEMENT_3D ( 'NONE', #3929, #4322, #1506 ) ;
+#354 = VECTOR ( 'NONE', #1868, 1000.000000000000000 ) ;
+#355 = CARTESIAN_POINT ( 'NONE', ( -2.482106781186544700, 20.99289321881345000, 4.299999999999999800 ) ) ;
+#356 = EDGE_LOOP ( 'NONE', ( #3643, #3519, #4591, #1243, #5103, #5102 ) ) ;
+#357 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#358 = ORIENTED_EDGE ( 'NONE', *, *, #3916, .F. ) ;
+#359 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -6.500000000000000000, -0.6999999999999999600 ) ) ;
+#360 = CIRCLE ( 'NONE', #1611, 4.250000000000000000 ) ;
+#361 = ORIENTED_EDGE ( 'NONE', *, *, #4395, .T. ) ;
+#362 = ADVANCED_FACE ( 'NONE', ( #52 ), #2076, .F. ) ;
+#363 = AXIS2_PLACEMENT_3D ( 'NONE', #4100, #1689, #4503 ) ;
+#364 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#365 = ORIENTED_EDGE ( 'NONE', *, *, #3422, .T. ) ;
+#366 = VECTOR ( 'NONE', #3119, 1000.000000000000100 ) ;
+#367 = ORIENTED_EDGE ( 'NONE', *, *, #865, .F. ) ;
+#368 = VECTOR ( 'NONE', #3360, 1000.000000000000000 ) ;
+#369 = ORIENTED_EDGE ( 'NONE', *, *, #3659, .T. ) ;
+#370 = PLANE ( 'NONE', #2425 ) ;
+#371 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 20.19999999999999900, 4.299999999999999800 ) ) ;
+#372 = ORIENTED_EDGE ( 'NONE', *, *, #2226, .T. ) ;
+#373 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362398700, -16.51000000000000500, -0.0000000000000000000 ) ) ;
+#374 = VERTEX_POINT ( 'NONE', #4002 ) ;
+#375 = VERTEX_POINT ( 'NONE', #1599 ) ;
+#376 = CARTESIAN_POINT ( 'NONE', ( -24.64999999999999900, 12.24398312239625300, 2.000000000000000000 ) ) ;
+#377 = FACE_OUTER_BOUND ( 'NONE', #2825, .T. ) ;
+#378 = ORIENTED_EDGE ( 'NONE', *, *, #1805, .F. ) ;
+#379 = ORIENTED_EDGE ( 'NONE', *, *, #1038, .T. ) ;
+#380 = VERTEX_POINT ( 'NONE', #1993 ) ;
+#381 = LINE ( 'NONE', #3391, #3598 ) ;
+#382 = ORIENTED_EDGE ( 'NONE', *, *, #4130, .T. ) ;
+#383 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 21.44999999999999900, 6.000000000000000000 ) ) ;
+#384 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#385 = EDGE_CURVE ( 'NONE', #3205, #1708, #2320, .T. ) ;
+#386 = ORIENTED_EDGE ( 'NONE', *, *, #1650, .T. ) ;
+#387 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318800800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#388 = EDGE_CURVE ( 'NONE', #3785, #3067, #3135, .T. ) ;
+#389 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#390 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362398700, -16.51000000000000500, -0.0000000000000000000 ) ) ;
+#391 = ADVANCED_FACE ( 'NONE', ( #5150 ), #885, .F. ) ;
+#392 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#393 = AXIS2_PLACEMENT_3D ( 'NONE', #5055, #2635, #228 ) ;
+#394 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#395 = VERTEX_POINT ( 'NONE', #1610 ) ;
+#396 = AXIS2_PLACEMENT_3D ( 'NONE', #4913, #2504, #93 ) ;
+#397 = CARTESIAN_POINT ( 'NONE', ( -13.56499942082922100, 21.48999942082920000, 4.299999999999999800 ) ) ;
+#398 = EDGE_CURVE ( 'NONE', #5222, #189, #1942, .T. ) ;
+#399 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, -19.69999999999999900, 0.0000000000000000000 ) ) ;
+#400 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#401 = CIRCLE ( 'NONE', #2649, 1.000000000000000900 ) ;
+#402 = CIRCLE ( 'NONE', #326, 0.9999999999999991100 ) ;
+#403 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#404 = VECTOR ( 'NONE', #3230, 1000.000000000000000 ) ;
+#405 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#406 = LINE ( 'NONE', #3305, #4568 ) ;
+#407 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#408 = VECTOR ( 'NONE', #2616, 1000.000000000000000 ) ;
+#409 = ADVANCED_FACE ( 'NONE', ( #3002 ), #1824, .F. ) ;
+#410 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#411 = FACE_OUTER_BOUND ( 'NONE', #798, .T. ) ;
+#412 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#413 = EDGE_LOOP ( 'NONE', ( #1900, #825, #822, #1317 ) ) ;
+#414 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, 0.0000000000000000000 ) ) ;
+#415 = FACE_OUTER_BOUND ( 'NONE', #425, .T. ) ;
+#416 = ADVANCED_FACE ( 'NONE', ( #4090 ), #3224, .T. ) ;
+#417 = EDGE_CURVE ( 'NONE', #4670, #1391, #746, .T. ) ;
+#418 = CARTESIAN_POINT ( 'NONE', ( -13.98210678118653900, -21.90710678118655300, 4.299999999999999800 ) ) ;
+#419 = VERTEX_POINT ( 'NONE', #2038 ) ;
+#420 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#421 = LINE ( 'NONE', #2397, #1379 ) ;
+#422 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#423 = CARTESIAN_POINT ( 'NONE', ( -23.19000000000000100, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#424 = LINE ( 'NONE', #4307, #1380 ) ;
+#425 = EDGE_LOOP ( 'NONE', ( #4050, #1094, #1448, #4402, #1458, #2778, #2896, #754, #2893, #3372, #2337, #2335, #4953, #3919, #3345, #3323, #5208, #4413, #1516, #635 ) ) ;
+#426 = AXIS2_PLACEMENT_3D ( 'NONE', #371, #3182, #781 ) ;
+#427 = PLANE ( 'NONE', #2379 ) ;
+#428 = ORIENTED_EDGE ( 'NONE', *, *, #2602, .T. ) ;
+#429 = AXIS2_PLACEMENT_3D ( 'NONE', #3196, #794, #3606 ) ;
+#430 = DIRECTION ( 'NONE', ( 0.5000000000000005600, 0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#431 = FACE_OUTER_BOUND ( 'NONE', #1493, .T. ) ;
+#432 = ORIENTED_EDGE ( 'NONE', *, *, #3216, .F. ) ;
+#433 = LINE ( 'NONE', #4358, #1508 ) ;
+#434 = VERTEX_POINT ( 'NONE', #1629 ) ;
+#435 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#436 = ORIENTED_EDGE ( 'NONE', *, *, #1605, .F. ) ;
+#437 = AXIS2_PLACEMENT_3D ( 'NONE', #4454, #4860, #2446 ) ;
+#438 = DIRECTION ( 'NONE', ( -0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#439 = ORIENTED_EDGE ( 'NONE', *, *, #2877, .T. ) ;
+#440 = EDGE_LOOP ( 'NONE', ( #2581, #1992, #5228, #2959, #3570, #3321, #772, #5151, #3959, #3017, #2782, #3400, #4480, #304, #3886, #832, #261, #2368, #2405, #1730 ) ) ;
+#441 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#442 = LINE ( 'NONE', #2793, #3396 ) ;
+#443 = ORIENTED_EDGE ( 'NONE', *, *, #1875, .T. ) ;
+#444 = VERTEX_POINT ( 'NONE', #2836 ) ;
+#445 = DIRECTION ( 'NONE', ( 1.224646799147352000E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#446 = ADVANCED_FACE ( 'NONE', ( #2885 ), #1701, .T. ) ;
+#447 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#448 = ORIENTED_EDGE ( 'NONE', *, *, #4308, .F. ) ;
+#449 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#450 = FACE_OUTER_BOUND ( 'NONE', #4177, .T. ) ;
+#451 = ORIENTED_EDGE ( 'NONE', *, *, #3588, .T. ) ;
+#452 = PRODUCT ( 'WALT_encl', 'WALT_encl', '', ( #50 ) ) ;
+#453 = CARTESIAN_POINT ( 'NONE', ( -18.32398312239625700, -19.57000000000000000, 2.000000000000000000 ) ) ;
+#454 = EDGE_CURVE ( 'NONE', #3339, #2829, #2192, .T. ) ;
+#455 = ADVANCED_FACE ( 'NONE', ( #4903 ), #1656, .T. ) ;
+#456 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#457 = ORIENTED_EDGE ( 'NONE', *, *, #1350, .F. ) ;
+#458 = CYLINDRICAL_SURFACE ( 'NONE', #4284, 4.250000000000000000 ) ;
+#459 = ORIENTED_EDGE ( 'NONE', *, *, #2149, .T. ) ;
+#460 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#461 = EDGE_CURVE ( 'NONE', #375, #1353, #2744, .T. ) ;
+#462 = DIRECTION ( 'NONE', ( -0.7071067811865475700, 0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#463 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#464 = APPLICATION_CONTEXT ( 'configuration controlled 3d designs of mechanical parts and assemblies' ) ;
+#465 = EDGE_CURVE ( 'NONE', #3025, #4350, #3706, .T. ) ;
+#466 = EDGE_CURVE ( 'NONE', #1519, #1578, #2624, .T. ) ;
+#467 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#468 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#469 = DIRECTION ( 'NONE', ( 0.5000000000000003300, 0.8660254037844383700, 0.0000000000000000000 ) ) ;
+#470 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#471 = ADVANCED_FACE ( 'NONE', ( #4769 ), #1670, .F. ) ;
+#472 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, -3.500000000000000000, 6.000000000000000000 ) ) ;
+#473 = AXIS2_PLACEMENT_3D ( 'NONE', #2879, #476, #3288 ) ;
+#474 = VECTOR ( 'NONE', #392, 1000.000000000000000 ) ;
+#475 = DATE_AND_TIME ( #1548, #2606 ) ;
+#476 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#477 = ADVANCED_FACE ( 'NONE', ( #3574 ), #4526, .T. ) ;
+#478 = VECTOR ( 'NONE', #208, 1000.000000000000000 ) ;
+#479 = APPROVAL_STATUS ( 'not_yet_approved' ) ;
+#480 = LINE ( 'NONE', #347, #4512 ) ;
+#481 = VECTOR ( 'NONE', #2290, 1000.000000000000000 ) ;
+#482 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#483 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#484 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#485 = LINE ( 'NONE', #2114, #4632 ) ;
+#486 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#487 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#488 = ORIENTED_EDGE ( 'NONE', *, *, #340, .F. ) ;
+#489 = FACE_OUTER_BOUND ( 'NONE', #3453, .T. ) ;
+#490 = EDGE_CURVE ( 'NONE', #4103, #1574, #360, .T. ) ;
+#491 = DIRECTION ( 'NONE', ( -6.123233995736770200E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#492 = LINE ( 'NONE', #1703, #1441 ) ;
+#493 = VERTEX_POINT ( 'NONE', #453 ) ;
+#494 = EDGE_CURVE ( 'NONE', #4861, #311, #3448, .T. ) ;
+#495 = ORIENTED_EDGE ( 'NONE', *, *, #4401, .F. ) ;
+#496 = ADVANCED_FACE ( 'NONE', ( #497 ), #4398, .F. ) ;
+#497 = FACE_OUTER_BOUND ( 'NONE', #1217, .T. ) ;
+#498 = ORIENTED_EDGE ( 'NONE', *, *, #466, .T. ) ;
+#499 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#500 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#501 = ORIENTED_EDGE ( 'NONE', *, *, #2408, .T. ) ;
+#502 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#503 = AXIS2_PLACEMENT_3D ( 'NONE', #2014, #821, #3629 ) ;
+#504 = VECTOR ( 'NONE', #1175, 1000.000000000000000 ) ;
+#505 = CARTESIAN_POINT ( 'NONE', ( -28.39129536136890100, 3.262384295071445000, 1.000000000000000000 ) ) ;
+#506 = VERTEX_POINT ( 'NONE', #4120 ) ;
+#507 = CARTESIAN_POINT ( 'NONE', ( 0.4178458973765375500, -4.500000000000000000, 4.299999999999999800 ) ) ;
+#508 = CIRCLE ( 'NONE', #2261, 4.250000000000000000 ) ;
+#509 = ORIENTED_EDGE ( 'NONE', *, *, #2410, .F. ) ;
+#510 = ORIENTED_EDGE ( 'NONE', *, *, #2562, .T. ) ;
+#511 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -19.19999999999999900, -0.6999999999999999600 ) ) ;
+#512 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#513 = EDGE_CURVE ( 'NONE', #4283, #3912, #235, .T. ) ;
+#514 = ORIENTED_EDGE ( 'NONE', *, *, #3456, .T. ) ;
+#515 = ORIENTED_EDGE ( 'NONE', *, *, #1860, .F. ) ;
+#516 = VECTOR ( 'NONE', #2798, 1000.000000000000000 ) ;
+#517 = ORIENTED_EDGE ( 'NONE', *, *, #4939, .F. ) ;
+#518 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, -19.19999999999999900, -0.6999999999999999600 ) ) ;
+#519 = ORIENTED_EDGE ( 'NONE', *, *, #3865, .F. ) ;
+#520 = AXIS2_PLACEMENT_3D ( 'NONE', #1282, #863, #4468 ) ;
+#521 = ORIENTED_EDGE ( 'NONE', *, *, #4994, .T. ) ;
+#522 = FACE_OUTER_BOUND ( 'NONE', #1878, .T. ) ;
+#523 = ORIENTED_EDGE ( 'NONE', *, *, #4718, .F. ) ;
+#524 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#525 = VERTEX_POINT ( 'NONE', #3361 ) ;
+#526 = EDGE_CURVE ( 'NONE', #2095, #3733, #2257, .T. ) ;
+#527 = CARTESIAN_POINT ( 'NONE', ( -7.774999999999999500, 20.69999999999999900, 4.299999999999999800 ) ) ;
+#528 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#529 = CARTESIAN_POINT ( 'NONE', ( -23.19000000000000100, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#530 = EDGE_CURVE ( 'NONE', #778, #2315, #2513, .T. ) ;
+#531 = EDGE_CURVE ( 'NONE', #1254, #958, #1325, .T. ) ;
+#532 = AXIS2_PLACEMENT_3D ( 'NONE', #4443, #18, #818 ) ;
+#533 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#534 = PLANE ( 'NONE', #681 ) ;
+#535 = VECTOR ( 'NONE', #468, 1000.000000000000000 ) ;
+#536 = CARTESIAN_POINT ( 'NONE', ( -2.482106781186544700, 20.99289321881345000, -0.6999999999999999600 ) ) ;
+#537 = VECTOR ( 'NONE', #3066, 1000.000000000000200 ) ;
+#538 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318800800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#539 = CYLINDRICAL_SURFACE ( 'NONE', #1929, 1.000000000000000000 ) ;
+#540 = AXIS2_PLACEMENT_3D ( 'NONE', #3004, #3419, #1019 ) ;
+#541 = LINE ( 'NONE', #3699, #2553 ) ;
+#542 = LINE ( 'NONE', #4628, #408 ) ;
+#543 = VECTOR ( 'NONE', #1342, 1000.000000000000000 ) ;
+#544 = PLANE ( 'NONE', #715 ) ;
+#545 = EDGE_LOOP ( 'NONE', ( #4904, #2627, #4914, #4318 ) ) ;
+#546 = CIRCLE ( 'NONE', #1427, 1.600000000000000800 ) ;
+#547 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#548 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -20.57000000000000000, 4.299999999999998000 ) ) ;
+#549 = ORIENTED_EDGE ( 'NONE', *, *, #1810, .F. ) ;
+#550 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 1.782507743686435200, 0.2999999999999999900 ) ) ;
+#551 = CARTESIAN_POINT ( 'NONE', ( -13.98210678118653900, 21.90710678118655300, 4.299999999999999800 ) ) ;
+#552 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#553 = FACE_OUTER_BOUND ( 'NONE', #2433, .T. ) ;
+#554 = VERTEX_POINT ( 'NONE', #2172 ) ;
+#555 = EDGE_CURVE ( 'NONE', #1574, #999, #2135, .T. ) ;
+#556 = ORIENTED_EDGE ( 'NONE', *, *, #2871, .T. ) ;
+#557 = FACE_OUTER_BOUND ( 'NONE', #1958, .T. ) ;
+#558 = ORIENTED_EDGE ( 'NONE', *, *, #3044, .F. ) ;
+#559 = EDGE_CURVE ( 'NONE', #4951, #4926, #3191, .T. ) ;
+#560 = PLANE ( 'NONE', #1261 ) ;
+#561 = VECTOR ( 'NONE', #5140, 1000.000000000000000 ) ;
+#562 = ORIENTED_EDGE ( 'NONE', *, *, #5070, .T. ) ;
+#563 = VERTEX_POINT ( 'NONE', #2187 ) ;
+#564 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, -21.19999999999999900, -0.6999999999999999600 ) ) ;
+#565 = ORIENTED_EDGE ( 'NONE', *, *, #2729, .T. ) ;
+#566 = ORIENTED_EDGE ( 'NONE', *, *, #1956, .F. ) ;
+#567 = VERTEX_POINT ( 'NONE', #4195 ) ;
+#568 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -10.27254486838324100, 6.000000000000000000 ) ) ;
+#569 = VECTOR ( 'NONE', #1995, 1000.000000000000000 ) ;
+#570 = ORIENTED_EDGE ( 'NONE', *, *, #4399, .F. ) ;
+#571 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#572 = EDGE_LOOP ( 'NONE', ( #2252, #144 ) ) ;
+#573 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.040834085586084300E-014, 0.0000000000000000000 ) ) ;
+#574 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#575 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#576 = AXIS2_PLACEMENT_3D ( 'NONE', #3862, #4259, #1856 ) ;
+#577 = EDGE_LOOP ( 'NONE', ( #1829, #898, #4935, #2333 ) ) ;
+#578 = FACE_OUTER_BOUND ( 'NONE', #4481, .T. ) ;
+#579 = VERTEX_POINT ( 'NONE', #1798 ) ;
+#580 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#581 = ORIENTED_EDGE ( 'NONE', *, *, #3637, .F. ) ;
+#582 = CYLINDRICAL_SURFACE ( 'NONE', #3470, 0.9999999999999991100 ) ;
+#583 = ORIENTED_EDGE ( 'NONE', *, *, #3041, .F. ) ;
+#584 = AXIS2_PLACEMENT_3D ( 'NONE', #399, #3219, #809 ) ;
+#585 = DIRECTION ( 'NONE', ( -0.5000000000000008900, 0.8660254037844381500, 0.0000000000000000000 ) ) ;
+#586 = CARTESIAN_POINT ( 'NONE', ( 24.64999999999999900, -12.24398312239625300, 2.000000000000000000 ) ) ;
+#587 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#588 = EDGE_CURVE ( 'NONE', #3841, #4283, #4272, .T. ) ;
+#589 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, -0.6999999999999992900 ) ) ;
+#590 = EDGE_CURVE ( 'NONE', #1147, #3276, #4149, .T. ) ;
+#591 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953617300E-015, 0.0000000000000000000 ) ) ;
+#592 = VECTOR ( 'NONE', #5191, 1000.000000000000000 ) ;
+#593 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#594 = AXIS2_PLACEMENT_3D ( 'NONE', #946, #3750, #1356 ) ;
+#595 = PLANE ( 'NONE', #4183 ) ;
+#596 = CYLINDRICAL_SURFACE ( 'NONE', #1686, 0.9999999999999991100 ) ;
+#597 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#598 = AXIS2_PLACEMENT_3D ( 'NONE', #271, #1494, #4310 ) ;
+#599 = FACE_OUTER_BOUND ( 'NONE', #2839, .T. ) ;
+#600 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.224646799147352000E-016, 0.0000000000000000000 ) ) ;
+#601 = LINE ( 'NONE', #1088, #4627 ) ;
+#602 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#603 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#604 = EDGE_CURVE ( 'NONE', #618, #1997, #5203, .T. ) ;
+#605 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, 21.69999999999999900, 4.299999999999999800 ) ) ;
+#606 = CC_DESIGN_APPROVAL ( #329, ( #4751 ) ) ;
+#607 = LINE ( 'NONE', #913, #2870 ) ;
+#608 = DIRECTION ( 'NONE', ( 0.5000000000000002200, 0.8660254037844384900, 0.0000000000000000000 ) ) ;
+#609 = AXIS2_PLACEMENT_3D ( 'NONE', #2674, #272, #3095 ) ;
+#610 = VERTEX_POINT ( 'NONE', #3807 ) ;
+#611 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#612 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, -16.32049935181331100 ) ) ;
+#613 = CIRCLE ( 'NONE', #2329, 0.9999999999999991100 ) ;
+#614 = LINE ( 'NONE', #2093, #1562 ) ;
+#615 = ORIENTED_EDGE ( 'NONE', *, *, #4997, .F. ) ;
+#616 = EDGE_CURVE ( 'NONE', #5156, #3462, #5082, .T. ) ;
+#617 = DIRECTION ( 'NONE', ( 6.735557395310440100E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#618 = VERTEX_POINT ( 'NONE', #4212 ) ;
+#619 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#620 = VECTOR ( 'NONE', #35, 1000.000000000000000 ) ;
+#621 = EDGE_CURVE ( 'NONE', #2810, #4405, #1882, .T. ) ;
+#622 = ORIENTED_EDGE ( 'NONE', *, *, #5085, .T. ) ;
+#623 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#624 = CARTESIAN_POINT ( 'NONE', ( 23.19000000000000100, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#625 = EDGE_CURVE ( 'NONE', #189, #419, #2939, .T. ) ;
+#626 = EDGE_LOOP ( 'NONE', ( #1352, #4470, #183, #310 ) ) ;
+#627 = PLANE ( 'NONE', #2652 ) ;
+#628 = LINE ( 'NONE', #589, #2749 ) ;
+#629 = CARTESIAN_POINT ( 'NONE', ( -14.47342712474618900, 20.27710678118655000, 2.000000000000000000 ) ) ;
+#630 = ORIENTED_EDGE ( 'NONE', *, *, #5075, .T. ) ;
+#631 = ORIENTED_EDGE ( 'NONE', *, *, #240, .F. ) ;
+#632 = FACE_OUTER_BOUND ( 'NONE', #678, .T. ) ;
+#633 = EDGE_CURVE ( 'NONE', #1899, #1948, #1999, .T. ) ;
+#634 = VERTEX_POINT ( 'NONE', #2619 ) ;
+#635 = ORIENTED_EDGE ( 'NONE', *, *, #914, .T. ) ;
+#636 = AXIS2_PLACEMENT_3D ( 'NONE', #281, #2689, #702 ) ;
+#637 = CARTESIAN_POINT ( 'NONE', ( -13.98210678118653900, -21.90710678118655300, 4.299999999999999800 ) ) ;
+#638 = EDGE_LOOP ( 'NONE', ( #1141, #3146, #62, #4695 ) ) ;
+#639 = ORIENTED_EDGE ( 'NONE', *, *, #5197, .F. ) ;
+#640 = FACE_OUTER_BOUND ( 'NONE', #2389, .T. ) ;
+#641 = ORIENTED_EDGE ( 'NONE', *, *, #1376, .T. ) ;
+#642 = ORIENTED_EDGE ( 'NONE', *, *, #2701, .T. ) ;
+#643 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362398700, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#644 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#645 = VERTEX_POINT ( 'NONE', #5037 ) ;
+#646 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#647 = LINE ( 'NONE', #128, #3856 ) ;
+#648 = ORIENTED_EDGE ( 'NONE', *, *, #2942, .T. ) ;
+#649 = CARTESIAN_POINT ( 'NONE', ( 24.06714285714285500, 13.05655776574934800, 4.299999999999999800 ) ) ;
+#650 = CARTESIAN_POINT ( 'NONE', ( -20.77499999999999900, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#651 = AXIS2_PLACEMENT_3D ( 'NONE', #1274, #2482, #4884 ) ;
+#652 = ORIENTED_EDGE ( 'NONE', *, *, #3193, .T. ) ;
+#653 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#654 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953617300E-015, 0.0000000000000000000 ) ) ;
+#655 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#656 = ORIENTED_EDGE ( 'NONE', *, *, #1327, .F. ) ;
+#657 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#658 = ORIENTED_EDGE ( 'NONE', *, *, #792, .T. ) ;
+#659 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#660 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#661 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, 20.33743026049845100, 4.299999999999999800 ) ) ;
+#662 = CYLINDRICAL_SURFACE ( 'NONE', #774, 1.600000000000000800 ) ;
+#663 = EDGE_CURVE ( 'NONE', #1643, #3183, #4954, .T. ) ;
+#664 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#665 = EDGE_CURVE ( 'NONE', #3577, #689, #1759, .T. ) ;
+#666 = VECTOR ( 'NONE', #61, 1000.000000000000000 ) ;
+#667 = ADVANCED_FACE ( 'NONE', ( #3892 ), #4247, .F. ) ;
+#668 = VECTOR ( 'NONE', #221, 1000.000000000000000 ) ;
+#669 = LINE ( 'NONE', #2002, #3748 ) ;
+#670 = VECTOR ( 'NONE', #994, 1000.000000000000000 ) ;
+#671 = LINE ( 'NONE', #4530, #543 ) ;
+#672 = CARTESIAN_POINT ( 'NONE', ( -1.805330085889910200, -21.66966991411009900, 4.299999999999999800 ) ) ;
+#673 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#674 = ORIENTED_EDGE ( 'NONE', *, *, #625, .T. ) ;
+#675 = PLANE ( 'NONE', #3227 ) ;
+#676 = LINE ( 'NONE', #1162, #2675 ) ;
+#677 = EDGE_CURVE ( 'NONE', #1967, #2154, #1628, .T. ) ;
+#678 = EDGE_LOOP ( 'NONE', ( #2595, #1335, #1334, #3457 ) ) ;
+#679 = CARTESIAN_POINT ( 'NONE', ( 19.99000000000000200, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#680 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 12.24398312239625500, 6.000000000000000000 ) ) ;
+#681 = AXIS2_PLACEMENT_3D ( 'NONE', #3346, #3752, #1357 ) ;
+#682 = VECTOR ( 'NONE', #3031, 1000.000000000000000 ) ;
+#683 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#684 = ORIENTED_EDGE ( 'NONE', *, *, #2447, .T. ) ;
+#685 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672604900, 20.69999999999999900, 0.0000000000000000000 ) ) ;
+#686 = ORIENTED_EDGE ( 'NONE', *, *, #2003, .F. ) ;
+#687 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -21.44999999999999900, 6.000000000000000000 ) ) ;
+#688 = LINE ( 'NONE', #1412, #4837 ) ;
+#689 = VERTEX_POINT ( 'NONE', #4630 ) ;
+#690 = LINE ( 'NONE', #2718, #2575 ) ;
+#691 = ADVANCED_FACE ( 'NONE', ( #553 ), #4265, .F. ) ;
+#692 = VERTEX_POINT ( 'NONE', #4649 ) ;
+#693 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#694 = EDGE_LOOP ( 'NONE', ( #4578, #2477, #3409, #2941 ) ) ;
+#695 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, 16.50999999999999400, 0.0000000000000000000 ) ) ;
+#696 = VERTEX_POINT ( 'NONE', #5069 ) ;
+#697 = EDGE_CURVE ( 'NONE', #1967, #237, #2681, .T. ) ;
+#698 = ORIENTED_EDGE ( 'NONE', *, *, #2147, .F. ) ;
+#699 = FACE_OUTER_BOUND ( 'NONE', #2636, .T. ) ;
+#700 = ORIENTED_EDGE ( 'NONE', *, *, #1913, .T. ) ;
+#701 = ORIENTED_EDGE ( 'NONE', *, *, #4399, .T. ) ;
+#702 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#703 = ORIENTED_EDGE ( 'NONE', *, *, #5139, .F. ) ;
+#704 = LINE ( 'NONE', #3890, #3786 ) ;
+#705 = EDGE_LOOP ( 'NONE', ( #4899, #5155, #1507, #300, #299, #159 ) ) ;
+#706 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#707 = AXIS2_PLACEMENT_3D ( 'NONE', #1488, #5109, #2684 ) ;
+#708 = DIRECTION ( 'NONE', ( 0.7071067811865474600, -0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#709 = AXIS2_PLACEMENT_3D ( 'NONE', #1226, #4038, #1625 ) ;
+#710 = ORIENTED_EDGE ( 'NONE', *, *, #4972, .F. ) ;
+#711 = FACE_OUTER_BOUND ( 'NONE', #4146, .T. ) ;
+#712 = VERTEX_POINT ( 'NONE', #3061 ) ;
+#713 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#714 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#715 = AXIS2_PLACEMENT_3D ( 'NONE', #3354, #3761, #1360 ) ;
+#716 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801500, -13.80999999999999500, -0.0000000000000000000 ) ) ;
+#717 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#718 = ADVANCED_FACE ( 'NONE', ( #3635 ), #3880, .T. ) ;
+#719 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 4.299999999999999800 ) ) ;
+#720 = EDGE_CURVE ( 'NONE', #743, #4970, #4831, .T. ) ;
+#721 = CARTESIAN_POINT ( 'NONE', ( -13.98210678118653900, 21.90710678118655300, 4.299999999999999800 ) ) ;
+#722 = EDGE_CURVE ( 'NONE', #2194, #2279, #4704, .T. ) ;
+#723 = LINE ( 'NONE', #3974, #2720 ) ;
+#724 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#725 = DIRECTION ( 'NONE', ( -6.735557395310440100E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#726 = LINE ( 'NONE', #1841, #3552 ) ;
+#727 = EDGE_CURVE ( 'NONE', #4840, #1711, #4583, .T. ) ;
+#728 = LINE ( 'NONE', #5137, #3938 ) ;
+#729 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#730 = VECTOR ( 'NONE', #3447, 1000.000000000000000 ) ;
+#731 = PLANE ( 'NONE', #1728 ) ;
+#732 = EDGE_CURVE ( 'NONE', #374, #57, #424, .T. ) ;
+#733 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 6.500000000000000000, -0.6999999999999999600 ) ) ;
+#734 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#735 = CYLINDRICAL_SURFACE ( 'NONE', #520, 4.250000000000000000 ) ;
+#736 = EDGE_LOOP ( 'NONE', ( #4835, #4031, #839, #4409 ) ) ;
+#737 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, 4.299999999999999800 ) ) ;
+#738 = VECTOR ( 'NONE', #2565, 1000.000000000000000 ) ;
+#739 = ADVANCED_FACE ( 'NONE', ( #3507 ), #2313, .F. ) ;
+#740 = AXIS2_PLACEMENT_3D ( 'NONE', #4874, #2860, #463 ) ;
+#741 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, 0.0000000000000000000 ) ) ;
+#742 = FACE_BOUND ( 'NONE', #3845, .T. ) ;
+#743 = VERTEX_POINT ( 'NONE', #695 ) ;
+#744 = ORIENTED_EDGE ( 'NONE', *, *, #956, .F. ) ;
+#745 = CARTESIAN_POINT ( 'NONE', ( 23.19000000000000100, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#746 = LINE ( 'NONE', #4843, #3954 ) ;
+#747 = ORIENTED_EDGE ( 'NONE', *, *, #2801, .F. ) ;
+#748 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
+#749 = VERTEX_POINT ( 'NONE', #3985 ) ;
+#750 = DIRECTION ( 'NONE', ( -0.7071067811865470200, -0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#751 = CIRCLE ( 'NONE', #2612, 1.000000000000000900 ) ;
+#752 = EDGE_LOOP ( 'NONE', ( #3449, #3096, #838, #2936 ) ) ;
+#753 = LINE ( 'NONE', #3755, #4656 ) ;
+#754 = ORIENTED_EDGE ( 'NONE', *, *, #2917, .F. ) ;
+#755 = EDGE_LOOP ( 'NONE', ( #4967, #3909, #826, #3241, #3531, #3389, #2452, #3009 ) ) ;
+#756 = DIRECTION ( 'NONE', ( -1.224646799147352000E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#757 = ORIENTED_EDGE ( 'NONE', *, *, #2917, .T. ) ;
+#758 = ORIENTED_EDGE ( 'NONE', *, *, #2916, .F. ) ;
+#759 = LINE ( 'NONE', #1554, #2902 ) ;
+#760 = EDGE_CURVE ( 'NONE', #2475, #1455, #292, .T. ) ;
+#761 = VERTEX_POINT ( 'NONE', #4389 ) ;
+#762 = ORIENTED_EDGE ( 'NONE', *, *, #2132, .T. ) ;
+#763 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#764 = EDGE_CURVE ( 'NONE', #3479, #2265, #3382, .T. ) ;
+#765 = DIRECTION ( 'NONE', ( -1.000000000000000000, -6.123233995736770200E-016, 0.0000000000000000000 ) ) ;
+#766 = ORIENTED_EDGE ( 'NONE', *, *, #4185, .T. ) ;
+#767 = VECTOR ( 'NONE', #3325, 1000.000000000000000 ) ;
+#768 = VERTEX_POINT ( 'NONE', #3500 ) ;
+#769 = DIRECTION ( 'NONE', ( 6.735557395310440100E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#770 = ADVANCED_FACE ( 'NONE', ( #2444 ), #2189, .F. ) ;
+#771 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
+#772 = ORIENTED_EDGE ( 'NONE', *, *, #4557, .T. ) ;
+#773 = AXIS2_PLACEMENT_3D ( 'NONE', #845, #3650, #1252 ) ;
+#774 = AXIS2_PLACEMENT_3D ( 'NONE', #487, #1310, #4121 ) ;
+#775 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#776 = EDGE_CURVE ( 'NONE', #311, #2584, #3256, .T. ) ;
+#777 = VERTEX_POINT ( 'NONE', #1598 ) ;
+#778 = VERTEX_POINT ( 'NONE', #1991 ) ;
+#779 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#780 = AXIS2_PLACEMENT_3D ( 'NONE', #4081, #2484, #69 ) ;
+#781 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#782 = FACE_OUTER_BOUND ( 'NONE', #3962, .T. ) ;
+#783 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#784 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#785 = EDGE_CURVE ( 'NONE', #3512, #4046, #4203, .T. ) ;
+#786 = FACE_OUTER_BOUND ( 'NONE', #3988, .T. ) ;
+#787 = CIRCLE ( 'NONE', #2590, 1.000000000000000900 ) ;
+#788 = CIRCLE ( 'NONE', #1120, 1.000000000000000900 ) ;
+#789 = DIRECTION ( 'NONE', ( -0.7071067811865474600, 0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#790 = EDGE_CURVE ( 'NONE', #351, #4588, #1122, .T. ) ;
+#791 = ADVANCED_FACE ( 'NONE', ( #170 ), #4085, .F. ) ;
+#792 = EDGE_CURVE ( 'NONE', #2091, #4080, #46, .T. ) ;
+#793 = FACE_OUTER_BOUND ( 'NONE', #29, .T. ) ;
+#794 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#795 = CIRCLE ( 'NONE', #4015, 0.9999999999999991100 ) ;
+#796 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#797 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#798 = EDGE_LOOP ( 'NONE', ( #1845, #3727, #3257, #4088 ) ) ;
+#799 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#800 = AXIS2_PLACEMENT_3D ( 'NONE', #4462, #4867, #2458 ) ;
+#801 = LINE ( 'NONE', #3140, #4826 ) ;
+#802 = CARTESIAN_POINT ( 'NONE', ( 19.99000000000000200, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#803 = EDGE_LOOP ( 'NONE', ( #2692, #1950, #4294, #3210 ) ) ;
+#804 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#805 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#806 = VECTOR ( 'NONE', #1423, 1000.000000000000000 ) ;
+#807 = ORIENTED_EDGE ( 'NONE', *, *, #2522, .F. ) ;
+#808 = AXIS2_PLACEMENT_3D ( 'NONE', #2371, #5187, #2774 ) ;
+#809 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#810 = VERTEX_POINT ( 'NONE', #4823 ) ;
+#811 = ADVANCED_FACE ( 'NONE', ( #1937 ), #2807, .F. ) ;
+#812 = FACE_OUTER_BOUND ( 'NONE', #2585, .T. ) ;
+#813 = ORIENTED_EDGE ( 'NONE', *, *, #1973, .F. ) ;
+#814 = LINE ( 'NONE', #2618, #2817 ) ;
+#815 = ORIENTED_EDGE ( 'NONE', *, *, #317, .F. ) ;
+#816 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#817 = ORIENTED_EDGE ( 'NONE', *, *, #4833, .T. ) ;
+#818 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#819 = ADVANCED_FACE ( 'NONE', ( #876 ), #1627, .F. ) ;
+#820 = ORIENTED_EDGE ( 'NONE', *, *, #13, .T. ) ;
+#821 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#822 = ORIENTED_EDGE ( 'NONE', *, *, #5174, .F. ) ;
+#823 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#824 = FACE_OUTER_BOUND ( 'NONE', #356, .T. ) ;
+#825 = ORIENTED_EDGE ( 'NONE', *, *, #4631, .F. ) ;
+#826 = ORIENTED_EDGE ( 'NONE', *, *, #835, .F. ) ;
+#827 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602000, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#828 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#829 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, -21.19999999999999900, 4.299999999999999800 ) ) ;
+#830 = EDGE_LOOP ( 'NONE', ( #877, #1700, #891, #1386 ) ) ;
+#831 = LINE ( 'NONE', #2650, #4042 ) ;
+#832 = ORIENTED_EDGE ( 'NONE', *, *, #3352, .F. ) ;
+#833 = CARTESIAN_POINT ( 'NONE', ( -1.805330085889910200, 21.66966991411009900, 4.299999999999999800 ) ) ;
+#834 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#835 = EDGE_CURVE ( 'NONE', #1874, #5223, #5146, .T. ) ;
+#836 = VERTEX_POINT ( 'NONE', #3626 ) ;
+#837 = VECTOR ( 'NONE', #1349, 1000.000000000000000 ) ;
+#838 = ORIENTED_EDGE ( 'NONE', *, *, #178, .F. ) ;
+#839 = ORIENTED_EDGE ( 'NONE', *, *, #3788, .T. ) ;
+#840 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#841 = EDGE_CURVE ( 'NONE', #2334, #3262, #2996, .T. ) ;
+#842 = DIRECTION ( 'NONE', ( -0.5000000000000008900, 0.8660254037844381500, 0.0000000000000000000 ) ) ;
+#843 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#844 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#845 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -19.57000000000000000, 2.000000000000000000 ) ) ;
+#846 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#847 = DIRECTION ( 'NONE', ( -0.8660254037844384900, 0.5000000000000004400, 0.0000000000000000000 ) ) ;
+#848 = ADVANCED_FACE ( 'NONE', ( #3949, #5013, #1818 ), #3646, .T. ) ;
+#849 = PLANE ( 'NONE', #5184 ) ;
+#850 = PLANE ( 'NONE', #3696 ) ;
+#851 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#852 = ADVANCED_FACE ( 'NONE', ( #742, #1698 ), #2037, .T. ) ;
+#853 = AXIS2_PLACEMENT_3D ( 'NONE', #3958, #1960, #4763 ) ;
+#854 = CYLINDRICAL_SURFACE ( 'NONE', #2743, 1.000000000000000900 ) ;
+#855 = PLANE ( 'NONE', #4742 ) ;
+#856 = COORDINATED_UNIVERSAL_TIME_OFFSET ( 8, 0, .BEHIND. ) ;
+#857 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#858 = DIRECTION ( 'NONE', ( -0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#859 = VECTOR ( 'NONE', #2983, 1000.000000000000000 ) ;
+#860 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, 4.500000000000000000, -0.6999999999999992900 ) ) ;
+#861 = VECTOR ( 'NONE', #3520, 1000.000000000000000 ) ;
+#862 = PLANE ( 'NONE', #4923 ) ;
+#863 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#864 = VECTOR ( 'NONE', #3040, 1000.000000000000000 ) ;
+#865 = EDGE_CURVE ( 'NONE', #896, #444, #4896, .T. ) ;
+#866 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#867 = FACE_OUTER_BOUND ( 'NONE', #3833, .T. ) ;
+#868 = EDGE_CURVE ( 'NONE', #3714, #1294, #2737, .T. ) ;
+#869 = APPROVAL_STATUS ( 'not_yet_approved' ) ;
+#870 = ORIENTED_EDGE ( 'NONE', *, *, #2003, .T. ) ;
+#871 = ADVANCED_FACE ( 'NONE', ( #3825 ), #4880, .F. ) ;
+#872 = LINE ( 'NONE', #4275, #354 ) ;
+#873 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#874 = DIRECTION ( 'NONE', ( 0.8660254037844389300, 0.4999999999999995600, -0.0000000000000000000 ) ) ;
+#875 = CARTESIAN_POINT ( 'NONE', ( -13.98210678118653900, 21.90710678118655300, -0.6999999999999999600 ) ) ;
+#876 = FACE_OUTER_BOUND ( 'NONE', #2473, .T. ) ;
+#877 = ORIENTED_EDGE ( 'NONE', *, *, #4761, .T. ) ;
+#878 = AXIS2_PLACEMENT_3D ( 'NONE', #198, #2999, #597 ) ;
+#879 = ADVANCED_FACE ( 'NONE', ( #3701 ), #855, .F. ) ;
+#880 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#881 = CARTESIAN_POINT ( 'NONE', ( -14.60342712474619900, 20.40710678118654900, 0.0000000000000000000 ) ) ;
+#882 = ORIENTED_EDGE ( 'NONE', *, *, #3759, .F. ) ;
+#883 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, -16.32049935181331100 ) ) ;
+#884 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, 19.19999999999999900, -0.6999999999999999600 ) ) ;
+#885 = CYLINDRICAL_SURFACE ( 'NONE', #4679, 1.000000000000000000 ) ;
+#886 = VERTEX_POINT ( 'NONE', #2868 ) ;
+#887 = EDGE_CURVE ( 'NONE', #3512, #2194, #2620, .T. ) ;
+#888 = EDGE_LOOP ( 'NONE', ( #2325, #2470, #3813, #3910 ) ) ;
+#889 = CC_DESIGN_SECURITY_CLASSIFICATION ( #4751, ( #3407 ) ) ;
+#890 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, 21.69999999999999900, 4.299999999999999800 ) ) ;
+#891 = ORIENTED_EDGE ( 'NONE', *, *, #1591, .F. ) ;
+#892 = AXIS2_PLACEMENT_3D ( 'NONE', #3905, #1495, #4312 ) ;
+#893 = ORIENTED_EDGE ( 'NONE', *, *, #178, .T. ) ;
+#894 = CIRCLE ( 'NONE', #363, 0.9999999999999991100 ) ;
+#895 = ORIENTED_EDGE ( 'NONE', *, *, #4972, .T. ) ;
+#896 = VERTEX_POINT ( 'NONE', #4894 ) ;
+#897 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#898 = ORIENTED_EDGE ( 'NONE', *, *, #1815, .F. ) ;
+#899 = ORIENTED_EDGE ( 'NONE', *, *, #250, .T. ) ;
+#900 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#901 = LINE ( 'NONE', #1566, #3971 ) ;
+#902 = ORIENTED_EDGE ( 'NONE', *, *, #905, .F. ) ;
+#903 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#904 = ORIENTED_EDGE ( 'NONE', *, *, #2602, .F. ) ;
+#905 = EDGE_CURVE ( 'NONE', #258, #1075, #614, .T. ) ;
+#906 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#907 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#908 = ORIENTED_EDGE ( 'NONE', *, *, #2118, .F. ) ;
+#909 = VECTOR ( 'NONE', #499, 1000.000000000000000 ) ;
+#910 = ORIENTED_EDGE ( 'NONE', *, *, #3380, .F. ) ;
+#911 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#912 = ADVANCED_FACE ( 'NONE', ( #2506 ), #1614, .T. ) ;
+#913 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 5.277604462221250500E-017, 4.299999999999999800 ) ) ;
+#914 = EDGE_CURVE ( 'NONE', #1068, #4804, #492, .T. ) ;
+#915 = EDGE_CURVE ( 'NONE', #163, #4664, #4689, .T. ) ;
+#916 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#917 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#918 = EDGE_CURVE ( 'NONE', #2584, #4804, #541, .T. ) ;
+#919 = AXIS2_PLACEMENT_3D ( 'NONE', #4677, #4269, #1058 ) ;
+#920 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#921 = ORIENTED_EDGE ( 'NONE', *, *, #3861, .F. ) ;
+#922 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -20.57000000000000000, 2.000000000000000000 ) ) ;
+#923 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#924 = ORIENTED_EDGE ( 'NONE', *, *, #4602, .T. ) ;
+#925 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#926 = VECTOR ( 'NONE', #3285, 1000.000000000000000 ) ;
+#927 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#928 = FACE_BOUND ( 'NONE', #752, .T. ) ;
+#929 = VECTOR ( 'NONE', #4864, 1000.000000000000000 ) ;
+#930 = VECTOR ( 'NONE', #1155, 1000.000000000000200 ) ;
+#931 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, -19.19999999999999900, 4.299999999999999800 ) ) ;
+#932 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#933 = LINE ( 'NONE', #1779, #3053 ) ;
+#934 = FACE_OUTER_BOUND ( 'NONE', #3965, .T. ) ;
+#935 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#936 = PLANE ( 'NONE', #2538 ) ;
+#937 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#938 = EDGE_LOOP ( 'NONE', ( #4298, #3826, #4643, #386 ) ) ;
+#939 = LINE ( 'NONE', #3121, #5077 ) ;
+#940 = EDGE_CURVE ( 'NONE', #3580, #2455, #406, .T. ) ;
+#941 = ORIENTED_EDGE ( 'NONE', *, *, #4997, .T. ) ;
+#942 = EDGE_CURVE ( 'NONE', #810, #4107, #1489, .T. ) ;
+#943 = VECTOR ( 'NONE', #587, 1000.000000000000000 ) ;
+#944 = ORIENTED_EDGE ( 'NONE', *, *, #2410, .T. ) ;
+#945 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, -18.71852980579590000, 2.000000000000000000 ) ) ;
+#946 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#947 = ORIENTED_EDGE ( 'NONE', *, *, #2440, .F. ) ;
+#948 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#949 = VECTOR ( 'NONE', #3010, 1000.000000000000000 ) ;
+#950 = ORIENTED_EDGE ( 'NONE', *, *, #3916, .T. ) ;
+#951 = VECTOR ( 'NONE', #1914, 1000.000000000000000 ) ;
+#952 = VERTEX_POINT ( 'NONE', #922 ) ;
+#953 = AXIS2_PLACEMENT_3D ( 'NONE', #147, #2949, #547 ) ;
+#954 = ORIENTED_EDGE ( 'NONE', *, *, #4073, .T. ) ;
+#955 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -20.19999999999999900, 4.299999999999999800 ) ) ;
+#956 = EDGE_CURVE ( 'NONE', #4541, #3760, #3620, .T. ) ;
+#957 = ORIENTED_EDGE ( 'NONE', *, *, #3249, .T. ) ;
+#958 = VERTEX_POINT ( 'NONE', #2528 ) ;
+#959 = LINE ( 'NONE', #1721, #4035 ) ;
+#960 = ORIENTED_EDGE ( 'NONE', *, *, #2027, .F. ) ;
+#961 = ORIENTED_EDGE ( 'NONE', *, *, #1434, .F. ) ;
+#962 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, 19.49289321881345000, -0.6999999999999999600 ) ) ;
+#963 = ORIENTED_EDGE ( 'NONE', *, *, #3928, .T. ) ;
+#964 = FACE_OUTER_BOUND ( 'NONE', #1394, .T. ) ;
+#965 = VERTEX_POINT ( 'NONE', #2123 ) ;
+#966 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#967 = EDGE_LOOP ( 'NONE', ( #3855, #1260, #2073, #2328, #4959, #4842 ) ) ;
+#968 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362397600, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#969 = FACE_OUTER_BOUND ( 'NONE', #1328, .T. ) ;
+#970 = EDGE_LOOP ( 'NONE', ( #5169, #2906, #2445, #4337, #439, #5092, #4964, #4963, #63, #2394, #4417, #251, #1813, #3978, #2764, #171, #1002, #1271, #3900, #2693 ) ) ;
+#971 = DIRECTION ( 'NONE', ( -1.224646799147352000E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#972 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 21.44999999999999900, 6.000000000000000000 ) ) ;
+#973 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, 21.69999999999999900, 4.299999999999999800 ) ) ;
+#974 = EDGE_CURVE ( 'NONE', #4706, #1341, #277, .T. ) ;
+#975 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#976 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953614200E-015, 0.0000000000000000000 ) ) ;
+#977 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -0.7500000000000000000, 6.000000000000000000 ) ) ;
+#978 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#979 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#980 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.628553204668610100E-017, 2.000000000000000000 ) ) ;
+#981 = FACE_OUTER_BOUND ( 'NONE', #1526, .T. ) ;
+#982 = FACE_OUTER_BOUND ( 'NONE', #1319, .T. ) ;
+#983 = ORIENTED_EDGE ( 'NONE', *, *, #1164, .F. ) ;
+#984 = VECTOR ( 'NONE', #1102, 1000.000000000000000 ) ;
+#985 = CYLINDRICAL_SURFACE ( 'NONE', #3642, 1.000000000000000000 ) ;
+#986 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, 21.69999999999999900, -0.6999999999999999600 ) ) ;
+#987 = CYLINDRICAL_SURFACE ( 'NONE', #4388, 1.000000000000000900 ) ;
+#988 = EDGE_CURVE ( 'NONE', #1536, #4732, #1365, .T. ) ;
+#989 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#990 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, 21.19999999999999900, 6.000000000000000000 ) ) ;
+#991 = CARTESIAN_POINT ( 'NONE', ( -13.98210678118653900, -21.90710678118655300, -0.6999999999999999600 ) ) ;
+#992 = ORIENTED_EDGE ( 'NONE', *, *, #1542, .T. ) ;
+#993 = EDGE_CURVE ( 'NONE', #4048, #2334, #3492, .T. ) ;
+#994 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#995 = LINE ( 'NONE', #3745, #1935 ) ;
+#996 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#997 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#998 = VECTOR ( 'NONE', #5226, 1000.000000000000000 ) ;
+#999 = VERTEX_POINT ( 'NONE', #1339 ) ;
+#1000 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1001 = EDGE_CURVE ( 'NONE', #1583, #2103, #155, .T. ) ;
+#1002 = ORIENTED_EDGE ( 'NONE', *, *, #3794, .T. ) ;
+#1003 = VECTOR ( 'NONE', #2792, 1000.000000000000000 ) ;
+#1004 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1005 = ORIENTED_EDGE ( 'NONE', *, *, #349, .F. ) ;
+#1006 = EDGE_CURVE ( 'NONE', #3841, #5113, #1240, .T. ) ;
+#1007 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1008 = EDGE_LOOP ( 'NONE', ( #4346, #3756, #1783, #1658 ) ) ;
+#1009 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1010 = ORIENTED_EDGE ( 'NONE', *, *, #3083, .F. ) ;
+#1011 = VERTEX_POINT ( 'NONE', #148 ) ;
+#1012 = DIRECTION ( 'NONE', ( 0.4999999999999996700, 0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#1013 = ORIENTED_EDGE ( 'NONE', *, *, #4124, .T. ) ;
+#1014 = VECTOR ( 'NONE', #4870, 1000.000000000000000 ) ;
+#1015 = EDGE_LOOP ( 'NONE', ( #674, #186, #1277, #1279, #3936, #5038 ) ) ;
+#1016 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1017 = EDGE_LOOP ( 'NONE', ( #2398, #2997, #3830, #4097 ) ) ;
+#1018 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1019 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1020 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953617300E-015, 0.0000000000000000000 ) ) ;
+#1021 = ORIENTED_EDGE ( 'NONE', *, *, #2453, .F. ) ;
+#1022 = ORIENTED_EDGE ( 'NONE', *, *, #1224, .F. ) ;
+#1023 = VECTOR ( 'NONE', #3565, 1000.000000000000000 ) ;
+#1024 = ORIENTED_EDGE ( 'NONE', *, *, #3829, .F. ) ;
+#1025 = ORIENTED_EDGE ( 'NONE', *, *, #3995, .T. ) ;
+#1026 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1027 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672604900, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1028 = ORIENTED_EDGE ( 'NONE', *, *, #3694, .F. ) ;
+#1029 = CIRCLE ( 'NONE', #138, 1.600000000000000800 ) ;
+#1030 = ORIENTED_EDGE ( 'NONE', *, *, #2600, .T. ) ;
+#1031 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199900, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#1032 = PLANE ( 'NONE', #3945 ) ;
+#1033 = ORIENTED_EDGE ( 'NONE', *, *, #191, .F. ) ;
+#1034 = CARTESIAN_POINT ( 'NONE', ( -2.482106781186544700, -20.99289321881345000, 4.299999999999999800 ) ) ;
+#1035 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -19.57000000000000000, 2.000000000000000000 ) ) ;
+#1036 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672599900, -19.69999999999999900, -0.6999999999999999600 ) ) ;
+#1037 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602700, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#1038 = EDGE_CURVE ( 'NONE', #2951, #4970, #3238, .T. ) ;
+#1039 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1040 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, 0.0000000000000000000 ) ) ;
+#1041 = EDGE_CURVE ( 'NONE', #2393, #3383, #4445, .T. ) ;
+#1042 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#1043 = VECTOR ( 'NONE', #1280, 1000.000000000000000 ) ;
+#1044 = AXIS2_PLACEMENT_3D ( 'NONE', #4428, #4444, #2040 ) ;
+#1045 = PLANE ( 'NONE', #576 ) ;
+#1046 = FACE_BOUND ( 'NONE', #4400, .T. ) ;
+#1047 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1048 = AXIS2_PLACEMENT_3D ( 'NONE', #1713, #102, #2914 ) ;
+#1049 = LINE ( 'NONE', #3640, #5066 ) ;
+#1050 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, -19.19999999999999900, 4.299999999999999800 ) ) ;
+#1051 = VECTOR ( 'NONE', #1130, 1000.000000000000000 ) ;
+#1052 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 20.19999999999999900, -0.6999999999999999600 ) ) ;
+#1053 = EDGE_CURVE ( 'NONE', #57, #1273, #2175, .T. ) ;
+#1054 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672599900, -19.69999999999999900, -0.6999999999999999600 ) ) ;
+#1055 = FACE_OUTER_BOUND ( 'NONE', #4041, .T. ) ;
+#1056 = VECTOR ( 'NONE', #3700, 1000.000000000000000 ) ;
+#1057 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, -6.250000000000000000, 6.000000000000000000 ) ) ;
+#1058 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1059 = ORIENTED_EDGE ( 'NONE', *, *, #5110, .T. ) ;
+#1060 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#1061 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1062 = VECTOR ( 'NONE', #708, 1000.000000000000100 ) ;
+#1063 = EDGE_LOOP ( 'NONE', ( #4724, #3863, #3275, #3139 ) ) ;
+#1064 = EDGE_CURVE ( 'NONE', #1959, #2145, #28, .T. ) ;
+#1065 = CARTESIAN_POINT ( 'NONE', ( -2.482106781186544700, 20.99289321881345000, 4.299999999999999800 ) ) ;
+#1066 = VECTOR ( 'NONE', #3797, 1000.000000000000000 ) ;
+#1067 = EDGE_CURVE ( 'NONE', #3406, #1284, #3114, .T. ) ;
+#1068 = VERTEX_POINT ( 'NONE', #4978 ) ;
+#1069 = CARTESIAN_POINT ( 'NONE', ( -24.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#1070 = EDGE_CURVE ( 'NONE', #4595, #5078, #4068, .T. ) ;
+#1071 = EDGE_LOOP ( 'NONE', ( #3192, #4022, #2148, #3165 ) ) ;
+#1072 = VECTOR ( 'NONE', #5044, 1000.000000000000000 ) ;
+#1073 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1074 = ORIENTED_EDGE ( 'NONE', *, *, #213, .F. ) ;
+#1075 = VERTEX_POINT ( 'NONE', #977 ) ;
+#1076 = LINE ( 'NONE', #2208, #4156 ) ;
+#1077 = ORIENTED_EDGE ( 'NONE', *, *, #3366, .T. ) ;
+#1078 = ORIENTED_EDGE ( 'NONE', *, *, #3868, .T. ) ;
+#1079 = AXIS2_PLACEMENT_3D ( 'NONE', #4617, #2209, #5011 ) ;
+#1080 = ORIENTED_EDGE ( 'NONE', *, *, #3309, .F. ) ;
+#1081 = ORIENTED_EDGE ( 'NONE', *, *, #1877, .F. ) ;
+#1082 = CYLINDRICAL_SURFACE ( 'NONE', #1555, 1.600000000000000800 ) ;
+#1083 = ORIENTED_EDGE ( 'NONE', *, *, #1104, .F. ) ;
+#1084 = ORIENTED_EDGE ( 'NONE', *, *, #4546, .F. ) ;
+#1085 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#1086 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1087 = ORIENTED_EDGE ( 'NONE', *, *, #663, .T. ) ;
+#1088 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, -13.81000000000000200, 0.0000000000000000000 ) ) ;
+#1089 = VECTOR ( 'NONE', #4243, 1000.000000000000000 ) ;
+#1090 = ORIENTED_EDGE ( 'NONE', *, *, #4985, .F. ) ;
+#1091 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1092 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1093 = AXIS2_PLACEMENT_3D ( 'NONE', #603, #1827, #4629 ) ;
+#1094 = ORIENTED_EDGE ( 'NONE', *, *, #776, .F. ) ;
+#1095 = VECTOR ( 'NONE', #115, 1000.000000000000000 ) ;
+#1096 = ORIENTED_EDGE ( 'NONE', *, *, #188, .T. ) ;
+#1097 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1098 = ADVANCED_FACE ( 'NONE', ( #982 ), #1806, .F. ) ;
+#1099 = PLANE ( 'NONE', #598 ) ;
+#1100 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#1101 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1102 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1103 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1104 = EDGE_CURVE ( 'NONE', #3516, #4206, #3935, .T. ) ;
+#1105 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, 16.50999999999999800, 2.500000000000000000 ) ) ;
+#1106 = EDGE_CURVE ( 'NONE', #749, #4840, #1923, .T. ) ;
+#1107 = VECTOR ( 'NONE', #3535, 1000.000000000000000 ) ;
+#1108 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#1109 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 19.19999999999999900, -0.6999999999999999600 ) ) ;
+#1110 = VECTOR ( 'NONE', #2952, 1000.000000000000000 ) ;
+#1111 = CYLINDRICAL_SURFACE ( 'NONE', #2955, 1.000000000000000900 ) ;
+#1112 = FACE_OUTER_BOUND ( 'NONE', #1825, .T. ) ;
+#1113 = ADVANCED_FACE ( 'NONE', ( #2723 ), #596, .T. ) ;
+#1114 = AXIS2_PLACEMENT_3D ( 'NONE', #1450, #2253, #3055 ) ;
+#1115 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362397600, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#1116 = LINE ( 'NONE', #2042, #1932 ) ;
+#1117 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, 19.49289321881345000, 2.000000000000000000 ) ) ;
+#1118 = ORIENTED_EDGE ( 'NONE', *, *, #940, .T. ) ;
+#1119 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1120 = AXIS2_PLACEMENT_3D ( 'NONE', #2770, #364, #3175 ) ;
+#1121 = FACE_OUTER_BOUND ( 'NONE', #4975, .T. ) ;
+#1122 = LINE ( 'NONE', #390, #1259 ) ;
+#1123 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1124 = ORIENTED_EDGE ( 'NONE', *, *, #4336, .T. ) ;
+#1125 = VERTEX_POINT ( 'NONE', #4614 ) ;
+#1126 = CARTESIAN_POINT ( 'NONE', ( 26.45000000000000300, -6.250000000000000000, 6.000000000000000000 ) ) ;
+#1127 = VECTOR ( 'NONE', #3184, 1000.000000000000000 ) ;
+#1128 = ORIENTED_EDGE ( 'NONE', *, *, #3352, .T. ) ;
+#1129 = LINE ( 'NONE', #1186, #3744 ) ;
+#1130 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1131 = EDGE_CURVE ( 'NONE', #315, #1221, #1679, .T. ) ;
+#1132 = ORIENTED_EDGE ( 'NONE', *, *, #1650, .F. ) ;
+#1133 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#1134 = VERTEX_POINT ( 'NONE', #2200 ) ;
+#1135 = EDGE_CURVE ( 'NONE', #2626, #4670, #4753, .T. ) ;
+#1136 = ORIENTED_EDGE ( 'NONE', *, *, #1535, .F. ) ;
+#1137 = ORIENTED_EDGE ( 'NONE', *, *, #5143, .T. ) ;
+#1138 = VECTOR ( 'NONE', #407, 1000.000000000000000 ) ;
+#1139 = EDGE_LOOP ( 'NONE', ( #1202, #4759, #2766, #521 ) ) ;
+#1140 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#1141 = ORIENTED_EDGE ( 'NONE', *, *, #4178, .F. ) ;
+#1142 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#1143 = ADVANCED_FACE ( 'NONE', ( #2608 ), #3818, .T. ) ;
+#1144 = ORIENTED_EDGE ( 'NONE', *, *, #2712, .F. ) ;
+#1145 = VERTEX_POINT ( 'NONE', #1418 ) ;
+#1146 = VECTOR ( 'NONE', #2344, 1000.000000000000000 ) ;
+#1147 = VERTEX_POINT ( 'NONE', #5028 ) ;
+#1148 = ORIENTED_EDGE ( 'NONE', *, *, #1848, .F. ) ;
+#1149 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 4.378679656440360700, 6.000000000000000000 ) ) ;
+#1150 = VERTEX_POINT ( 'NONE', #3416 ) ;
+#1151 = CIRCLE ( 'NONE', #5080, 1.000000000000000900 ) ;
+#1152 = ORIENTED_EDGE ( 'NONE', *, *, #2440, .T. ) ;
+#1153 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, -0.6999999999999992900 ) ) ;
+#1154 = EDGE_CURVE ( 'NONE', #4103, #493, #3811, .T. ) ;
+#1155 = DIRECTION ( 'NONE', ( -0.5000000000000005600, -0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#1156 = PRODUCT_RELATED_PRODUCT_CATEGORY ( 'detail', '', ( #452 ) ) ;
+#1157 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -2.878679656440359800, 1.000000000000000000 ) ) ;
+#1158 = AXIS2_PLACEMENT_3D ( 'NONE', #5128, #2714, #302 ) ;
+#1159 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#1160 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -2.368891848716884900, 1.000000000000000000 ) ) ;
+#1161 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -19.21000000000000100, 0.0000000000000000000 ) ) ;
+#1162 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318800800, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#1163 = EDGE_CURVE ( 'NONE', #3953, #896, #1551, .T. ) ;
+#1164 = EDGE_CURVE ( 'NONE', #2644, #4359, #726, .T. ) ;
+#1165 = ADVANCED_FACE ( 'NONE', ( #2862 ), #4642, .F. ) ;
+#1166 = FACE_OUTER_BOUND ( 'NONE', #4091, .T. ) ;
+#1167 = EDGE_CURVE ( 'NONE', #311, #1582, #1428, .T. ) ;
+#1168 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1169 = DATE_AND_TIME ( #2233, #3298 ) ;
+#1170 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1171 = LINE ( 'NONE', #5079, #4252 ) ;
+#1172 = EDGE_CURVE ( 'NONE', #1904, #2626, #341, .T. ) ;
+#1173 = LINE ( 'NONE', #2787, #2120 ) ;
+#1174 = FACE_OUTER_BOUND ( 'NONE', #3443, .T. ) ;
+#1175 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1176 = VECTOR ( 'NONE', #1741, 1000.000000000000000 ) ;
+#1177 = CARTESIAN_POINT ( 'NONE', ( -8.753959254591981600E-017, 4.500000000000000000, 4.299999999999999800 ) ) ;
+#1178 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -19.19999999999999900, -0.6999999999999999600 ) ) ;
+#1179 = VECTOR ( 'NONE', #3775, 1000.000000000000000 ) ;
+#1180 = ORIENTED_EDGE ( 'NONE', *, *, #1106, .F. ) ;
+#1181 = LINE ( 'NONE', #1100, #3316 ) ;
+#1182 = AXIS2_PLACEMENT_3D ( 'NONE', #1299, #2100, #4898 ) ;
+#1183 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1184 = VERTEX_POINT ( 'NONE', #2254 ) ;
+#1185 = ORIENTED_EDGE ( 'NONE', *, *, #2070, .F. ) ;
+#1186 = CARTESIAN_POINT ( 'NONE', ( -20.02500000000000200, -22.19999999999999900, 4.299999999999999800 ) ) ;
+#1187 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 20.57000000000000000, 4.299999999999998000 ) ) ;
+#1188 = VECTOR ( 'NONE', #3772, 1000.000000000000000 ) ;
+#1189 = CARTESIAN_POINT ( 'NONE', ( 18.13655776574935200, -18.98714285714286000, 4.299999999999999800 ) ) ;
+#1190 = ORIENTED_EDGE ( 'NONE', *, *, #1316, .F. ) ;
+#1191 = ADVANCED_FACE ( 'NONE', ( #3429 ), #5065, .T. ) ;
+#1192 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#1193 = VERTEX_POINT ( 'NONE', #2651 ) ;
+#1194 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#1195 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, -20.33743026049845100, 4.299999999999999800 ) ) ;
+#1196 = VECTOR ( 'NONE', #4653, 1000.000000000000000 ) ;
+#1197 = ORIENTED_EDGE ( 'NONE', *, *, #1570, .F. ) ;
+#1198 = FACE_OUTER_BOUND ( 'NONE', #3069, .T. ) ;
+#1199 = VERTEX_POINT ( 'NONE', #3876 ) ;
+#1200 = ORIENTED_EDGE ( 'NONE', *, *, #1283, .F. ) ;
+#1201 = VERTEX_POINT ( 'NONE', #1453 ) ;
+#1202 = ORIENTED_EDGE ( 'NONE', *, *, #2077, .F. ) ;
+#1203 = FACE_OUTER_BOUND ( 'NONE', #1568, .T. ) ;
+#1204 = EDGE_CURVE ( 'NONE', #1125, #712, #2356, .T. ) ;
+#1205 = ORIENTED_EDGE ( 'NONE', *, *, #3088, .F. ) ;
+#1206 = ORIENTED_EDGE ( 'NONE', *, *, #4326, .F. ) ;
+#1207 = EDGE_CURVE ( 'NONE', #2010, #2197, #4624, .T. ) ;
+#1208 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, 19.49289321881345000, 1.000000000000000000 ) ) ;
+#1209 = ORIENTED_EDGE ( 'NONE', *, *, #172, .T. ) ;
+#1210 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1211 = FACE_OUTER_BOUND ( 'NONE', #2965, .T. ) ;
+#1212 = ORIENTED_EDGE ( 'NONE', *, *, #1815, .T. ) ;
+#1213 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#1214 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362398700, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#1215 = VERTEX_POINT ( 'NONE', #3073 ) ;
+#1216 = EDGE_LOOP ( 'NONE', ( #954, #4028, #3395, #379 ) ) ;
+#1217 = EDGE_LOOP ( 'NONE', ( #1832, #1247, #1287, #1285 ) ) ;
+#1218 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 20.19999999999999900, 0.0000000000000000000 ) ) ;
+#1219 = ADVANCED_FACE ( 'NONE', ( #3301, #4378 ), #264, .F. ) ;
+#1220 = DIRECTION ( 'NONE', ( 0.5000000000000001100, -0.8660254037844386000, 0.0000000000000000000 ) ) ;
+#1221 = VERTEX_POINT ( 'NONE', #2661 ) ;
+#1222 = AXIS2_PLACEMENT_3D ( 'NONE', #4550, #2137, #4946 ) ;
+#1223 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1224 = EDGE_CURVE ( 'NONE', #3459, #3912, #1171, .T. ) ;
+#1225 = ADVANCED_FACE ( 'NONE', ( #2236 ), #3611, .F. ) ;
+#1226 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, -19.57000000000000000, 2.000000000000000000 ) ) ;
+#1227 = AXIS2_PLACEMENT_3D ( 'NONE', #968, #1368, #4180 ) ;
+#1228 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#1229 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#1230 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1231 = CARTESIAN_POINT ( 'NONE', ( -13.06789321881344000, -20.99289321881345000, 4.299999999999999800 ) ) ;
+#1232 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1233 = DIRECTION ( 'NONE', ( 0.4999999999999997800, -0.8660254037844387100, 0.0000000000000000000 ) ) ;
+#1234 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1235 = LINE ( 'NONE', #3738, #3306 ) ;
+#1236 = PLANE ( 'NONE', #4515 ) ;
+#1237 = FACE_OUTER_BOUND ( 'NONE', #4906, .T. ) ;
+#1238 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1239 = EDGE_CURVE ( 'NONE', #4646, #2810, #2494, .T. ) ;
+#1240 = LINE ( 'NONE', #4162, #2299 ) ;
+#1241 = CARTESIAN_POINT ( 'NONE', ( -0.9465728752538080700, -20.40710678118654900, -0.6999999999999999600 ) ) ;
+#1242 = LINE ( 'NONE', #2665, #3368 ) ;
+#1243 = ORIENTED_EDGE ( 'NONE', *, *, #4110, .T. ) ;
+#1244 = ADVANCED_FACE ( 'NONE', ( #99 ), #2416, .T. ) ;
+#1245 = FACE_OUTER_BOUND ( 'NONE', #2258, .T. ) ;
+#1246 = DIRECTION ( 'NONE', ( 0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#1247 = ORIENTED_EDGE ( 'NONE', *, *, #1604, .F. ) ;
+#1248 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -20.57000000000000000, 4.299999999999998000 ) ) ;
+#1249 = AXIS2_PLACEMENT_3D ( 'NONE', #2775, #3597, #2372 ) ;
+#1250 = VECTOR ( 'NONE', #394, 1000.000000000000000 ) ;
+#1251 = ADVANCED_FACE ( 'NONE', ( #3171 ), #2, .F. ) ;
+#1252 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1253 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199200, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#1254 = VERTEX_POINT ( 'NONE', #1231 ) ;
+#1255 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1256 = LINE ( 'NONE', #1228, #3253 ) ;
+#1257 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800800, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#1258 = CARTESIAN_POINT ( 'NONE', ( 23.19000000000000100, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#1259 = VECTOR ( 'NONE', #3608, 1000.000000000000100 ) ;
+#1260 = ORIENTED_EDGE ( 'NONE', *, *, #835, .T. ) ;
+#1261 = AXIS2_PLACEMENT_3D ( 'NONE', #4179, #3371, #975 ) ;
+#1262 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
+#1263 = ORIENTED_EDGE ( 'NONE', *, *, #417, .F. ) ;
+#1264 = LINE ( 'NONE', #1159, #3386 ) ;
+#1265 = ORIENTED_EDGE ( 'NONE', *, *, #4766, .T. ) ;
+#1266 = LINE ( 'NONE', #4789, #2196 ) ;
+#1267 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1268 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 2.000000000000000000 ) ) ;
+#1269 = ORIENTED_EDGE ( 'NONE', *, *, #123, .T. ) ;
+#1270 = EDGE_CURVE ( 'NONE', #952, #374, #2119, .T. ) ;
+#1271 = ORIENTED_EDGE ( 'NONE', *, *, #915, .T. ) ;
+#1272 = CARTESIAN_POINT ( 'NONE', ( 7.925134423841990700E-016, -6.000000000000000000, 2.000000000000000000 ) ) ;
+#1273 = VERTEX_POINT ( 'NONE', #20 ) ;
+#1274 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#1275 = ORIENTED_EDGE ( 'NONE', *, *, #2746, .F. ) ;
+#1276 = LINE ( 'NONE', #4047, #3271 ) ;
+#1277 = ORIENTED_EDGE ( 'NONE', *, *, #2046, .F. ) ;
+#1278 = DESIGN_CONTEXT ( 'detailed design', #464, 'design' ) ;
+#1279 = ORIENTED_EDGE ( 'NONE', *, *, #2732, .F. ) ;
+#1280 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1281 = ADVANCED_FACE ( 'NONE', ( #1046, #4132, #5185, #3046, #1983, #928 ), #3237, .F. ) ;
+#1282 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#1283 = EDGE_CURVE ( 'NONE', #645, #2943, #1865, .T. ) ;
+#1284 = VERTEX_POINT ( 'NONE', #2461 ) ;
+#1285 = ORIENTED_EDGE ( 'NONE', *, *, #248, .F. ) ;
+#1286 = LINE ( 'NONE', #1126, #1402 ) ;
+#1287 = ORIENTED_EDGE ( 'NONE', *, *, #4665, .F. ) ;
+#1288 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1289 = ADVANCED_FACE ( 'NONE', ( #786 ), #427, .F. ) ;
+#1290 = PERSON_AND_ORGANIZATION_ROLE ( 'creator' ) ;
+#1291 = EDGE_CURVE ( 'NONE', #645, #4951, #3875, .T. ) ;
+#1292 = ORIENTED_EDGE ( 'NONE', *, *, #1538, .T. ) ;
+#1293 = VECTOR ( 'NONE', #5114, 1000.000000000000100 ) ;
+#1294 = VERTEX_POINT ( 'NONE', #1672 ) ;
+#1295 = EDGE_CURVE ( 'NONE', #4951, #1134, #1742, .T. ) ;
+#1296 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1297 = CARTESIAN_POINT ( 'NONE', ( -14.60342712474619900, 20.40710678118654900, -0.6999999999999999600 ) ) ;
+#1298 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#1299 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#1300 = AXIS2_PLACEMENT_3D ( 'NONE', #3318, #927, #3734 ) ;
+#1301 = ADVANCED_FACE ( 'NONE', ( #2788 ), #2660, .F. ) ;
+#1302 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1303 = VECTOR ( 'NONE', #2020, 1000.000000000000000 ) ;
+#1304 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#1305 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1306 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, 20.33743026049845100, 0.0000000000000000000 ) ) ;
+#1307 = VECTOR ( 'NONE', #3469, 1000.000000000000000 ) ;
+#1308 = DIRECTION ( 'NONE', ( -0.5000000000000004400, 0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#1309 = FACE_OUTER_BOUND ( 'NONE', #2273, .T. ) ;
+#1310 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1311 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#1312 = CIRCLE ( 'NONE', #33, 1.000000000000000000 ) ;
+#1313 = EDGE_CURVE ( 'NONE', #1449, #2829, #4812, .T. ) ;
+#1314 = CIRCLE ( 'NONE', #2930, 1.000000000000000000 ) ;
+#1315 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1316 = EDGE_CURVE ( 'NONE', #2526, #2386, #4942, .T. ) ;
+#1317 = ORIENTED_EDGE ( 'NONE', *, *, #3659, .F. ) ;
+#1318 = CIRCLE ( 'NONE', #331, 1.000000000000000900 ) ;
+#1319 = EDGE_LOOP ( 'NONE', ( #3020, #5162, #1497, #3540 ) ) ;
+#1320 = CARTESIAN_POINT ( 'NONE', ( -7.774999999999999500, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1321 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1322 = VERTEX_POINT ( 'NONE', #2099 ) ;
+#1323 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1324 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, 4.500000000000000000, 4.299999999999999800 ) ) ;
+#1325 = CIRCLE ( 'NONE', #2157, 0.9999999999999991100 ) ;
+#1326 = VERTEX_POINT ( 'NONE', #2493 ) ;
+#1327 = EDGE_CURVE ( 'NONE', #4955, #4976, #2550, .T. ) ;
+#1328 = EDGE_LOOP ( 'NONE', ( #3496, #4607, #4605, #4013 ) ) ;
+#1329 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1330 = VECTOR ( 'NONE', #1864, 1000.000000000000000 ) ;
+#1331 = AXIS2_PLACEMENT_3D ( 'NONE', #4902, #4886, #2105 ) ;
+#1332 = ORIENTED_EDGE ( 'NONE', *, *, #887, .T. ) ;
+#1333 = VECTOR ( 'NONE', #150, 1000.000000000000000 ) ;
+#1334 = ORIENTED_EDGE ( 'NONE', *, *, #1006, .F. ) ;
+#1335 = ORIENTED_EDGE ( 'NONE', *, *, #282, .F. ) ;
+#1336 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1337 = ORIENTED_EDGE ( 'NONE', *, *, #2916, .T. ) ;
+#1338 = ORIENTED_EDGE ( 'NONE', *, *, #2927, .F. ) ;
+#1339 = CARTESIAN_POINT ( 'NONE', ( -24.06714285714285500, -13.05655776574935000, 4.299999999999999800 ) ) ;
+#1340 = CIRCLE ( 'NONE', #473, 0.9999999999999991100 ) ;
+#1341 = VERTEX_POINT ( 'NONE', #85 ) ;
+#1342 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1343 = VERTEX_POINT ( 'NONE', #2892 ) ;
+#1344 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, -0.0000000000000000000 ) ) ;
+#1345 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1346 = ORIENTED_EDGE ( 'NONE', *, *, #3578, .F. ) ;
+#1347 = LINE ( 'NONE', #4989, #4422 ) ;
+#1348 = ORIENTED_EDGE ( 'NONE', *, *, #4908, .T. ) ;
+#1349 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#1350 = EDGE_CURVE ( 'NONE', #1341, #4611, #401, .T. ) ;
+#1351 = EDGE_CURVE ( 'NONE', #419, #4478, #1607, .T. ) ;
+#1352 = ORIENTED_EDGE ( 'NONE', *, *, #2701, .F. ) ;
+#1353 = VERTEX_POINT ( 'NONE', #2509 ) ;
+#1354 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1355 = AXIS2_PLACEMENT_3D ( 'NONE', #2068, #2480, #58 ) ;
+#1356 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1357 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1358 = PLANE ( 'NONE', #3336 ) ;
+#1359 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1360 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1361 = LINE ( 'NONE', #1320, #4439 ) ;
+#1362 = FACE_OUTER_BOUND ( 'NONE', #1071, .T. ) ;
+#1363 = VECTOR ( 'NONE', #4012, 1000.000000000000000 ) ;
+#1364 = PLANE ( 'NONE', #2558 ) ;
+#1365 = LINE ( 'NONE', #1739, #3367 ) ;
+#1366 = DIRECTION ( 'NONE', ( 0.7071067811865474600, 0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#1367 = LINE ( 'NONE', #649, #4569 ) ;
+#1368 = DIRECTION ( 'NONE', ( 0.8660254037844388200, -0.4999999999999997200, 0.0000000000000000000 ) ) ;
+#1369 = VECTOR ( 'NONE', #3791, 1000.000000000000000 ) ;
+#1370 = PLANE ( 'NONE', #3881 ) ;
+#1371 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#1372 = ORIENTED_EDGE ( 'NONE', *, *, #142, .T. ) ;
+#1373 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1374 = FACE_OUTER_BOUND ( 'NONE', #4099, .T. ) ;
+#1375 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1376 = EDGE_CURVE ( 'NONE', #1199, #3250, #3488, .T. ) ;
+#1377 = ORIENTED_EDGE ( 'NONE', *, *, #588, .F. ) ;
+#1378 = EDGE_CURVE ( 'NONE', #1692, #1201, #4563, .T. ) ;
+#1379 = VECTOR ( 'NONE', #796, 1000.000000000000000 ) ;
+#1380 = VECTOR ( 'NONE', #3079, 1000.000000000000000 ) ;
+#1381 = ORIENTED_EDGE ( 'NONE', *, *, #1503, .F. ) ;
+#1382 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199500, -13.80999999999999500, -0.0000000000000000000 ) ) ;
+#1383 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#1384 = ORIENTED_EDGE ( 'NONE', *, *, #2927, .T. ) ;
+#1385 = LINE ( 'NONE', #1762, #296 ) ;
+#1386 = ORIENTED_EDGE ( 'NONE', *, *, #2291, .T. ) ;
+#1387 = VECTOR ( 'NONE', #2348, 1000.000000000000000 ) ;
+#1388 = DIRECTION ( 'NONE', ( -1.330566893520345700E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1389 = ORIENTED_EDGE ( 'NONE', *, *, #4936, .F. ) ;
+#1390 = AXIS2_PLACEMENT_3D ( 'NONE', #5063, #3468, #1871 ) ;
+#1391 = VERTEX_POINT ( 'NONE', #3725 ) ;
+#1392 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362397600, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#1393 = ORIENTED_EDGE ( 'NONE', *, *, #4511, .T. ) ;
+#1394 = EDGE_LOOP ( 'NONE', ( #3509, #175, #1515, #950, #817, #815 ) ) ;
+#1395 = VECTOR ( 'NONE', #1232, 1000.000000000000000 ) ;
+#1396 = ORIENTED_EDGE ( 'NONE', *, *, #2639, .F. ) ;
+#1397 = ORIENTED_EDGE ( 'NONE', *, *, #1936, .F. ) ;
+#1398 = AXIS2_PLACEMENT_3D ( 'NONE', #3568, #1168, #3980 ) ;
+#1399 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1400 = ORIENTED_EDGE ( 'NONE', *, *, #993, .F. ) ;
+#1401 = ORIENTED_EDGE ( 'NONE', *, *, #2664, .T. ) ;
+#1402 = VECTOR ( 'NONE', #1537, 1000.000000000000000 ) ;
+#1403 = VERTEX_POINT ( 'NONE', #4532 ) ;
+#1404 = ORIENTED_EDGE ( 'NONE', *, *, #346, .T. ) ;
+#1405 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, -16.51000000000000500, 0.0000000000000000000 ) ) ;
+#1406 = ORIENTED_EDGE ( 'NONE', *, *, #2522, .T. ) ;
+#1407 = LINE ( 'NONE', #679, #2476 ) ;
+#1408 = ORIENTED_EDGE ( 'NONE', *, *, #1848, .T. ) ;
+#1409 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 19.19999999999999900, 2.000000000000000000 ) ) ;
+#1410 = DIRECTION ( 'NONE', ( 0.5000000000000004400, -0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#1411 = EDGE_CURVE ( 'NONE', #563, #3098, #1361, .T. ) ;
+#1412 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 2.368891848716884900, 1.000000000000000000 ) ) ;
+#1413 = EDGE_CURVE ( 'NONE', #237, #2991, #273, .T. ) ;
+#1414 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199500, -13.80999999999999500, -0.0000000000000000000 ) ) ;
+#1415 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800500, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#1416 = AXIS2_PLACEMENT_3D ( 'NONE', #1383, #4190, #1775 ) ;
+#1417 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1418 = CARTESIAN_POINT ( 'NONE', ( 24.06714285714285500, 13.05655776574935000, 4.299999999999999800 ) ) ;
+#1419 = AXIS2_PLACEMENT_3D ( 'NONE', #1108, #2298, #3922 ) ;
+#1420 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1421 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1422 = EDGE_CURVE ( 'NONE', #4467, #5156, #3359, .T. ) ;
+#1423 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#1424 = VECTOR ( 'NONE', #2795, 1000.000000000000000 ) ;
+#1425 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#1426 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -5.000000000000000000, 6.000000000000000000 ) ) ;
+#1427 = AXIS2_PLACEMENT_3D ( 'NONE', #1585, #4394, #1982 ) ;
+#1428 = CIRCLE ( 'NONE', #2740, 1.000000000000000900 ) ;
+#1429 = VECTOR ( 'NONE', #2355, 1000.000000000000000 ) ;
+#1430 = LINE ( 'NONE', #3913, #343 ) ;
+#1431 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1432 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1433 = CIRCLE ( 'NONE', #2134, 1.000000000000000900 ) ;
+#1434 = EDGE_CURVE ( 'NONE', #5099, #2704, #1235, .T. ) ;
+#1435 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#1436 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1437 = ORIENTED_EDGE ( 'NONE', *, *, #2345, .F. ) ;
+#1438 = EDGE_CURVE ( 'NONE', #3159, #5096, #3615, .T. ) ;
+#1439 = VECTOR ( 'NONE', #3859, 1000.000000000000100 ) ;
+#1440 = EDGE_CURVE ( 'NONE', #4547, #3459, #2243, .T. ) ;
+#1441 = VECTOR ( 'NONE', #4516, 1000.000000000000000 ) ;
+#1442 = ORIENTED_EDGE ( 'NONE', *, *, #3578, .T. ) ;
+#1443 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#1444 = EDGE_CURVE ( 'NONE', #2905, #4290, #4385, .T. ) ;
+#1445 = ORIENTED_EDGE ( 'NONE', *, *, #4593, .F. ) ;
+#1446 = VECTOR ( 'NONE', #1103, 1000.000000000000000 ) ;
+#1447 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1448 = ORIENTED_EDGE ( 'NONE', *, *, #1167, .T. ) ;
+#1449 = VERTEX_POINT ( 'NONE', #3754 ) ;
+#1450 = CARTESIAN_POINT ( 'NONE', ( 24.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#1451 = FACE_OUTER_BOUND ( 'NONE', #268, .T. ) ;
+#1452 = ORIENTED_EDGE ( 'NONE', *, *, #1207, .F. ) ;
+#1453 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 4.378679656440360700, 1.000000000000000000 ) ) ;
+#1454 = FACE_OUTER_BOUND ( 'NONE', #1777, .T. ) ;
+#1455 = VERTEX_POINT ( 'NONE', #2544 ) ;
+#1456 = ORIENTED_EDGE ( 'NONE', *, *, #2234, .F. ) ;
+#1457 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1458 = ORIENTED_EDGE ( 'NONE', *, *, #764, .T. ) ;
+#1459 = AXIS2_PLACEMENT_3D ( 'NONE', #2534, #113, #2922 ) ;
+#1460 = ORIENTED_EDGE ( 'NONE', *, *, #4210, .F. ) ;
+#1461 = CIRCLE ( 'NONE', #4618, 1.600000000000000800 ) ;
+#1462 = ORIENTED_EDGE ( 'NONE', *, *, #727, .T. ) ;
+#1463 = ORIENTED_EDGE ( 'NONE', *, *, #1503, .T. ) ;
+#1464 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#1465 = CARTESIAN_POINT ( 'NONE', ( -19.99000000000000200, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#1466 = ORIENTED_EDGE ( 'NONE', *, *, #3113, .T. ) ;
+#1467 = VECTOR ( 'NONE', #2168, 1000.000000000000000 ) ;
+#1468 = ORIENTED_EDGE ( 'NONE', *, *, #3585, .F. ) ;
+#1469 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800800, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#1470 = ORIENTED_EDGE ( 'NONE', *, *, #2972, .F. ) ;
+#1471 = CARTESIAN_POINT ( 'NONE', ( -27.97669089436529800, 0.7500000000000000000, 1.000000000000000000 ) ) ;
+#1472 = CARTESIAN_POINT ( 'NONE', ( -31.17500000000000100, -2.600000000000000100, 1.200000000000000000 ) ) ;
+#1473 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#1474 = DIRECTION ( 'NONE', ( 0.7071067811865470200, -0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#1475 = EDGE_CURVE ( 'NONE', #493, #2686, #3177, .T. ) ;
+#1476 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1477 = EDGE_CURVE ( 'NONE', #712, #4722, #105, .T. ) ;
+#1478 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 20.19999999999999900, 4.299999999999999800 ) ) ;
+#1479 = CARTESIAN_POINT ( 'NONE', ( -30.84749741877120100, 2.927502581228814900, 0.8724974187711870300 ) ) ;
+#1480 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#1481 = VECTOR ( 'NONE', #880, 1000.000000000000000 ) ;
+#1482 = ADVANCED_FACE ( 'NONE', ( #1987 ), #1758, .F. ) ;
+#1483 = CYLINDRICAL_SURFACE ( 'NONE', #4744, 4.250000000000000000 ) ;
+#1484 = DIRECTION ( 'NONE', ( 0.5000000000000008900, -0.8660254037844381500, 0.0000000000000000000 ) ) ;
+#1485 = LINE ( 'NONE', #2427, #1363 ) ;
+#1486 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1487 = EDGE_CURVE ( 'NONE', #3369, #5078, #933, .T. ) ;
+#1488 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672604900, 19.69999999999999900, -0.6999999999999999600 ) ) ;
+#1489 = LINE ( 'NONE', #3720, #2426 ) ;
+#1490 = LINE ( 'NONE', #1846, #2428 ) ;
+#1491 = LINE ( 'NONE', #2162, #1618 ) ;
+#1492 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 19.19999999999999900, 0.0000000000000000000 ) ) ;
+#1493 = EDGE_LOOP ( 'NONE', ( #2594, #3631, #2092, #1024 ) ) ;
+#1494 = DIRECTION ( 'NONE', ( 0.8660254037844388200, 0.4999999999999998300, -0.0000000000000000000 ) ) ;
+#1495 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1496 = LINE ( 'NONE', #1178, #3499 ) ;
+#1497 = ORIENTED_EDGE ( 'NONE', *, *, #4270, .F. ) ;
+#1498 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 0.7500000000000000000, 6.000000000000000000 ) ) ;
+#1499 = VECTOR ( 'NONE', #4806, 1000.000000000000000 ) ;
+#1500 = EDGE_CURVE ( 'NONE', #2455, #4955, #5190, .T. ) ;
+#1501 = VECTOR ( 'NONE', #3451, 1000.000000000000000 ) ;
+#1502 = VERTEX_POINT ( 'NONE', #149 ) ;
+#1503 = EDGE_CURVE ( 'NONE', #1343, #2091, #2928, .T. ) ;
+#1504 = ORIENTED_EDGE ( 'NONE', *, *, #4739, .T. ) ;
+#1505 = CIRCLE ( 'NONE', #2525, 4.250000000000000000 ) ;
+#1506 = DIRECTION ( 'NONE', ( -6.123233995736769700E-017, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1507 = ORIENTED_EDGE ( 'NONE', *, *, #2773, .F. ) ;
+#1508 = VECTOR ( 'NONE', #332, 1000.000000000000000 ) ;
+#1509 = ADVANCED_FACE ( 'NONE', ( #793 ), #3882, .F. ) ;
+#1510 = ORIENTED_EDGE ( 'NONE', *, *, #191, .T. ) ;
+#1511 = ORIENTED_EDGE ( 'NONE', *, *, #2577, .T. ) ;
+#1512 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#1513 = AXIS2_PLACEMENT_3D ( 'NONE', #3203, #797, #1210 ) ;
+#1514 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#1515 = ORIENTED_EDGE ( 'NONE', *, *, #2485, .F. ) ;
+#1516 = ORIENTED_EDGE ( 'NONE', *, *, #2044, .T. ) ;
+#1517 = ORIENTED_EDGE ( 'NONE', *, *, #1939, .F. ) ;
+#1518 = VECTOR ( 'NONE', #2403, 1000.000000000000000 ) ;
+#1519 = VERTEX_POINT ( 'NONE', #1796 ) ;
+#1520 = AXIS2_PLACEMENT_3D ( 'NONE', #2411, #3222, #400 ) ;
+#1521 = ORIENTED_EDGE ( 'NONE', *, *, #1500, .F. ) ;
+#1522 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1523 = EDGE_LOOP ( 'NONE', ( #2018, #4161, #3564, #523 ) ) ;
+#1524 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1525 = VECTOR ( 'NONE', #2678, 1000.000000000000000 ) ;
+#1526 = EDGE_LOOP ( 'NONE', ( #4719, #3385, #3636, #1028, #1152 ) ) ;
+#1527 = AXIS2_PLACEMENT_3D ( 'NONE', #12, #4875, #2474 ) ;
+#1528 = AXIS2_PLACEMENT_3D ( 'NONE', #986, #3801, #1399 ) ;
+#1529 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1530 = ORIENTED_EDGE ( 'NONE', *, *, #2247, .F. ) ;
+#1531 = LINE ( 'NONE', #2655, #3539 ) ;
+#1532 = VERTEX_POINT ( 'NONE', #4990 ) ;
+#1533 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#1534 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1535 = EDGE_CURVE ( 'NONE', #2265, #3032, #5071, .T. ) ;
+#1536 = VERTEX_POINT ( 'NONE', #2185 ) ;
+#1537 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1538 = EDGE_CURVE ( 'NONE', #139, #2471, #4003, .T. ) ;
+#1539 = ADVANCED_FACE ( 'NONE', ( #1055 ), #2668, .F. ) ;
+#1540 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672604900, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1541 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1542 = EDGE_CURVE ( 'NONE', #1772, #2648, #4819, .T. ) ;
+#1543 = CARTESIAN_POINT ( 'NONE', ( -13.06789321881344000, 20.99289321881345000, -0.6999999999999999600 ) ) ;
+#1544 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -2.368891848716884900, 6.000000000000000000 ) ) ;
+#1545 = EDGE_CURVE ( 'NONE', #2201, #3262, #1748, .T. ) ;
+#1546 = VERTEX_POINT ( 'NONE', #1409 ) ;
+#1547 = LINE ( 'NONE', #1253, #94 ) ;
+#1548 = CALENDAR_DATE ( 2016, 8, 12 ) ;
+#1549 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -19.57000000000000000, 6.000000000000000000 ) ) ;
+#1550 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 22.19999999999999900, -0.6999999999999999600 ) ) ;
+#1551 = LINE ( 'NONE', #4625, #478 ) ;
+#1552 = VECTOR ( 'NONE', #3960, 1000.000000000000100 ) ;
+#1553 = VECTOR ( 'NONE', #3057, 1000.000000000000000 ) ;
+#1554 = CARTESIAN_POINT ( 'NONE', ( -19.99000000000000200, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#1555 = AXIS2_PLACEMENT_3D ( 'NONE', #4888, #2080, #4497 ) ;
+#1556 = FACE_OUTER_BOUND ( 'NONE', #1924, .T. ) ;
+#1557 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1558 = ORIENTED_EDGE ( 'NONE', *, *, #1750, .T. ) ;
+#1559 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1560 = VECTOR ( 'NONE', #3815, 1000.000000000000000 ) ;
+#1561 = ADVANCED_FACE ( 'NONE', ( #1615 ), #2555, .F. ) ;
+#1562 = VECTOR ( 'NONE', #1685, 1000.000000000000000 ) ;
+#1563 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1564 = ORIENTED_EDGE ( 'NONE', *, *, #974, .F. ) ;
+#1565 = ORIENTED_EDGE ( 'NONE', *, *, #3901, .F. ) ;
+#1566 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, 19.49289321881345000, -0.6999999999999999600 ) ) ;
+#1567 = VECTOR ( 'NONE', #857, 1000.000000000000000 ) ;
+#1568 = EDGE_LOOP ( 'NONE', ( #4262, #141, #3090, #615 ) ) ;
+#1569 = DIRECTION ( 'NONE', ( -0.5000000000000001100, 0.8660254037844386000, 0.0000000000000000000 ) ) ;
+#1570 = EDGE_CURVE ( 'NONE', #4664, #1215, #4690, .T. ) ;
+#1571 = ORIENTED_EDGE ( 'NONE', *, *, #1723, .F. ) ;
+#1572 = VECTOR ( 'NONE', #4684, 1000.000000000000000 ) ;
+#1573 = EDGE_CURVE ( 'NONE', #1147, #380, #542, .T. ) ;
+#1574 = VERTEX_POINT ( 'NONE', #5034 ) ;
+#1575 = LINE ( 'NONE', #2958, #1333 ) ;
+#1576 = ORIENTED_EDGE ( 'NONE', *, *, #2548, .F. ) ;
+#1577 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1578 = VERTEX_POINT ( 'NONE', #3028 ) ;
+#1579 = ORIENTED_EDGE ( 'NONE', *, *, #1438, .T. ) ;
+#1580 = ORIENTED_EDGE ( 'NONE', *, *, #4368, .T. ) ;
+#1581 = LINE ( 'NONE', #2407, #3153 ) ;
+#1582 = VERTEX_POINT ( 'NONE', #2225 ) ;
+#1583 = VERTEX_POINT ( 'NONE', #3846 ) ;
+#1584 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1585 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#1586 = AXIS2_PLACEMENT_3D ( 'NONE', #3311, #925, #3334 ) ;
+#1587 = ORIENTED_EDGE ( 'NONE', *, *, #1167, .F. ) ;
+#1588 = VECTOR ( 'NONE', #4159, 1000.000000000000100 ) ;
+#1589 = VERTEX_POINT ( 'NONE', #3434 ) ;
+#1590 = PLANE ( 'NONE', #2989 ) ;
+#1591 = EDGE_CURVE ( 'NONE', #3340, #395, #1490, .T. ) ;
+#1592 = ADVANCED_FACE ( 'NONE', ( #3493 ), #4245, .F. ) ;
+#1593 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1594 = ORIENTED_EDGE ( 'NONE', *, *, #2177, .F. ) ;
+#1595 = CARTESIAN_POINT ( 'NONE', ( -24.64999999999999900, 12.24398312239625300, 4.299999999999999800 ) ) ;
+#1596 = AXIS2_PLACEMENT_3D ( 'NONE', #1311, #4123, #1714 ) ;
+#1597 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1598 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199200, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#1599 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#1600 = CARTESIAN_POINT ( 'NONE', ( -14.60342712474619900, 20.40710678118654900, -0.6999999999999999600 ) ) ;
+#1601 = PLANE ( 'NONE', #3572 ) ;
+#1602 = ORIENTED_EDGE ( 'NONE', *, *, #760, .T. ) ;
+#1603 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1604 = EDGE_CURVE ( 'NONE', #525, #1145, #1367, .T. ) ;
+#1605 = EDGE_CURVE ( 'NONE', #1215, #5006, #279, .T. ) ;
+#1606 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#1607 = LINE ( 'NONE', #486, #3746 ) ;
+#1608 = CIRCLE ( 'NONE', #3937, 1.000000000000000000 ) ;
+#1609 = FACE_OUTER_BOUND ( 'NONE', #4220, .T. ) ;
+#1610 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#1611 = AXIS2_PLACEMENT_3D ( 'NONE', #2026, #917, #1315 ) ;
+#1612 = ADVANCED_FACE ( 'NONE', ( #3623 ), #157, .T. ) ;
+#1613 = DIRECTION ( 'NONE', ( 0.4999999999999997200, 0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#1614 = CYLINDRICAL_SURFACE ( 'NONE', #59, 1.000000000000000000 ) ;
+#1615 = FACE_OUTER_BOUND ( 'NONE', #3058, .T. ) ;
+#1616 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1617 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, -4.500000000000000000, -0.6999999999999992900 ) ) ;
+#1618 = VECTOR ( 'NONE', #4957, 1000.000000000000000 ) ;
+#1619 = ORIENTED_EDGE ( 'NONE', *, *, #2763, .T. ) ;
+#1620 = DIRECTION ( 'NONE', ( 0.7071067811865470200, -0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#1621 = FACE_OUTER_BOUND ( 'NONE', #2517, .T. ) ;
+#1622 = AXIS2_PLACEMENT_3D ( 'NONE', #4236, #5036, #623 ) ;
+#1623 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1624 = ORIENTED_EDGE ( 'NONE', *, *, #559, .T. ) ;
+#1625 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1626 = PLANE ( 'NONE', #3779 ) ;
+#1627 = PLANE ( 'NONE', #2316 ) ;
+#1628 = CIRCLE ( 'NONE', #3225, 1.000000000000000000 ) ;
+#1629 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, 4.299999999999999800 ) ) ;
+#1630 = ORIENTED_EDGE ( 'NONE', *, *, #4703, .F. ) ;
+#1631 = ORIENTED_EDGE ( 'NONE', *, *, #1542, .F. ) ;
+#1632 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1633 = ADVANCED_FACE ( 'NONE', ( #4446 ), #3240, .F. ) ;
+#1634 = VECTOR ( 'NONE', #5090, 1000.000000000000000 ) ;
+#1635 = ORIENTED_EDGE ( 'NONE', *, *, #4032, .F. ) ;
+#1636 = VECTOR ( 'NONE', #3134, 1000.000000000000000 ) ;
+#1637 = ORIENTED_EDGE ( 'NONE', *, *, #2089, .F. ) ;
+#1638 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, 16.50999999999999800, 2.500000000000000000 ) ) ;
+#1639 = VERTEX_POINT ( 'NONE', #1473 ) ;
+#1640 = ADVANCED_FACE ( 'NONE', ( #4319 ), #1111, .F. ) ;
+#1641 = ORIENTED_EDGE ( 'NONE', *, *, #3637, .T. ) ;
+#1642 = ORIENTED_EDGE ( 'NONE', *, *, #1295, .T. ) ;
+#1643 = VERTEX_POINT ( 'NONE', #5084 ) ;
+#1644 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1645 = LINE ( 'NONE', #4415, #569 ) ;
+#1646 = EDGE_CURVE ( 'NONE', #1708, #2197, #2300, .T. ) ;
+#1647 = ORIENTED_EDGE ( 'NONE', *, *, #1573, .T. ) ;
+#1648 = ORIENTED_EDGE ( 'NONE', *, *, #1131, .F. ) ;
+#1649 = CARTESIAN_POINT ( 'NONE', ( 23.19000000000000100, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#1650 = EDGE_CURVE ( 'NONE', #810, #127, #1242, .T. ) ;
+#1651 = CARTESIAN_POINT ( 'NONE', ( 18.13655776574935200, 18.98714285714286000, 4.299999999999999800 ) ) ;
+#1652 = ORIENTED_EDGE ( 'NONE', *, *, #4905, .T. ) ;
+#1653 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1654 = LINE ( 'NONE', #5118, #2472 ) ;
+#1655 = ORIENTED_EDGE ( 'NONE', *, *, #2308, .F. ) ;
+#1656 = PLANE ( 'NONE', #800 ) ;
+#1657 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1658 = ORIENTED_EDGE ( 'NONE', *, *, #4227, .T. ) ;
+#1659 = VECTOR ( 'NONE', #3063, 1000.000000000000000 ) ;
+#1660 = ORIENTED_EDGE ( 'NONE', *, *, #3006, .F. ) ;
+#1661 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -21.44999999999999900, 4.299999999999999800 ) ) ;
+#1662 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, -16.51000000000000900, 0.0000000000000000000 ) ) ;
+#1663 = DIRECTION ( 'NONE', ( -0.7071067811865475700, -0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#1664 = AXIS2_PLACEMENT_3D ( 'NONE', #672, #3480, #1085 ) ;
+#1665 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 5.000000000000000000, -0.6999999999999999600 ) ) ;
+#1666 = APPLICATION_PROTOCOL_DEFINITION ( 'international standard', 'config_control_design', 1994, #2062 ) ;
+#1667 = ADVANCED_FACE ( 'NONE', ( #3116 ), #675, .F. ) ;
+#1668 = AXIS2_PLACEMENT_3D ( 'NONE', #4034, #1623, #4435 ) ;
+#1669 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1670 = PLANE ( 'NONE', #1355 ) ;
+#1671 = ADVANCED_FACE ( 'NONE', ( #2178 ), #4696, .F. ) ;
+#1672 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602700, 16.51000000000000500, 0.0000000000000000000 ) ) ;
+#1673 = DIRECTION ( 'NONE', ( 0.7071067811865475700, -0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#1674 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, 20.69999999999999900, 4.299999999999999800 ) ) ;
+#1675 = CIRCLE ( 'NONE', #1953, 0.9999999999999991100 ) ;
+#1676 = EDGE_CURVE ( 'NONE', #4909, #4347, #5132, .T. ) ;
+#1677 = DATE_AND_TIME ( #2721, #3809 ) ;
+#1678 = EDGE_CURVE ( 'NONE', #3154, #1341, #4191, .T. ) ;
+#1679 = CIRCLE ( 'NONE', #878, 1.600000000000000800 ) ;
+#1680 = APPROVAL_STATUS ( 'not_yet_approved' ) ;
+#1681 = LINE ( 'NONE', #3943, #4626 ) ;
+#1682 = VECTOR ( 'NONE', #659, 1000.000000000000000 ) ;
+#1683 = ORIENTED_EDGE ( 'NONE', *, *, #92, .F. ) ;
+#1684 = CARTESIAN_POINT ( 'NONE', ( 17.34000000000000000, -16.51000000000000200, 2.000000000000000000 ) ) ;
+#1685 = DIRECTION ( 'NONE', ( -1.000000000000000000, 6.123233995736770200E-016, 0.0000000000000000000 ) ) ;
+#1686 = AXIS2_PLACEMENT_3D ( 'NONE', #990, #2589, #3803 ) ;
+#1687 = FACE_OUTER_BOUND ( 'NONE', #4119, .T. ) ;
+#1688 = DIRECTION ( 'NONE', ( -0.4999999999999996700, -0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#1689 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1690 = AXIS2_PLACEMENT_3D ( 'NONE', #3508, #2314, #5127 ) ;
+#1691 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1692 = VERTEX_POINT ( 'NONE', #1912 ) ;
+#1693 = ADVANCED_FACE ( 'NONE', ( #1925 ), #4717, .T. ) ;
+#1694 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -22.19999999999999900, 4.299999999999999800 ) ) ;
+#1695 = CIRCLE ( 'NONE', #2861, 1.000000000000000000 ) ;
+#1696 = EDGE_CURVE ( 'NONE', #1403, #108, #5000, .T. ) ;
+#1697 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362398700, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#1698 = FACE_OUTER_BOUND ( 'NONE', #3587, .T. ) ;
+#1699 = ORIENTED_EDGE ( 'NONE', *, *, #5194, .T. ) ;
+#1700 = ORIENTED_EDGE ( 'NONE', *, *, #327, .F. ) ;
+#1701 = CYLINDRICAL_SURFACE ( 'NONE', #532, 0.9999999999999991100 ) ;
+#1702 = EDGE_LOOP ( 'NONE', ( #3889, #1789, #581, #1190 ) ) ;
+#1703 = CARTESIAN_POINT ( 'NONE', ( -20.77499999999999900, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#1704 = ORIENTED_EDGE ( 'NONE', *, *, #1164, .T. ) ;
+#1705 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#1706 = EDGE_CURVE ( 'NONE', #4640, #3339, #2053, .T. ) ;
+#1707 = FACE_OUTER_BOUND ( 'NONE', #4771, .T. ) ;
+#1708 = VERTEX_POINT ( 'NONE', #2705 ) ;
+#1709 = ORIENTED_EDGE ( 'NONE', *, *, #4969, .F. ) ;
+#1710 = FACE_OUTER_BOUND ( 'NONE', #3147, .T. ) ;
+#1711 = VERTEX_POINT ( 'NONE', #3927 ) ;
+#1712 = VERTEX_POINT ( 'NONE', #1922 ) ;
+#1713 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#1714 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1715 = EDGE_CURVE ( 'NONE', #375, #1716, #4885, .T. ) ;
+#1716 = VERTEX_POINT ( 'NONE', #4738 ) ;
+#1717 = EDGE_CURVE ( 'NONE', #2881, #1772, #728, .T. ) ;
+#1718 = VERTEX_POINT ( 'NONE', #316 ) ;
+#1719 = LINE ( 'NONE', #3992, #3721 ) ;
+#1720 = ORIENTED_EDGE ( 'NONE', *, *, #4868, .F. ) ;
+#1721 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, 16.50999999999999400, 2.500000000000000000 ) ) ;
+#1722 = ORIENTED_EDGE ( 'NONE', *, *, #2547, .T. ) ;
+#1723 = EDGE_CURVE ( 'NONE', #1353, #4350, #2724, .T. ) ;
+#1724 = VERTEX_POINT ( 'NONE', #2717 ) ;
+#1725 = FACE_OUTER_BOUND ( 'NONE', #2515, .T. ) ;
+#1726 = ORIENTED_EDGE ( 'NONE', *, *, #4502, .F. ) ;
+#1727 = ADVANCED_FACE ( 'NONE', ( #599 ), #4752, .F. ) ;
+#1728 = AXIS2_PLACEMENT_3D ( 'NONE', #3087, #1375, #4362 ) ;
+#1729 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -4.378679656440360700, 1.000000000000000000 ) ) ;
+#1730 = ORIENTED_EDGE ( 'NONE', *, *, #625, .F. ) ;
+#1731 = CARTESIAN_POINT ( 'NONE', ( -29.27499999999999900, 4.500000000000000000, -0.6999999999999999600 ) ) ;
+#1732 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, 0.0000000000000000000 ) ) ;
+#1733 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, -4.500000000000000000, 4.299999999999999800 ) ) ;
+#1734 = CARTESIAN_POINT ( 'NONE', ( 24.06714285714285500, -13.05655776574935000, 2.000000000000000000 ) ) ;
+#1735 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1736 = AXIS2_PLACEMENT_3D ( 'NONE', #507, #3726, #1321 ) ;
+#1737 = PLANE ( 'NONE', #1222 ) ;
+#1738 = VECTOR ( 'NONE', #1323, 1000.000000000000000 ) ;
+#1739 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, -19.49289321881345000, -0.6999999999999999600 ) ) ;
+#1740 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1741 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1742 = LINE ( 'NONE', #1272, #666 ) ;
+#1743 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, -16.32049935181331100 ) ) ;
+#1744 = FACE_OUTER_BOUND ( 'NONE', #4271, .T. ) ;
+#1745 = CARTESIAN_POINT ( 'NONE', ( -24.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#1746 = VECTOR ( 'NONE', #3994, 1000.000000000000100 ) ;
+#1747 = EDGE_CURVE ( 'NONE', #1971, #2475, #4754, .T. ) ;
+#1748 = CIRCLE ( 'NONE', #4005, 1.600000000000000800 ) ;
+#1749 = CIRCLE ( 'NONE', #5091, 1.000000000000000900 ) ;
+#1750 = EDGE_CURVE ( 'NONE', #1294, #2032, #3686, .T. ) ;
+#1751 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, 4.299999999999999800 ) ) ;
+#1752 = EDGE_LOOP ( 'NONE', ( #2511, #4675, #1128, #2079 ) ) ;
+#1753 = LINE ( 'NONE', #3975, #2561 ) ;
+#1754 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 21.44999999999999900, 6.000000000000000000 ) ) ;
+#1755 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1756 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602000, -16.51000000000000500, -0.0000000000000000000 ) ) ;
+#1757 = ORIENTED_EDGE ( 'NONE', *, *, #84, .T. ) ;
+#1758 = PLANE ( 'NONE', #3736 ) ;
+#1759 = LINE ( 'NONE', #211, #3762 ) ;
+#1760 = ORIENTED_EDGE ( 'NONE', *, *, #490, .F. ) ;
+#1761 = EDGE_CURVE ( 'NONE', #1273, #4426, #1681, .T. ) ;
+#1762 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#1763 = ORIENTED_EDGE ( 'NONE', *, *, #3551, .F. ) ;
+#1764 = LINE ( 'NONE', #1785, #2571 ) ;
+#1765 = AXIS2_PLACEMENT_3D ( 'NONE', #2169, #2570, #158 ) ;
+#1766 = ORIENTED_EDGE ( 'NONE', *, *, #3111, .F. ) ;
+#1767 = CARTESIAN_POINT ( 'NONE', ( -23.19000000000000100, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#1768 = VECTOR ( 'NONE', #1529, 1000.000000000000000 ) ;
+#1769 = ORIENTED_EDGE ( 'NONE', *, *, #1313, .F. ) ;
+#1770 = ORIENTED_EDGE ( 'NONE', *, *, #2280, .F. ) ;
+#1771 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1772 = VERTEX_POINT ( 'NONE', #1157 ) ;
+#1773 = VERTEX_POINT ( 'NONE', #4370 ) ;
+#1774 = ORIENTED_EDGE ( 'NONE', *, *, #388, .T. ) ;
+#1775 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1776 = VECTOR ( 'NONE', #5176, 1000.000000000000000 ) ;
+#1777 = EDGE_LOOP ( 'NONE', ( #2015, #1437, #1468, #256 ) ) ;
+#1778 = VERTEX_POINT ( 'NONE', #1964 ) ;
+#1779 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#1780 = ORIENTED_EDGE ( 'NONE', *, *, #4994, .F. ) ;
+#1781 = CYLINDRICAL_SURFACE ( 'NONE', #1114, 1.000000000000000900 ) ;
+#1782 = EDGE_CURVE ( 'NONE', #3760, #4541, #2610, .T. ) ;
+#1783 = ORIENTED_EDGE ( 'NONE', *, *, #5085, .F. ) ;
+#1784 = CARTESIAN_POINT ( 'NONE', ( -18.13655776574935200, 18.98714285714286000, 2.000000000000000000 ) ) ;
+#1785 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, -4.500000000000000000, -0.6999999999999992900 ) ) ;
+#1786 = EDGE_CURVE ( 'NONE', #4595, #2069, #2496, .T. ) ;
+#1787 = EDGE_LOOP ( 'NONE', ( #2404, #231, #262, #2911 ) ) ;
+#1788 = VECTOR ( 'NONE', #5043, 1000.000000000000000 ) ;
+#1789 = ORIENTED_EDGE ( 'NONE', *, *, #3193, .F. ) ;
+#1790 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1791 = AXIS2_PLACEMENT_3D ( 'NONE', #4016, #3223, #816 ) ;
+#1792 = CARTESIAN_POINT ( 'NONE', ( 17.34000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#1793 = DIRECTION ( 'NONE', ( -0.7071067811865474600, -0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#1794 = AXIS2_PLACEMENT_3D ( 'NONE', #420, #3231, #840 ) ;
+#1795 = DIRECTION ( 'NONE', ( -1.330566893520345700E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1796 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, -0.6999999999999992900 ) ) ;
+#1797 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1798 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1799 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, -20.69999999999999900, 4.299999999999999800 ) ) ;
+#1800 = CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT ( #4066, #2863, ( #452 ) ) ;
+#1801 = AXIS2_PLACEMENT_3D ( 'NONE', #153, #1790, #4589 ) ;
+#1802 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#1803 = LINE ( 'NONE', #1177, #3808 ) ;
+#1804 = APPROVAL_DATE_TIME ( #2858, #2326 ) ;
+#1805 = EDGE_CURVE ( 'NONE', #1343, #2621, #480, .T. ) ;
+#1806 = CYLINDRICAL_SURFACE ( 'NONE', #1416, 1.000000000000000900 ) ;
+#1807 = VECTOR ( 'NONE', #1541, 1000.000000000000000 ) ;
+#1808 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#1809 = FACE_OUTER_BOUND ( 'NONE', #1523, .T. ) ;
+#1810 = EDGE_CURVE ( 'NONE', #3956, #3154, #342, .T. ) ;
+#1811 = VECTOR ( 'NONE', #3689, 1000.000000000000000 ) ;
+#1812 = EDGE_CURVE ( 'NONE', #1184, #4838, #3553, .T. ) ;
+#1813 = ORIENTED_EDGE ( 'NONE', *, *, #697, .F. ) ;
+#1814 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1815 = EDGE_CURVE ( 'NONE', #749, #1254, #3430, .T. ) ;
+#1816 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 22.19999999999999900, -0.6999999999999999600 ) ) ;
+#1817 = FACE_OUTER_BOUND ( 'NONE', #4304, .T. ) ;
+#1818 = FACE_BOUND ( 'NONE', #2886, .T. ) ;
+#1819 = ORIENTED_EDGE ( 'NONE', *, *, #4070, .F. ) ;
+#1820 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, 20.33743026049845100, 2.000000000000000000 ) ) ;
+#1821 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#1822 = ORIENTED_EDGE ( 'NONE', *, *, #3414, .F. ) ;
+#1823 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#1824 = CYLINDRICAL_SURFACE ( 'NONE', #4945, 0.9999999999999991100 ) ;
+#1825 = EDGE_LOOP ( 'NONE', ( #2840, #247, #245, #2382 ) ) ;
+#1826 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, 20.33743026049845100, -0.6999999999999999600 ) ) ;
+#1827 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1828 = AXIS2_PLACEMENT_3D ( 'NONE', #890, #4112, #1288 ) ;
+#1829 = ORIENTED_EDGE ( 'NONE', *, *, #531, .F. ) ;
+#1830 = FACE_OUTER_BOUND ( 'NONE', #4720, .T. ) ;
+#1831 = DIRECTION ( 'NONE', ( -1.224646799147352000E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1832 = ORIENTED_EDGE ( 'NONE', *, *, #5129, .F. ) ;
+#1833 = VECTOR ( 'NONE', #100, 1000.000000000000000 ) ;
+#1834 = EDGE_LOOP ( 'NONE', ( #2816, #184, #5149, #4334 ) ) ;
+#1835 = ORIENTED_EDGE ( 'NONE', *, *, #45, .F. ) ;
+#1836 = EDGE_LOOP ( 'NONE', ( #2524, #2523, #2520, #5165 ) ) ;
+#1837 = ORIENTED_EDGE ( 'NONE', *, *, #3296, .T. ) ;
+#1838 = VECTOR ( 'NONE', #5180, 1000.000000000000000 ) ;
+#1839 = ORIENTED_EDGE ( 'NONE', *, *, #4395, .F. ) ;
+#1840 = ORIENTED_EDGE ( 'NONE', *, *, #974, .T. ) ;
+#1841 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#1842 = ORIENTED_EDGE ( 'NONE', *, *, #2216, .T. ) ;
+#1843 = DIRECTION ( 'NONE', ( 0.7071067811865470200, -0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#1844 = VECTOR ( 'NONE', #920, 1000.000000000000000 ) ;
+#1845 = ORIENTED_EDGE ( 'NONE', *, *, #1313, .T. ) ;
+#1846 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199900, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#1847 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#1848 = EDGE_CURVE ( 'NONE', #896, #3780, #2357, .T. ) ;
+#1849 = ORIENTED_EDGE ( 'NONE', *, *, #3452, .F. ) ;
+#1850 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1851 = EDGE_CURVE ( 'NONE', #4601, #3714, #4380, .T. ) ;
+#1852 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#1853 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -19.19999999999999900, -0.6999999999999999600 ) ) ;
+#1854 = VERTEX_POINT ( 'NONE', #2395 ) ;
+#1855 = AXIS2_PLACEMENT_3D ( 'NONE', #4060, #1657, #4465 ) ;
+#1856 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1857 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1858 = AXIS2_PLACEMENT_3D ( 'NONE', #2607, #3008, #608 ) ;
+#1859 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1860 = EDGE_CURVE ( 'NONE', #1997, #2991, #3302, .T. ) ;
+#1861 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1862 = VECTOR ( 'NONE', #2104, 1000.000000000000000 ) ;
+#1863 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#1864 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1865 = LINE ( 'NONE', #37, #3998 ) ;
+#1866 = CIRCLE ( 'NONE', #3123, 1.000000000000000900 ) ;
+#1867 = LINE ( 'NONE', #3590, #1746 ) ;
+#1868 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1869 = VECTOR ( 'NONE', #979, 1000.000000000000000 ) ;
+#1870 = LINE ( 'NONE', #97, #2796 ) ;
+#1871 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1872 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#1873 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1874 = VERTEX_POINT ( 'NONE', #1606 ) ;
+#1875 = EDGE_CURVE ( 'NONE', #2810, #1899, #1173, .T. ) ;
+#1876 = VECTOR ( 'NONE', #4344, 1000.000000000000000 ) ;
+#1877 = EDGE_CURVE ( 'NONE', #2103, #3212, #3172, .T. ) ;
+#1878 = EDGE_LOOP ( 'NONE', ( #4592, #4711, #3142, #2884, #4204, #3596 ) ) ;
+#1879 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#1880 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1881 = ADVANCED_FACE ( 'NONE', ( #1309 ), #3048, .T. ) ;
+#1882 = LINE ( 'NONE', #3005, #806 ) ;
+#1883 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1884 = ORIENTED_EDGE ( 'NONE', *, *, #230, .T. ) ;
+#1885 = AXIS2_PLACEMENT_3D ( 'NONE', #5035, #2207, #834 ) ;
+#1886 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1887 = ORIENTED_EDGE ( 'NONE', *, *, #4648, .F. ) ;
+#1888 = CIRCLE ( 'NONE', #636, 1.000000000000000000 ) ;
+#1889 = ORIENTED_EDGE ( 'NONE', *, *, #4326, .T. ) ;
+#1890 = FACE_OUTER_BOUND ( 'NONE', #1787, .T. ) ;
+#1891 = ORIENTED_EDGE ( 'NONE', *, *, #3743, .F. ) ;
+#1892 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681198800, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#1893 = ORIENTED_EDGE ( 'NONE', *, *, #4453, .F. ) ;
+#1894 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, -21.19999999999999900, -0.6999999999999999600 ) ) ;
+#1895 = ORIENTED_EDGE ( 'NONE', *, *, #4868, .T. ) ;
+#1896 = AXIS2_PLACEMENT_3D ( 'NONE', #295, #3435, #3117 ) ;
+#1897 = ORIENTED_EDGE ( 'NONE', *, *, #2773, .T. ) ;
+#1898 = CIRCLE ( 'NONE', #1300, 4.250000000000003600 ) ;
+#1899 = VERTEX_POINT ( 'NONE', #2429 ) ;
+#1900 = ORIENTED_EDGE ( 'NONE', *, *, #1975, .F. ) ;
+#1901 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, -19.49289321881345000, -0.6999999999999999600 ) ) ;
+#1902 = VERTEX_POINT ( 'NONE', #4437 ) ;
+#1903 = LINE ( 'NONE', #1471, #4299 ) ;
+#1904 = VERTEX_POINT ( 'NONE', #14 ) ;
+#1905 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1906 = ORIENTED_EDGE ( 'NONE', *, *, #1224, .T. ) ;
+#1907 = LINE ( 'NONE', #4239, #1788 ) ;
+#1908 = EDGE_LOOP ( 'NONE', ( #648, #2615, #3207, #2152 ) ) ;
+#1909 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1910 = ADVANCED_FACE ( 'NONE', ( #101 ), #2033, .F. ) ;
+#1911 = CARTESIAN_POINT ( 'NONE', ( -24.06714285714285500, 13.05655776574934800, 4.299999999999999800 ) ) ;
+#1912 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 2.878679656440359800, 1.000000000000000000 ) ) ;
+#1913 = EDGE_CURVE ( 'NONE', #3793, #1221, #1984, .T. ) ;
+#1914 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#1915 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800800, -19.21000000000000100, 0.0000000000000000000 ) ) ;
+#1916 = EDGE_CURVE ( 'NONE', #2197, #1193, #1049, .T. ) ;
+#1917 = PLANE ( 'NONE', #1690 ) ;
+#1918 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#1919 = AXIS2_PLACEMENT_3D ( 'NONE', #3924, #1522, #4328 ) ;
+#1920 = LINE ( 'NONE', #1161, #2857 ) ;
+#1921 = APPROVAL_PERSON_ORGANIZATION ( #2980, #2326, #3678 ) ;
+#1922 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, 12.24398312239625500, 2.000000000000000000 ) ) ;
+#1923 = LINE ( 'NONE', #4597, #4883 ) ;
+#1924 = EDGE_LOOP ( 'NONE', ( #3973, #4917, #2901, #288 ) ) ;
+#1925 = FACE_OUTER_BOUND ( 'NONE', #5111, .T. ) ;
+#1926 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#1927 = VECTOR ( 'NONE', #3844, 1000.000000000000000 ) ;
+#1928 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1929 = AXIS2_PLACEMENT_3D ( 'NONE', #4801, #1183, #2391 ) ;
+#1930 = ADVANCED_FACE ( 'NONE', ( #2924 ), #4055, .F. ) ;
+#1931 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -5.000000000000000000, 6.000000000000000000 ) ) ;
+#1932 = VECTOR ( 'NONE', #2856, 1000.000000000000000 ) ;
+#1933 = ORIENTED_EDGE ( 'NONE', *, *, #4178, .T. ) ;
+#1934 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, -19.49289321881345000, 0.0000000000000000000 ) ) ;
+#1935 = VECTOR ( 'NONE', #2549, 1000.000000000000000 ) ;
+#1936 = EDGE_CURVE ( 'NONE', #2095, #1391, #4134, .T. ) ;
+#1937 = FACE_OUTER_BOUND ( 'NONE', #3347, .T. ) ;
+#1938 = ORIENTED_EDGE ( 'NONE', *, *, #2077, .T. ) ;
+#1939 = EDGE_CURVE ( 'NONE', #4405, #419, #3999, .T. ) ;
+#1940 = ORIENTED_EDGE ( 'NONE', *, *, #993, .T. ) ;
+#1941 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 20.57000000000000000, 4.299999999999998000 ) ) ;
+#1942 = LINE ( 'NONE', #1213, #5019 ) ;
+#1943 = ORIENTED_EDGE ( 'NONE', *, *, #4703, .T. ) ;
+#1944 = EDGE_LOOP ( 'NONE', ( #1021, #308, #5023, #1005 ) ) ;
+#1945 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1946 = AXIS2_PLACEMENT_3D ( 'NONE', #1035, #3840, #1436 ) ;
+#1947 = CARTESIAN_POINT ( 'NONE', ( -13.56499942082922100, 21.48999942082920000, 4.299999999999999800 ) ) ;
+#1948 = VERTEX_POINT ( 'NONE', #2993 ) ;
+#1949 = EDGE_LOOP ( 'NONE', ( #47, #882, #4215, #558 ) ) ;
+#1950 = ORIENTED_EDGE ( 'NONE', *, *, #5045, .F. ) ;
+#1951 = FACE_OUTER_BOUND ( 'NONE', #2598, .T. ) ;
+#1952 = VERTEX_POINT ( 'NONE', #1414 ) ;
+#1953 = AXIS2_PLACEMENT_3D ( 'NONE', #4419, #1996, #4810 ) ;
+#1954 = VERTEX_POINT ( 'NONE', #3412 ) ;
+#1955 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 19.19999999999999900, 4.299999999999999800 ) ) ;
+#1956 = EDGE_CURVE ( 'NONE', #2198, #1322, #787, .T. ) ;
+#1957 = ADVANCED_FACE ( 'NONE', ( #3877 ), #2220, .F. ) ;
+#1958 = EDGE_LOOP ( 'NONE', ( #4418, #1205, #3337, #3820 ) ) ;
+#1959 = VERTEX_POINT ( 'NONE', #5020 ) ;
+#1960 = DIRECTION ( 'NONE', ( 0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#1961 = VECTOR ( 'NONE', #756, 1000.000000000000000 ) ;
+#1962 = ORIENTED_EDGE ( 'NONE', *, *, #385, .T. ) ;
+#1963 = ORIENTED_EDGE ( 'NONE', *, *, #494, .T. ) ;
+#1964 = CARTESIAN_POINT ( 'NONE', ( -14.60342712474619900, -20.40710678118654900, 0.0000000000000000000 ) ) ;
+#1965 = AXIS2_PLACEMENT_3D ( 'NONE', #339, #5166, #3162 ) ;
+#1966 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1967 = VERTEX_POINT ( 'NONE', #2230 ) ;
+#1968 = LINE ( 'NONE', #2122, #767 ) ;
+#1969 = ORIENTED_EDGE ( 'NONE', *, *, #152, .T. ) ;
+#1970 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
+#1971 = VERTEX_POINT ( 'NONE', #4241 ) ;
+#1972 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1973 = EDGE_CURVE ( 'NONE', #1011, #743, #2789, .T. ) ;
+#1974 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#1975 = EDGE_CURVE ( 'NONE', #3205, #2947, #1866, .T. ) ;
+#1976 = EDGE_CURVE ( 'NONE', #4166, #610, #4814, .T. ) ;
+#1977 = EDGE_CURVE ( 'NONE', #4213, #4026, #4943, .T. ) ;
+#1978 = DIRECTION ( 'NONE', ( -0.1674394999967895000, -0.9858823529411738800, 0.0000000000000000000 ) ) ;
+#1979 = ADVANCED_FACE ( 'NONE', ( #1744 ), #3616, .F. ) ;
+#1980 = LINE ( 'NONE', #3687, #1862 ) ;
+#1981 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 2.000000000000000000 ) ) ;
+#1982 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1983 = FACE_OUTER_BOUND ( 'NONE', #4933, .T. ) ;
+#1984 = LINE ( 'NONE', #1649, #5186 ) ;
+#1985 = LINE ( 'NONE', #4749, #930 ) ;
+#1986 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#1987 = FACE_OUTER_BOUND ( 'NONE', #638, .T. ) ;
+#1988 = DIRECTION ( 'NONE', ( 1.000000000000000000, -1.734723475976805500E-015, 0.0000000000000000000 ) ) ;
+#1989 = VECTOR ( 'NONE', #3280, 1000.000000000000000 ) ;
+#1990 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, 4.500000000000000000, -0.6999999999999992900 ) ) ;
+#1991 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#1992 = ORIENTED_EDGE ( 'NONE', *, *, #465, .F. ) ;
+#1993 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#1994 = VECTOR ( 'NONE', #1514, 1000.000000000000000 ) ;
+#1995 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#1996 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#1997 = VERTEX_POINT ( 'NONE', #3871 ) ;
+#1998 = DIRECTION ( 'NONE', ( 0.4999999999999996700, 0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#1999 = LINE ( 'NONE', #2604, #2808 ) ;
+#2000 = VERTEX_POINT ( 'NONE', #1469 ) ;
+#2001 = ADVANCED_FACE ( 'NONE', ( #2551 ), #1483, .T. ) ;
+#2002 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, 19.49289321881345000, 4.299999999999999800 ) ) ;
+#2003 = EDGE_CURVE ( 'NONE', #3463, #2951, #2662, .T. ) ;
+#2004 = ORIENTED_EDGE ( 'NONE', *, *, #5075, .F. ) ;
+#2005 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#2006 = VECTOR ( 'NONE', #1831, 1000.000000000000000 ) ;
+#2007 = VERTEX_POINT ( 'NONE', #2657 ) ;
+#2008 = EDGE_CURVE ( 'NONE', #3032, #5031, #1608, .T. ) ;
+#2009 = VECTOR ( 'NONE', #3029, 1000.000000000000100 ) ;
+#2010 = VERTEX_POINT ( 'NONE', #4678 ) ;
+#2011 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, 18.71852980579590000, 4.299999999999999800 ) ) ;
+#2012 = ORIENTED_EDGE ( 'NONE', *, *, #4473, .F. ) ;
+#2013 = ORIENTED_EDGE ( 'NONE', *, *, #4805, .T. ) ;
+#2014 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, -10.27254486838324100, -0.6999999999999999600 ) ) ;
+#2015 = ORIENTED_EDGE ( 'NONE', *, *, #3515, .T. ) ;
+#2016 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800800, -19.21000000000000100, 0.0000000000000000000 ) ) ;
+#2017 = CYLINDRICAL_SURFACE ( 'NONE', #1828, 0.9999999999999991100 ) ;
+#2018 = ORIENTED_EDGE ( 'NONE', *, *, #677, .F. ) ;
+#2019 = ORIENTED_EDGE ( 'NONE', *, *, #217, .F. ) ;
+#2020 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2021 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 6.000000000000000000 ) ) ;
+#2022 = AXIS2_PLACEMENT_3D ( 'NONE', #1754, #3355, #966 ) ;
+#2023 = ORIENTED_EDGE ( 'NONE', *, *, #4401, .T. ) ;
+#2024 = LINE ( 'NONE', #3038, #3091 ) ;
+#2025 = ORIENTED_EDGE ( 'NONE', *, *, #760, .F. ) ;
+#2027 = EDGE_CURVE ( 'NONE', #2704, #4976, #4686, .T. ) ;
+#2026 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 2.000000000000000000 ) ) ;
+#2028 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2029 = ORIENTED_EDGE ( 'NONE', *, *, #2345, .T. ) ;
+#2030 = EDGE_LOOP ( 'NONE', ( #3583, #3582, #4655, #1025 ) ) ;
+#2031 = CYLINDRICAL_SURFACE ( 'NONE', #1586, 1.000000000000000900 ) ;
+#2032 = VERTEX_POINT ( 'NONE', #2286 ) ;
+#2033 = PLANE ( 'NONE', #1794 ) ;
+#2034 = ADVANCED_FACE ( 'NONE', ( #1362 ), #2672, .T. ) ;
+#2035 = AXIS2_PLACEMENT_3D ( 'NONE', #4615, #2204, #5005 ) ;
+#2036 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#2037 = PLANE ( 'NONE', #3390 ) ;
+#2038 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, -22.19999999999999900, 4.299999999999999800 ) ) ;
+#2039 = ADVANCED_FACE ( 'NONE', ( #2422 ), #1099, .F. ) ;
+#2040 = DIRECTION ( 'NONE', ( 1.330566893520345700E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2041 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2042 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.57000000000000000, 2.000000000000000000 ) ) ;
+#2043 = CARTESIAN_POINT ( 'NONE', ( -14.47342712474619100, -20.27710678118655400, 4.299999999999999800 ) ) ;
+#2044 = EDGE_CURVE ( 'NONE', #2961, #1068, #402, .T. ) ;
+#2045 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2046 = EDGE_CURVE ( 'NONE', #3319, #4478, #4440, .T. ) ;
+#2047 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
+#2048 = FACE_OUTER_BOUND ( 'NONE', #3967, .T. ) ;
+#2049 = PLANE ( 'NONE', #166 ) ;
+#2050 = VECTOR ( 'NONE', #1366, 1000.000000000000100 ) ;
+#2051 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -12.24398312239625300, 2.000000000000000000 ) ) ;
+#2052 = AXIS2_PLACEMENT_3D ( 'NONE', #4221, #1814, #4621 ) ;
+#2053 = LINE ( 'NONE', #1115, #2864 ) ;
+#2054 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -0.7500000000000000000, 1.000000000000000000 ) ) ;
+#2055 = ADVANCED_FACE ( 'NONE', ( #1237 ), #4315, .T. ) ;
+#2056 = FACE_OUTER_BOUND ( 'NONE', #2164, .T. ) ;
+#2057 = CARTESIAN_POINT ( 'NONE', ( 2.000000000000000000, 6.000000000000000000, 4.299999999999999800 ) ) ;
+#2058 = PRODUCT_DEFINITION ( 'UNKNOWN', '', #3407, #1278 ) ;
+#2059 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#2060 = FACE_OUTER_BOUND ( 'NONE', #1008, .T. ) ;
+#2061 = ADVANCED_FACE ( 'NONE', ( #2297, #3233 ), #1917, .T. ) ;
+#2062 = APPLICATION_CONTEXT ( 'configuration controlled 3d designs of mechanical parts and assemblies' ) ;
+#2063 = EDGE_LOOP ( 'NONE', ( #515, #74, #4223, #4701 ) ) ;
+#2064 = FACE_OUTER_BOUND ( 'NONE', #2248, .T. ) ;
+#2065 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -21.44999999999999900, 6.000000000000000000 ) ) ;
+#2066 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#2067 = VECTOR ( 'NONE', #2432, 1000.000000000000000 ) ;
+#2068 = CARTESIAN_POINT ( 'NONE', ( 26.45000000000000300, 6.250000000000000000, 6.000000000000000000 ) ) ;
+#2069 = VERTEX_POINT ( 'NONE', #1512 ) ;
+#2070 = EDGE_CURVE ( 'NONE', #4537, #1639, #4565, .T. ) ;
+#2071 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2072 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, 19.49289321881345000, 4.299999999999999800 ) ) ;
+#2073 = ORIENTED_EDGE ( 'NONE', *, *, #2548, .T. ) ;
+#2074 = PLANE ( 'NONE', #225 ) ;
+#2075 = PLANE ( 'NONE', #780 ) ;
+#2076 = CYLINDRICAL_SURFACE ( 'NONE', #4411, 0.9999999999999995600 ) ;
+#2077 = EDGE_CURVE ( 'NONE', #3276, #768, #154, .T. ) ;
+#2078 = ORIENTED_EDGE ( 'NONE', *, *, #4227, .F. ) ;
+#2079 = ORIENTED_EDGE ( 'NONE', *, *, #4608, .T. ) ;
+#2080 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2081 = ORIENTED_EDGE ( 'NONE', *, *, #3926, .F. ) ;
+#2082 = EDGE_LOOP ( 'NONE', ( #684, #3639, #3906, #658, #3204, #2144, #4281, #4788, #3722, #4785, #1118, #2219, #2101, #1630, #294, #565, #2141, #3077, #3075, #2630, #2532, #1462, #3589, #5141 ) ) ;
+#2083 = PERSON_AND_ORGANIZATION_ROLE ( 'creator' ) ;
+#2084 = VECTOR ( 'NONE', #469, 1000.000000000000200 ) ;
+#2085 = EDGE_CURVE ( 'NONE', #380, #1724, #25, .T. ) ;
+#2086 = ORIENTED_EDGE ( 'NONE', *, *, #3176, .T. ) ;
+#2087 = ORIENTED_EDGE ( 'NONE', *, *, #4671, .T. ) ;
+#2088 = ADVANCED_FACE ( 'NONE', ( #2048 ), #2338, .T. ) ;
+#2089 = EDGE_CURVE ( 'NONE', #2890, #1201, #5125, .T. ) ;
+#2090 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 20.57000000000000000, 4.299999999999998000 ) ) ;
+#2091 = VERTEX_POINT ( 'NONE', #733 ) ;
+#2092 = ORIENTED_EDGE ( 'NONE', *, *, #2942, .F. ) ;
+#2093 = CARTESIAN_POINT ( 'NONE', ( -27.97669089436529800, -0.7500000000000000000, 6.000000000000000000 ) ) ;
+#2094 = VECTOR ( 'NONE', #2700, 1000.000000000000000 ) ;
+#2095 = VERTEX_POINT ( 'NONE', #3544 ) ;
+#2096 = CARTESIAN_POINT ( 'NONE', ( 24.64999999999999900, 12.24398312239625300, 2.000000000000000000 ) ) ;
+#2097 = VERTEX_POINT ( 'NONE', #4364 ) ;
+#2098 = ADVANCED_FACE ( 'NONE', ( #4065 ), #854, .F. ) ;
+#2099 = CARTESIAN_POINT ( 'NONE', ( 18.13655776574935200, -18.98714285714286000, 2.000000000000000000 ) ) ;
+#2100 = DIRECTION ( 'NONE', ( -0.8660254037844388200, 0.4999999999999997200, 0.0000000000000000000 ) ) ;
+#2101 = ORIENTED_EDGE ( 'NONE', *, *, #70, .F. ) ;
+#2102 = VECTOR ( 'NONE', #873, 1000.000000000000000 ) ;
+#2103 = VERTEX_POINT ( 'NONE', #2738 ) ;
+#2104 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2105 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2106 = AXIS2_PLACEMENT_3D ( 'NONE', #2108, #491, #3299 ) ;
+#2107 = EDGE_LOOP ( 'NONE', ( #4544, #1460 ) ) ;
+#2108 = CARTESIAN_POINT ( 'NONE', ( -27.97669089436529800, -0.7500000000000000000, 6.000000000000000000 ) ) ;
+#2109 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 19.19999999999999900, 0.0000000000000000000 ) ) ;
+#2110 = DIRECTION ( 'NONE', ( 0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#2111 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2112 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#2113 = ADVANCED_FACE ( 'NONE', ( #981 ), #338, .T. ) ;
+#2114 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#2115 = LINE ( 'NONE', #883, #3043 ) ;
+#2116 = EDGE_CURVE ( 'NONE', #563, #4646, #4993, .T. ) ;
+#2117 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2118 = EDGE_CURVE ( 'NONE', #4359, #2626, #1920, .T. ) ;
+#2119 = CIRCLE ( 'NONE', #773, 1.000000000000000900 ) ;
+#2120 = VECTOR ( 'NONE', #2803, 1000.000000000000000 ) ;
+#2121 = LINE ( 'NONE', #3941, #1051 ) ;
+#2122 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -10.27254486838324100, 2.000000000000000000 ) ) ;
+#2123 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#2124 = VECTOR ( 'NONE', #4131, 1000.000000000000000 ) ;
+#2125 = AXIS2_PLACEMENT_3D ( 'NONE', #2165, #4960, #2557 ) ;
+#2126 = VECTOR ( 'NONE', #4184, 1000.000000000000000 ) ;
+#2127 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2128 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, 19.49289321881345000, 2.000000000000000000 ) ) ;
+#2129 = VERTEX_POINT ( 'NONE', #1563 ) ;
+#2130 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2131 = LINE ( 'NONE', #3510, #4143 ) ;
+#2132 = EDGE_CURVE ( 'NONE', #2194, #4026, #3931, .T. ) ;
+#2133 = ORIENTED_EDGE ( 'NONE', *, *, #3997, .T. ) ;
+#2134 = AXIS2_PLACEMENT_3D ( 'NONE', #1745, #4556, #2146 ) ;
+#2135 = LINE ( 'NONE', #162, #1066 ) ;
+#2136 = ORIENTED_EDGE ( 'NONE', *, *, #1812, .F. ) ;
+#2137 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2138 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2139 = EDGE_LOOP ( 'NONE', ( #1400, #1269, #297, #5051 ) ) ;
+#2140 = LINE ( 'NONE', #1040, #1072 ) ;
+#2141 = ORIENTED_EDGE ( 'NONE', *, *, #633, .F. ) ;
+#2142 = ORIENTED_EDGE ( 'NONE', *, *, #4718, .T. ) ;
+#2143 = FACE_OUTER_BOUND ( 'NONE', #1752, .T. ) ;
+#2144 = ORIENTED_EDGE ( 'NONE', *, *, #2180, .T. ) ;
+#2145 = VERTEX_POINT ( 'NONE', #2761 ) ;
+#2146 = DIRECTION ( 'NONE', ( 1.000000000000000000, -1.734723475976805500E-015, 0.0000000000000000000 ) ) ;
+#2147 = EDGE_CURVE ( 'NONE', #1068, #2641, #723, .T. ) ;
+#2148 = ORIENTED_EDGE ( 'NONE', *, *, #1283, .T. ) ;
+#2149 = EDGE_CURVE ( 'NONE', #4547, #4283, #1803, .T. ) ;
+#2150 = ORIENTED_EDGE ( 'NONE', *, *, #2347, .T. ) ;
+#2151 = FACE_OUTER_BOUND ( 'NONE', #3532, .T. ) ;
+#2152 = ORIENTED_EDGE ( 'NONE', *, *, #665, .F. ) ;
+#2153 = ORIENTED_EDGE ( 'NONE', *, *, #5070, .F. ) ;
+#2154 = VERTEX_POINT ( 'NONE', #4797 ) ;
+#2155 = FACE_BOUND ( 'NONE', #4730, .T. ) ;
+#2156 = EDGE_LOOP ( 'NONE', ( #4558, #1397, #1396, #2462 ) ) ;
+#2157 = AXIS2_PLACEMENT_3D ( 'NONE', #2974, #575, #3379 ) ;
+#2158 = PLANE ( 'NONE', #2022 ) ;
+#2159 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, -18.71852980579590000, 4.299999999999999800 ) ) ;
+#2160 = AXIS2_PLACEMENT_3D ( 'NONE', #1052, #4263, #3450 ) ;
+#2161 = ORIENTED_EDGE ( 'NONE', *, *, #940, .F. ) ;
+#2162 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#2163 = LINE ( 'NONE', #4538, #1095 ) ;
+#2164 = EDGE_LOOP ( 'NONE', ( #1517, #5076, #4171, #4973 ) ) ;
+#2165 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -19.57000000000000000, 2.000000000000000000 ) ) ;
+#2166 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2167 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#2168 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2169 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 2.000000000000000000 ) ) ;
+#2170 = EDGE_CURVE ( 'NONE', #1854, #2455, #4748, .T. ) ;
+#2171 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2172 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -12.24398312239625300, 2.000000000000000000 ) ) ;
+#2173 = LINE ( 'NONE', #355, #592 ) ;
+#2174 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2175 = LINE ( 'NONE', #2159, #2050 ) ;
+#2176 = ORIENTED_EDGE ( 'NONE', *, *, #340, .T. ) ;
+#2177 = EDGE_CURVE ( 'NONE', #3580, #4919, #2173, .T. ) ;
+#2178 = FACE_OUTER_BOUND ( 'NONE', #5046, .T. ) ;
+#2179 = FACE_OUTER_BOUND ( 'NONE', #626, .T. ) ;
+#2180 = EDGE_CURVE ( 'NONE', #2641, #4663, #1675, .T. ) ;
+#2181 = ORIENTED_EDGE ( 'NONE', *, *, #3995, .F. ) ;
+#2182 = LINE ( 'NONE', #2021, #36 ) ;
+#2183 = PLANE ( 'NONE', #3362 ) ;
+#2184 = ORIENTED_EDGE ( 'NONE', *, *, #1877, .T. ) ;
+#2185 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, -19.49289321881345000, 0.0000000000000000000 ) ) ;
+#2186 = LINE ( 'NONE', #2831, #3505 ) ;
+#2187 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2188 = AXIS2_PLACEMENT_3D ( 'NONE', #687, #3495, #1101 ) ;
+#2189 = CYLINDRICAL_SURFACE ( 'NONE', #1249, 1.000000000000000900 ) ;
+#2190 = ORIENTED_EDGE ( 'NONE', *, *, #230, .F. ) ;
+#2191 = ORIENTED_EDGE ( 'NONE', *, *, #4505, .T. ) ;
+#2192 = LINE ( 'NONE', #3648, #3831 ) ;
+#2193 = AXIS2_PLACEMENT_3D ( 'NONE', #3605, #384, #5200 ) ;
+#2194 = VERTEX_POINT ( 'NONE', #1600 ) ;
+#2195 = PLANE ( 'NONE', #3132 ) ;
+#2196 = VECTOR ( 'NONE', #350, 1000.000000000000000 ) ;
+#2197 = VERTEX_POINT ( 'NONE', #1194 ) ;
+#2198 = VERTEX_POINT ( 'NONE', #2406 ) ;
+#2199 = EDGE_LOOP ( 'NONE', ( #2967, #1906, #361, #2262 ) ) ;
+#2200 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, 2.000000000000000000 ) ) ;
+#2201 = VERTEX_POINT ( 'NONE', #4004 ) ;
+#2202 = LINE ( 'NONE', #4847, #1138 ) ;
+#2203 = VERTEX_POINT ( 'NONE', #5209 ) ;
+#2204 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2205 = ORIENTED_EDGE ( 'NONE', *, *, #92, .T. ) ;
+#2206 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2207 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2208 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 1.543412575162920000, 1.000000000000000000 ) ) ;
+#2209 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2210 = ORIENTED_EDGE ( 'NONE', *, *, #633, .T. ) ;
+#2211 = ORIENTED_EDGE ( 'NONE', *, *, #1291, .T. ) ;
+#2212 = VECTOR ( 'NONE', #3148, 1000.000000000000000 ) ;
+#2213 = EDGE_CURVE ( 'NONE', #4046, #2640, #2605, .T. ) ;
+#2214 = ORIENTED_EDGE ( 'NONE', *, *, #1163, .F. ) ;
+#2215 = PLANE ( 'NONE', #1858 ) ;
+#2216 = EDGE_CURVE ( 'NONE', #1343, #5018, #4622, .T. ) ;
+#2217 = ORIENTED_EDGE ( 'NONE', *, *, #217, .T. ) ;
+#2218 = VECTOR ( 'NONE', #2293, 1000.000000000000000 ) ;
+#2219 = ORIENTED_EDGE ( 'NONE', *, *, #1500, .T. ) ;
+#2220 = PLANE ( 'NONE', #3398 ) ;
+#2221 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2222 = AXIS2_PLACEMENT_3D ( 'NONE', #3394, #4229, #1821 ) ;
+#2223 = AXIS2_PLACEMENT_3D ( 'NONE', #4343, #2722, #323 ) ;
+#2224 = PLANE ( 'NONE', #3921 ) ;
+#2225 = CARTESIAN_POINT ( 'NONE', ( -14.47342712474619100, 20.27710678118655400, 4.299999999999999800 ) ) ;
+#2226 = EDGE_CURVE ( 'NONE', #4226, #1902, #3546, .T. ) ;
+#2227 = ORIENTED_EDGE ( 'NONE', *, *, #1805, .T. ) ;
+#2228 = CARTESIAN_POINT ( 'NONE', ( -1.076572875253784400, 20.27710678118655000, 2.000000000000000000 ) ) ;
+#2229 = AXIS2_PLACEMENT_3D ( 'NONE', #5178, #2762, #357 ) ;
+#2230 = CARTESIAN_POINT ( 'NONE', ( -0.9465728752538080700, -20.40710678118654900, -0.6999999999999999600 ) ) ;
+#2231 = DIRECTION ( 'NONE', ( 1.000000000000000000, -0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2232 = VECTOR ( 'NONE', #460, 1000.000000000000000 ) ;
+#2233 = CALENDAR_DATE ( 2016, 8, 12 ) ;
+#2234 = EDGE_CURVE ( 'NONE', #2947, #4732, #2490, .T. ) ;
+#2235 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2236 = FACE_OUTER_BOUND ( 'NONE', #888, .T. ) ;
+#2237 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2238 = VECTOR ( 'NONE', #4803, 1000.000000000000100 ) ;
+#2239 = VECTOR ( 'NONE', #1524, 1000.000000000000000 ) ;
+#2240 = EDGE_CURVE ( 'NONE', #2305, #1326, #335, .T. ) ;
+#2241 = DIRECTION ( 'NONE', ( -0.8660254037844383700, -0.5000000000000006700, 0.0000000000000000000 ) ) ;
+#2242 = EDGE_CURVE ( 'NONE', #1639, #5099, #3426, .T. ) ;
+#2243 = LINE ( 'NONE', #4950, #4256 ) ;
+#2244 = LINE ( 'NONE', #2174, #2126 ) ;
+#2245 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2246 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -10.27254486838324100, 4.299999999999999800 ) ) ;
+#2247 = EDGE_CURVE ( 'NONE', #645, #434, #2352, .T. ) ;
+#2248 = EDGE_LOOP ( 'NONE', ( #2969, #2087, #3014, #3012 ) ) ;
+#2249 = AXIS2_PLACEMENT_3D ( 'NONE', #322, #1476, #1928 ) ;
+#2250 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#2251 = CIRCLE ( 'NONE', #1158, 1.600000000000000800 ) ;
+#2252 = ORIENTED_EDGE ( 'NONE', *, *, #4130, .F. ) ;
+#2253 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2254 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, 12.24398312239625500, 4.299999999999999800 ) ) ;
+#2255 = ORIENTED_EDGE ( 'NONE', *, *, #2116, .F. ) ;
+#2256 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -21.44999999999999900, 4.299999999999999800 ) ) ;
+#2257 = LINE ( 'NONE', #3498, #1188 ) ;
+#2258 = EDGE_LOOP ( 'NONE', ( #2632, #630, #2181, #3279 ) ) ;
+#2259 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2260 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, -20.33743026049845100, -0.6999999999999999600 ) ) ;
+#2261 = AXIS2_PLACEMENT_3D ( 'NONE', #2545, #137, #2937 ) ;
+#2262 = ORIENTED_EDGE ( 'NONE', *, *, #282, .T. ) ;
+#2263 = LINE ( 'NONE', #3033, #4153 ) ;
+#2264 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, 19.19999999999999900, 2.000000000000000000 ) ) ;
+#2265 = VERTEX_POINT ( 'NONE', #2841 ) ;
+#2266 = VECTOR ( 'NONE', #574, 1000.000000000000000 ) ;
+#2267 = ORIENTED_EDGE ( 'NONE', *, *, #3287, .F. ) ;
+#2268 = ORIENTED_EDGE ( 'NONE', *, *, #88, .F. ) ;
+#2269 = ORIENTED_EDGE ( 'NONE', *, *, #559, .F. ) ;
+#2270 = ORIENTED_EDGE ( 'NONE', *, *, #4905, .F. ) ;
+#2271 = FACE_OUTER_BOUND ( 'NONE', #705, .T. ) ;
+#2272 = ORIENTED_EDGE ( 'NONE', *, *, #914, .F. ) ;
+#2273 = EDGE_LOOP ( 'NONE', ( #436, #2576, #3049, #2019 ) ) ;
+#2274 = PLANE ( 'NONE', #1664 ) ;
+#2275 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, -0.6999999999999999600 ) ) ;
+#2276 = ADVANCED_FACE ( 'NONE', ( #3297 ), #3242, .F. ) ;
+#2277 = ORIENTED_EDGE ( 'NONE', *, *, #4878, .F. ) ;
+#2278 = VECTOR ( 'NONE', #5004, 1000.000000000000000 ) ;
+#2279 = VERTEX_POINT ( 'NONE', #2437 ) ;
+#2280 = EDGE_CURVE ( 'NONE', #777, #1449, #1547, .T. ) ;
+#2281 = VERTEX_POINT ( 'NONE', #2054 ) ;
+#2282 = ORIENTED_EDGE ( 'NONE', *, *, #2940, .F. ) ;
+#2283 = DIRECTION ( 'NONE', ( 0.4999999999999996700, 0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#2284 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#2285 = EDGE_CURVE ( 'NONE', #1959, #4482, #212, .T. ) ;
+#2286 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#2287 = ORIENTED_EDGE ( 'NONE', *, *, #2340, .F. ) ;
+#2288 = PLANE ( 'NONE', #2188 ) ;
+#2289 = AXIS2_PLACEMENT_3D ( 'NONE', #564, #3374, #976 ) ;
+#2290 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2291 = EDGE_CURVE ( 'NONE', #3340, #4588, #4249, .T. ) ;
+#2292 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953617300E-015, 0.0000000000000000000 ) ) ;
+#2293 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2294 = AXIS2_PLACEMENT_3D ( 'NONE', #176, #2985, #585 ) ;
+#2295 = VECTOR ( 'NONE', #2920, 1000.000000000000000 ) ;
+#2296 = ADVANCED_FACE ( 'NONE', ( #1166 ), #862, .T. ) ;
+#2297 = FACE_BOUND ( 'NONE', #4599, .T. ) ;
+#2298 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2299 = VECTOR ( 'NONE', #4561, 1000.000000000000000 ) ;
+#2300 = LINE ( 'NONE', #4682, #30 ) ;
+#2301 = ORIENTED_EDGE ( 'NONE', *, *, #663, .F. ) ;
+#2302 = LINE ( 'NONE', #978, #1369 ) ;
+#2303 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2304 = EDGE_CURVE ( 'NONE', #1075, #2881, #5182, .T. ) ;
+#2305 = VERTEX_POINT ( 'NONE', #39 ) ;
+#2306 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2307 = VECTOR ( 'NONE', #4024, 1000.000000000000000 ) ;
+#2308 = EDGE_CURVE ( 'NONE', #1578, #3159, #4127, .T. ) ;
+#2309 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2310 = ORIENTED_EDGE ( 'NONE', *, *, #2285, .F. ) ;
+#2311 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#2312 = CIRCLE ( 'NONE', #2685, 1.600000000000000800 ) ;
+#2313 = CYLINDRICAL_SURFACE ( 'NONE', #707, 1.000000000000000000 ) ;
+#2314 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2315 = VERTEX_POINT ( 'NONE', #470 ) ;
+#2316 = AXIS2_PLACEMENT_3D ( 'NONE', #1229, #403, #4441 ) ;
+#2317 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -12.24398312239625300, 2.000000000000000000 ) ) ;
+#2318 = ORIENTED_EDGE ( 'NONE', *, *, #3474, .F. ) ;
+#2319 = AXIS2_PLACEMENT_3D ( 'NONE', #31, #2849, #445 ) ;
+#2320 = LINE ( 'NONE', #4410, #1003 ) ;
+#2321 = ORIENTED_EDGE ( 'NONE', *, *, #1747, .T. ) ;
+#2322 = VECTOR ( 'NONE', #1557, 1000.000000000000000 ) ;
+#2323 = ADVANCED_FACE ( 'NONE', ( #5062 ), #2074, .F. ) ;
+#2324 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#2325 = ORIENTED_EDGE ( 'NONE', *, *, #790, .T. ) ;
+#2326 = APPROVAL ( #869, 'UNSPECIFIED' ) ;
+#2327 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2328 = ORIENTED_EDGE ( 'NONE', *, *, #3252, .T. ) ;
+#2329 = AXIS2_PLACEMENT_3D ( 'NONE', #192, #2994, #591 ) ;
+#2330 = ORIENTED_EDGE ( 'NONE', *, *, #3366, .F. ) ;
+#2331 = CIRCLE ( 'NONE', #4784, 1.600000000000000800 ) ;
+#2332 = EDGE_LOOP ( 'NONE', ( #372, #3021, #3666, #367, #1408, #2341, #1884, #3765, #2950, #1683, #5164, #2268, #1504, #122, #1840, #3852, #1837, #4455, #3411, #3282, #1760, #4708, #4966, #4698, #4286, #3202, #124, #642, #4787, #639, #2184, #3284, #3157, #2682, #1384, #1641 ) ) ;
+#2333 = ORIENTED_EDGE ( 'NONE', *, *, #4608, .F. ) ;
+#2334 = VERTEX_POINT ( 'NONE', #4504 ) ;
+#2335 = ORIENTED_EDGE ( 'NONE', *, *, #2027, .T. ) ;
+#2336 = VECTOR ( 'NONE', #4735, 1000.000000000000000 ) ;
+#2337 = ORIENTED_EDGE ( 'NONE', *, *, #2845, .T. ) ;
+#2338 = PLANE ( 'NONE', #2223 ) ;
+#2339 = CARTESIAN_POINT ( 'NONE', ( 18.13655776574935200, -18.98714285714286000, 4.299999999999999800 ) ) ;
+#2340 = EDGE_CURVE ( 'NONE', #2948, #2905, #2115, .T. ) ;
+#2341 = ORIENTED_EDGE ( 'NONE', *, *, #3824, .T. ) ;
+#2342 = LINE ( 'NONE', #3104, #2218 ) ;
+#2343 = ORIENTED_EDGE ( 'NONE', *, *, #4477, .T. ) ;
+#2344 = DIRECTION ( 'NONE', ( -1.000000000000000000, 6.123233995736770200E-016, 0.0000000000000000000 ) ) ;
+#2345 = EDGE_CURVE ( 'NONE', #2709, #696, #1980, .T. ) ;
+#2346 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.330566893520345700E-016, 0.0000000000000000000 ) ) ;
+#2347 = EDGE_CURVE ( 'NONE', #3516, #2032, #2918, .T. ) ;
+#2348 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2349 = ADVANCED_FACE ( 'NONE', ( #3993 ), #662, .F. ) ;
+#2350 = DIRECTION ( 'NONE', ( -0.7071067811865474600, 0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#2351 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 1.782507743686435200, 0.2999999999999999900 ) ) ;
+#2352 = LINE ( 'NONE', #4852, #4372 ) ;
+#2353 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 4.299999999999999800 ) ) ;
+#2354 = DATE_AND_TIME ( #3427, #4507 ) ;
+#2355 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2356 = LINE ( 'NONE', #1472, #218 ) ;
+#2357 = CIRCLE ( 'NONE', #429, 1.000000000000000900 ) ;
+#2358 = CARTESIAN_POINT ( 'NONE', ( -27.97669089436529800, 0.7500000000000000000, 6.000000000000000000 ) ) ;
+#2359 = LINE ( 'NONE', #1540, #4381 ) ;
+#2360 = PLANE ( 'NONE', #3601 ) ;
+#2361 = PLANE ( 'NONE', #3979 ) ;
+#2362 = LINE ( 'NONE', #3350, #223 ) ;
+#2363 = ORIENTED_EDGE ( 'NONE', *, *, #5010, .T. ) ;
+#2364 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, -19.49289321881345000, 4.299999999999999800 ) ) ;
+#2365 = LINE ( 'NONE', #3872, #229 ) ;
+#2366 = ADVANCED_FACE ( 'NONE', ( #782 ), #4927, .T. ) ;
+#2367 = EDGE_CURVE ( 'NONE', #2890, #4909, #4809, .T. ) ;
+#2368 = ORIENTED_EDGE ( 'NONE', *, *, #621, .T. ) ;
+#2369 = EDGE_CURVE ( 'NONE', #2756, #3195, #3873, .T. ) ;
+#2370 = ORIENTED_EDGE ( 'NONE', *, *, #1591, .T. ) ;
+#2371 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -20.19999999999999900, 4.299999999999999800 ) ) ;
+#2372 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2373 = VECTOR ( 'NONE', #4509, 1000.000000000000000 ) ;
+#2374 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2375 = VERTEX_POINT ( 'NONE', #2128 ) ;
+#2376 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2377 = ORIENTED_EDGE ( 'NONE', *, *, #4438, .T. ) ;
+#2378 = CARTESIAN_POINT ( 'NONE', ( -1.567893218813450000, 21.90710678118655300, -0.6999999999999999600 ) ) ;
+#2379 = AXIS2_PLACEMENT_3D ( 'NONE', #4869, #858, #3671 ) ;
+#2380 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 0.7500000000000000000, 1.000000000000000000 ) ) ;
+#2381 = VECTOR ( 'NONE', #3045, 1000.000000000000200 ) ;
+#2382 = ORIENTED_EDGE ( 'NONE', *, *, #3244, .T. ) ;
+#2383 = VECTOR ( 'NONE', #3796, 1000.000000000000000 ) ;
+#2384 = ORIENTED_EDGE ( 'NONE', *, *, #4876, .F. ) ;
+#2385 = AXIS2_PLACEMENT_3D ( 'NONE', #3827, #1026, #205 ) ;
+#2386 = VERTEX_POINT ( 'NONE', #1734 ) ;
+#2387 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2388 = EDGE_CURVE ( 'NONE', #4467, #4520, #2163, .T. ) ;
+#2389 = EDGE_LOOP ( 'NONE', ( #4897, #1209, #2400, #227 ) ) ;
+#2390 = ORIENTED_EDGE ( 'NONE', *, *, #4766, .F. ) ;
+#2391 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2392 = FACE_BOUND ( 'NONE', #3849, .T. ) ;
+#2393 = VERTEX_POINT ( 'NONE', #528 ) ;
+#2394 = ORIENTED_EDGE ( 'NONE', *, *, #3216, .T. ) ;
+#2395 = CARTESIAN_POINT ( 'NONE', ( -1.567893218813450000, 21.90710678118655300, 4.299999999999999800 ) ) ;
+#2396 = AXIS2_PLACEMENT_3D ( 'NONE', #4539, #2130, #4938 ) ;
+#2397 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#2398 = ORIENTED_EDGE ( 'NONE', *, *, #5181, .F. ) ;
+#2399 = FACE_OUTER_BOUND ( 'NONE', #3737, .T. ) ;
+#2400 = ORIENTED_EDGE ( 'NONE', *, *, #1434, .T. ) ;
+#2401 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2402 = ADVANCED_FACE ( 'NONE', ( #6 ), #2031, .F. ) ;
+#2403 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2404 = ORIENTED_EDGE ( 'NONE', *, *, #4557, .F. ) ;
+#2405 = ORIENTED_EDGE ( 'NONE', *, *, #1939, .T. ) ;
+#2406 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, -20.57000000000000000, 2.000000000000000000 ) ) ;
+#2407 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362397600, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#2408 = EDGE_CURVE ( 'NONE', #2000, #2644, #3097, .T. ) ;
+#2409 = EDGE_LOOP ( 'NONE', ( #130, #3695, #1726, #4667 ) ) ;
+#2410 = EDGE_CURVE ( 'NONE', #1075, #139, #5112, .T. ) ;
+#2411 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 20.19999999999999900, -0.6999999999999999600 ) ) ;
+#2412 = DIRECTION ( 'NONE', ( 0.7071067811865475700, 0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#2413 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 13.72875387184322100, -0.6999999999999999600 ) ) ;
+#2414 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2415 = AXIS2_PLACEMENT_3D ( 'NONE', #2486, #91, #77 ) ;
+#2416 = PLANE ( 'NONE', #503 ) ;
+#2417 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2418 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#2419 = ADVANCED_FACE ( 'NONE', ( #2785 ), #2215, .F. ) ;
+#2420 = VECTOR ( 'NONE', #3310, 1000.000000000000000 ) ;
+#2421 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -1.543412575162920000, 6.000000000000000000 ) ) ;
+#2422 = FACE_OUTER_BOUND ( 'NONE', #307, .T. ) ;
+#2423 = LINE ( 'NONE', #1241, #4442 ) ;
+#2424 = ADVANCED_FACE ( 'NONE', ( #2966 ), #3918, .T. ) ;
+#2425 = AXIS2_PLACEMENT_3D ( 'NONE', #5202, #2790, #389 ) ;
+#2426 = VECTOR ( 'NONE', #4128, 1000.000000000000000 ) ;
+#2427 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, -20.33743026049845100, 2.000000000000000000 ) ) ;
+#2428 = VECTOR ( 'NONE', #4647, 1000.000000000000000 ) ;
+#2429 = CARTESIAN_POINT ( 'NONE', ( -2.482106781186544700, -20.99289321881345000, -0.6999999999999999600 ) ) ;
+#2430 = VECTOR ( 'NONE', #2956, 1000.000000000000200 ) ;
+#2431 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2432 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2433 = EDGE_LOOP ( 'NONE', ( #2272, #254, #1013, #3903, #3904, #2945 ) ) ;
+#2434 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2435 = LINE ( 'NONE', #472, #1811 ) ;
+#2436 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2437 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2438 = ORIENTED_EDGE ( 'NONE', *, *, #2768, .F. ) ;
+#2439 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, -19.49289321881345000, 2.000000000000000000 ) ) ;
+#2440 = EDGE_CURVE ( 'NONE', #108, #4525, #4979, .T. ) ;
+#2441 = LINE ( 'NONE', #1208, #4459 ) ;
+#2442 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2443 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2444 = FACE_OUTER_BOUND ( 'NONE', #4930, .T. ) ;
+#2445 = ORIENTED_EDGE ( 'NONE', *, *, #2132, .F. ) ;
+#2446 = DIRECTION ( 'NONE', ( 1.330566893520345700E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2447 = EDGE_CURVE ( 'NONE', #3072, #712, #1907, .T. ) ;
+#2448 = VERTEX_POINT ( 'NONE', #2228 ) ;
+#2449 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2450 = ADVANCED_FACE ( 'NONE', ( #969 ), #627, .F. ) ;
+#2451 = VECTOR ( 'NONE', #5142, 1000.000000000000000 ) ;
+#2452 = ORIENTED_EDGE ( 'NONE', *, *, #3047, .T. ) ;
+#2453 = EDGE_CURVE ( 'NONE', #1773, #3521, #4862, .T. ) ;
+#2454 = LINE ( 'NONE', #5159, #1387 ) ;
+#2455 = VERTEX_POINT ( 'NONE', #215 ) ;
+#2456 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2457 = ORIENTED_EDGE ( 'NONE', *, *, #2508, .T. ) ;
+#2458 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2459 = ADVANCED_FACE ( 'NONE', ( #711 ), #582, .T. ) ;
+#2460 = ORIENTED_EDGE ( 'NONE', *, *, #4817, .T. ) ;
+#2461 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2462 = ORIENTED_EDGE ( 'NONE', *, *, #3896, .T. ) ;
+#2463 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, 16.50999999999999800, 2.500000000000000000 ) ) ;
+#2464 = ORIENTED_EDGE ( 'NONE', *, *, #3088, .T. ) ;
+#2465 = LINE ( 'NONE', #4021, #4598 ) ;
+#2466 = ORIENTED_EDGE ( 'NONE', *, *, #4240, .F. ) ;
+#2467 = EDGE_LOOP ( 'NONE', ( #2696, #5195, #3214, #963, #3702, #2370 ) ) ;
+#2468 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199900, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#2469 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2470 = ORIENTED_EDGE ( 'NONE', *, *, #2291, .F. ) ;
+#2471 = VERTEX_POINT ( 'NONE', #3454 ) ;
+#2472 = VECTOR ( 'NONE', #289, 1000.000000000000000 ) ;
+#2473 = EDGE_LOOP ( 'NONE', ( #2501, #3081, #960, #961, #4447, #1510 ) ) ;
+#2474 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2475 = VERTEX_POINT ( 'NONE', #1057 ) ;
+#2476 = VECTOR ( 'NONE', #2670, 1000.000000000000000 ) ;
+#2477 = ORIENTED_EDGE ( 'NONE', *, *, #1646, .F. ) ;
+#2478 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -20.19999999999999900, 2.000000000000000000 ) ) ;
+#2479 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2480 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2481 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2482 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2483 = ADVANCED_FACE ( 'NONE', ( #2708 ), #3869, .T. ) ;
+#2484 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2485 = EDGE_CURVE ( 'NONE', #1716, #4069, #4733, .T. ) ;
+#2486 = CARTESIAN_POINT ( 'NONE', ( 24.64999999999999900, 12.24398312239625300, 4.299999999999999800 ) ) ;
+#2487 = ADVANCED_FACE ( 'NONE', ( #2593 ), #458, .T. ) ;
+#2488 = EDGE_CURVE ( 'NONE', #2069, #4855, #3792, .T. ) ;
+#2489 = SECURITY_CLASSIFICATION_LEVEL ( 'unclassified' ) ;
+#2490 = LINE ( 'NONE', #4829, #4506 ) ;
+#2491 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#2492 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2493 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, 20.57000000000000000, 4.299999999999998000 ) ) ;
+#2494 = CIRCLE ( 'NONE', #4641, 1.000000000000000000 ) ;
+#2495 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602000, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#2496 = LINE ( 'NONE', #763, #1429 ) ;
+#2497 = CIRCLE ( 'NONE', #4987, 4.250000000000003600 ) ;
+#2498 = EDGE_CURVE ( 'NONE', #234, #778, #1531, .T. ) ;
+#2499 = PLANE ( 'NONE', #2106 ) ;
+#2500 = VECTOR ( 'NONE', #4135, 1000.000000000000000 ) ;
+#2501 = ORIENTED_EDGE ( 'NONE', *, *, #70, .T. ) ;
+#2502 = ADVANCED_FACE ( 'NONE', ( #3665 ), #4683, .F. ) ;
+#2503 = CIRCLE ( 'NONE', #892, 1.000000000000000000 ) ;
+#2504 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2505 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, -19.49289321881345000, -0.6999999999999999600 ) ) ;
+#2506 = FACE_OUTER_BOUND ( 'NONE', #146, .T. ) ;
+#2507 = ORIENTED_EDGE ( 'NONE', *, *, #1239, .F. ) ;
+#2508 = EDGE_CURVE ( 'NONE', #4781, #3343, #1407, .T. ) ;
+#2509 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#2510 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2511 = ORIENTED_EDGE ( 'NONE', *, *, #1411, .F. ) ;
+#2512 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2513 = LINE ( 'NONE', #1371, #3322 ) ;
+#2514 = EDGE_CURVE ( 'NONE', #2943, #1134, #4610, .T. ) ;
+#2515 = EDGE_LOOP ( 'NONE', ( #4790, #2908, #4891, #4414 ) ) ;
+#2516 = EDGE_CURVE ( 'NONE', #2832, #3340, #320, .T. ) ;
+#2517 = EDGE_LOOP ( 'NONE', ( #2507, #2255, #1849, #3003 ) ) ;
+#2518 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2519 = LINE ( 'NONE', #1142, #1572 ) ;
+#2520 = ORIENTED_EDGE ( 'NONE', *, *, #4737, .F. ) ;
+#2521 = VECTOR ( 'NONE', #3349, 1000.000000000000000 ) ;
+#2522 = EDGE_CURVE ( 'NONE', #139, #1772, #4491, .T. ) ;
+#2523 = ORIENTED_EDGE ( 'NONE', *, *, #555, .F. ) ;
+#2524 = ORIENTED_EDGE ( 'NONE', *, *, #4543, .F. ) ;
+#2525 = AXIS2_PLACEMENT_3D ( 'NONE', #2563, #3777, #333 ) ;
+#2526 = VERTEX_POINT ( 'NONE', #3503 ) ;
+#2527 = ORIENTED_EDGE ( 'NONE', *, *, #5143, .F. ) ;
+#2528 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, -20.69999999999999900, 4.299999999999999800 ) ) ;
+#2529 = LINE ( 'NONE', #1665, #252 ) ;
+#2530 = ORIENTED_EDGE ( 'NONE', *, *, #2977, .T. ) ;
+#2531 = DIRECTION ( 'NONE', ( -0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#2532 = ORIENTED_EDGE ( 'NONE', *, *, #1106, .T. ) ;
+#2533 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, -19.49289321881345000, 0.0000000000000000000 ) ) ;
+#2534 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#2535 = ORIENTED_EDGE ( 'NONE', *, *, #3320, .F. ) ;
+#2536 = LINE ( 'NONE', #4977, #1467 ) ;
+#2537 = ORIENTED_EDGE ( 'NONE', *, *, #3315, .F. ) ;
+#2538 = AXIS2_PLACEMENT_3D ( 'NONE', #4937, #3739, #1345 ) ;
+#2539 = CLOSED_SHELL ( 'NONE', ( #4412, #1979, #2055, #477, #4693, #3482, #3716, #4062, #3217, #4494, #1561, #2349, #1539, #2098, #391, #791, #1281, #1165, #871, #3632, #3987, #4067, #3586, #4045, #4363, #4464, #3108, #2679, #471, #819, #2727, #2804, #1289, #4011, #2713, #691, #3089, #4877, #1930, #2113, #2366, #103, #2897, #3236, #4434, #1143, #265, #48, #309, #848, #4800, #3558, #4089, #2811, #2487, #4747, #2001, #3885, #3670, #4457, #3186, #5119, #15, #4901, #811, #2483, #3667, #2872, #455, #718, #5189, #3911, #3163, #3957, #3138, #4331, #4853, #1251, #2034, #83, #416, #5217, #3541, #1727, #852, #4727, #4513, #4039, #2088, #5094, #3260, #1671, #1191, #1244, #2502, #2296, #1693, #1219, #739, #2865, #1612, #1881, #4882, #5145, #446, #2402, #3313, #2459, #1113, #3232, #362, #912, #4859, #4309, #409, #5171, #3688, #2424, #1482, #4096, #3610, #3267, #2450, #2784, #496, #2847, #1509, #1633, #770, #1301, #1640, #1098, #2419, #4827, #4498, #41, #667, #2323, #2842, #1592, #283, #4772, #2039, #1910, #2754, #3291, #3638, #1957, #1225, #879, #1667, #2276, #2061, #3930, #4122, #3506, #4383, #19, #4431, #334 ) ) ;
+#2540 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2541 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -21.44999999999999900, 6.000000000000000000 ) ) ;
+#2542 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 19.57000000000000000, 2.000000000000000000 ) ) ;
+#2543 = ORIENTED_EDGE ( 'NONE', *, *, #3083, .T. ) ;
+#2544 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, -6.250000000000000000, 4.299999999999999800 ) ) ;
+#2545 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 2.000000000000000000 ) ) ;
+#2546 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199900, -19.21000000000000400, -0.0000000000000000000 ) ) ;
+#2547 = EDGE_CURVE ( 'NONE', #4664, #1199, #2342, .T. ) ;
+#2548 = EDGE_CURVE ( 'NONE', #5223, #3953, #3283, .T. ) ;
+#2549 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2550 = LINE ( 'NONE', #1298, #1481 ) ;
+#2551 = FACE_OUTER_BOUND ( 'NONE', #2899, .T. ) ;
+#2552 = CIRCLE ( 'NONE', #1668, 1.000000000000000000 ) ;
+#2553 = VECTOR ( 'NONE', #900, 1000.000000000000000 ) ;
+#2554 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#2555 = CYLINDRICAL_SURFACE ( 'NONE', #135, 1.600000000000000800 ) ;
+#2556 = LINE ( 'NONE', #1756, #2430 ) ;
+#2557 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2558 = AXIS2_PLACEMENT_3D ( 'NONE', #2933, #1771, #4574 ) ;
+#2559 = VERTEX_POINT ( 'NONE', #1915 ) ;
+#2560 = CARTESIAN_POINT ( 'NONE', ( -1.567893218813450000, -21.90710678118655300, 4.299999999999999800 ) ) ;
+#2561 = VECTOR ( 'NONE', #4377, 1000.000000000000000 ) ;
+#2562 = EDGE_CURVE ( 'NONE', #2991, #2756, #201, .T. ) ;
+#2563 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#2564 = ORIENTED_EDGE ( 'NONE', *, *, #1440, .F. ) ;
+#2565 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2566 = VECTOR ( 'NONE', #2666, 1000.000000000000200 ) ;
+#2567 = ORIENTED_EDGE ( 'NONE', *, *, #119, .T. ) ;
+#2568 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2569 = ORIENTED_EDGE ( 'NONE', *, *, #4939, .T. ) ;
+#2570 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2571 = VECTOR ( 'NONE', #3884, 1000.000000000000000 ) ;
+#2572 = ORIENTED_EDGE ( 'NONE', *, *, #3414, .T. ) ;
+#2573 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2574 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2575 = VECTOR ( 'NONE', #729, 1000.000000000000000 ) ;
+#2576 = ORIENTED_EDGE ( 'NONE', *, *, #3799, .F. ) ;
+#2577 = EDGE_CURVE ( 'NONE', #2903, #1773, #1151, .T. ) ;
+#2578 = ORIENTED_EDGE ( 'NONE', *, *, #1270, .F. ) ;
+#2579 = ORIENTED_EDGE ( 'NONE', *, *, #790, .F. ) ;
+#2580 = EDGE_CURVE ( 'NONE', #1589, #3459, #4232, .T. ) ;
+#2581 = ORIENTED_EDGE ( 'NONE', *, *, #4929, .F. ) ;
+#2582 = LINE ( 'NONE', #5207, #3523 ) ;
+#2583 = VERTEX_POINT ( 'NONE', #1684 ) ;
+#2584 = VERTEX_POINT ( 'NONE', #2090 ) ;
+#2585 = EDGE_LOOP ( 'NONE', ( #2438, #899, #5041, #1963, #4533, #2384, #3460, #1404 ) ) ;
+#2586 = VECTOR ( 'NONE', #1909, 1000.000000000000000 ) ;
+#2587 = EDGE_LOOP ( 'NONE', ( #1445, #1594, #53, #5153 ) ) ;
+#2588 = DIRECTION ( 'NONE', ( -0.8660254037844388200, 0.4999999999999998300, 0.0000000000000000000 ) ) ;
+#2589 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2590 = AXIS2_PLACEMENT_3D ( 'NONE', #3016, #611, #3423 ) ;
+#2591 = ORIENTED_EDGE ( 'NONE', *, *, #3485, .T. ) ;
+#2592 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 19.19999999999999900, 0.0000000000000000000 ) ) ;
+#2593 = FACE_OUTER_BOUND ( 'NONE', #2702, .T. ) ;
+#2594 = ORIENTED_EDGE ( 'NONE', *, *, #1676, .T. ) ;
+#2595 = ORIENTED_EDGE ( 'NONE', *, *, #3551, .T. ) ;
+#2596 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, -1.240472735837103900, 4.299999999999999800 ) ) ;
+#2597 = CC_DESIGN_APPROVAL ( #2326, ( #2058 ) ) ;
+#2598 = EDGE_LOOP ( 'NONE', ( #2739, #1087, #4423, #3312 ) ) ;
+#2599 = AXIS2_PLACEMENT_3D ( 'NONE', #2978, #4186, #168 ) ;
+#2600 = EDGE_CURVE ( 'NONE', #634, #3793, #1029, .T. ) ;
+#2601 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, -21.19999999999999900, 6.000000000000000000 ) ) ;
+#2602 = EDGE_CURVE ( 'NONE', #1718, #3599, #3158, .T. ) ;
+#2603 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#2604 = CARTESIAN_POINT ( 'NONE', ( -1.805330085889910200, -21.66966991411009900, -0.6999999999999999600 ) ) ;
+#2605 = CIRCLE ( 'NONE', #3295, 0.9999999999999991100 ) ;
+#2606 = LOCAL_TIME ( 12, 52, 21.00000000000000000, #3682 ) ;
+#2607 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, 16.50999999999999400, 2.500000000000000000 ) ) ;
+#2608 = FACE_OUTER_BOUND ( 'NONE', #3062, .T. ) ;
+#2609 = EDGE_CURVE ( 'NONE', #1199, #4175, #5168, .T. ) ;
+#2610 = CIRCLE ( 'NONE', #4576, 1.600000000000000800 ) ;
+#2611 = LINE ( 'NONE', #4285, #1553 ) ;
+#2612 = AXIS2_PLACEMENT_3D ( 'NONE', #435, #2844, #23 ) ;
+#2613 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2614 = LINE ( 'NONE', #3731, #2500 ) ;
+#2615 = ORIENTED_EDGE ( 'NONE', *, *, #3662, .T. ) ;
+#2616 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#2617 = LINE ( 'NONE', #1415, #1560 ) ;
+#2618 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -6.500000000000000000, 6.000000000000000000 ) ) ;
+#2619 = CARTESIAN_POINT ( 'NONE', ( 19.99000000000000200, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#2620 = LINE ( 'NONE', #1297, #4639 ) ;
+#2621 = VERTEX_POINT ( 'NONE', #4118 ) ;
+#2622 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#2623 = ORIENTED_EDGE ( 'NONE', *, *, #1761, .T. ) ;
+#2624 = LINE ( 'NONE', #4075, #4645 ) ;
+#2625 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -19.19999999999999900, 4.299999999999999800 ) ) ;
+#2626 = VERTEX_POINT ( 'NONE', #3698 ) ;
+#2627 = ORIENTED_EDGE ( 'NONE', *, *, #388, .F. ) ;
+#2628 = LINE ( 'NONE', #352, #4907 ) ;
+#2629 = ORIENTED_EDGE ( 'NONE', *, *, #3622, .T. ) ;
+#2630 = ORIENTED_EDGE ( 'NONE', *, *, #4700, .T. ) ;
+#2631 = VECTOR ( 'NONE', #4133, 1000.000000000000000 ) ;
+#2632 = ORIENTED_EDGE ( 'NONE', *, *, #3006, .T. ) ;
+#2633 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2634 = ORIENTED_EDGE ( 'NONE', *, *, #3176, .F. ) ;
+#2635 = DIRECTION ( 'NONE', ( -0.8660254037844398200, -0.4999999999999980000, 0.0000000000000000000 ) ) ;
+#2636 = EDGE_LOOP ( 'NONE', ( #3473, #2277, #1819, #1565 ) ) ;
+#2637 = ORIENTED_EDGE ( 'NONE', *, *, #3691, .F. ) ;
+#2638 = CIRCLE ( 'NONE', #4083, 1.600000000000000800 ) ;
+#2639 = EDGE_CURVE ( 'NONE', #5029, #2095, #3026, .T. ) ;
+#2640 = VERTEX_POINT ( 'NONE', #2109 ) ;
+#2641 = VERTEX_POINT ( 'NONE', #3304 ) ;
+#2642 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2643 = EDGE_CURVE ( 'NONE', #1773, #493, #2907, .T. ) ;
+#2644 = VERTEX_POINT ( 'NONE', #4126 ) ;
+#2645 = CIRCLE ( 'NONE', #953, 1.000000000000000000 ) ;
+#2646 = ORIENTED_EDGE ( 'NONE', *, *, #349, .T. ) ;
+#2647 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2648 = VERTEX_POINT ( 'NONE', #1729 ) ;
+#2649 = AXIS2_PLACEMENT_3D ( 'NONE', #4518, #2111, #4921 ) ;
+#2650 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 21.44999999999999900, 4.299999999999999800 ) ) ;
+#2651 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, 20.69999999999999900, 0.0000000000000000000 ) ) ;
+#2652 = AXIS2_PLACEMENT_3D ( 'NONE', #3438, #1039, #3854 ) ;
+#2653 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2654 = EDGE_CURVE ( 'NONE', #1711, #1532, #81, .T. ) ;
+#2655 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, 16.50999999999999800, 2.500000000000000000 ) ) ;
+#2656 = AXIS2_PLACEMENT_3D ( 'NONE', #955, #3757, #1359 ) ;
+#2657 = CARTESIAN_POINT ( 'NONE', ( -19.99000000000000200, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#2658 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#2659 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2660 = CYLINDRICAL_SURFACE ( 'NONE', #2919, 1.000000000000000900 ) ;
+#2661 = CARTESIAN_POINT ( 'NONE', ( 23.19000000000000100, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#2662 = LINE ( 'NONE', #259, #537 ) ;
+#2663 = FACE_OUTER_BOUND ( 'NONE', #1017, .T. ) ;
+#2664 = EDGE_CURVE ( 'NONE', #554, #1712, #1968, .T. ) ;
+#2665 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#2666 = DIRECTION ( 'NONE', ( -0.5000000000000002200, -0.8660254037844384900, 0.0000000000000000000 ) ) ;
+#2667 = EDGE_CURVE ( 'NONE', #610, #3790, #4925, .T. ) ;
+#2668 = CYLINDRICAL_SURFACE ( 'NONE', #4811, 1.000000000000000000 ) ;
+#2669 = FACE_OUTER_BOUND ( 'NONE', #967, .T. ) ;
+#2670 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2671 = EDGE_LOOP ( 'NONE', ( #2876, #5048 ) ) ;
+#2672 = PLANE ( 'NONE', #66 ) ;
+#2673 = AXIS2_PLACEMENT_3D ( 'NONE', #605, #3420, #1020 ) ;
+#2674 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 2.000000000000000000 ) ) ;
+#2675 = VECTOR ( 'NONE', #3561, 1000.000000000000100 ) ;
+#2676 = AXIS2_PLACEMENT_3D ( 'NONE', #660, #2653, #3059 ) ;
+#2677 = VERTEX_POINT ( 'NONE', #4932 ) ;
+#2678 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2679 = ADVANCED_FACE ( 'NONE', ( #5047 ), #1737, .T. ) ;
+#2680 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2681 = LINE ( 'NONE', #2260, #1501 ) ;
+#2682 = ORIENTED_EDGE ( 'NONE', *, *, #4141, .T. ) ;
+#2683 = DIRECTION ( 'NONE', ( 0.7071067811865475700, 0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#2684 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2685 = AXIS2_PLACEMENT_3D ( 'NONE', #2036, #4841, #2431 ) ;
+#2686 = VERTEX_POINT ( 'NONE', #4144 ) ;
+#2687 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2688 = VECTOR ( 'NONE', #1097, 1000.000000000000000 ) ;
+#2689 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2690 = EDGE_LOOP ( 'NONE', ( #2211, #1624, #1660, #1530 ) ) ;
+#2691 = LINE ( 'NONE', #716, #1636 ) ;
+#2692 = ORIENTED_EDGE ( 'NONE', *, *, #915, .F. ) ;
+#2693 = ORIENTED_EDGE ( 'NONE', *, *, #1605, .T. ) ;
+#2694 = DIRECTION ( 'NONE', ( -0.5000000000000005600, 0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#2695 = ORIENTED_EDGE ( 'NONE', *, *, #3353, .F. ) ;
+#2696 = ORIENTED_EDGE ( 'NONE', *, *, #3585, .T. ) ;
+#2697 = CIRCLE ( 'NONE', #4248, 1.000000000000000900 ) ;
+#2698 = ORIENTED_EDGE ( 'NONE', *, *, #2180, .F. ) ;
+#2699 = ORIENTED_EDGE ( 'NONE', *, *, #2805, .T. ) ;
+#2700 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2701 = EDGE_CURVE ( 'NONE', #4166, #761, #647, .T. ) ;
+#2702 = EDGE_LOOP ( 'NONE', ( #305, #3466, #253, #1338 ) ) ;
+#2703 = CYLINDRICAL_SURFACE ( 'NONE', #1390, 1.000000000000000900 ) ;
+#2704 = VERTEX_POINT ( 'NONE', #533 ) ;
+#2705 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#2706 = EDGE_CURVE ( 'NONE', #374, #4426, #4792, .T. ) ;
+#2707 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, -19.21000000000000400, -0.0000000000000000000 ) ) ;
+#2708 = FACE_OUTER_BOUND ( 'NONE', #3115, .T. ) ;
+#2709 = VERTEX_POINT ( 'NONE', #3749 ) ;
+#2710 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2711 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, 18.71852980579590000, 2.000000000000000000 ) ) ;
+#2712 = EDGE_CURVE ( 'NONE', #4976, #1854, #2645, .T. ) ;
+#2713 = ADVANCED_FACE ( 'NONE', ( #1725 ), #2158, .F. ) ;
+#2714 = DIRECTION ( 'NONE', ( -0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2715 = CARTESIAN_POINT ( 'NONE', ( -14.60342712474619900, -20.40710678118654900, -0.6999999999999999600 ) ) ;
+#2716 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, 21.69999999999999900, -0.6999999999999999600 ) ) ;
+#2717 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#2718 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 19.19999999999999900, -0.6999999999999999600 ) ) ;
+#2719 = PLANE ( 'NONE', #2777 ) ;
+#2720 = VECTOR ( 'NONE', #3990, 1000.000000000000000 ) ;
+#2721 = CALENDAR_DATE ( 2016, 8, 12 ) ;
+#2722 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2723 = FACE_OUTER_BOUND ( 'NONE', #5158, .T. ) ;
+#2724 = LINE ( 'NONE', #1133, #1807 ) ;
+#2725 = CIRCLE ( 'NONE', #2830, 1.600000000000000800 ) ;
+#2726 = CARTESIAN_POINT ( 'NONE', ( 17.34000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#2727 = ADVANCED_FACE ( 'NONE', ( #522 ), #1364, .T. ) ;
+#2728 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#2729 = EDGE_CURVE ( 'NONE', #4478, #1948, #4669, .T. ) ;
+#2730 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#2731 = VECTOR ( 'NONE', #3286, 1000.000000000000000 ) ;
+#2732 = EDGE_CURVE ( 'NONE', #1971, #3319, #2536, .T. ) ;
+#2733 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -19.19999999999999900, 4.299999999999999800 ) ) ;
+#2734 = ORIENTED_EDGE ( 'NONE', *, *, #4739, .F. ) ;
+#2735 = FACE_OUTER_BOUND ( 'NONE', #2139, .T. ) ;
+#2736 = PLANE ( 'NONE', #3436 ) ;
+#2737 = LINE ( 'NONE', #3669, #4764 ) ;
+#2738 = CARTESIAN_POINT ( 'NONE', ( -1.076572875253784400, -20.27710678118655000, 2.000000000000000000 ) ) ;
+#2739 = ORIENTED_EDGE ( 'NONE', *, *, #3149, .F. ) ;
+#2740 = AXIS2_PLACEMENT_3D ( 'NONE', #4260, #1857, #4666 ) ;
+#2741 = PLANE ( 'NONE', #2229 ) ;
+#2742 = ORIENTED_EDGE ( 'NONE', *, *, #590, .T. ) ;
+#2743 = AXIS2_PLACEMENT_3D ( 'NONE', #1549, #1945, #4756 ) ;
+#2744 = LINE ( 'NONE', #2851, #620 ) ;
+#2745 = ORIENTED_EDGE ( 'NONE', *, *, #461, .F. ) ;
+#2746 = EDGE_CURVE ( 'NONE', #1952, #351, #3728, .T. ) ;
+#2747 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#2748 = EDGE_LOOP ( 'NONE', ( #2567, #2318, #3649, #5212 ) ) ;
+#2749 = VECTOR ( 'NONE', #3408, 1000.000000000000000 ) ;
+#2750 = VERTEX_POINT ( 'NONE', #1792 ) ;
+#2751 = VECTOR ( 'NONE', #2510, 1000.000000000000000 ) ;
+#2752 = EDGE_LOOP ( 'NONE', ( #1587, #3857, #2734, #2270 ) ) ;
+#2753 = EDGE_LOOP ( 'NONE', ( #1893, #4154, #1263, #4584, #908, #2695 ) ) ;
+#2754 = ADVANCED_FACE ( 'NONE', ( #2399 ), #2183, .F. ) ;
+#2755 = ORIENTED_EDGE ( 'NONE', *, *, #465, .T. ) ;
+#2756 = VERTEX_POINT ( 'NONE', #2970 ) ;
+#2757 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 2.500000000000000000 ) ) ;
+#2758 = VECTOR ( 'NONE', #775, 1000.000000000000000 ) ;
+#2759 = ORIENTED_EDGE ( 'NONE', *, *, #3044, .T. ) ;
+#2760 = ORIENTED_EDGE ( 'NONE', *, *, #905, .T. ) ;
+#2761 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -20.57000000000000000, 4.299999999999998000 ) ) ;
+#2762 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2763 = EDGE_CURVE ( 'NONE', #2265, #1546, #381, .T. ) ;
+#2764 = ORIENTED_EDGE ( 'NONE', *, *, #4010, .F. ) ;
+#2765 = VECTOR ( 'NONE', #1336, 1000.000000000000000 ) ;
+#2766 = ORIENTED_EDGE ( 'NONE', *, *, #2976, .T. ) ;
+#2767 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2768 = EDGE_CURVE ( 'NONE', #3774, #1150, #1347, .T. ) ;
+#2769 = EDGE_CURVE ( 'NONE', #2097, #2756, #3472, .T. ) ;
+#2770 = CARTESIAN_POINT ( 'NONE', ( 24.64999999999999900, 12.24398312239625300, 4.299999999999999800 ) ) ;
+#2771 = EDGE_LOOP ( 'NONE', ( #2828, #3907, #4390, #1348 ) ) ;
+#2772 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2773 = EDGE_CURVE ( 'NONE', #3956, #4838, #3342, .T. ) ;
+#2774 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2775 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#2776 = CARTESIAN_POINT ( 'NONE', ( -18.13655776574935200, -18.98714285714286000, 4.299999999999999800 ) ) ;
+#2777 = AXIS2_PLACEMENT_3D ( 'NONE', #4986, #3064, #216 ) ;
+#2778 = ORIENTED_EDGE ( 'NONE', *, *, #1535, .T. ) ;
+#2779 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 20.19999999999999900, 4.299999999999999800 ) ) ;
+#2780 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2781 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 6.000000000000000000 ) ) ;
+#2782 = ORIENTED_EDGE ( 'NONE', *, *, #2285, .T. ) ;
+#2783 = ADVANCED_BREP_SHAPE_REPRESENTATION ( 'WALT_encl', ( #4058, #1527 ), #164 ) ;
+#2784 = ADVANCED_FACE ( 'NONE', ( #140, #1211 ), #595, .F. ) ;
+#2785 = FACE_OUTER_BOUND ( 'NONE', #4349, .T. ) ;
+#2786 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, 20.69999999999999900, 4.299999999999999800 ) ) ;
+#2787 = CARTESIAN_POINT ( 'NONE', ( -2.482106781186544700, -20.99289321881345000, 4.299999999999999800 ) ) ;
+#2788 = FACE_OUTER_BOUND ( 'NONE', #4824, .T. ) ;
+#2789 = LINE ( 'NONE', #4636, #668 ) ;
+#2790 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2791 = CIRCLE ( 'NONE', #584, 1.000000000000000900 ) ;
+#2792 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2793 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#2794 = VECTOR ( 'NONE', #180, 1000.000000000000000 ) ;
+#2795 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#2796 = VECTOR ( 'NONE', #4918, 1000.000000000000000 ) ;
+#2797 = ORIENTED_EDGE ( 'NONE', *, *, #727, .F. ) ;
+#2798 = DIRECTION ( 'NONE', ( 6.123233995736769700E-017, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#2799 = VECTOR ( 'NONE', #734, 1000.000000000000000 ) ;
+#2800 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, 19.19999999999999900, 4.299999999999999800 ) ) ;
+#2801 = EDGE_CURVE ( 'NONE', #2448, #2375, #3213, .T. ) ;
+#2802 = EDGE_LOOP ( 'NONE', ( #4767, #5014, #4421, #382 ) ) ;
+#2803 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2804 = ADVANCED_FACE ( 'NONE', ( #5227 ), #2981, .F. ) ;
+#2805 = EDGE_CURVE ( 'NONE', #380, #1874, #4170, .T. ) ;
+#2806 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 4.299999999999999800 ) ) ;
+#2807 = PLANE ( 'NONE', #1791 ) ;
+#2808 = VECTOR ( 'NONE', #3823, 1000.000000000000000 ) ;
+#2809 = CARTESIAN_POINT ( 'NONE', ( -8.753959254591981600E-017, 4.500000000000000000, 4.299999999999999800 ) ) ;
+#2810 = VERTEX_POINT ( 'NONE', #1034 ) ;
+#2811 = ADVANCED_FACE ( 'NONE', ( #2155, #964 ), #3837, .T. ) ;
+#2812 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, -21.69999999999999900, 4.299999999999999800 ) ) ;
+#2813 = ORIENTED_EDGE ( 'NONE', *, *, #2304, .F. ) ;
+#2814 = PLANE ( 'NONE', #4654 ) ;
+#2815 = VECTOR ( 'NONE', #4620, 1000.000000000000000 ) ;
+#2816 = ORIENTED_EDGE ( 'NONE', *, *, #2089, .T. ) ;
+#2817 = VECTOR ( 'NONE', #2309, 1000.000000000000000 ) ;
+#2818 = ORIENTED_EDGE ( 'NONE', *, *, #1851, .F. ) ;
+#2819 = DIRECTION ( 'NONE', ( -0.8660254037844388200, 0.4999999999999997200, 0.0000000000000000000 ) ) ;
+#2820 = ORIENTED_EDGE ( 'NONE', *, *, #868, .F. ) ;
+#2821 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 19.19999999999999900, 2.000000000000000000 ) ) ;
+#2822 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#2823 = EDGE_CURVE ( 'NONE', #634, #315, #2024, .T. ) ;
+#2824 = ORIENTED_EDGE ( 'NONE', *, *, #49, .T. ) ;
+#2825 = EDGE_LOOP ( 'NONE', ( #2161, #3707, #365, #3718 ) ) ;
+#2826 = ORIENTED_EDGE ( 'NONE', *, *, #3156, .F. ) ;
+#2827 = LINE ( 'NONE', #2016, #306 ) ;
+#2828 = ORIENTED_EDGE ( 'NONE', *, *, #2746, .T. ) ;
+#2829 = VERTEX_POINT ( 'NONE', #3444 ) ;
+#2830 = AXIS2_PLACEMENT_3D ( 'NONE', #266, #3076, #673 ) ;
+#2831 = CARTESIAN_POINT ( 'NONE', ( -28.39129536136890100, 3.262384295071445000, 6.000000000000000000 ) ) ;
+#2832 = VERTEX_POINT ( 'NONE', #643 ) ;
+#2833 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#2834 = LINE ( 'NONE', #263, #4974 ) ;
+#2835 = ORIENTED_EDGE ( 'NONE', *, *, #398, .F. ) ;
+#2836 = CARTESIAN_POINT ( 'NONE', ( 17.34000000000000000, 16.51000000000000200, 2.000000000000000000 ) ) ;
+#2837 = EDGE_LOOP ( 'NONE', ( #2267, #3333, #3557, #1895 ) ) ;
+#2838 = VECTOR ( 'NONE', #1879, 1000.000000000000000 ) ;
+#2839 = EDGE_LOOP ( 'NONE', ( #5042, #1839, #3970, #1377 ) ) ;
+#2840 = ORIENTED_EDGE ( 'NONE', *, *, #1973, .T. ) ;
+#2841 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 19.19999999999999900, 4.299999999999999800 ) ) ;
+#2842 = ADVANCED_FACE ( 'NONE', ( #5106, #2960 ), #1045, .T. ) ;
+#2843 = DIRECTION ( 'NONE', ( 0.8660254037844383700, -0.5000000000000006700, 0.0000000000000000000 ) ) ;
+#2844 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2845 = EDGE_CURVE ( 'NONE', #3778, #2704, #831, .T. ) ;
+#2846 = PLANE ( 'NONE', #204 ) ;
+#2847 = ADVANCED_FACE ( 'NONE', ( #3914 ), #1781, .F. ) ;
+#2848 = EDGE_CURVE ( 'NONE', #4347, #3518, #1903, .T. ) ;
+#2849 = DIRECTION ( 'NONE', ( 1.000000000000000000, -1.224646799147352000E-016, 0.0000000000000000000 ) ) ;
+#2850 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -20.19999999999999900, 2.000000000000000000 ) ) ;
+#2851 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#2852 = AXIS2_PLACEMENT_3D ( 'NONE', #2833, #4057, #1653 ) ;
+#2853 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2854 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, -21.69999999999999900, -0.6999999999999999600 ) ) ;
+#2855 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2856 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2857 = VECTOR ( 'NONE', #3161, 1000.000000000000000 ) ;
+#2858 = DATE_AND_TIME ( #3932, #4995 ) ;
+#2859 = CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT ( #724, #1290, ( #2058 ) ) ;
+#2860 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2861 = AXIS2_PLACEMENT_3D ( 'NONE', #5022, #2613, #206 ) ;
+#2862 = FACE_OUTER_BOUND ( 'NONE', #4980, .T. ) ;
+#2863 = PERSON_AND_ORGANIZATION_ROLE ( 'design_owner' ) ;
+#2864 = VECTOR ( 'NONE', #3112, 1000.000000000000100 ) ;
+#2865 = ADVANCED_FACE ( 'NONE', ( #4856 ), #2703, .F. ) ;
+#2866 = LINE ( 'NONE', #4668, #730 ) ;
+#2867 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.938893903907234700E-015, 0.0000000000000000000 ) ) ;
+#2868 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -6.250000000000000000, 4.299999999999999800 ) ) ;
+#2869 = CARTESIAN_POINT ( 'NONE', ( -0.9465728752538080700, 20.40710678118654900, -0.6999999999999999600 ) ) ;
+#2870 = VECTOR ( 'NONE', #3705, 1000.000000000000000 ) ;
+#2871 = EDGE_CURVE ( 'NONE', #1502, #3841, #2834, .T. ) ;
+#2872 = ADVANCED_FACE ( 'NONE', ( #578 ), #2274, .F. ) ;
+#2873 = CARTESIAN_POINT ( 'NONE', ( -24.64999999999999900, 12.24398312239625300, 4.299999999999999800 ) ) ;
+#2874 = PLANE ( 'NONE', #1182 ) ;
+#2875 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#2876 = ORIENTED_EDGE ( 'NONE', *, *, #4936, .T. ) ;
+#2877 = EDGE_CURVE ( 'NONE', #2279, #2129, #704, .T. ) ;
+#2878 = FACE_OUTER_BOUND ( 'NONE', #1015, .T. ) ;
+#2879 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -20.19999999999999900, -0.6999999999999999600 ) ) ;
+#2880 = VECTOR ( 'NONE', #843, 1000.000000000000000 ) ;
+#2881 = VERTEX_POINT ( 'NONE', #3874 ) ;
+#2882 = DATE_TIME_ROLE ( 'classification_date' ) ;
+#2883 = EDGE_CURVE ( 'NONE', #1708, #2129, #4729, .T. ) ;
+#2884 = ORIENTED_EDGE ( 'NONE', *, *, #3315, .T. ) ;
+#2885 = FACE_OUTER_BOUND ( 'NONE', #5, .T. ) ;
+#2886 = EDGE_LOOP ( 'NONE', ( #4536, #3502, #118, #4863 ) ) ;
+#2887 = AXIS2_PLACEMENT_3D ( 'NONE', #44, #462, #1663 ) ;
+#2888 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, -10.27254486838324100, 0.0000000000000000000 ) ) ;
+#2889 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, 21.19999999999999900, 4.299999999999999800 ) ) ;
+#2890 = VERTEX_POINT ( 'NONE', #3099 ) ;
+#2891 = VECTOR ( 'NONE', #4873, 1000.000000000000000 ) ;
+#2892 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 6.500000000000000000, 6.000000000000000000 ) ) ;
+#2893 = ORIENTED_EDGE ( 'NONE', *, *, #2240, .T. ) ;
+#2894 = LINE ( 'NONE', #3294, #4910 ) ;
+#2895 = ORIENTED_EDGE ( 'NONE', *, *, #1070, .F. ) ;
+#2896 = ORIENTED_EDGE ( 'NONE', *, *, #2008, .T. ) ;
+#2897 = ADVANCED_FACE ( 'NONE', ( #450 ), #2288, .T. ) ;
+#2898 = PLANE ( 'NONE', #1596 ) ;
+#2899 = EDGE_LOOP ( 'NONE', ( #3773, #4101, #134, #4293 ) ) ;
+#2900 = AXIS2_PLACEMENT_3D ( 'NONE', #518, #1329, #717 ) ;
+#2901 = ORIENTED_EDGE ( 'NONE', *, *, #4505, .F. ) ;
+#2902 = VECTOR ( 'NONE', #2767, 1000.000000000000000 ) ;
+#2903 = VERTEX_POINT ( 'NONE', #1480 ) ;
+#2904 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, 19.49289321881345000, 0.0000000000000000000 ) ) ;
+#2905 = VERTEX_POINT ( 'NONE', #4314 ) ;
+#2906 = ORIENTED_EDGE ( 'NONE', *, *, #1977, .T. ) ;
+#2907 = LINE ( 'NONE', #4893, #1844 ) ;
+#2908 = ORIENTED_EDGE ( 'NONE', *, *, #4876, .T. ) ;
+#2909 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#2910 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2911 = ORIENTED_EDGE ( 'NONE', *, *, #1761, .F. ) ;
+#2912 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, -21.69999999999999900, -0.6999999999999999600 ) ) ;
+#2913 = DIRECTION ( 'NONE', ( 0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#2914 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2915 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, 19.69999999999999900, 0.0000000000000000000 ) ) ;
+#2916 = EDGE_CURVE ( 'NONE', #2640, #4830, #4606, .T. ) ;
+#2917 = EDGE_CURVE ( 'NONE', #2305, #5031, #1654, .T. ) ;
+#2918 = LINE ( 'NONE', #4900, #4940 ) ;
+#2919 = AXIS2_PLACEMENT_3D ( 'NONE', #2873, #2481, #4077 ) ;
+#2920 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2921 = VECTOR ( 'NONE', #1688, 1000.000000000000100 ) ;
+#2922 = DIRECTION ( 'NONE', ( 0.1674394999967895000, -0.9858823529411738800, 0.0000000000000000000 ) ) ;
+#2923 = AXIS2_PLACEMENT_3D ( 'NONE', #2311, #5123, #2710 ) ;
+#2924 = FACE_OUTER_BOUND ( 'NONE', #4660, .T. ) ;
+#2925 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, -3.500000000000000000, 6.000000000000000000 ) ) ;
+#2926 = LINE ( 'NONE', #4384, #3879 ) ;
+#2927 = EDGE_CURVE ( 'NONE', #2583, #2386, #3658, .T. ) ;
+#2928 = LINE ( 'NONE', #4181, #1869 ) ;
+#2929 = LINE ( 'NONE', #4129, #4949 ) ;
+#2930 = AXIS2_PLACEMENT_3D ( 'NONE', #151, #2962, #552 ) ;
+#2931 = ORIENTED_EDGE ( 'NONE', *, *, #1135, .T. ) ;
+#2932 = LINE ( 'NONE', #325, #2799 ) ;
+#2933 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#2934 = ORIENTED_EDGE ( 'NONE', *, *, #4495, .F. ) ;
+#2935 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2936 = ORIENTED_EDGE ( 'NONE', *, *, #1438, .F. ) ;
+#2937 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2938 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2939 = LINE ( 'NONE', #4237, #4017 ) ;
+#2940 = EDGE_CURVE ( 'NONE', #1353, #5222, #3533, .T. ) ;
+#2941 = ORIENTED_EDGE ( 'NONE', *, *, #3870, .T. ) ;
+#2942 = EDGE_CURVE ( 'NONE', #3577, #3518, #313, .T. ) ;
+#2943 = VERTEX_POINT ( 'NONE', #5134 ) ;
+#2944 = VECTOR ( 'NONE', #3810, 1000.000000000000000 ) ;
+#2945 = ORIENTED_EDGE ( 'NONE', *, *, #4872, .T. ) ;
+#2946 = FACE_OUTER_BOUND ( 'NONE', #4303, .T. ) ;
+#2947 = VERTEX_POINT ( 'NONE', #2715 ) ;
+#2948 = VERTEX_POINT ( 'NONE', #3125 ) ;
+#2949 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#2950 = ORIENTED_EDGE ( 'NONE', *, *, #4070, .T. ) ;
+#2951 = VERTEX_POINT ( 'NONE', #1918 ) ;
+#2952 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2953 = FACE_OUTER_BOUND ( 'NONE', #4485, .T. ) ;
+#2954 = ORIENTED_EDGE ( 'NONE', *, *, #3041, .T. ) ;
+#2955 = AXIS2_PLACEMENT_3D ( 'NONE', #1069, #4287, #3866 ) ;
+#2956 = DIRECTION ( 'NONE', ( 0.5000000000000003300, 0.8660254037844383700, 0.0000000000000000000 ) ) ;
+#2957 = ORIENTED_EDGE ( 'NONE', *, *, #3296, .F. ) ;
+#2958 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.69999999999999900, 0.0000000000000000000 ) ) ;
+#2959 = ORIENTED_EDGE ( 'NONE', *, *, #4371, .T. ) ;
+#2960 = FACE_OUTER_BOUND ( 'NONE', #3190, .T. ) ;
+#2961 = VERTEX_POINT ( 'NONE', #721 ) ;
+#2962 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2963 = AXIS2_PLACEMENT_3D ( 'NONE', #2011, #2412, #5224 ) ;
+#2964 = EDGE_LOOP ( 'NONE', ( #4571, #451, #448, #941 ) ) ;
+#2965 = EDGE_LOOP ( 'NONE', ( #4540, #921, #1835, #4911 ) ) ;
+#2966 = FACE_OUTER_BOUND ( 'NONE', #545, .T. ) ;
+#2967 = ORIENTED_EDGE ( 'NONE', *, *, #2580, .T. ) ;
+#2968 = PLANE ( 'NONE', #1801 ) ;
+#2969 = ORIENTED_EDGE ( 'NONE', *, *, #3417, .T. ) ;
+#2970 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -19.19999999999999900, 0.0000000000000000000 ) ) ;
+#2971 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2972 = EDGE_CURVE ( 'NONE', #506, #2475, #1286, .T. ) ;
+#2973 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2974 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, -21.69999999999999900, 4.299999999999999800 ) ) ;
+#2975 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#2976 = EDGE_CURVE ( 'NONE', #886, #1455, #3405, .T. ) ;
+#2977 = EDGE_CURVE ( 'NONE', #2281, #2881, #195, .T. ) ;
+#2978 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#2979 = VECTOR ( 'NONE', #1972, 1000.000000000000000 ) ;
+#2980 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#2981 = PLANE ( 'NONE', #4714 ) ;
+#2982 = VECTOR ( 'NONE', #2568, 1000.000000000000000 ) ;
+#2983 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2984 = VECTOR ( 'NONE', #1091, 1000.000000000000000 ) ;
+#2985 = DIRECTION ( 'NONE', ( 0.8660254037844381500, 0.5000000000000008900, -0.0000000000000000000 ) ) ;
+#2986 = LINE ( 'NONE', #291, #220 ) ;
+#2987 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#2988 = DIRECTION ( 'NONE', ( -1.330566893520345700E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#2989 = AXIS2_PLACEMENT_3D ( 'NONE', #4397, #4799, #2387 ) ;
+#2990 = FACE_OUTER_BOUND ( 'NONE', #239, .T. ) ;
+#2991 = VERTEX_POINT ( 'NONE', #1934 ) ;
+#2992 = VERTEX_POINT ( 'NONE', #2726 ) ;
+#2993 = CARTESIAN_POINT ( 'NONE', ( -1.567893218813450000, -21.90710678118655300, -0.6999999999999999600 ) ) ;
+#2994 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#2995 = FACE_OUTER_BOUND ( 'NONE', #1834, .T. ) ;
+#2996 = LINE ( 'NONE', #423, #2880 ) ;
+#2997 = ORIENTED_EDGE ( 'NONE', *, *, #2369, .F. ) ;
+#2998 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, -0.6999999999999999600 ) ) ;
+#2999 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3000 = ORIENTED_EDGE ( 'NONE', *, *, #3732, .F. ) ;
+#3001 = DIRECTION ( 'NONE', ( 1.224646799147352000E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3002 = FACE_OUTER_BOUND ( 'NONE', #577, .T. ) ;
+#3003 = ORIENTED_EDGE ( 'NONE', *, *, #1875, .F. ) ;
+#3004 = CARTESIAN_POINT ( 'NONE', ( -7.774999999999999500, -20.69999999999999900, 4.299999999999999800 ) ) ;
+#3005 = CARTESIAN_POINT ( 'NONE', ( -1.805330085889910200, -21.66966991411009900, 4.299999999999999800 ) ) ;
+#3006 = EDGE_CURVE ( 'NONE', #434, #4926, #73, .T. ) ;
+#3007 = ORIENTED_EDGE ( 'NONE', *, *, #526, .F. ) ;
+#3008 = DIRECTION ( 'NONE', ( 0.8660254037844386000, -0.5000000000000003300, 0.0000000000000000000 ) ) ;
+#3009 = ORIENTED_EDGE ( 'NONE', *, *, #3289, .T. ) ;
+#3010 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3011 = EDGE_CURVE ( 'NONE', #1578, #2677, #4224, .T. ) ;
+#3012 = ORIENTED_EDGE ( 'NONE', *, *, #2388, .T. ) ;
+#3013 = FACE_OUTER_BOUND ( 'NONE', #3137, .T. ) ;
+#3014 = ORIENTED_EDGE ( 'NONE', *, *, #1422, .F. ) ;
+#3015 = ORIENTED_EDGE ( 'NONE', *, *, #1786, .T. ) ;
+#3016 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, -19.57000000000000000, 2.000000000000000000 ) ) ;
+#3017 = ORIENTED_EDGE ( 'NONE', *, *, #1064, .F. ) ;
+#3018 = ORIENTED_EDGE ( 'NONE', *, *, #2769, .F. ) ;
+#3019 = LINE ( 'NONE', #344, #1023 ) ;
+#3020 = ORIENTED_EDGE ( 'NONE', *, *, #2643, .F. ) ;
+#3021 = ORIENTED_EDGE ( 'NONE', *, *, #4665, .T. ) ;
+#3022 = AXIS2_PLACEMENT_3D ( 'NONE', #5053, #224, #3039 ) ;
+#3023 = DIRECTION ( 'NONE', ( -1.000000000000000000, -6.735557395310440100E-016, 0.0000000000000000000 ) ) ;
+#3024 = AXIS2_PLACEMENT_3D ( 'NONE', #1931, #750, #3560 ) ;
+#3025 = VERTEX_POINT ( 'NONE', #4760 ) ;
+#3026 = LINE ( 'NONE', #2909, #909 ) ;
+#3027 = ORIENTED_EDGE ( 'NONE', *, *, #3180, .T. ) ;
+#3028 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, -0.6999999999999992900 ) ) ;
+#3029 = DIRECTION ( 'NONE', ( -0.7071067811865474600, -0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#3030 = EDGE_CURVE ( 'NONE', #3406, #1718, #5163, .T. ) ;
+#3031 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3032 = VERTEX_POINT ( 'NONE', #1955 ) ;
+#3033 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 21.44999999999999900, 6.000000000000000000 ) ) ;
+#3034 = AXIS2_PLACEMENT_3D ( 'NONE', #2781, #784, #3604 ) ;
+#3035 = CARTESIAN_POINT ( 'NONE', ( -18.13655776574935200, -18.98714285714286000, 2.000000000000000000 ) ) ;
+#3036 = ORIENTED_EDGE ( 'NONE', *, *, #2213, .T. ) ;
+#3037 = EDGE_CURVE ( 'NONE', #179, #1125, #4355, .T. ) ;
+#3038 = CARTESIAN_POINT ( 'NONE', ( 19.99000000000000200, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#3039 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3040 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3041 = EDGE_CURVE ( 'NONE', #4611, #3956, #3019, .T. ) ;
+#3042 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3043 = VECTOR ( 'NONE', #87, 1000.000000000000000 ) ;
+#3044 = EDGE_CURVE ( 'NONE', #4026, #4046, #901, .T. ) ;
+#3045 = DIRECTION ( 'NONE', ( -0.5000000000000005600, 0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#3046 = FACE_BOUND ( 'NONE', #4354, .T. ) ;
+#3047 = EDGE_CURVE ( 'NONE', #3778, #1326, #5040, .T. ) ;
+#3048 = CYLINDRICAL_SURFACE ( 'NONE', #1520, 1.000000000000000000 ) ;
+#3049 = ORIENTED_EDGE ( 'NONE', *, *, #4174, .F. ) ;
+#3050 = FACE_OUTER_BOUND ( 'NONE', #2063, .T. ) ;
+#3051 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3052 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#3053 = VECTOR ( 'NONE', #4579, 1000.000000000000000 ) ;
+#3054 = CIRCLE ( 'NONE', #3477, 1.000000000000000900 ) ;
+#3055 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3056 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3057 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3058 = EDGE_LOOP ( 'NONE', ( #2301, #1389, #2457, #2572 ) ) ;
+#3059 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3060 = LINE ( 'NONE', #719, #1994 ) ;
+#3061 = CARTESIAN_POINT ( 'NONE', ( -29.27499999999999900, -4.500000000000000000, -0.6999999999999999600 ) ) ;
+#3062 = EDGE_LOOP ( 'NONE', ( #1631, #807, #1292, #321 ) ) ;
+#3063 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3064 = DIRECTION ( 'NONE', ( 6.123233995736770200E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3065 = VECTOR ( 'NONE', #2988, 1000.000000000000000 ) ;
+#3066 = DIRECTION ( 'NONE', ( -0.5000000000000002200, -0.8660254037844384900, 0.0000000000000000000 ) ) ;
+#3067 = VERTEX_POINT ( 'NONE', #2364 ) ;
+#3068 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3069 = EDGE_LOOP ( 'NONE', ( #3143, #2086, #67, #2176, #2755, #1571, #2745, #5173 ) ) ;
+#3070 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3071 = LINE ( 'NONE', #1492, #4958 ) ;
+#3072 = VERTEX_POINT ( 'NONE', #359 ) ;
+#3073 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, 19.49289321881345000, -0.6999999999999999600 ) ) ;
+#3074 = FACE_BOUND ( 'NONE', #5131, .T. ) ;
+#3075 = ORIENTED_EDGE ( 'NONE', *, *, #1411, .T. ) ;
+#3076 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3077 = ORIENTED_EDGE ( 'NONE', *, *, #3452, .T. ) ;
+#3078 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3079 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3080 = ORIENTED_EDGE ( 'NONE', *, *, #1717, .F. ) ;
+#3081 = ORIENTED_EDGE ( 'NONE', *, *, #1327, .T. ) ;
+#3082 = LINE ( 'NONE', #916, #4163 ) ;
+#3083 = EDGE_CURVE ( 'NONE', #3760, #2007, #759, .T. ) ;
+#3084 = ORIENTED_EDGE ( 'NONE', *, *, #490, .T. ) ;
+#3085 = ORIENTED_EDGE ( 'NONE', *, *, #4543, .T. ) ;
+#3086 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3087 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#3088 = EDGE_CURVE ( 'NONE', #4601, #1954, #4916, .T. ) ;
+#3089 = ADVANCED_FACE ( 'NONE', ( #3851 ), #1590, .T. ) ;
+#3090 = ORIENTED_EDGE ( 'NONE', *, *, #4059, .F. ) ;
+#3091 = VECTOR ( 'NONE', #3428, 1000.000000000000000 ) ;
+#3092 = EDGE_LOOP ( 'NONE', ( #1010, #744, #5147, #1466 ) ) ;
+#3093 = ORIENTED_EDGE ( 'NONE', *, *, #1070, .T. ) ;
+#3094 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3095 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.632680918566406600E-015, 0.0000000000000000000 ) ) ;
+#3096 = ORIENTED_EDGE ( 'NONE', *, *, #1444, .F. ) ;
+#3097 = LINE ( 'NONE', #3741, #4176 ) ;
+#3098 = VERTEX_POINT ( 'NONE', #1986 ) ;
+#3099 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 4.378679656440360700, 6.000000000000000000 ) ) ;
+#3100 = AXIS2_PLACEMENT_3D ( 'NONE', #3415, #1009, #3817 ) ;
+#3101 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, 19.19999999999999900, -0.6999999999999999600 ) ) ;
+#3102 = EDGE_CURVE ( 'NONE', #3776, #4663, #1719, .T. ) ;
+#3103 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, -6.250000000000000000, 6.000000000000000000 ) ) ;
+#3104 = CARTESIAN_POINT ( 'NONE', ( -0.9465728752538080700, 20.40710678118654900, 0.0000000000000000000 ) ) ;
+#3105 = AXIS2_PLACEMENT_3D ( 'NONE', #3804, #3001, #600 ) ;
+#3106 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3107 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -21.44999999999999900, 6.000000000000000000 ) ) ;
+#3108 = ADVANCED_FACE ( 'NONE', ( #640 ), #5196, .T. ) ;
+#3109 = VECTOR ( 'NONE', #3933, 1000.000000000000000 ) ;
+#3110 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 19.19999999999999900, -0.6999999999999999600 ) ) ;
+#3111 = EDGE_CURVE ( 'NONE', #4640, #1449, #1581, .T. ) ;
+#3112 = DIRECTION ( 'NONE', ( -0.4999999999999996700, -0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#3113 = EDGE_CURVE ( 'NONE', #4029, #2007, #4786, .T. ) ;
+#3114 = LINE ( 'NONE', #3764, #2982 ) ;
+#3115 = EDGE_LOOP ( 'NONE', ( #1180, #1212, #4169, #1699 ) ) ;
+#3116 = FACE_OUTER_BOUND ( 'NONE', #938, .T. ) ;
+#3117 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3118 = VECTOR ( 'NONE', #619, 1000.000000000000000 ) ;
+#3119 = DIRECTION ( 'NONE', ( 0.7071067811865474600, -0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#3120 = AXIS2_PLACEMENT_3D ( 'NONE', #1268, #4074, #1669 ) ;
+#3121 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, -16.51000000000000900, 2.500000000000000000 ) ) ;
+#3122 = FACE_OUTER_BOUND ( 'NONE', #2837, .T. ) ;
+#3123 = AXIS2_PLACEMENT_3D ( 'NONE', #3858, #1447, #4255 ) ;
+#3124 = ORIENTED_EDGE ( 'NONE', *, *, #119, .F. ) ;
+#3125 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, -0.6999999999999992900 ) ) ;
+#3126 = CIRCLE ( 'NONE', #3501, 1.000000000000000000 ) ;
+#3127 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3128 = CC_DESIGN_DATE_AND_TIME_ASSIGNMENT ( #1677, #4087, ( #2058 ) ) ;
+#3129 = VECTOR ( 'NONE', #4807, 1000.000000000000100 ) ;
+#3130 = ORIENTED_EDGE ( 'NONE', *, *, #868, .T. ) ;
+#3131 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, -1.240472735837103900, 4.299999999999999800 ) ) ;
+#3132 = AXIS2_PLACEMENT_3D ( 'NONE', #4201, #3413, #1007 ) ;
+#3133 = ORIENTED_EDGE ( 'NONE', *, *, #4368, .F. ) ;
+#3134 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3135 = LINE ( 'NONE', #4808, #4207 ) ;
+#3136 = PLANE ( 'NONE', #2249 ) ;
+#3137 = EDGE_LOOP ( 'NONE', ( #2931, #4043, #703, #4524 ) ) ;
+#3138 = ADVANCED_FACE ( 'NONE', ( #4662 ), #3215, .F. ) ;
+#3139 = ORIENTED_EDGE ( 'NONE', *, *, #314, .T. ) ;
+#3140 = CARTESIAN_POINT ( 'NONE', ( 17.34000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#3141 = EDGE_CURVE ( 'NONE', #1134, #4451, #3591, .T. ) ;
+#3142 = ORIENTED_EDGE ( 'NONE', *, *, #1573, .F. ) ;
+#3143 = ORIENTED_EDGE ( 'NONE', *, *, #2485, .T. ) ;
+#3144 = VERTEX_POINT ( 'NONE', #2821 ) ;
+#3145 = LINE ( 'NONE', #2463, #4102 ) ;
+#3146 = ORIENTED_EDGE ( 'NONE', *, *, #1477, .F. ) ;
+#3147 = EDGE_LOOP ( 'NONE', ( #3199, #4158, #4157, #631 ) ) ;
+#3148 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3149 = EDGE_CURVE ( 'NONE', #1643, #4781, #2638, .T. ) ;
+#3150 = ORIENTED_EDGE ( 'NONE', *, *, #1936, .T. ) ;
+#3151 = ORIENTED_EDGE ( 'NONE', *, *, #5174, .T. ) ;
+#3152 = PLANE ( 'NONE', #853 ) ;
+#3153 = VECTOR ( 'NONE', #5216, 1000.000000000000000 ) ;
+#3154 = VERTEX_POINT ( 'NONE', #11 ) ;
+#3155 = ORIENTED_EDGE ( 'NONE', *, *, #1487, .F. ) ;
+#3156 = EDGE_CURVE ( 'NONE', #3793, #634, #1461, .T. ) ;
+#3157 = ORIENTED_EDGE ( 'NONE', *, *, #1956, .T. ) ;
+#3158 = LINE ( 'NONE', #2888, #4116 ) ;
+#3159 = VERTEX_POINT ( 'NONE', #414 ) ;
+#3160 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, -16.32049935181331100 ) ) ;
+#3161 = DIRECTION ( 'NONE', ( -0.5000000000000001100, 0.8660254037844386000, 0.0000000000000000000 ) ) ;
+#3162 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3163 = ADVANCED_FACE ( 'NONE', ( #377 ), #2846, .T. ) ;
+#3164 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#3165 = ORIENTED_EDGE ( 'NONE', *, *, #2514, .T. ) ;
+#3166 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#3167 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.735557395310440100E-016, 0.0000000000000000000 ) ) ;
+#3168 = VECTOR ( 'NONE', #2855, 1000.000000000000000 ) ;
+#3169 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3170 = DIRECTION ( 'NONE', ( 0.7071067811865474600, -0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#3171 = FACE_OUTER_BOUND ( 'NONE', #2690, .T. ) ;
+#3172 = CIRCLE ( 'NONE', #709, 1.000000000000000000 ) ;
+#3173 = ORIENTED_EDGE ( 'NONE', *, *, #3548, .T. ) ;
+#3174 = VECTOR ( 'NONE', #5136, 1000.000000000000000 ) ;
+#3175 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.938893903907222100E-015, 0.0000000000000000000 ) ) ;
+#3176 = EDGE_CURVE ( 'NONE', #4069, #2198, #3464, .T. ) ;
+#3177 = CIRCLE ( 'NONE', #2125, 1.000000000000000900 ) ;
+#3178 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#3179 = LINE ( 'NONE', #3798, #5193 ) ;
+#3180 = EDGE_CURVE ( 'NONE', #2091, #4722, #2529, .T. ) ;
+#3181 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3182 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3183 = VERTEX_POINT ( 'NONE', #1258 ) ;
+#3184 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3185 = LINE ( 'NONE', #286, #1062 ) ;
+#3186 = ADVANCED_FACE ( 'NONE', ( #2392, #2271 ), #4475, .T. ) ;
+#3187 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3188 = ORIENTED_EDGE ( 'NONE', *, *, #88, .T. ) ;
+#3189 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3190 = EDGE_LOOP ( 'NONE', ( #870, #1969, #766, #293, #2191, #1393 ) ) ;
+#3191 = LINE ( 'NONE', #980, #117 ) ;
+#3192 = ORIENTED_EDGE ( 'NONE', *, *, #1295, .F. ) ;
+#3193 = EDGE_CURVE ( 'NONE', #4226, #5087, #3338, .T. ) ;
+#3194 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672371800, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#3195 = VERTEX_POINT ( 'NONE', #3677 ) ;
+#3196 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, 19.57000000000000000, 2.000000000000000000 ) ) ;
+#3197 = VECTOR ( 'NONE', #1443, 1000.000000000000000 ) ;
+#3198 = AXIS2_PLACEMENT_3D ( 'NONE', #2975, #580, #3381 ) ;
+#3199 = ORIENTED_EDGE ( 'NONE', *, *, #1378, .F. ) ;
+#3200 = ORIENTED_EDGE ( 'NONE', *, *, #2654, .F. ) ;
+#3201 = VECTOR ( 'NONE', #2574, 1000.000000000000000 ) ;
+#3202 = ORIENTED_EDGE ( 'NONE', *, *, #2706, .T. ) ;
+#3203 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#3204 = ORIENTED_EDGE ( 'NONE', *, *, #4124, .F. ) ;
+#3205 = VERTEX_POINT ( 'NONE', #2045 ) ;
+#3206 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3207 = ORIENTED_EDGE ( 'NONE', *, *, #4634, .F. ) ;
+#3208 = DIRECTION ( 'NONE', ( 0.4999999999999997800, 0.8660254037844387100, 0.0000000000000000000 ) ) ;
+#3209 = VECTOR ( 'NONE', #4508, 1000.000000000000000 ) ;
+#3210 = ORIENTED_EDGE ( 'NONE', *, *, #2547, .F. ) ;
+#3211 = ORIENTED_EDGE ( 'NONE', *, *, #4634, .T. ) ;
+#3212 = VERTEX_POINT ( 'NONE', #4084 ) ;
+#3213 = LINE ( 'NONE', #1820, #257 ) ;
+#3214 = ORIENTED_EDGE ( 'NONE', *, *, #942, .T. ) ;
+#3215 = PLANE ( 'NONE', #4353 ) ;
+#3216 = EDGE_CURVE ( 'NONE', #4732, #4117, #1340, .T. ) ;
+#3217 = ADVANCED_FACE ( 'NONE', ( #131 ), #1082, .F. ) ;
+#3218 = EDGE_CURVE ( 'NONE', #2832, #351, #4292, .T. ) ;
+#3219 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3220 = EDGE_LOOP ( 'NONE', ( #3027, #1933, #2527, #378, #1463 ) ) ;
+#3221 = ORIENTED_EDGE ( 'NONE', *, *, #5172, .T. ) ;
+#3222 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3223 = DIRECTION ( 'NONE', ( -0.7071067811865470200, -0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#3224 = PLANE ( 'NONE', #5206 ) ;
+#3225 = AXIS2_PLACEMENT_3D ( 'NONE', #1054, #3867, #1457 ) ;
+#3226 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3227 = AXIS2_PLACEMENT_3D ( 'NONE', #3894, #1486, #4306 ) ;
+#3228 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3229 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3230 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3231 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3232 = ADVANCED_FACE ( 'NONE', ( #1203 ), #2017, .F. ) ;
+#3233 = FACE_OUTER_BOUND ( 'NONE', #2467, .T. ) ;
+#3234 = DIRECTION ( 'NONE', ( 0.8660254037844381500, 0.5000000000000008900, -0.0000000000000000000 ) ) ;
+#3235 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#3236 = ADVANCED_FACE ( 'NONE', ( #2151 ), #2499, .T. ) ;
+#3237 = PLANE ( 'NONE', #1855 ) ;
+#3238 = LINE ( 'NONE', #2554, #1110 ) ;
+#3239 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#3240 = CYLINDRICAL_SURFACE ( 'NONE', #2676, 1.000000000000000900 ) ;
+#3241 = ORIENTED_EDGE ( 'NONE', *, *, #2805, .F. ) ;
+#3242 = PLANE ( 'NONE', #4379 ) ;
+#3243 = LINE ( 'NONE', #4231, #5133 ) ;
+#3244 = EDGE_CURVE ( 'NONE', #567, #1011, #3082, .T. ) ;
+#3245 = EDGE_LOOP ( 'NONE', ( #1084, #3432, #4713, #2578 ) ) ;
+#3246 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3247 = CIRCLE ( 'NONE', #3300, 1.600000000000000800 ) ;
+#3248 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#3249 = EDGE_CURVE ( 'NONE', #3463, #743, #959, .T. ) ;
+#3250 = VERTEX_POINT ( 'NONE', #2904 ) ;
+#3251 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#3252 = EDGE_CURVE ( 'NONE', #3953, #2750, #1898, .T. ) ;
+#3253 = VECTOR ( 'NONE', #4040, 1000.000000000000100 ) ;
+#3254 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.040834085586084300E-014, 0.0000000000000000000 ) ) ;
+#3255 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#3256 = LINE ( 'NONE', #1187, #4333 ) ;
+#3257 = ORIENTED_EDGE ( 'NONE', *, *, #1706, .F. ) ;
+#3258 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3259 = ORIENTED_EDGE ( 'NONE', *, *, #792, .F. ) ;
+#3260 = ADVANCED_FACE ( 'NONE', ( #2953 ), #3710, .F. ) ;
+#3261 = VECTOR ( 'NONE', #1559, 1000.000000000000000 ) ;
+#3262 = VERTEX_POINT ( 'NONE', #106 ) ;
+#3263 = LINE ( 'NONE', #3166, #54 ) ;
+#3264 = AXIS2_PLACEMENT_3D ( 'NONE', #805, #3618, #1223 ) ;
+#3265 = ORIENTED_EDGE ( 'NONE', *, *, #3735, .F. ) ;
+#3266 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3267 = ADVANCED_FACE ( 'NONE', ( #5221 ), #2736, .F. ) ;
+#3268 = ORIENTED_EDGE ( 'NONE', *, *, #2240, .F. ) ;
+#3269 = EDGE_LOOP ( 'NONE', ( #4549, #3341, #3951, #2623 ) ) ;
+#3270 = APPLICATION_PROTOCOL_DEFINITION ( 'international standard', 'config_control_design', 1994, #464 ) ;
+#3271 = VECTOR ( 'NONE', #2041, 1000.000000000000000 ) ;
+#3272 = ORIENTED_EDGE ( 'NONE', *, *, #4382, .T. ) ;
+#3273 = EDGE_LOOP ( 'NONE', ( #1576, #4777, #1148, #2214 ) ) ;
+#3274 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, 4.500000000000000000, -0.6999999999999992900 ) ) ;
+#3275 = ORIENTED_EDGE ( 'NONE', *, *, #4141, .F. ) ;
+#3276 = VERTEX_POINT ( 'NONE', #336 ) ;
+#3277 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3278 = VECTOR ( 'NONE', #4745, 1000.000000000000000 ) ;
+#3279 = ORIENTED_EDGE ( 'NONE', *, *, #13, .F. ) ;
+#3280 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3281 = CARTESIAN_POINT ( 'NONE', ( 24.06714285714285500, -13.05655776574934800, 4.299999999999999800 ) ) ;
+#3282 = ORIENTED_EDGE ( 'NONE', *, *, #4737, .T. ) ;
+#3283 = CIRCLE ( 'NONE', #2923, 1.000000000000000900 ) ;
+#3284 = ORIENTED_EDGE ( 'NONE', *, *, #4436, .F. ) ;
+#3285 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3286 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3287 = EDGE_CURVE ( 'NONE', #2648, #2281, #5101, .T. ) ;
+#3288 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.938893903907234700E-015, 0.0000000000000000000 ) ) ;
+#3289 = EDGE_CURVE ( 'NONE', #1326, #5161, #3908, .T. ) ;
+#3290 = APPROVAL_ROLE ( '' ) ;
+#3291 = ADVANCED_FACE ( 'NONE', ( #4848, #824 ), #2361, .T. ) ;
+#3292 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3293 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#3294 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, -19.21000000000000400, -0.0000000000000000000 ) ) ;
+#3295 = AXIS2_PLACEMENT_3D ( 'NONE', #1218, #4023, #1616 ) ;
+#3296 = EDGE_CURVE ( 'NONE', #4611, #2203, #3781, .T. ) ;
+#3297 = FACE_OUTER_BOUND ( 'NONE', #4222, .T. ) ;
+#3298 = LOCAL_TIME ( 12, 52, 21.00000000000000000, #4374 ) ;
+#3299 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.123233995736770200E-016, 0.0000000000000000000 ) ) ;
+#3300 = AXIS2_PLACEMENT_3D ( 'NONE', #410, #3228, #828 ) ;
+#3301 = FACE_OUTER_BOUND ( 'NONE', #2082, .T. ) ;
+#3302 = LINE ( 'NONE', #4014, #4253 ) ;
+#3303 = LINE ( 'NONE', #1911, #2239 ) ;
+#3304 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, 22.19999999999999900, -0.6999999999999999600 ) ) ;
+#3305 = CARTESIAN_POINT ( 'NONE', ( -1.805330085889910200, 21.66966991411009900, -0.6999999999999999600 ) ) ;
+#3306 = VECTOR ( 'NONE', #4147, 1000.000000000000000 ) ;
+#3307 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3308 = LINE ( 'NONE', #3364, #4387 ) ;
+#3309 = EDGE_CURVE ( 'NONE', #2203, #1712, #2697, .T. ) ;
+#3310 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3311 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, -19.69999999999999900, -0.6999999999999999600 ) ) ;
+#3312 = ORIENTED_EDGE ( 'NONE', *, *, #2508, .F. ) ;
+#3313 = ADVANCED_FACE ( 'NONE', ( #699 ), #3653, .T. ) ;
+#3314 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953611000E-015, 0.0000000000000000000 ) ) ;
+#3315 = EDGE_CURVE ( 'NONE', #1147, #4537, #1645, .T. ) ;
+#3316 = VECTOR ( 'NONE', #2694, 1000.000000000000200 ) ;
+#3317 = EDGE_CURVE ( 'NONE', #4541, #4029, #4721, .T. ) ;
+#3318 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#3319 = VERTEX_POINT ( 'NONE', #1192 ) ;
+#3320 = EDGE_CURVE ( 'NONE', #3714, #3516, #2582, .T. ) ;
+#3321 = ORIENTED_EDGE ( 'NONE', *, *, #2667, .F. ) ;
+#3322 = VECTOR ( 'NONE', #4582, 1000.000000000000000 ) ;
+#3323 = ORIENTED_EDGE ( 'NONE', *, *, #1422, .T. ) ;
+#3324 = AXIS2_PLACEMENT_3D ( 'NONE', #4794, #2374, #1978 ) ;
+#3325 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#3326 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3327 = EDGE_LOOP ( 'NONE', ( #1452, #4113, #1962, #4108 ) ) ;
+#3328 = LINE ( 'NONE', #1465, #1196 ) ;
+#3329 = ORIENTED_EDGE ( 'NONE', *, *, #2304, .T. ) ;
+#3330 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 6.250000000000000000, 6.000000000000000000 ) ) ;
+#3331 = LINE ( 'NONE', #4982, #2383 ) ;
+#3332 = ORIENTED_EDGE ( 'NONE', *, *, #5135, .T. ) ;
+#3333 = ORIENTED_EDGE ( 'NONE', *, *, #4266, .F. ) ;
+#3334 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3335 = ORIENTED_EDGE ( 'NONE', *, *, #314, .F. ) ;
+#3336 = AXIS2_PLACEMENT_3D ( 'NONE', #4564, #2166, #4961 ) ;
+#3337 = ORIENTED_EDGE ( 'NONE', *, *, #3800, .F. ) ;
+#3338 = LINE ( 'NONE', #866, #4416 ) ;
+#3339 = VERTEX_POINT ( 'NONE', #3612 ) ;
+#3340 = VERTEX_POINT ( 'NONE', #3602 ) ;
+#3341 = ORIENTED_EDGE ( 'NONE', *, *, #732, .T. ) ;
+#3342 = CIRCLE ( 'NONE', #3100, 4.250000000000003600 ) ;
+#3343 = VERTEX_POINT ( 'NONE', #802 ) ;
+#3344 = CARTESIAN_POINT ( 'NONE', ( -1.076572875253784400, -20.27710678118655000, 1.000000000000000000 ) ) ;
+#3345 = ORIENTED_EDGE ( 'NONE', *, *, #4593, .T. ) ;
+#3346 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#3347 = EDGE_LOOP ( 'NONE', ( #3, #4851, #3782, #1774 ) ) ;
+#3348 = DIRECTION ( 'NONE', ( 1.224646799147352000E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3349 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3350 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#3351 = ORIENTED_EDGE ( 'NONE', *, *, #2488, .T. ) ;
+#3352 = EDGE_CURVE ( 'NONE', #4646, #958, #2465, .T. ) ;
+#3353 = EDGE_CURVE ( 'NONE', #2559, #4359, #2827, .T. ) ;
+#3354 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 2.368891848716884900, 6.000000000000000000 ) ) ;
+#3355 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3356 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3357 = PLANE ( 'NONE', #1227 ) ;
+#3358 = DIRECTION ( 'NONE', ( 0.7071067811865470200, -8.659560562354918100E-017, 0.7071067811865480200 ) ) ;
+#3359 = LINE ( 'NONE', #527, #222 ) ;
+#3360 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3361 = CARTESIAN_POINT ( 'NONE', ( 24.06714285714285500, 13.05655776574935000, 2.000000000000000000 ) ) ;
+#3362 = AXIS2_PLACEMENT_3D ( 'NONE', #4594, #2588, #173 ) ;
+#3363 = VECTOR ( 'NONE', #1905, 1000.000000000000000 ) ;
+#3364 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#3365 = FACE_OUTER_BOUND ( 'NONE', #4274, .T. ) ;
+#3366 = EDGE_CURVE ( 'NONE', #1711, #4971, #442, .T. ) ;
+#3367 = VECTOR ( 'NONE', #4528, 1000.000000000000000 ) ;
+#3368 = VECTOR ( 'NONE', #3078, 1000.000000000000000 ) ;
+#3369 = VERTEX_POINT ( 'NONE', #4828 ) ;
+#3370 = LINE ( 'NONE', #4562, #4321 ) ;
+#3371 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3372 = ORIENTED_EDGE ( 'NONE', *, *, #3047, .F. ) ;
+#3373 = VECTOR ( 'NONE', #3189, 1000.000000000000000 ) ;
+#3374 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3375 = ORIENTED_EDGE ( 'NONE', *, *, #3759, .T. ) ;
+#3376 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#3377 = FACE_OUTER_BOUND ( 'NONE', #4849, .T. ) ;
+#3378 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#3379 = DIRECTION ( 'NONE', ( 1.000000000000000000, -1.387778780781446900E-014, 0.0000000000000000000 ) ) ;
+#3380 = EDGE_CURVE ( 'NONE', #2315, #1011, #4479, .T. ) ;
+#3381 = DIRECTION ( 'NONE', ( 1.000000000000000000, 3.469446951953614200E-015, 0.0000000000000000000 ) ) ;
+#3382 = CIRCLE ( 'NONE', #426, 0.9999999999999991100 ) ;
+#3383 = VERTEX_POINT ( 'NONE', #3630 ) ;
+#3384 = EDGE_CURVE ( 'NONE', #3790, #761, #1276, .T. ) ;
+#3385 = ORIENTED_EDGE ( 'NONE', *, *, #1204, .T. ) ;
+#3386 = VECTOR ( 'NONE', #3556, 1000.000000000000000 ) ;
+#3387 = ORIENTED_EDGE ( 'NONE', *, *, #5172, .F. ) ;
+#3388 = LINE ( 'NONE', #2358, #182 ) ;
+#3389 = ORIENTED_EDGE ( 'NONE', *, *, #3530, .T. ) ;
+#3390 = AXIS2_PLACEMENT_3D ( 'NONE', #3235, #2456, #34 ) ;
+#3391 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 19.19999999999999900, 4.299999999999999800 ) ) ;
+#3392 = EDGE_LOOP ( 'NONE', ( #4111, #1022, #2564, #459 ) ) ;
+#3393 = EDGE_LOOP ( 'NONE', ( #1580, #4200, #82, #5167 ) ) ;
+#3394 = CARTESIAN_POINT ( 'NONE', ( -28.39129536136890100, -3.262384295071445000, 6.000000000000000000 ) ) ;
+#3395 = ORIENTED_EDGE ( 'NONE', *, *, #152, .F. ) ;
+#3396 = VECTOR ( 'NONE', #4, 1000.000000000000000 ) ;
+#3397 = ORIENTED_EDGE ( 'NONE', *, *, #27, .F. ) ;
+#3398 = AXIS2_PLACEMENT_3D ( 'NONE', #1031, #3832, #1431 ) ;
+#3399 = CARTESIAN_POINT ( 'NONE', ( -24.06714285714285500, 13.05655776574935000, 4.299999999999999800 ) ) ;
+#3400 = ORIENTED_EDGE ( 'NONE', *, *, #3868, .F. ) ;
+#3401 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3402 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3403 = ORIENTED_EDGE ( 'NONE', *, *, #2763, .F. ) ;
+#3404 = EDGE_LOOP ( 'NONE', ( #2282, #4393, #4425, #2835 ) ) ;
+#3405 = LINE ( 'NONE', #4351, #3278 ) ;
+#3406 = VERTEX_POINT ( 'NONE', #2436 ) ;
+#3407 = PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE ( 'ANY', '', #452, .NOT_KNOWN. ) ;
+#3408 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3409 = ORIENTED_EDGE ( 'NONE', *, *, #2883, .T. ) ;
+#3410 = VECTOR ( 'NONE', #3486, 1000.000000000000000 ) ;
+#3411 = ORIENTED_EDGE ( 'NONE', *, *, #2664, .F. ) ;
+#3412 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318800800, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#3413 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3414 = EDGE_CURVE ( 'NONE', #3343, #3183, #2331, .T. ) ;
+#3415 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#3416 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#3417 = EDGE_CURVE ( 'NONE', #4520, #579, #2202, .T. ) ;
+#3418 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#3419 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3420 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3421 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3422 = EDGE_CURVE ( 'NONE', #4919, #1854, #4348, .T. ) ;
+#3423 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3424 = AXIS2_PLACEMENT_3D ( 'NONE', #829, #3229, #412 ) ;
+#3425 = PLANE ( 'NONE', #4499 ) ;
+#3426 = LINE ( 'NONE', #2418, #1303 ) ;
+#3427 = CALENDAR_DATE ( 2016, 8, 12 ) ;
+#3428 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3429 = FACE_OUTER_BOUND ( 'NONE', #3327, .T. ) ;
+#3430 = LINE ( 'NONE', #3996, #219 ) ;
+#3431 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#3432 = ORIENTED_EDGE ( 'NONE', *, *, #16, .F. ) ;
+#3433 = VECTOR ( 'NONE', #2680, 1000.000000000000000 ) ;
+#3434 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, -4.500000000000000000, -0.6999999999999992900 ) ) ;
+#3435 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3436 = AXIS2_PLACEMENT_3D ( 'NONE', #3160, #748, #3559 ) ;
+#3437 = VECTOR ( 'NONE', #4148, 1000.000000000000000 ) ;
+#3438 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, -16.32049935181331100 ) ) ;
+#3439 = ORIENTED_EDGE ( 'NONE', *, *, #1913, .F. ) ;
+#3440 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3441 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, 20.69999999999999900, 0.0000000000000000000 ) ) ;
+#3442 = FACE_OUTER_BOUND ( 'NONE', #2156, .T. ) ;
+#3443 = EDGE_LOOP ( 'NONE', ( #2957, #2954, #1897, #2363 ) ) ;
+#3444 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681198800, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#3445 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#3446 = EDGE_LOOP ( 'NONE', ( #114, #4238, #5021, #1704 ) ) ;
+#3447 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3448 = LINE ( 'NONE', #2822, #2373 ) ;
+#3449 = ORIENTED_EDGE ( 'NONE', *, *, #188, .F. ) ;
+#3450 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3451 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3452 = EDGE_CURVE ( 'NONE', #1899, #563, #187, .T. ) ;
+#3453 = EDGE_LOOP ( 'NONE', ( #3593, #2530, #4661, #992 ) ) ;
+#3454 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -4.378679656440360700, 6.000000000000000000 ) ) ;
+#3455 = VECTOR ( 'NONE', #4339, 1000.000000000000000 ) ;
+#3456 = EDGE_CURVE ( 'NONE', #4855, #554, #64, .T. ) ;
+#3457 = ORIENTED_EDGE ( 'NONE', *, *, #2871, .F. ) ;
+#3458 = LINE ( 'NONE', #551, #368 ) ;
+#3459 = VERTEX_POINT ( 'NONE', #860 ) ;
+#3460 = ORIENTED_EDGE ( 'NONE', *, *, #1487, .T. ) ;
+#3461 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3462 = VERTEX_POINT ( 'NONE', #3672 ) ;
+#3463 = VERTEX_POINT ( 'NONE', #4078 ) ;
+#3464 = LINE ( 'NONE', #3661, #4542 ) ;
+#3465 = ORIENTED_EDGE ( 'NONE', *, *, #4761, .F. ) ;
+#3466 = ORIENTED_EDGE ( 'NONE', *, *, #317, .T. ) ;
+#3467 = AXIS2_PLACEMENT_3D ( 'NONE', #161, #2973, #573 ) ;
+#3468 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3469 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3470 = AXIS2_PLACEMENT_3D ( 'NONE', #4651, #4250, #2245 ) ;
+#3471 = ORIENTED_EDGE ( 'NONE', *, *, #956, .T. ) ;
+#3472 = LINE ( 'NONE', #4613, #2278 ) ;
+#3473 = ORIENTED_EDGE ( 'NONE', *, *, #2008, .F. ) ;
+#3474 = EDGE_CURVE ( 'NONE', #234, #4033, #3145, .T. ) ;
+#3475 = FACE_OUTER_BOUND ( 'NONE', #3269, .T. ) ;
+#3476 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, 4.299999999999999800 ) ) ;
+#3477 = AXIS2_PLACEMENT_3D ( 'NONE', #2096, #4895, #2492 ) ;
+#3478 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#3479 = VERTEX_POINT ( 'NONE', #2072 ) ;
+#3480 = DIRECTION ( 'NONE', ( 0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#3481 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3482 = ADVANCED_FACE ( 'NONE', ( #1951 ), #5030, .F. ) ;
+#3483 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, -18.71852980579590000, -0.6999999999999999600 ) ) ;
+#3484 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, 19.69999999999999900, -0.6999999999999999600 ) ) ;
+#3485 = EDGE_CURVE ( 'NONE', #2097, #1536, #894, .T. ) ;
+#3486 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3487 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -0.7500000000000000000, 6.000000000000000000 ) ) ;
+#3488 = LINE ( 'NONE', #1306, #2420 ) ;
+#3489 = VECTOR ( 'NONE', #3887, 1000.000000000000000 ) ;
+#3490 = LINE ( 'NONE', #418, #404 ) ;
+#3491 = AXIS2_PLACEMENT_3D ( 'NONE', #1847, #2241, #5056 ) ;
+#3492 = CIRCLE ( 'NONE', #594, 1.600000000000000800 ) ;
+#3493 = FACE_OUTER_BOUND ( 'NONE', #3709, .T. ) ;
+#3494 = LINE ( 'NONE', #4600, #160 ) ;
+#3495 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3496 = ORIENTED_EDGE ( 'NONE', *, *, #3997, .F. ) ;
+#3497 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 0.7500000000000000000, 6.000000000000000000 ) ) ;
+#3498 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#3499 = VECTOR ( 'NONE', #3991, 1000.000000000000000 ) ;
+#3500 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 6.250000000000000000, 4.299999999999999800 ) ) ;
+#3501 = AXIS2_PLACEMENT_3D ( 'NONE', #2850, #447, #3254 ) ;
+#3502 = ORIENTED_EDGE ( 'NONE', *, *, #3952, .T. ) ;
+#3503 = CARTESIAN_POINT ( 'NONE', ( 24.06714285714285500, -13.05655776574935000, 4.299999999999999800 ) ) ;
+#3504 = PLANE ( 'NONE', #353 ) ;
+#3505 = VECTOR ( 'NONE', #1246, 1000.000000000000000 ) ;
+#3506 = ADVANCED_FACE ( 'NONE', ( #3013 ), #2874, .F. ) ;
+#3507 = FACE_OUTER_BOUND ( 'NONE', #803, .T. ) ;
+#3508 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 2.500000000000000000 ) ) ;
+#3509 = ORIENTED_EDGE ( 'NONE', *, *, #345, .F. ) ;
+#3510 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, -18.71852980579590000, 0.0000000000000000000 ) ) ;
+#3511 = EDGE_CURVE ( 'NONE', #2709, #810, #3963, .T. ) ;
+#3512 = VERTEX_POINT ( 'NONE', #881 ) ;
+#3513 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3514 = FACE_OUTER_BOUND ( 'NONE', #4680, .T. ) ;
+#3515 = EDGE_CURVE ( 'NONE', #3704, #696, #2894, .T. ) ;
+#3516 = VERTEX_POINT ( 'NONE', #907 ) ;
+#3517 = LINE ( 'NONE', #3554, #2322 ) ;
+#3518 = VERTEX_POINT ( 'NONE', #3703 ) ;
+#3519 = ORIENTED_EDGE ( 'NONE', *, *, #3320, .T. ) ;
+#3520 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3521 = VERTEX_POINT ( 'NONE', #4519 ) ;
+#3522 = EDGE_LOOP ( 'NONE', ( #1822, #2081 ) ) ;
+#3523 = VECTOR ( 'NONE', #3208, 1000.000000000000100 ) ;
+#3524 = EDGE_CURVE ( 'NONE', #2032, #777, #5157, .T. ) ;
+#3525 = ORIENTED_EDGE ( 'NONE', *, *, #2170, .F. ) ;
+#3526 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, -16.51000000000000900, 2.500000000000000000 ) ) ;
+#3527 = ORIENTED_EDGE ( 'NONE', *, *, #2972, .T. ) ;
+#3528 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -20.19999999999999900, 0.0000000000000000000 ) ) ;
+#3529 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 22.19999999999999900, -0.6999999999999999600 ) ) ;
+#3530 = EDGE_CURVE ( 'NONE', #1724, #3778, #3842, .T. ) ;
+#3531 = ORIENTED_EDGE ( 'NONE', *, *, #2085, .T. ) ;
+#3532 = EDGE_LOOP ( 'NONE', ( #4915, #1720, #2760, #3329 ) ) ;
+#3533 = LINE ( 'NONE', #3107, #4484 ) ;
+#3534 = ORIENTED_EDGE ( 'NONE', *, *, #4648, .T. ) ;
+#3535 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3536 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3537 = PRODUCT_DEFINITION_SHAPE ( 'NONE', 'NONE', #2058 ) ;
+#3538 = ORIENTED_EDGE ( 'NONE', *, *, #4199, .F. ) ;
+#3539 = VECTOR ( 'NONE', #2283, 1000.000000000000100 ) ;
+#3540 = ORIENTED_EDGE ( 'NONE', *, *, #1475, .F. ) ;
+#3541 = ADVANCED_FACE ( 'NONE', ( #632 ), #86, .F. ) ;
+#3542 = AXIS2_PLACEMENT_3D ( 'NONE', #383, #4008, #1603 ) ;
+#3543 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3544 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, -13.81000000000000200, 2.500000000000000000 ) ) ;
+#3545 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, 4.500000000000000000, 4.299999999999999800 ) ) ;
+#3546 = LINE ( 'NONE', #4430, #1424 ) ;
+#3547 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#3548 = EDGE_CURVE ( 'NONE', #3521, #999, #4779, .T. ) ;
+#3549 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#3550 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602700, 16.51000000000000500, 0.0000000000000000000 ) ) ;
+#3551 = EDGE_CURVE ( 'NONE', #1502, #1589, #3713, .T. ) ;
+#3552 = VECTOR ( 'NONE', #2237, 1000.000000000000000 ) ;
+#3553 = CIRCLE ( 'NONE', #156, 1.000000000000000900 ) ;
+#3554 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#3555 = LINE ( 'NONE', #1892, #481 ) ;
+#3556 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3557 = ORIENTED_EDGE ( 'NONE', *, *, #278, .T. ) ;
+#3558 = ADVANCED_FACE ( 'NONE', ( #1710 ), #936, .T. ) ;
+#3559 = DIRECTION ( 'NONE', ( -0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3560 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3561 = DIRECTION ( 'NONE', ( -0.4999999999999979500, 0.8660254037844397100, 0.0000000000000000000 ) ) ;
+#3562 = CIRCLE ( 'NONE', #2656, 0.9999999999999991100 ) ;
+#3563 = AXIS2_PLACEMENT_3D ( 'NONE', #4051, #1632, #4448 ) ;
+#3564 = ORIENTED_EDGE ( 'NONE', *, *, #604, .F. ) ;
+#3565 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3566 = CIRCLE ( 'NONE', #4216, 1.000000000000000900 ) ;
+#3567 = EDGE_CURVE ( 'NONE', #444, #525, #508, .T. ) ;
+#3568 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#3569 = VECTOR ( 'NONE', #2512, 1000.000000000000000 ) ;
+#3570 = ORIENTED_EDGE ( 'NONE', *, *, #3847, .T. ) ;
+#3571 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#3572 = AXIS2_PLACEMENT_3D ( 'NONE', #804, #3614, #1220 ) ;
+#3573 = ORIENTED_EDGE ( 'NONE', *, *, #3317, .F. ) ;
+#3574 = FACE_OUTER_BOUND ( 'NONE', #1063, .T. ) ;
+#3575 = ORIENTED_EDGE ( 'NONE', *, *, #4270, .T. ) ;
+#3576 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3577 = VERTEX_POINT ( 'NONE', #4534 ) ;
+#3578 = EDGE_CURVE ( 'NONE', #4537, #768, #3581, .T. ) ;
+#3579 = LINE ( 'NONE', #4725, #3455 ) ;
+#3580 = VERTEX_POINT ( 'NONE', #536 ) ;
+#3581 = LINE ( 'NONE', #3330, #2521 ) ;
+#3582 = ORIENTED_EDGE ( 'NONE', *, *, #2514, .F. ) ;
+#3583 = ORIENTED_EDGE ( 'NONE', *, *, #3141, .F. ) ;
+#3584 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318800800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#3585 = EDGE_CURVE ( 'NONE', #395, #2709, #753, .T. ) ;
+#3586 = ADVANCED_FACE ( 'NONE', ( #4535 ), #1358, .F. ) ;
+#3587 = EDGE_LOOP ( 'NONE', ( #820, #2934, #1200, #126 ) ) ;
+#3588 = EDGE_CURVE ( 'NONE', #4663, #2961, #3458, .T. ) ;
+#3589 = ORIENTED_EDGE ( 'NONE', *, *, #2654, .T. ) ;
+#3590 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#3591 = LINE ( 'NONE', #4433, #516 ) ;
+#3592 = ORIENTED_EDGE ( 'NONE', *, *, #3030, .F. ) ;
+#3593 = ORIENTED_EDGE ( 'NONE', *, *, #3287, .T. ) ;
+#3594 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3595 = EDGE_CURVE ( 'NONE', #3599, #4175, #1575, .T. ) ;
+#3596 = ORIENTED_EDGE ( 'NONE', *, *, #2242, .T. ) ;
+#3597 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3598 = VECTOR ( 'NONE', #3802, 1000.000000000000000 ) ;
+#3599 = VERTEX_POINT ( 'NONE', #4182 ) ;
+#3600 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3601 = AXIS2_PLACEMENT_3D ( 'NONE', #3920, #4770, #1970 ) ;
+#3602 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199900, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#3603 = ORIENTED_EDGE ( 'NONE', *, *, #4210, .T. ) ;
+#3604 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3605 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, -21.19999999999999900, 4.299999999999999800 ) ) ;
+#3606 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3607 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3608 = DIRECTION ( 'NONE', ( -0.4999999999999996700, -0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#3609 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3610 = ADVANCED_FACE ( 'NONE', ( #244 ), #1370, .F. ) ;
+#3611 = PLANE ( 'NONE', #4920 ) ;
+#3612 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681198800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#3613 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 13.72875387184322100, 6.000000000000000000 ) ) ;
+#3614 = DIRECTION ( 'NONE', ( -0.8660254037844386000, -0.5000000000000001100, 0.0000000000000000000 ) ) ;
+#3615 = LINE ( 'NONE', #1732, #1176 ) ;
+#3616 = CYLINDRICAL_SURFACE ( 'NONE', #4489, 1.000000000000000900 ) ;
+#3617 = CYLINDRICAL_SURFACE ( 'NONE', #2193, 0.9999999999999991100 ) ;
+#3618 = DIRECTION ( 'NONE', ( -0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3619 = VERTEX_POINT ( 'NONE', #3770 ) ;
+#3620 = CIRCLE ( 'NONE', #4487, 1.600000000000000800 ) ;
+#3621 = PLANE ( 'NONE', #5121 ) ;
+#3622 = EDGE_CURVE ( 'NONE', #3369, #965, #4406, .T. ) ;
+#3623 = FACE_OUTER_BOUND ( 'NONE', #1949, .T. ) ;
+#3624 = FACE_BOUND ( 'NONE', #2671, .T. ) ;
+#3625 = EDGE_CURVE ( 'NONE', #4117, #3195, #3331, .T. ) ;
+#3626 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, -16.51000000000000900, 0.0000000000000000000 ) ) ;
+#3627 = ORIENTED_EDGE ( 'NONE', *, *, #4546, .T. ) ;
+#3628 = VECTOR ( 'NONE', #1998, 1000.000000000000100 ) ;
+#3629 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3630 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#3631 = ORIENTED_EDGE ( 'NONE', *, *, #2848, .T. ) ;
+#3632 = ADVANCED_FACE ( 'NONE', ( #1198 ), #4194, .F. ) ;
+#3633 = CARTESIAN_POINT ( 'NONE', ( -1.567893218813450000, -21.90710678118655300, 4.299999999999999800 ) ) ;
+#3634 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#3635 = FACE_OUTER_BOUND ( 'NONE', #4125, .T. ) ;
+#3636 = ORIENTED_EDGE ( 'NONE', *, *, #2447, .F. ) ;
+#3637 = EDGE_CURVE ( 'NONE', #2386, #4226, #4282, .T. ) ;
+#3638 = ADVANCED_FACE ( 'NONE', ( #1454 ), #181, .F. ) ;
+#3639 = ORIENTED_EDGE ( 'NONE', *, *, #1477, .T. ) ;
+#3640 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, -10.27254486838324100, 0.0000000000000000000 ) ) ;
+#3641 = LINE ( 'NONE', #1189, #2451 ) ;
+#3642 = AXIS2_PLACEMENT_3D ( 'NONE', #2601, #1016, #1420 ) ;
+#3643 = ORIENTED_EDGE ( 'NONE', *, *, #1851, .T. ) ;
+#3644 = LINE ( 'NONE', #3966, #4590 ) ;
+#3645 = ORIENTED_EDGE ( 'NONE', *, *, #2453, .T. ) ;
+#3646 = PLANE ( 'NONE', #2852 ) ;
+#3647 = ORIENTED_EDGE ( 'NONE', *, *, #4110, .F. ) ;
+#3648 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681198800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#3649 = ORIENTED_EDGE ( 'NONE', *, *, #4185, .F. ) ;
+#3650 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3651 = ORIENTED_EDGE ( 'NONE', *, *, #3567, .T. ) ;
+#3652 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3653 = CYLINDRICAL_SURFACE ( 'NONE', #5057, 1.000000000000000000 ) ;
+#3654 = ORIENTED_EDGE ( 'NONE', *, *, #1604, .T. ) ;
+#3655 = ORIENTED_EDGE ( 'NONE', *, *, #621, .F. ) ;
+#3656 = DIRECTION ( 'NONE', ( -0.5000000000000003300, -0.8660254037844383700, 0.0000000000000000000 ) ) ;
+#3657 = ORIENTED_EDGE ( 'NONE', *, *, #4758, .T. ) ;
+#3658 = CIRCLE ( 'NONE', #1919, 4.250000000000000000 ) ;
+#3659 = EDGE_CURVE ( 'NONE', #2947, #1778, #125, .T. ) ;
+#3660 = ORIENTED_EDGE ( 'NONE', *, *, #1064, .T. ) ;
+#3661 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#3662 = EDGE_CURVE ( 'NONE', #3518, #1692, #1076, .T. ) ;
+#3663 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
+#3664 = EDGE_LOOP ( 'NONE', ( #3835, #1943, #1033, #1185, #1442, #1780, #2025, #4007 ) ) ;
+#3665 = FACE_OUTER_BOUND ( 'NONE', #736, .T. ) ;
+#3666 = ORIENTED_EDGE ( 'NONE', *, *, #3567, .F. ) ;
+#3667 = ADVANCED_FACE ( 'NONE', ( #2143 ), #203, .T. ) ;
+#3668 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3669 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602700, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#3670 = ADVANCED_FACE ( 'NONE', ( #3074, #5093 ), #4209, .T. ) ;
+#3671 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3672 = CARTESIAN_POINT ( 'NONE', ( -13.06789321881344000, 20.99289321881345000, 4.299999999999999800 ) ) ;
+#3673 = AXIS2_PLACEMENT_3D ( 'NONE', #236, #3051, #654 ) ;
+#3674 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, 18.71852980579590000, -0.6999999999999999600 ) ) ;
+#3675 = DIRECTION ( 'NONE', ( -0.5000000000000005600, -0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#3676 = APPROVAL ( #1680, 'UNSPECIFIED' ) ;
+#3677 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -19.19999999999999900, -0.6999999999999999600 ) ) ;
+#3678 = APPROVAL_ROLE ( '' ) ;
+#3679 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#3680 = VECTOR ( 'NONE', #2780, 1000.000000000000000 ) ;
+#3681 = DIRECTION ( 'NONE', ( -0.4999999999999995600, 0.8660254037844389300, 0.0000000000000000000 ) ) ;
+#3682 = COORDINATED_UNIVERSAL_TIME_OFFSET ( 8, 0, .BEHIND. ) ;
+#3683 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -13.81000000000000200, 0.0000000000000000000 ) ) ;
+#3684 = EDGE_CURVE ( 'NONE', #1254, #4104, #4027, .T. ) ;
+#3685 = VECTOR ( 'NONE', #1012, 1000.000000000000100 ) ;
+#3686 = LINE ( 'NONE', #3550, #1552 ) ;
+#3687 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602000, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#3688 = ADVANCED_FACE ( 'NONE', ( #1890 ), #4962, .T. ) ;
+#3689 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3690 = VECTOR ( 'NONE', #1873, 1000.000000000000000 ) ;
+#3691 = EDGE_CURVE ( 'NONE', #1532, #3072, #5213, .T. ) ;
+#3692 = PLANE ( 'NONE', #2887 ) ;
+#3693 = CYLINDRICAL_SURFACE ( 'NONE', #1331, 1.600000000000000800 ) ;
+#3694 = EDGE_CURVE ( 'NONE', #108, #3072, #814, .T. ) ;
+#3695 = ORIENTED_EDGE ( 'NONE', *, *, #887, .F. ) ;
+#3696 = AXIS2_PLACEMENT_3D ( 'NONE', #1257, #2442, #4854 ) ;
+#3697 = FACE_OUTER_BOUND ( 'NONE', #4267, .T. ) ;
+#3698 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, -16.51000000000000500, 0.0000000000000000000 ) ) ;
+#3699 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 21.44999999999999900, 4.299999999999999800 ) ) ;
+#3700 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3701 = FACE_OUTER_BOUND ( 'NONE', #2771, .T. ) ;
+#3702 = ORIENTED_EDGE ( 'NONE', *, *, #2516, .T. ) ;
+#3703 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 0.7500000000000000000, 1.000000000000000000 ) ) ;
+#3704 = VERTEX_POINT ( 'NONE', #2707 ) ;
+#3705 = DIRECTION ( 'NONE', ( 6.123233995736769700E-017, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3706 = LINE ( 'NONE', #1248, #1567 ) ;
+#3707 = ORIENTED_EDGE ( 'NONE', *, *, #2177, .T. ) ;
+#3708 = AXIS2_PLACEMENT_3D ( 'NONE', #1981, #4795, #2376 ) ;
+#3709 = EDGE_LOOP ( 'NONE', ( #1889, #2820, #2818, #2464 ) ) ;
+#3710 = PLANE ( 'NONE', #2900 ) ;
+#3711 = LINE ( 'NONE', #3529, #504 ) ;
+#3712 = ORIENTED_EDGE ( 'NONE', *, *, #1538, .F. ) ;
+#3713 = LINE ( 'NONE', #1733, #2631 ) ;
+#3714 = VERTEX_POINT ( 'NONE', #1037 ) ;
+#3715 = EDGE_LOOP ( 'NONE', ( #428, #4845, #4280, #5215 ) ) ;
+#3716 = ADVANCED_FACE ( 'NONE', ( #2946 ), #3771, .F. ) ;
+#3717 = CARTESIAN_POINT ( 'NONE', ( -20.02500000000000200, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#3718 = ORIENTED_EDGE ( 'NONE', *, *, #2170, .T. ) ;
+#3719 = VERTEX_POINT ( 'NONE', #1117 ) ;
+#3720 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#3721 = VECTOR ( 'NONE', #789, 1000.000000000000100 ) ;
+#3722 = ORIENTED_EDGE ( 'NONE', *, *, #3417, .F. ) ;
+#3723 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, 20.33743026049845100, -0.6999999999999999600 ) ) ;
+#3724 = ORIENTED_EDGE ( 'NONE', *, *, #1696, .T. ) ;
+#3725 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, -13.81000000000000200, 0.0000000000000000000 ) ) ;
+#3726 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3727 = ORIENTED_EDGE ( 'NONE', *, *, #454, .F. ) ;
+#3728 = LINE ( 'NONE', #1382, #4548 ) ;
+#3729 = ORIENTED_EDGE ( 'NONE', *, *, #4612, .F. ) ;
+#3730 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 2.368891848716884900, 6.000000000000000000 ) ) ;
+#3731 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#3732 = EDGE_CURVE ( 'NONE', #2448, #2305, #4839, .T. ) ;
+#3733 = VERTEX_POINT ( 'NONE', #3526 ) ;
+#3734 = DIRECTION ( 'NONE', ( -0.1674394999967844500, 0.9858823529411746600, 0.0000000000000000000 ) ) ;
+#3735 = EDGE_CURVE ( 'NONE', #1971, #5222, #3902, .T. ) ;
+#3736 = AXIS2_PLACEMENT_3D ( 'NONE', #550, #3358, #971 ) ;
+#3737 = EDGE_LOOP ( 'NONE', ( #1558, #4793, #2535, #3130 ) ) ;
+#3738 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#3739 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3740 = AXIS2_PLACEMENT_3D ( 'NONE', #4268, #1863, #4676 ) ;
+#3741 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800800, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#3742 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#3743 = EDGE_CURVE ( 'NONE', #127, #1952, #2691, .T. ) ;
+#3744 = VECTOR ( 'NONE', #1597, 1000.000000000000000 ) ;
+#3745 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, -16.32049935181331100 ) ) ;
+#3746 = VECTOR ( 'NONE', #906, 1000.000000000000000 ) ;
+#3747 = VECTOR ( 'NONE', #1004, 1000.000000000000000 ) ;
+#3748 = VECTOR ( 'NONE', #4818, 1000.000000000000000 ) ;
+#3749 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602000, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#3750 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3751 = VECTOR ( 'NONE', #4235, 1000.000000000000000 ) ;
+#3752 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3753 = CIRCLE ( 'NONE', #4559, 1.000000000000000900 ) ;
+#3754 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362397600, 16.51000000000000500, 0.0000000000000000000 ) ) ;
+#3755 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#3756 = ORIENTED_EDGE ( 'NONE', *, *, #4382, .F. ) ;
+#3757 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3758 = LINE ( 'NONE', #1947, #3888 ) ;
+#3759 = EDGE_CURVE ( 'NONE', #2640, #4213, #690, .T. ) ;
+#3760 = VERTEX_POINT ( 'NONE', #318 ) ;
+#3761 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3762 = VECTOR ( 'NONE', #3848, 1000.000000000000000 ) ;
+#3763 = ORIENTED_EDGE ( 'NONE', *, *, #5033, .T. ) ;
+#3764 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, -10.27254486838324100, -0.6999999999999999600 ) ) ;
+#3765 = ORIENTED_EDGE ( 'NONE', *, *, #2801, .T. ) ;
+#3766 = AXIS2_PLACEMENT_3D ( 'NONE', #3483, #5108, #2683 ) ;
+#3767 = CARTESIAN_POINT ( 'NONE', ( 19.99000000000000200, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#3768 = LINE ( 'NONE', #2284, #2688 ) ;
+#3769 = ORIENTED_EDGE ( 'NONE', *, *, #3289, .F. ) ;
+#3770 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#3771 = CYLINDRICAL_SURFACE ( 'NONE', #1896, 1.600000000000000800 ) ;
+#3772 = DIRECTION ( 'NONE', ( 0.5000000000000008900, -0.8660254037844381500, 0.0000000000000000000 ) ) ;
+#3773 = ORIENTED_EDGE ( 'NONE', *, *, #4288, .F. ) ;
+#3774 = VERTEX_POINT ( 'NONE', #4740 ) ;
+#3775 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3776 = VERTEX_POINT ( 'NONE', #1543 ) ;
+#3777 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3778 = VERTEX_POINT ( 'NONE', #1941 ) ;
+#3779 = AXIS2_PLACEMENT_3D ( 'NONE', #422, #3234, #842 ) ;
+#3780 = VERTEX_POINT ( 'NONE', #1140 ) ;
+#3781 = CIRCLE ( 'NONE', #3708, 4.250000000000000000 ) ;
+#3782 = ORIENTED_EDGE ( 'NONE', *, *, #5197, .T. ) ;
+#3783 = ORIENTED_EDGE ( 'NONE', *, *, #3588, .F. ) ;
+#3784 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, 19.49289321881345000, 0.0000000000000000000 ) ) ;
+#3785 = VERTEX_POINT ( 'NONE', #3946 ) ;
+#3786 = VECTOR ( 'NONE', #2659, 1000.000000000000000 ) ;
+#3787 = ORIENTED_EDGE ( 'NONE', *, *, #4196, .F. ) ;
+#3788 = EDGE_CURVE ( 'NONE', #4115, #1193, #433, .T. ) ;
+#3789 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#3790 = VERTEX_POINT ( 'NONE', #2733 ) ;
+#3791 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3792 = LINE ( 'NONE', #4295, #2838 ) ;
+#3793 = VERTEX_POINT ( 'NONE', #745 ) ;
+#3794 = EDGE_CURVE ( 'NONE', #1284, #163, #2454, .T. ) ;
+#3795 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, 19.19999999999999900, 4.299999999999999800 ) ) ;
+#3796 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3797 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3798 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 2.878679656440359800, 6.000000000000000000 ) ) ;
+#3799 = EDGE_CURVE ( 'NONE', #3250, #1215, #3644, .T. ) ;
+#3800 = EDGE_CURVE ( 'NONE', #3339, #4601, #298, .T. ) ;
+#3801 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3802 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3803 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3804 = CARTESIAN_POINT ( 'NONE', ( 2.000000000000000000, 6.000000000000000000, 4.299999999999999800 ) ) ;
+#3805 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#3806 = DIRECTION ( 'NONE', ( -0.8660254037844383700, -0.5000000000000005600, 0.0000000000000000000 ) ) ;
+#3807 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -19.19999999999999900, 4.299999999999999800 ) ) ;
+#3808 = VECTOR ( 'NONE', #1584, 1000.000000000000000 ) ;
+#3809 = LOCAL_TIME ( 12, 52, 21.00000000000000000, #4881 ) ;
+#3810 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3811 = CIRCLE ( 'NONE', #1946, 1.000000000000000900 ) ;
+#3812 = VECTOR ( 'NONE', #2327, 1000.000000000000000 ) ;
+#3813 = ORIENTED_EDGE ( 'NONE', *, *, #2516, .F. ) ;
+#3814 = LINE ( 'NONE', #246, #1682 ) ;
+#3815 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3816 = CIRCLE ( 'NONE', #4931, 1.000000000000000900 ) ;
+#3817 = DIRECTION ( 'NONE', ( 0.1674394999967844500, 0.9858823529411746600, 0.0000000000000000000 ) ) ;
+#3818 = PLANE ( 'NONE', #2222 ) ;
+#3819 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3820 = ORIENTED_EDGE ( 'NONE', *, *, #454, .T. ) ;
+#3821 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3822 = CIRCLE ( 'NONE', #1528, 0.9999999999999991100 ) ;
+#3823 = DIRECTION ( 'NONE', ( 0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#3824 = EDGE_CURVE ( 'NONE', #3780, #5161, #3517, .T. ) ;
+#3825 = FACE_OUTER_BOUND ( 'NONE', #284, .T. ) ;
+#3826 = ORIENTED_EDGE ( 'NONE', *, *, #4908, .F. ) ;
+#3827 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -20.19999999999999900, 4.299999999999999800 ) ) ;
+#3828 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 3.500000000000000000, 0.2999999999999999900 ) ) ;
+#3829 = EDGE_CURVE ( 'NONE', #4909, #3577, #3388, .T. ) ;
+#3830 = ORIENTED_EDGE ( 'NONE', *, *, #2562, .F. ) ;
+#3831 = VECTOR ( 'NONE', #844, 1000.000000000000000 ) ;
+#3832 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3833 = EDGE_LOOP ( 'NONE', ( #3084, #5220, #4635, #2646 ) ) ;
+#3834 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3835 = ORIENTED_EDGE ( 'NONE', *, *, #2732, .T. ) ;
+#3836 = AXIS2_PLACEMENT_3D ( 'NONE', #1047, #3056, #657 ) ;
+#3837 = PLANE ( 'NONE', #24 ) ;
+#3838 = CIRCLE ( 'NONE', #3424, 0.9999999999999991100 ) ;
+#3839 = ORIENTED_EDGE ( 'NONE', *, *, #3788, .F. ) ;
+#3840 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3841 = VERTEX_POINT ( 'NONE', #3984 ) ;
+#3842 = LINE ( 'NONE', #2112, #1833 ) ;
+#3843 = ORIENTED_EDGE ( 'NONE', *, *, #4502, .T. ) ;
+#3844 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3845 = EDGE_LOOP ( 'NONE', ( #4492, #199, #3893, #556 ) ) ;
+#3846 = CARTESIAN_POINT ( 'NONE', ( -1.076572875253784400, -20.27710678118655000, 4.299999999999999800 ) ) ;
+#3847 = EDGE_CURVE ( 'NONE', #3067, #3790, #4712, .T. ) ;
+#3848 = DIRECTION ( 'NONE', ( -6.735557395310440100E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#3849 = EDGE_LOOP ( 'NONE', ( #3471, #4086 ) ) ;
+#3850 = ORIENTED_EDGE ( 'NONE', *, *, #3799, .T. ) ;
+#3851 = FACE_OUTER_BOUND ( 'NONE', #5032, .T. ) ;
+#3852 = ORIENTED_EDGE ( 'NONE', *, *, #1350, .T. ) ;
+#3853 = ORIENTED_EDGE ( 'NONE', *, *, #2639, .T. ) ;
+#3854 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3855 = ORIENTED_EDGE ( 'NONE', *, *, #4234, .T. ) ;
+#3856 = VECTOR ( 'NONE', #2540, 1000.000000000000000 ) ;
+#3857 = ORIENTED_EDGE ( 'NONE', *, *, #494, .F. ) ;
+#3858 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, -19.69999999999999900, -0.6999999999999999600 ) ) ;
+#3859 = DIRECTION ( 'NONE', ( 0.4999999999999997800, -0.8660254037844387100, 0.0000000000000000000 ) ) ;
+#3860 = ORIENTED_EDGE ( 'NONE', *, *, #4114, .F. ) ;
+#3861 = EDGE_CURVE ( 'NONE', #2948, #1519, #1266, .T. ) ;
+#3862 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 2.500000000000000000 ) ) ;
+#3863 = ORIENTED_EDGE ( 'NONE', *, *, #4329, .T. ) ;
+#3864 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 0.7500000000000000000, 6.000000000000000000 ) ) ;
+#3865 = EDGE_CURVE ( 'NONE', #1718, #618, #3263, .T. ) ;
+#3866 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3867 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3868 = EDGE_CURVE ( 'NONE', #4971, #4482, #1129, .T. ) ;
+#3869 = PLANE ( 'NONE', #3740 ) ;
+#3870 = EDGE_CURVE ( 'NONE', #2129, #1193, #4211, .T. ) ;
+#3871 = CARTESIAN_POINT ( 'NONE', ( -0.9465728752538080700, -20.40710678118654900, 0.0000000000000000000 ) ) ;
+#3872 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#3873 = LINE ( 'NONE', #511, #3569 ) ;
+#3874 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -0.7500000000000000000, 1.000000000000000000 ) ) ;
+#3875 = LINE ( 'NONE', #4488, #5064 ) ;
+#3876 = CARTESIAN_POINT ( 'NONE', ( -0.9465728752538080700, 20.40710678118654900, 0.0000000000000000000 ) ) ;
+#3877 = FACE_OUTER_BOUND ( 'NONE', #830, .T. ) ;
+#3878 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, 2.000000000000000000 ) ) ;
+#3879 = VECTOR ( 'NONE', #1569, 1000.000000000000000 ) ;
+#3880 = PLANE ( 'NONE', #4844 ) ;
+#3881 = AXIS2_PLACEMENT_3D ( 'NONE', #1743, #4581, #2171 ) ;
+#3882 = CYLINDRICAL_SURFACE ( 'NONE', #4554, 1.000000000000000900 ) ;
+#3883 = CYLINDRICAL_SURFACE ( 'NONE', #4297, 1.600000000000000800 ) ;
+#3884 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3885 = ADVANCED_FACE ( 'NONE', ( #4580 ), #370, .T. ) ;
+#3886 = ORIENTED_EDGE ( 'NONE', *, *, #531, .T. ) ;
+#3887 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3888 = VECTOR ( 'NONE', #2350, 1000.000000000000100 ) ;
+#3889 = ORIENTED_EDGE ( 'NONE', *, *, #4833, .F. ) ;
+#3890 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#3891 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, 22.19999999999999900, -0.6999999999999999600 ) ) ;
+#3892 = FACE_OUTER_BOUND ( 'NONE', #2748, .T. ) ;
+#3893 = ORIENTED_EDGE ( 'NONE', *, *, #4805, .F. ) ;
+#3894 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#3895 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3896 = EDGE_CURVE ( 'NONE', #5029, #4670, #421, .T. ) ;
+#3897 = ORIENTED_EDGE ( 'NONE', *, *, #3141, .T. ) ;
+#3898 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -2.878679656440359800, 6.000000000000000000 ) ) ;
+#3899 = LINE ( 'NONE', #680, #2815 ) ;
+#3900 = ORIENTED_EDGE ( 'NONE', *, *, #1570, .T. ) ;
+#3901 = EDGE_CURVE ( 'NONE', #5031, #2375, #2441, .T. ) ;
+#3902 = LINE ( 'NONE', #1926, #561 ) ;
+#3903 = ORIENTED_EDGE ( 'NONE', *, *, #4817, .F. ) ;
+#3904 = ORIENTED_EDGE ( 'NONE', *, *, #89, .F. ) ;
+#3905 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 20.19999999999999900, 0.0000000000000000000 ) ) ;
+#3906 = ORIENTED_EDGE ( 'NONE', *, *, #3180, .F. ) ;
+#3907 = ORIENTED_EDGE ( 'NONE', *, *, #3218, .F. ) ;
+#3908 = LINE ( 'NONE', #4773, #1776 ) ;
+#3909 = ORIENTED_EDGE ( 'NONE', *, *, #4802, .T. ) ;
+#3910 = ORIENTED_EDGE ( 'NONE', *, *, #3218, .T. ) ;
+#3911 = ADVANCED_FACE ( 'NONE', ( #3377 ), #4427, .F. ) ;
+#3912 = VERTEX_POINT ( 'NONE', #1990 ) ;
+#3913 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#3914 = FACE_OUTER_BOUND ( 'NONE', #1702, .T. ) ;
+#3915 = ORIENTED_EDGE ( 'NONE', *, *, #5194, .F. ) ;
+#3916 = EDGE_CURVE ( 'NONE', #1716, #5087, #290, .T. ) ;
+#3917 = ORIENTED_EDGE ( 'NONE', *, *, #4032, .T. ) ;
+#3918 = CYLINDRICAL_SURFACE ( 'NONE', #2385, 1.000000000000000000 ) ;
+#3919 = ORIENTED_EDGE ( 'NONE', *, *, #3422, .F. ) ;
+#3920 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 2.500000000000000000 ) ) ;
+#3921 = AXIS2_PLACEMENT_3D ( 'NONE', #4244, #1843, #4644 ) ;
+#3922 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3923 = DIRECTION ( 'NONE', ( 0.7071067811865474600, 0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#3924 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 2.000000000000000000 ) ) ;
+#3925 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#3926 = EDGE_CURVE ( 'NONE', #3183, #3343, #2312, .T. ) ;
+#3927 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#3928 = EDGE_CURVE ( 'NONE', #4107, #2832, #1256, .T. ) ;
+#3929 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 5.277604462221250500E-017, 4.299999999999999800 ) ) ;
+#3930 = ADVANCED_FACE ( 'NONE', ( #1121 ), #1626, .F. ) ;
+#3931 = LINE ( 'NONE', #5177, #4879 ) ;
+#3932 = CALENDAR_DATE ( 2016, 8, 12 ) ;
+#3933 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3934 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -6.250000000000000000, 6.000000000000000000 ) ) ;
+#3935 = LINE ( 'NONE', #174, #859 ) ;
+#3936 = ORIENTED_EDGE ( 'NONE', *, *, #3735, .T. ) ;
+#3937 = AXIS2_PLACEMENT_3D ( 'NONE', #1478, #4301, #1883 ) ;
+#3938 = VECTOR ( 'NONE', #725, 1000.000000000000000 ) ;
+#3939 = LINE ( 'NONE', #226, #864 ) ;
+#3940 = CARTESIAN_POINT ( 'NONE', ( -27.97669089436529800, -0.7500000000000000000, 1.000000000000000000 ) ) ;
+#3941 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#3942 = CC_DESIGN_APPROVAL ( #3676, ( #3407 ) ) ;
+#3943 = CARTESIAN_POINT ( 'NONE', ( -13.68921356237310000, -19.49289321881345000, 4.299999999999999800 ) ) ;
+#3944 = VECTOR ( 'NONE', #3675, 1000.000000000000200 ) ;
+#3945 = AXIS2_PLACEMENT_3D ( 'NONE', #2221, #3834, #1432 ) ;
+#3946 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, -19.49289321881345000, 2.000000000000000000 ) ) ;
+#3947 = ORIENTED_EDGE ( 'NONE', *, *, #3524, .F. ) ;
+#3948 = LINE ( 'NONE', #2167, #738 ) ;
+#3949 = FACE_OUTER_BOUND ( 'NONE', #4783, .T. ) ;
+#3950 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3951 = ORIENTED_EDGE ( 'NONE', *, *, #1053, .T. ) ;
+#3952 = EDGE_CURVE ( 'NONE', #689, #2890, #2186, .T. ) ;
+#3953 = VERTEX_POINT ( 'NONE', #1651 ) ;
+#3954 = VECTOR ( 'NONE', #2434, 1000.000000000000000 ) ;
+#3955 = EDGE_CURVE ( 'NONE', #761, #3785, #3126, .T. ) ;
+#3956 = VERTEX_POINT ( 'NONE', #4460 ) ;
+#3957 = ADVANCED_FACE ( 'NONE', ( #2064 ), #4865, .F. ) ;
+#3958 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, -20.33743026049845100, -0.6999999999999999600 ) ) ;
+#3959 = ORIENTED_EDGE ( 'NONE', *, *, #16, .T. ) ;
+#3960 = DIRECTION ( 'NONE', ( 0.4999999999999997800, 0.8660254037844387100, 0.0000000000000000000 ) ) ;
+#3961 = LINE ( 'NONE', #2057, #2891 ) ;
+#3962 = EDGE_LOOP ( 'NONE', ( #40, #2637, #517, #3724 ) ) ;
+#3963 = LINE ( 'NONE', #2495, #2084 ) ;
+#3964 = EDGE_CURVE ( 'NONE', #4426, #4166, #43, .T. ) ;
+#3965 = EDGE_LOOP ( 'NONE', ( #4340, #2543, #3603, #3573 ) ) ;
+#3966 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, 19.49289321881345000, -0.6999999999999999600 ) ) ;
+#3967 = EDGE_LOOP ( 'NONE', ( #5214, #2142, #519, #3592 ) ) ;
+#3968 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#3969 = ORIENTED_EDGE ( 'NONE', *, *, #3353, .T. ) ;
+#3970 = ORIENTED_EDGE ( 'NONE', *, *, #513, .F. ) ;
+#3971 = VECTOR ( 'NONE', #1974, 1000.000000000000000 ) ;
+#3972 = ORIENTED_EDGE ( 'NONE', *, *, #604, .T. ) ;
+#3973 = ORIENTED_EDGE ( 'NONE', *, *, #3380, .T. ) ;
+#3974 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#3975 = CARTESIAN_POINT ( 'NONE', ( 17.34000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#3976 = ORIENTED_EDGE ( 'NONE', *, *, #3156, .T. ) ;
+#3977 = VECTOR ( 'NONE', #110, 1000.000000000000000 ) ;
+#3978 = ORIENTED_EDGE ( 'NONE', *, *, #677, .T. ) ;
+#3979 = AXIS2_PLACEMENT_3D ( 'NONE', #2757, #3169, #771 ) ;
+#3980 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3981 = ORIENTED_EDGE ( 'NONE', *, *, #2044, .F. ) ;
+#3982 = ORIENTED_EDGE ( 'NONE', *, *, #526, .T. ) ;
+#3983 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3984 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, -4.500000000000000000, 4.299999999999999800 ) ) ;
+#3985 = CARTESIAN_POINT ( 'NONE', ( -13.06789321881344000, -20.99289321881345000, -0.6999999999999999600 ) ) ;
+#3986 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, 4.299999999999999800 ) ) ;
+#3987 = ADVANCED_FACE ( 'NONE', ( #4330 ), #2049, .F. ) ;
+#3988 = EDGE_LOOP ( 'NONE', ( #3211, #4778, #1637, #4716 ) ) ;
+#3989 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#3990 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#3991 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#3992 = CARTESIAN_POINT ( 'NONE', ( -13.56499942082922100, 21.48999942082920000, -0.6999999999999999600 ) ) ;
+#3993 = FACE_OUTER_BOUND ( 'NONE', #3092, .T. ) ;
+#3994 = DIRECTION ( 'NONE', ( 0.4999999999999996700, 0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#3995 = EDGE_CURVE ( 'NONE', #51, #4451, #4082, .T. ) ;
+#3996 = CARTESIAN_POINT ( 'NONE', ( -13.06789321881344000, -20.99289321881345000, 4.299999999999999800 ) ) ;
+#3997 = EDGE_CURVE ( 'NONE', #2677, #4290, #995, .T. ) ;
+#3998 = VECTOR ( 'NONE', #2853, 1000.000000000000000 ) ;
+#3999 = CIRCLE ( 'NONE', #2052, 1.000000000000000000 ) ;
+#4000 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, 18.71852980579590000, 0.0000000000000000000 ) ) ;
+#4001 = LINE ( 'NONE', #2353, #5188 ) ;
+#4002 = CARTESIAN_POINT ( 'NONE', ( -14.47342712474618900, -20.27710678118655000, 2.000000000000000000 ) ) ;
+#4003 = LINE ( 'NONE', #4603, #670 ) ;
+#4004 = CARTESIAN_POINT ( 'NONE', ( -19.99000000000000200, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#4005 = AXIS2_PLACEMENT_3D ( 'NONE', #3418, #1018, #3821 ) ;
+#4006 = VECTOR ( 'NONE', #484, 1000.000000000000000 ) ;
+#4007 = ORIENTED_EDGE ( 'NONE', *, *, #1747, .F. ) ;
+#4008 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4009 = LINE ( 'NONE', #3934, #1876 ) ;
+#4010 = EDGE_CURVE ( 'NONE', #3406, #2154, #5144, .T. ) ;
+#4011 = ADVANCED_FACE ( 'NONE', ( #2995 ), #544, .F. ) ;
+#4012 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4013 = ORIENTED_EDGE ( 'NONE', *, *, #1444, .T. ) ;
+#4014 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, -20.33743026049845100, 0.0000000000000000000 ) ) ;
+#4015 = AXIS2_PLACEMENT_3D ( 'NONE', #2912, #502, #3307 ) ;
+#4016 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, -20.33743026049845100, 4.299999999999999800 ) ) ;
+#4017 = VECTOR ( 'NONE', #997, 1000.000000000000000 ) ;
+#4018 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4019 = ORIENTED_EDGE ( 'NONE', *, *, #1038, .F. ) ;
+#4020 = DIRECTION ( 'NONE', ( 0.8660254037844388200, -0.4999999999999997200, 0.0000000000000000000 ) ) ;
+#4021 = CARTESIAN_POINT ( 'NONE', ( -7.774999999999999500, -20.69999999999999900, 4.299999999999999800 ) ) ;
+#4022 = ORIENTED_EDGE ( 'NONE', *, *, #1291, .F. ) ;
+#4023 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4024 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4025 = LINE ( 'NONE', #2622, #2944 ) ;
+#4026 = VERTEX_POINT ( 'NONE', #962 ) ;
+#4027 = LINE ( 'NONE', #210, #2009 ) ;
+#4028 = ORIENTED_EDGE ( 'NONE', *, *, #4674, .F. ) ;
+#4029 = VERTEX_POINT ( 'NONE', #1767 ) ;
+#4030 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4031 = ORIENTED_EDGE ( 'NONE', *, *, #4137, .T. ) ;
+#4032 = EDGE_CURVE ( 'NONE', #2903, #2069, #3948, .T. ) ;
+#4033 = VERTEX_POINT ( 'NONE', #4155 ) ;
+#4034 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -20.19999999999999900, -0.6999999999999999600 ) ) ;
+#4035 = VECTOR ( 'NONE', #2117, 1000.000000000000000 ) ;
+#4036 = ORIENTED_EDGE ( 'NONE', *, *, #2823, .T. ) ;
+#4037 = ORIENTED_EDGE ( 'NONE', *, *, #1131, .T. ) ;
+#4038 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4039 = ADVANCED_FACE ( 'NONE', ( #4892 ), #560, .F. ) ;
+#4040 = DIRECTION ( 'NONE', ( 0.4999999999999995600, -0.8660254037844389300, 0.0000000000000000000 ) ) ;
+#4041 = EDGE_LOOP ( 'NONE', ( #3729, #488, #1081, #4164 ) ) ;
+#4042 = VECTOR ( 'NONE', #241, 1000.000000000000000 ) ;
+#4043 = ORIENTED_EDGE ( 'NONE', *, *, #3896, .F. ) ;
+#4044 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4045 = ADVANCED_FACE ( 'NONE', ( #1817 ), #2968, .T. ) ;
+#4046 = VERTEX_POINT ( 'NONE', #3784 ) ;
+#4047 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -19.19999999999999900, 1.000000000000000000 ) ) ;
+#4048 = VERTEX_POINT ( 'NONE', #4988 ) ;
+#4049 = VECTOR ( 'NONE', #5015, 1000.000000000000000 ) ;
+#4050 = ORIENTED_EDGE ( 'NONE', *, *, #918, .F. ) ;
+#4051 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672371800, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#4052 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#4053 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4054 = PLANE ( 'NONE', #437 ) ;
+#4055 = PLANE ( 'NONE', #2319 ) ;
+#4056 = CARTESIAN_POINT ( 'NONE', ( -7.774999999999999500, 20.69999999999999900, 4.299999999999999800 ) ) ;
+#4057 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4058 = MANIFOLD_SOLID_BREP ( 'Cut-Extrude2', #2539 ) ;
+#4059 = EDGE_CURVE ( 'NONE', #3776, #579, #3822, .T. ) ;
+#4060 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4061 = CARTESIAN_POINT ( 'NONE', ( -1.805330085889910200, 21.66966991411009900, 4.299999999999999800 ) ) ;
+#4062 = ADVANCED_FACE ( 'NONE', ( #2735 ), #4762, .F. ) ;
+#4063 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4064 = EDGE_CURVE ( 'NONE', #1546, #3719, #613, .T. ) ;
+#4065 = FACE_OUTER_BOUND ( 'NONE', #3245, .T. ) ;
+#4066 = PERSON_AND_ORGANIZATION ( #4866, #5126 ) ;
+#4067 = ADVANCED_FACE ( 'NONE', ( #2878 ), #2195, .T. ) ;
+#4068 = LINE ( 'NONE', #568, #4998 ) ;
+#4069 = VERTEX_POINT ( 'NONE', #1802 ) ;
+#4070 = EDGE_CURVE ( 'NONE', #2375, #3144, #1695, .T. ) ;
+#4071 = VECTOR ( 'NONE', #4357, 1000.000000000000000 ) ;
+#4072 = LINE ( 'NONE', #3431, #1927 ) ;
+#4073 = EDGE_CURVE ( 'NONE', #4970, #3619, #2617, .T. ) ;
+#4074 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4075 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 6.000000000000000000, -0.6999999999999992900 ) ) ;
+#4076 = LINE ( 'NONE', #4871, #5138 ) ;
+#4077 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4078 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, 16.50999999999999400, 2.500000000000000000 ) ) ;
+#4079 = DIRECTION ( 'NONE', ( -0.7071067811865475700, -0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#4080 = VERTEX_POINT ( 'NONE', #1816 ) ;
+#4081 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#4082 = LINE ( 'NONE', #1751, #4202 ) ;
+#4083 = AXIS2_PLACEMENT_3D ( 'NONE', #3634, #1238, #4053 ) ;
+#4084 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, -20.57000000000000000, 2.000000000000000000 ) ) ;
+#4085 = CYLINDRICAL_SURFACE ( 'NONE', #1513, 1.000000000000000900 ) ;
+#4086 = ORIENTED_EDGE ( 'NONE', *, *, #1782, .T. ) ;
+#4087 = DATE_TIME_ROLE ( 'creation_date' ) ;
+#4088 = ORIENTED_EDGE ( 'NONE', *, *, #3111, .T. ) ;
+#4089 = ADVANCED_FACE ( 'NONE', ( #489 ), #1032, .T. ) ;
+#4090 = FACE_OUTER_BOUND ( 'NONE', #3392, .T. ) ;
+#4091 = EDGE_LOOP ( 'NONE', ( #4407, #1332, #762, #2759 ) ) ;
+#4092 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4093 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4094 = ORIENTED_EDGE ( 'NONE', *, *, #248, .T. ) ;
+#4095 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#4096 = ADVANCED_FACE ( 'NONE', ( #3697 ), #3425, .F. ) ;
+#4097 = ORIENTED_EDGE ( 'NONE', *, *, #1413, .F. ) ;
+#4098 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362397600, 16.51000000000000500, 0.0000000000000000000 ) ) ;
+#4099 = EDGE_LOOP ( 'NONE', ( #71, #4094, #4208, #652, #358, #4765, #2023, #4300, #1059, #4846, #1647, #2699 ) ) ;
+#4100 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -20.19999999999999900, 0.0000000000000000000 ) ) ;
+#4101 = ORIENTED_EDGE ( 'NONE', *, *, #3252, .F. ) ;
+#4102 = VECTOR ( 'NONE', #38, 1000.000000000000000 ) ;
+#4103 = VERTEX_POINT ( 'NONE', #3035 ) ;
+#4104 = VERTEX_POINT ( 'NONE', #637 ) ;
+#4105 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4106 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#4107 = VERTEX_POINT ( 'NONE', #4637 ) ;
+#4108 = ORIENTED_EDGE ( 'NONE', *, *, #1646, .T. ) ;
+#4109 = VECTOR ( 'NONE', #5170, 1000.000000000000000 ) ;
+#4110 = EDGE_CURVE ( 'NONE', #4206, #4640, #4638, .T. ) ;
+#4111 = ORIENTED_EDGE ( 'NONE', *, *, #513, .T. ) ;
+#4112 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4113 = ORIENTED_EDGE ( 'NONE', *, *, #4631, .T. ) ;
+#4114 = EDGE_CURVE ( 'NONE', #3154, #3774, #3566, .T. ) ;
+#4115 = VERTEX_POINT ( 'NONE', #3441 ) ;
+#4116 = VECTOR ( 'NONE', #1302, 1000.000000000000000 ) ;
+#4117 = VERTEX_POINT ( 'NONE', #1853 ) ;
+#4118 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 3.500000000000000000, 6.000000000000000000 ) ) ;
+#4119 = EDGE_LOOP ( 'NONE', ( #3439, #5100, #4036, #4037 ) ) ;
+#4120 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -6.250000000000000000, 6.000000000000000000 ) ) ;
+#4121 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4122 = ADVANCED_FACE ( 'NONE', ( #3442 ), #238, .F. ) ;
+#4123 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4124 = EDGE_CURVE ( 'NONE', #2641, #4080, #872, .T. ) ;
+#4125 = EDGE_LOOP ( 'NONE', ( #747, #4774, #757, #4999 ) ) ;
+#4126 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#4127 = LINE ( 'NONE', #68, #926 ) ;
+#4128 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4129 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672599900, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#4130 = EDGE_CURVE ( 'NONE', #3262, #2201, #4522, .T. ) ;
+#4131 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4132 = FACE_BOUND ( 'NONE', #196, .T. ) ;
+#4133 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4134 = LINE ( 'NONE', #3251, #929 ) ;
+#4135 = DIRECTION ( 'NONE', ( 0.5000000000000008900, -0.8660254037844381500, 0.0000000000000000000 ) ) ;
+#4136 = VECTOR ( 'NONE', #328, 1000.000000000000000 ) ;
+#4137 = EDGE_CURVE ( 'NONE', #2279, #4115, #2365, .T. ) ;
+#4138 = EDGE_CURVE ( 'NONE', #4276, #4069, #1318, .T. ) ;
+#4139 = VECTOR ( 'NONE', #3348, 1000.000000000000000 ) ;
+#4140 = LINE ( 'NONE', #4095, #1056 ) ;
+#4141 = EDGE_CURVE ( 'NONE', #1322, #2583, #4391, .T. ) ;
+#4142 = ORIENTED_EDGE ( 'NONE', *, *, #4288, .T. ) ;
+#4143 = VECTOR ( 'NONE', #3923, 1000.000000000000100 ) ;
+#4144 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -20.57000000000000000, 2.000000000000000000 ) ) ;
+#4145 = CARTESIAN_POINT ( 'NONE', ( -24.64999999999999900, -12.24398312239625300, 2.000000000000000000 ) ) ;
+#4146 = EDGE_LOOP ( 'NONE', ( #4552, #3387, #4342, #3403 ) ) ;
+#4147 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4148 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4149 = LINE ( 'NONE', #4992, #943 ) ;
+#4150 = ORIENTED_EDGE ( 'NONE', *, *, #1696, .F. ) ;
+#4151 = AXIS2_PLACEMENT_3D ( 'NONE', #586, #3402, #996 ) ;
+#4152 = ORIENTED_EDGE ( 'NONE', *, *, #4138, .F. ) ;
+#4153 = VECTOR ( 'NONE', #3421, 1000.000000000000000 ) ;
+#4154 = ORIENTED_EDGE ( 'NONE', *, *, #4941, .F. ) ;
+#4155 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, 16.50999999999999800, 0.0000000000000000000 ) ) ;
+#4156 = VECTOR ( 'NONE', #5009, 1000.000000000000000 ) ;
+#4157 = ORIENTED_EDGE ( 'NONE', *, *, #2848, .F. ) ;
+#4158 = ORIENTED_EDGE ( 'NONE', *, *, #3662, .F. ) ;
+#4159 = DIRECTION ( 'NONE', ( 0.7071067811865474600, 0.7071067811865474600, -0.0000000000000000000 ) ) ;
+#4160 = ORIENTED_EDGE ( 'NONE', *, *, #1041, .F. ) ;
+#4161 = ORIENTED_EDGE ( 'NONE', *, *, #5199, .F. ) ;
+#4162 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, -4.500000000000000000, 4.299999999999999800 ) ) ;
+#4163 = VECTOR ( 'NONE', #90, 1000.000000000000000 ) ;
+#4164 = ORIENTED_EDGE ( 'NONE', *, *, #1001, .F. ) ;
+#4165 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199900, -19.21000000000000400, -0.0000000000000000000 ) ) ;
+#4166 = VERTEX_POINT ( 'NONE', #5098 ) ;
+#4167 = CARTESIAN_POINT ( 'NONE', ( -29.27499999999999900, 3.401456484631774800E-015, -0.6999999999999999600 ) ) ;
+#4168 = DIRECTION ( 'NONE', ( -0.5000000000000004400, 0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#4169 = ORIENTED_EDGE ( 'NONE', *, *, #3684, .T. ) ;
+#4170 = LINE ( 'NONE', #1435, #1089 ) ;
+#4171 = ORIENTED_EDGE ( 'NONE', *, *, #2729, .F. ) ;
+#4172 = CARTESIAN_POINT ( 'NONE', ( 19.99000000000000200, -16.51000000000000200, 2.500000000000002200 ) ) ;
+#4173 = ORIENTED_EDGE ( 'NONE', *, *, #461, .T. ) ;
+#4174 = EDGE_CURVE ( 'NONE', #4830, #3250, #2503, .T. ) ;
+#4175 = VERTEX_POINT ( 'NONE', #685 ) ;
+#4176 = VECTOR ( 'NONE', #937, 1000.000000000000000 ) ;
+#4177 = EDGE_LOOP ( 'NONE', ( #4832, #80, #202, #2310 ) ) ;
+#4178 = EDGE_CURVE ( 'NONE', #4722, #179, #109, .T. ) ;
+#4179 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, -10.27254486838324100, -0.6999999999999999600 ) ) ;
+#4180 = DIRECTION ( 'NONE', ( 0.4999999999999997200, 0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#4181 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 6.500000000000000000, 6.000000000000000000 ) ) ;
+#4182 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, 20.69999999999999900, 0.0000000000000000000 ) ) ;
+#4183 = AXIS2_PLACEMENT_3D ( 'NONE', #2998, #1417, #4228 ) ;
+#4184 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4185 = EDGE_CURVE ( 'NONE', #303, #234, #1181, .T. ) ;
+#4186 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4187 = VECTOR ( 'NONE', #3950, 1000.000000000000000 ) ;
+#4188 = DIRECTION ( 'NONE', ( 0.4999999999999995600, -0.8660254037844389300, 0.0000000000000000000 ) ) ;
+#4189 = VECTOR ( 'NONE', #3356, 1000.000000000000000 ) ;
+#4190 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4191 = LINE ( 'NONE', #276, #2984 ) ;
+#4192 = PLANE ( 'NONE', #2294 ) ;
+#4193 = LINE ( 'NONE', #209, #3118 ) ;
+#4194 = PLANE ( 'NONE', #4424 ) ;
+#4195 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#4196 = EDGE_CURVE ( 'NONE', #1582, #3479, #3185, .T. ) ;
+#4197 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4198 = FACE_OUTER_BOUND ( 'NONE', #1944, .T. ) ;
+#4199 = EDGE_CURVE ( 'NONE', #1778, #1536, #2131, .T. ) ;
+#4200 = ORIENTED_EDGE ( 'NONE', *, *, #530, .F. ) ;
+#4201 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#4202 = VECTOR ( 'NONE', #4560, 1000.000000000000000 ) ;
+#4203 = LINE ( 'NONE', #4000, #3129 ) ;
+#4204 = ORIENTED_EDGE ( 'NONE', *, *, #2070, .T. ) ;
+#4205 = AXIS2_PLACEMENT_3D ( 'NONE', #3255, #2479, #56 ) ;
+#4206 = VERTEX_POINT ( 'NONE', #4324 ) ;
+#4207 = VECTOR ( 'NONE', #2401, 1000.000000000000000 ) ;
+#4208 = ORIENTED_EDGE ( 'NONE', *, *, #2226, .F. ) ;
+#4209 = PLANE ( 'NONE', #1093 ) ;
+#4210 = EDGE_CURVE ( 'NONE', #2007, #4029, #2251, .T. ) ;
+#4211 = LINE ( 'NONE', #4408, #1499 ) ;
+#4212 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672599900, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#4213 = VERTEX_POINT ( 'NONE', #1109 ) ;
+#4214 = CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT ( #3549, #2083, ( #3407 ) ) ;
+#4215 = ORIENTED_EDGE ( 'NONE', *, *, #2213, .F. ) ;
+#4216 = AXIS2_PLACEMENT_3D ( 'NONE', #2250, #5061, #2647 ) ;
+#4217 = EDGE_LOOP ( 'NONE', ( #902, #194, #3712, #509 ) ) ;
+#4218 = VECTOR ( 'NONE', #4466, 1000.000000000000000 ) ;
+#4219 = EDGE_LOOP ( 'NONE', ( #710, #498, #4529, #1579 ) ) ;
+#4220 = EDGE_LOOP ( 'NONE', ( #3397, #2330, #2797, #3915 ) ) ;
+#4221 = CARTESIAN_POINT ( 'NONE', ( -0.8607864376269039900, -21.19999999999999900, 4.299999999999999800 ) ) ;
+#4222 = EDGE_LOOP ( 'NONE', ( #1265, #1132, #4352, #2029 ) ) ;
+#4223 = ORIENTED_EDGE ( 'NONE', *, *, #697, .T. ) ;
+#4224 = LINE ( 'NONE', #1153, #2094 ) ;
+#4225 = ORIENTED_EDGE ( 'NONE', *, *, #49, .F. ) ;
+#4226 = VERTEX_POINT ( 'NONE', #2317 ) ;
+#4227 = EDGE_CURVE ( 'NONE', #3733, #836, #939, .T. ) ;
+#4228 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4229 = DIRECTION ( 'NONE', ( 0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#4230 = ORIENTED_EDGE ( 'NONE', *, *, #3794, .F. ) ;
+#4231 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -0.7500000000000000000, 6.000000000000000000 ) ) ;
+#4232 = LINE ( 'NONE', #3274, #2102 ) ;
+#4233 = ORIENTED_EDGE ( 'NONE', *, *, #2609, .T. ) ;
+#4234 = EDGE_CURVE ( 'NONE', #692, #1874, #3060, .T. ) ;
+#4235 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4236 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -20.19999999999999900, 4.299999999999999800 ) ) ;
+#4237 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, -22.19999999999999900, 4.299999999999999800 ) ) ;
+#4238 = ORIENTED_EDGE ( 'NONE', *, *, #1172, .F. ) ;
+#4239 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -5.000000000000000000, -0.6999999999999999600 ) ) ;
+#4240 = EDGE_CURVE ( 'NONE', #506, #886, #4009, .T. ) ;
+#4241 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#4242 = ORIENTED_EDGE ( 'NONE', *, *, #5010, .F. ) ;
+#4243 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4244 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 5.000000000000000000, 6.000000000000000000 ) ) ;
+#4245 = PLANE ( 'NONE', #393 ) ;
+#4246 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 0.0000000000000000000, -0.6999999999999999600 ) ) ;
+#4247 = PLANE ( 'NONE', #3491 ) ;
+#4248 = AXIS2_PLACEMENT_3D ( 'NONE', #376, #3187, #783 ) ;
+#4249 = LINE ( 'NONE', #2468, #2232 ) ;
+#4250 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4251 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -2.878679656440359800, 6.000000000000000000 ) ) ;
+#4252 = VECTOR ( 'NONE', #3206, 1000.000000000000000 ) ;
+#4253 = VECTOR ( 'NONE', #2005, 1000.000000000000000 ) ;
+#4254 = LINE ( 'NONE', #4332, #3174 ) ;
+#4255 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4256 = VECTOR ( 'NONE', #136, 1000.000000000000000 ) ;
+#4257 = ORIENTED_EDGE ( 'NONE', *, *, #3524, .T. ) ;
+#4258 = LINE ( 'NONE', #4983, #1989 ) ;
+#4259 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4260 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 19.57000000000000000, 4.299999999999999800 ) ) ;
+#4261 = PLANE ( 'NONE', #4726 ) ;
+#4262 = ORIENTED_EDGE ( 'NONE', *, *, #616, .F. ) ;
+#4263 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4264 = VECTOR ( 'NONE', #4707, 1000.000000000000100 ) ;
+#4265 = PLANE ( 'NONE', #4311 ) ;
+#4266 = EDGE_CURVE ( 'NONE', #2471, #2648, #5198, .T. ) ;
+#4267 = EDGE_LOOP ( 'NONE', ( #1655, #4728, #2133, #1096 ) ) ;
+#4268 = CARTESIAN_POINT ( 'NONE', ( -13.56499942082922100, -21.48999942082920000, 4.299999999999999800 ) ) ;
+#4269 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4270 = EDGE_CURVE ( 'NONE', #2686, #2903, #2932, .T. ) ;
+#4271 = EDGE_LOOP ( 'NONE', ( #3860, #4396, #1564, #5104 ) ) ;
+#4272 = LINE ( 'NONE', #2596, #3065 ) ;
+#4273 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4274 = EDGE_LOOP ( 'NONE', ( #319, #5083, #1763, #2013 ) ) ;
+#4275 = CARTESIAN_POINT ( 'NONE', ( -20.77499999999999900, 22.19999999999999900, -0.6999999999999999600 ) ) ;
+#4276 = VERTEX_POINT ( 'NONE', #2339 ) ;
+#4277 = AXIS2_PLACEMENT_3D ( 'NONE', #4672, #2259, #5068 ) ;
+#4278 = DIRECTION ( 'NONE', ( -1.000000000000000000, -6.123233995736770200E-016, 0.0000000000000000000 ) ) ;
+#4279 = LINE ( 'NONE', #2246, #3197 ) ;
+#4280 = ORIENTED_EDGE ( 'NONE', *, *, #1067, .F. ) ;
+#4281 = ORIENTED_EDGE ( 'NONE', *, *, #3102, .F. ) ;
+#4282 = CIRCLE ( 'NONE', #4151, 1.000000000000000900 ) ;
+#4283 = VERTEX_POINT ( 'NONE', #3545 ) ;
+#4284 = AXIS2_PLACEMENT_3D ( 'NONE', #1464, #5073, #664 ) ;
+#4285 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#4286 = ORIENTED_EDGE ( 'NONE', *, *, #1270, .T. ) ;
+#4287 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4288 = EDGE_CURVE ( 'NONE', #2750, #444, #801, .T. ) ;
+#4289 = DIRECTION ( 'NONE', ( 0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4290 = VERTEX_POINT ( 'NONE', #5148 ) ;
+#4291 = VERTEX_POINT ( 'NONE', #1550 ) ;
+#4292 = LINE ( 'NONE', #1697, #3209 ) ;
+#4293 = ORIENTED_EDGE ( 'NONE', *, *, #865, .T. ) ;
+#4294 = ORIENTED_EDGE ( 'NONE', *, *, #2609, .F. ) ;
+#4295 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -10.27254486838324100, 4.299999999999999800 ) ) ;
+#4296 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 1.782507743686435200, 6.000000000000000000 ) ) ;
+#4297 = AXIS2_PLACEMENT_3D ( 'NONE', #3293, #903, #1305 ) ;
+#4298 = ORIENTED_EDGE ( 'NONE', *, *, #3743, .T. ) ;
+#4299 = VECTOR ( 'NONE', #4278, 1000.000000000000000 ) ;
+#4300 = ORIENTED_EDGE ( 'NONE', *, *, #4240, .T. ) ;
+#4301 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4302 = CARTESIAN_POINT ( 'NONE', ( 17.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#4303 = EDGE_LOOP ( 'NONE', ( #2826, #700, #701, #143 ) ) ;
+#4304 = EDGE_LOOP ( 'NONE', ( #4596, #2466, #3527, #1602 ) ) ;
+#4305 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800500, 13.81000000000000200, 0.0000000000000000000 ) ) ;
+#4306 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4307 = CARTESIAN_POINT ( 'NONE', ( -14.47342712474618900, -20.27710678118655000, 4.299999999999999800 ) ) ;
+#4308 = EDGE_CURVE ( 'NONE', #3462, #2961, #3758, .T. ) ;
+#4309 = ADVANCED_FACE ( 'NONE', ( #1621 ), #4697, .F. ) ;
+#4310 = DIRECTION ( 'NONE', ( -0.4999999999999997800, 0.8660254037844387100, 0.0000000000000000000 ) ) ;
+#4311 = AXIS2_PLACEMENT_3D ( 'NONE', #650, #1861, #4673 ) ;
+#4312 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4313 = AXIS2_PLACEMENT_3D ( 'NONE', #3101, #2306, #5122 ) ;
+#4314 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, 0.0000000000000000000 ) ) ;
+#4315 = CYLINDRICAL_SURFACE ( 'NONE', #1419, 4.250000000000000000 ) ;
+#4316 = AXIS2_PLACEMENT_3D ( 'NONE', #2478, #55, #2867 ) ;
+#4317 = VECTOR ( 'NONE', #1755, 1000.000000000000000 ) ;
+#4318 = ORIENTED_EDGE ( 'NONE', *, *, #3384, .F. ) ;
+#4319 = FACE_OUTER_BOUND ( 'NONE', #1836, .T. ) ;
+#4320 = ORIENTED_EDGE ( 'NONE', *, *, #4341, .F. ) ;
+#4321 = VECTOR ( 'NONE', #1795, 1000.000000000000000 ) ;
+#4322 = DIRECTION ( 'NONE', ( -1.000000000000000000, 6.123233995736769700E-017, 0.0000000000000000000 ) ) ;
+#4323 = ORIENTED_EDGE ( 'NONE', *, *, #1916, .T. ) ;
+#4324 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#4325 = FACE_OUTER_BOUND ( 'NONE', #3446, .T. ) ;
+#4326 = EDGE_CURVE ( 'NONE', #1954, #1294, #676, .T. ) ;
+#4327 = ORIENTED_EDGE ( 'NONE', *, *, #3595, .T. ) ;
+#4328 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4329 = EDGE_CURVE ( 'NONE', #2992, #2583, #1753, .T. ) ;
+#4330 = FACE_OUTER_BOUND ( 'NONE', #3404, .T. ) ;
+#4331 = ADVANCED_FACE ( 'NONE', ( #415 ), #2741, .T. ) ;
+#4332 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4333 = VECTOR ( 'NONE', #4791, 1000.000000000000000 ) ;
+#4334 = ORIENTED_EDGE ( 'NONE', *, *, #2367, .F. ) ;
+#4335 = CARTESIAN_POINT ( 'NONE', ( 25.65000000000000200, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#4336 = EDGE_CURVE ( 'NONE', #2334, #4048, #546, .T. ) ;
+#4337 = ORIENTED_EDGE ( 'NONE', *, *, #722, .T. ) ;
+#4338 = LINE ( 'NONE', #2776, #1127 ) ;
+#4339 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4340 = ORIENTED_EDGE ( 'NONE', *, *, #1782, .F. ) ;
+#4341 = EDGE_CURVE ( 'NONE', #4117, #2097, #1496, .T. ) ;
+#4342 = ORIENTED_EDGE ( 'NONE', *, *, #4064, .F. ) ;
+#4343 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4344 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4345 = ORIENTED_EDGE ( 'NONE', *, *, #2280, .T. ) ;
+#4346 = ORIENTED_EDGE ( 'NONE', *, *, #4453, .T. ) ;
+#4347 = VERTEX_POINT ( 'NONE', #2380 ) ;
+#4348 = LINE ( 'NONE', #4061, #4218 ) ;
+#4349 = EDGE_LOOP ( 'NONE', ( #4490, #4019, #686, #957 ) ) ;
+#4350 = VERTEX_POINT ( 'NONE', #4775 ) ;
+#4351 = CARTESIAN_POINT ( 'NONE', ( 26.45000000000000300, -6.250000000000000000, 4.299999999999999800 ) ) ;
+#4352 = ORIENTED_EDGE ( 'NONE', *, *, #3511, .F. ) ;
+#4353 = AXIS2_PLACEMENT_3D ( 'NONE', #397, #4030, #1620 ) ;
+#4354 = EDGE_LOOP ( 'NONE', ( #4586, #4924, #813, #910, #3133, #3124 ) ) ;
+#4355 = LINE ( 'NONE', #2351, #1961 ) ;
+#4356 = ORIENTED_EDGE ( 'NONE', *, *, #1207, .T. ) ;
+#4357 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4358 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.69999999999999900, 0.0000000000000000000 ) ) ;
+#4359 = VERTEX_POINT ( 'NONE', #4798 ) ;
+#4360 = VECTOR ( 'NONE', #2687, 1000.000000000000000 ) ;
+#4361 = ORIENTED_EDGE ( 'NONE', *, *, #5045, .T. ) ;
+#4362 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4363 = ADVANCED_FACE ( 'NONE', ( #1374 ), #5192, .T. ) ;
+#4364 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -19.19999999999999900, 0.0000000000000000000 ) ) ;
+#4365 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4366 = CARTESIAN_POINT ( 'NONE', ( 25.77499999999999900, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4367 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, -4.500000000000000000, -0.6999999999999992900 ) ) ;
+#4368 = EDGE_CURVE ( 'NONE', #4033, #2315, #287, .T. ) ;
+#4369 = ORIENTED_EDGE ( 'NONE', *, *, #988, .T. ) ;
+#4370 = CARTESIAN_POINT ( 'NONE', ( -18.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#4371 = EDGE_CURVE ( 'NONE', #1583, #3067, #4573, .T. ) ;
+#4372 = VECTOR ( 'NONE', #21, 1000.000000000000000 ) ;
+#4373 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4374 = COORDINATED_UNIVERSAL_TIME_OFFSET ( 8, 0, .BEHIND. ) ;
+#4375 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, -20.69999999999999900, 4.299999999999999800 ) ) ;
+#4376 = AXIS2_PLACEMENT_3D ( 'NONE', #2275, #3481, #1086 ) ;
+#4377 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4378 = FACE_BOUND ( 'NONE', #970, .T. ) ;
+#4379 = AXIS2_PLACEMENT_3D ( 'NONE', #827, #847, #3656 ) ;
+#4380 = LINE ( 'NONE', #387, #2238 ) ;
+#4381 = VECTOR ( 'NONE', #3543, 1000.000000000000000 ) ;
+#4382 = EDGE_CURVE ( 'NONE', #2000, #2559, #4452, .T. ) ;
+#4383 = ADVANCED_FACE ( 'NONE', ( #4325 ), #1601, .F. ) ;
+#4384 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#4385 = LINE ( 'NONE', #1344, #2124 ) ;
+#4386 = DIRECTION ( 'NONE', ( -0.7071067811865480200, -0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4387 = VECTOR ( 'NONE', #1373, 1000.000000000000000 ) ;
+#4388 = AXIS2_PLACEMENT_3D ( 'NONE', #4052, #1234, #9 ) ;
+#4389 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -19.19999999999999900, 2.000000000000000000 ) ) ;
+#4390 = ORIENTED_EDGE ( 'NONE', *, *, #3928, .F. ) ;
+#4391 = CIRCLE ( 'NONE', #609, 4.250000000000000000 ) ;
+#4392 = CARTESIAN_POINT ( 'NONE', ( -23.19000000000000100, 16.51000000000000200, 73.56569754550879700 ) ) ;
+#4393 = ORIENTED_EDGE ( 'NONE', *, *, #1723, .T. ) ;
+#4394 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4395 = EDGE_CURVE ( 'NONE', #3912, #5113, #165, .T. ) ;
+#4396 = ORIENTED_EDGE ( 'NONE', *, *, #1678, .T. ) ;
+#4397 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 13.72875387184322100, 6.000000000000000000 ) ) ;
+#4398 = CYLINDRICAL_SURFACE ( 'NONE', #2415, 1.000000000000000900 ) ;
+#4399 = EDGE_CURVE ( 'NONE', #1221, #315, #3247, .T. ) ;
+#4400 = EDGE_LOOP ( 'NONE', ( #132, #1206, #1709, #1769, #1770, #3947 ) ) ;
+#4401 = EDGE_CURVE ( 'NONE', #375, #506, #2182, .T. ) ;
+#4402 = ORIENTED_EDGE ( 'NONE', *, *, #4196, .T. ) ;
+#4403 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4404 = LINE ( 'NONE', #653, #2381 ) ;
+#4405 = VERTEX_POINT ( 'NONE', #3633 ) ;
+#4406 = LINE ( 'NONE', #972, #2266 ) ;
+#4407 = ORIENTED_EDGE ( 'NONE', *, *, #785, .F. ) ;
+#4408 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4409 = ORIENTED_EDGE ( 'NONE', *, *, #3870, .F. ) ;
+#4410 = CARTESIAN_POINT ( 'NONE', ( -19.79753212705254800, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4411 = AXIS2_PLACEMENT_3D ( 'NONE', #973, #3600, #4996 ) ;
+#4412 = ADVANCED_FACE ( 'NONE', ( #4198 ), #987, .F. ) ;
+#4413 = ORIENTED_EDGE ( 'NONE', *, *, #4308, .T. ) ;
+#4414 = ORIENTED_EDGE ( 'NONE', *, *, #4872, .F. ) ;
+#4415 = CARTESIAN_POINT ( 'NONE', ( 26.45000000000000300, 6.250000000000000000, 6.000000000000000000 ) ) ;
+#4416 = VECTOR ( 'NONE', #3266, 1000.000000000000000 ) ;
+#4417 = ORIENTED_EDGE ( 'NONE', *, *, #3625, .T. ) ;
+#4418 = ORIENTED_EDGE ( 'NONE', *, *, #4969, .T. ) ;
+#4419 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, 21.19999999999999900, -0.6999999999999999600 ) ) ;
+#4420 = CARTESIAN_POINT ( 'NONE', ( -24.70769145362398300, 16.50999999999999800, 0.0000000000000000000 ) ) ;
+#4421 = ORIENTED_EDGE ( 'NONE', *, *, #841, .T. ) ;
+#4422 = VECTOR ( 'NONE', #989, 1000.000000000000000 ) ;
+#4423 = ORIENTED_EDGE ( 'NONE', *, *, #3926, .T. ) ;
+#4424 = AXIS2_PLACEMENT_3D ( 'NONE', #3378, #1797, #4604 ) ;
+#4425 = ORIENTED_EDGE ( 'NONE', *, *, #4929, .T. ) ;
+#4426 = VERTEX_POINT ( 'NONE', #2439 ) ;
+#4427 = PLANE ( 'NONE', #2963 ) ;
+#4428 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.628553204668610100E-017, 4.299999999999999800 ) ) ;
+#4429 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4430 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 2.000000000000000000 ) ) ;
+#4431 = ADVANCED_FACE ( 'NONE', ( #2060 ), #2814, .F. ) ;
+#4432 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#4433 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, 5.277604462221250500E-017, 2.000000000000000000 ) ) ;
+#4434 = ADVANCED_FACE ( 'NONE', ( #3122 ), #849, .T. ) ;
+#4435 = DIRECTION ( 'NONE', ( 1.000000000000000000, 1.040834085586084300E-014, 0.0000000000000000000 ) ) ;
+#4436 = EDGE_CURVE ( 'NONE', #2198, #3212, #4076, .T. ) ;
+#4437 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 12.24398312239625500, 2.000000000000000000 ) ) ;
+#4438 = EDGE_CURVE ( 'NONE', #952, #2686, #1116, .T. ) ;
+#4439 = VECTOR ( 'NONE', #2127, 1000.000000000000000 ) ;
+#4440 = LINE ( 'NONE', #4705, #3363 ) ;
+#4441 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4442 = VECTOR ( 'NONE', #1644, 1000.000000000000000 ) ;
+#4443 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -20.19999999999999900, -0.6999999999999999600 ) ) ;
+#4444 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.330566893520345700E-016, 0.0000000000000000000 ) ) ;
+#4445 = LINE ( 'NONE', #2541, #4189 ) ;
+#4446 = FACE_OUTER_BOUND ( 'NONE', #3273, .T. ) ;
+#4447 = ORIENTED_EDGE ( 'NONE', *, *, #2242, .F. ) ;
+#4448 = DIRECTION ( 'NONE', ( 1.000000000000000000, 3.469446951953614200E-015, 0.0000000000000000000 ) ) ;
+#4449 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, -16.51000000000000900, 2.500000000000000000 ) ) ;
+#4450 = FACE_BOUND ( 'NONE', #4650, .T. ) ;
+#4451 = VERTEX_POINT ( 'NONE', #42 ) ;
+#4452 = LINE ( 'NONE', #5210, #1250 ) ;
+#4453 = EDGE_CURVE ( 'NONE', #836, #2559, #5003, .T. ) ;
+#4454 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, -1.240472735837101900, 4.299999999999999800 ) ) ;
+#4455 = ORIENTED_EDGE ( 'NONE', *, *, #3309, .T. ) ;
+#4456 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4457 = ADVANCED_FACE ( 'NONE', ( #867 ), #735, .T. ) ;
+#4458 = AXIS2_PLACEMENT_3D ( 'NONE', #1036, #5054, #646 ) ;
+#4459 = VECTOR ( 'NONE', #3607, 1000.000000000000000 ) ;
+#4460 = CARTESIAN_POINT ( 'NONE', ( -18.13655776574935200, 18.98714285714286000, 4.299999999999999800 ) ) ;
+#4461 = VECTOR ( 'NONE', #1577, 1000.000000000000000 ) ;
+#4462 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#4463 = ORIENTED_EDGE ( 'NONE', *, *, #3595, .F. ) ;
+#4464 = ADVANCED_FACE ( 'NONE', ( #2990 ), #2075, .T. ) ;
+#4465 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4466 = DIRECTION ( 'NONE', ( 0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4467 = VERTEX_POINT ( 'NONE', #1674 ) ;
+#4468 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4469 = CIRCLE ( 'NONE', #3324, 4.250000000000000000 ) ;
+#4470 = ORIENTED_EDGE ( 'NONE', *, *, #1976, .T. ) ;
+#4471 = VECTOR ( 'NONE', #5175, 1000.000000000000000 ) ;
+#4472 = ORIENTED_EDGE ( 'NONE', *, *, #3485, .F. ) ;
+#4473 = EDGE_CURVE ( 'NONE', #4525, #1125, #2435, .T. ) ;
+#4474 = ORIENTED_EDGE ( 'NONE', *, *, #988, .F. ) ;
+#4475 = PLANE ( 'NONE', #740 ) ;
+#4476 = ORIENTED_EDGE ( 'NONE', *, *, #1376, .F. ) ;
+#4477 = EDGE_CURVE ( 'NONE', #4213, #5006, #4889, .T. ) ;
+#4478 = VERTEX_POINT ( 'NONE', #4106 ) ;
+#4479 = LINE ( 'NONE', #823, #1395 ) ;
+#4480 = ORIENTED_EDGE ( 'NONE', *, *, #27, .T. ) ;
+#4481 = EDGE_LOOP ( 'NONE', ( #2210, #924, #3655, #443 ) ) ;
+#4482 = VERTEX_POINT ( 'NONE', #1694 ) ;
+#4483 = EDGE_LOOP ( 'NONE', ( #4327, #4361, #4230, #3763 ) ) ;
+#4484 = VECTOR ( 'NONE', #3513, 1000.000000000000000 ) ;
+#4485 = EDGE_LOOP ( 'NONE', ( #4734, #4609, #5205, #4956 ) ) ;
+#4486 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4487 = AXIS2_PLACEMENT_3D ( 'NONE', #524, #3326, #932 ) ;
+#4488 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, 4.299999999999999800 ) ) ;
+#4489 = AXIS2_PLACEMENT_3D ( 'NONE', #1852, #5060, #3440 ) ;
+#4490 = ORIENTED_EDGE ( 'NONE', *, *, #720, .T. ) ;
+#4491 = LINE ( 'NONE', #3898, #4360 ) ;
+#4492 = ORIENTED_EDGE ( 'NONE', *, *, #588, .T. ) ;
+#4493 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -0.7500000000000000000, 6.000000000000000000 ) ) ;
+#4494 = ADVANCED_FACE ( 'NONE', ( #1687 ), #3693, .F. ) ;
+#4495 = EDGE_CURVE ( 'NONE', #2943, #51, #607, .T. ) ;
+#4496 = CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT ( #2491, #4890, ( #3407 ) ) ;
+#4497 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4498 = ADVANCED_FACE ( 'NONE', ( #1556 ), #2898, .F. ) ;
+#4499 = AXIS2_PLACEMENT_3D ( 'NONE', #612, #2231, #5050 ) ;
+#4500 = APPROVAL_ROLE ( '' ) ;
+#4501 = CARTESIAN_POINT ( 'NONE', ( -12.36078643762690100, 20.69999999999999900, 4.299999999999999800 ) ) ;
+#4502 = EDGE_CURVE ( 'NONE', #4115, #3512, #3816, .T. ) ;
+#4503 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4504 = CARTESIAN_POINT ( 'NONE', ( -23.19000000000000100, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#4505 = EDGE_CURVE ( 'NONE', #778, #567, #485, .T. ) ;
+#4506 = VECTOR ( 'NONE', #8, 1000.000000000000100 ) ;
+#4507 = LOCAL_TIME ( 12, 52, 21.00000000000000000, #337 ) ;
+#4508 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4509 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4510 = VECTOR ( 'NONE', #644, 1000.000000000000000 ) ;
+#4511 = EDGE_CURVE ( 'NONE', #567, #3463, #2614, .T. ) ;
+#4512 = VECTOR ( 'NONE', #4386, 1000.000000000000000 ) ;
+#4513 = ADVANCED_FACE ( 'NONE', ( #348 ), #4928, .F. ) ;
+#4514 = LINE ( 'NONE', #4694, #3433 ) ;
+#4515 = AXIS2_PLACEMENT_3D ( 'NONE', #1638, #2819, #10 ) ;
+#4516 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4517 = LINE ( 'NONE', #3742, #3437 ) ;
+#4518 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 19.57000000000000000, 2.000000000000000000 ) ) ;
+#4519 = CARTESIAN_POINT ( 'NONE', ( -18.13655776574935200, -18.98714285714286000, 4.299999999999999800 ) ) ;
+#4520 = VERTEX_POINT ( 'NONE', #1735 ) ;
+#4521 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4522 = CIRCLE ( 'NONE', #5017, 1.600000000000000800 ) ;
+#4523 = AXIS2_PLACEMENT_3D ( 'NONE', #2854, #456, #3258 ) ;
+#4524 = ORIENTED_EDGE ( 'NONE', *, *, #1172, .T. ) ;
+#4525 = VERTEX_POINT ( 'NONE', #2925 ) ;
+#4526 = CYLINDRICAL_SURFACE ( 'NONE', #1885, 4.250000000000000000 ) ;
+#4527 = EDGE_LOOP ( 'NONE', ( #2742, #1938, #1346, #2537 ) ) ;
+#4528 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4529 = ORIENTED_EDGE ( 'NONE', *, *, #2308, .T. ) ;
+#4530 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.57000000000000000, 2.000000000000000000 ) ) ;
+#4531 = LINE ( 'NONE', #2264, #1330 ) ;
+#4532 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#4533 = ORIENTED_EDGE ( 'NONE', *, *, #776, .T. ) ;
+#4534 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 0.7500000000000000000, 6.000000000000000000 ) ) ;
+#4535 = FACE_OUTER_BOUND ( 'NONE', #3664, .T. ) ;
+#4536 = ORIENTED_EDGE ( 'NONE', *, *, #665, .T. ) ;
+#4537 = VERTEX_POINT ( 'NONE', #116 ) ;
+#4538 = CARTESIAN_POINT ( 'NONE', ( -3.189213562373095300, 20.69999999999999900, 4.299999999999999800 ) ) ;
+#4539 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4540 = ORIENTED_EDGE ( 'NONE', *, *, #466, .F. ) ;
+#4541 = VERTEX_POINT ( 'NONE', #529 ) ;
+#4542 = VECTOR ( 'NONE', #851, 1000.000000000000000 ) ;
+#4543 = EDGE_CURVE ( 'NONE', #999, #4855, #1433, .T. ) ;
+#4544 = ORIENTED_EDGE ( 'NONE', *, *, #3113, .F. ) ;
+#4545 = ORIENTED_EDGE ( 'NONE', *, *, #3515, .F. ) ;
+#4546 = EDGE_CURVE ( 'NONE', #2145, #952, #4517, .T. ) ;
+#4547 = VERTEX_POINT ( 'NONE', #1324 ) ;
+#4548 = VECTOR ( 'NONE', #4188, 1000.000000000000100 ) ;
+#4549 = ORIENTED_EDGE ( 'NONE', *, *, #2706, .F. ) ;
+#4550 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#4551 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 19.57000000000000000, 2.000000000000000000 ) ) ;
+#4552 = ORIENTED_EDGE ( 'NONE', *, *, #764, .F. ) ;
+#4553 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4554 = AXIS2_PLACEMENT_3D ( 'NONE', #3376, #2573, #4981 ) ;
+#4555 = AXIS2_PLACEMENT_3D ( 'NONE', #5088, #269, #3086 ) ;
+#4556 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4557 = EDGE_CURVE ( 'NONE', #610, #1273, #3562, .T. ) ;
+#4558 = ORIENTED_EDGE ( 'NONE', *, *, #417, .T. ) ;
+#4559 = AXIS2_PLACEMENT_3D ( 'NONE', #4145, #1740, #4553 ) ;
+#4560 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4561 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4562 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, -1.240472735837101900, 4.299999999999999800 ) ) ;
+#4563 = LINE ( 'NONE', #505, #4685 ) ;
+#4564 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#4565 = LINE ( 'NONE', #4731, #1107 ) ;
+#4566 = SHAPE_DEFINITION_REPRESENTATION ( #3537, #2783 ) ;
+#4567 = EDGE_CURVE ( 'NONE', #3383, #4482, #2362, .T. ) ;
+#4568 = VECTOR ( 'NONE', #2110, 1000.000000000000000 ) ;
+#4569 = VECTOR ( 'NONE', #3042, 1000.000000000000000 ) ;
+#4570 = VECTOR ( 'NONE', #948, 1000.000000000000000 ) ;
+#4571 = ORIENTED_EDGE ( 'NONE', *, *, #3102, .T. ) ;
+#4572 = AXIS2_PLACEMENT_3D ( 'NONE', #3484, #1092, #3895 ) ;
+#4573 = LINE ( 'NONE', #1195, #3373 ) ;
+#4574 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4575 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4576 = AXIS2_PLACEMENT_3D ( 'NONE', #3571, #1170, #3983 ) ;
+#4577 = PLANE ( 'NONE', #1765 ) ;
+#4578 = ORIENTED_EDGE ( 'NONE', *, *, #1916, .F. ) ;
+#4579 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4580 = FACE_OUTER_BOUND ( 'NONE', #1139, .T. ) ;
+#4581 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4582 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4583 = CIRCLE ( 'NONE', #5116, 0.9999999999999991100 ) ;
+#4584 = ORIENTED_EDGE ( 'NONE', *, *, #1135, .F. ) ;
+#4585 = AXIS2_PLACEMENT_3D ( 'NONE', #4056, #3668, #1267 ) ;
+#4586 = ORIENTED_EDGE ( 'NONE', *, *, #4073, .F. ) ;
+#4587 = FACE_BOUND ( 'NONE', #572, .T. ) ;
+#4588 = VERTEX_POINT ( 'NONE', #2546 ) ;
+#4589 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4590 = VECTOR ( 'NONE', #1966, 1000.000000000000000 ) ;
+#4591 = ORIENTED_EDGE ( 'NONE', *, *, #1104, .T. ) ;
+#4592 = ORIENTED_EDGE ( 'NONE', *, *, #172, .F. ) ;
+#4593 = EDGE_CURVE ( 'NONE', #4919, #4467, #1314, .T. ) ;
+#4594 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602700, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#4595 = VERTEX_POINT ( 'NONE', #4965 ) ;
+#4596 = ORIENTED_EDGE ( 'NONE', *, *, #2976, .F. ) ;
+#4597 = CARTESIAN_POINT ( 'NONE', ( -13.56499942082922100, -21.48999942082920000, -0.6999999999999999600 ) ) ;
+#4598 = VECTOR ( 'NONE', #4429, 1000.000000000000000 ) ;
+#4599 = EDGE_LOOP ( 'NONE', ( #1648, #570 ) ) ;
+#4600 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#4601 = VERTEX_POINT ( 'NONE', #538 ) ;
+#4602 = EDGE_CURVE ( 'NONE', #1948, #4405, #4757, .T. ) ;
+#4603 = CARTESIAN_POINT ( 'NONE', ( -28.39129536136890100, -3.262384295071445000, 6.000000000000000000 ) ) ;
+#4604 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4605 = ORIENTED_EDGE ( 'NONE', *, *, #2340, .T. ) ;
+#4606 = LINE ( 'NONE', #4709, #2586 ) ;
+#4607 = ORIENTED_EDGE ( 'NONE', *, *, #45, .T. ) ;
+#4608 = EDGE_CURVE ( 'NONE', #958, #3098, #3308, .T. ) ;
+#4609 = ORIENTED_EDGE ( 'NONE', *, *, #2369, .T. ) ;
+#4610 = LINE ( 'NONE', #3476, #3410 ) ;
+#4611 = VERTEX_POINT ( 'NONE', #1784 ) ;
+#4612 = EDGE_CURVE ( 'NONE', #3025, #1583, #107, .T. ) ;
+#4613 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, -19.19999999999999900, 0.0000000000000000000 ) ) ;
+#4614 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, -3.500000000000000000, 0.2999999999999999900 ) ) ;
+#4615 = CARTESIAN_POINT ( 'NONE', ( 24.64999999999999900, -12.24398312239625300, 4.299999999999999800 ) ) ;
+#4616 = CARTESIAN_POINT ( 'NONE', ( -14.60342712474619900, -20.40710678118654900, -0.6999999999999999600 ) ) ;
+#4617 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672599900, -19.69999999999999900, 0.0000000000000000000 ) ) ;
+#4618 = AXIS2_PLACEMENT_3D ( 'NONE', #4858, #2443, #26 ) ;
+#4619 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -1.543412575162920000, 6.000000000000000000 ) ) ;
+#4620 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4621 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4622 = LINE ( 'NONE', #3613, #474 ) ;
+#4623 = APPROVAL_DATE_TIME ( #475, #3676 ) ;
+#4624 = LINE ( 'NONE', #1872, #1307 ) ;
+#4625 = CARTESIAN_POINT ( 'NONE', ( 18.13655776574935200, 18.98714285714286000, 4.299999999999999800 ) ) ;
+#4626 = VECTOR ( 'NONE', #330, 1000.000000000000000 ) ;
+#4627 = VECTOR ( 'NONE', #1484, 1000.000000000000000 ) ;
+#4628 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -10.27254486838324100, 6.000000000000000000 ) ) ;
+#4629 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4630 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, 2.878679656440359800, 6.000000000000000000 ) ) ;
+#4631 = EDGE_CURVE ( 'NONE', #2010, #3205, #2244, .T. ) ;
+#4632 = VECTOR ( 'NONE', #4521, 1000.000000000000000 ) ;
+#4633 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, 0.0000000000000000000 ) ) ;
+#4634 = EDGE_CURVE ( 'NONE', #689, #1692, #3179, .T. ) ;
+#4635 = ORIENTED_EDGE ( 'NONE', *, *, #3548, .F. ) ;
+#4636 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318801500, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#4637 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681199500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#4638 = LINE ( 'NONE', #1042, #1439 ) ;
+#4639 = VECTOR ( 'NONE', #3277, 1000.000000000000000 ) ;
+#4640 = VERTEX_POINT ( 'NONE', #1392 ) ;
+#4641 = AXIS2_PLACEMENT_3D ( 'NONE', #2812, #405, #3226 ) ;
+#4642 = PLANE ( 'NONE', #3022 ) ;
+#4643 = ORIENTED_EDGE ( 'NONE', *, *, #942, .F. ) ;
+#4644 = DIRECTION ( 'NONE', ( 0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4645 = VECTOR ( 'NONE', #4486, 1000.000000000000000 ) ;
+#4646 = VERTEX_POINT ( 'NONE', #1799 ) ;
+#4647 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4648 = EDGE_CURVE ( 'NONE', #1712, #1184, #4258, .T. ) ;
+#4649 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 12.24398312239625500, 4.299999999999999800 ) ) ;
+#4650 = EDGE_LOOP ( 'NONE', ( #2004, #2269, #1642, #3897 ) ) ;
+#4651 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, 20.19999999999999900, 4.299999999999999800 ) ) ;
+#4652 = FACE_OUTER_BOUND ( 'NONE', #2030, .T. ) ;
+#4653 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4654 = AXIS2_PLACEMENT_3D ( 'NONE', #4449, #2843, #430 ) ;
+#4655 = ORIENTED_EDGE ( 'NONE', *, *, #4495, .T. ) ;
+#4656 = VECTOR ( 'NONE', #4168, 1000.000000000000200 ) ;
+#4657 = ORIENTED_EDGE ( 'NONE', *, *, #89, .T. ) ;
+#4658 = ORIENTED_EDGE ( 'NONE', *, *, #2216, .F. ) ;
+#4659 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4660 = EDGE_LOOP ( 'NONE', ( #1137, #5095, #2012, #1074 ) ) ;
+#4661 = ORIENTED_EDGE ( 'NONE', *, *, #1717, .T. ) ;
+#4662 = FACE_OUTER_BOUND ( 'NONE', #2964, .T. ) ;
+#4663 = VERTEX_POINT ( 'NONE', #875 ) ;
+#4664 = VERTEX_POINT ( 'NONE', #2869 ) ;
+#4665 = EDGE_CURVE ( 'NONE', #1902, #525, #3054, .T. ) ;
+#4666 = DIRECTION ( 'NONE', ( 1.000000000000000000, -6.938893903907222100E-015, 0.0000000000000000000 ) ) ;
+#4667 = ORIENTED_EDGE ( 'NONE', *, *, #4137, .F. ) ;
+#4668 = CARTESIAN_POINT ( 'NONE', ( -14.47342712474618900, 20.27710678118655000, 4.299999999999999800 ) ) ;
+#4669 = CIRCLE ( 'NONE', #2289, 1.000000000000000000 ) ;
+#4670 = VERTEX_POINT ( 'NONE', #3683 ) ;
+#4671 = EDGE_CURVE ( 'NONE', #579, #5156, #5072, .T. ) ;
+#4672 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 20.19999999999999900, -0.6999999999999999600 ) ) ;
+#4673 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4674 = EDGE_CURVE ( 'NONE', #303, #3619, #4140, .T. ) ;
+#4675 = ORIENTED_EDGE ( 'NONE', *, *, #2116, .T. ) ;
+#4676 = DIRECTION ( 'NONE', ( 0.7071067811865470200, 0.7071067811865480200, 0.0000000000000000000 ) ) ;
+#4677 = CARTESIAN_POINT ( 'NONE', ( -20.02500000000000200, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#4678 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, -20.69999999999999900, 0.0000000000000000000 ) ) ;
+#4679 = AXIS2_PLACEMENT_3D ( 'NONE', #5204, #799, #3609 ) ;
+#4680 = EDGE_LOOP ( 'NONE', ( #3982, #622, #501, #3332, #243, #3853 ) ) ;
+#4681 = DIRECTION ( 'NONE', ( 0.5773502691896257300, -0.5773502691896257300, -0.5773502691896257300 ) ) ;
+#4682 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4683 = PLANE ( 'NONE', #4555 ) ;
+#4684 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4685 = VECTOR ( 'NONE', #2913, 1000.000000000000000 ) ;
+#4686 = LINE ( 'NONE', #3478, #3489 ) ;
+#4687 = FACE_OUTER_BOUND ( 'NONE', #5225, .T. ) ;
+#4688 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4689 = CIRCLE ( 'NONE', #5002, 1.000000000000000000 ) ;
+#4690 = LINE ( 'NONE', #1826, #3751 ) ;
+#4691 = LINE ( 'NONE', #4165, #4570 ) ;
+#4692 = CARTESIAN_POINT ( 'NONE', ( -25.77499999999999900, -10.27254486838324100, -0.6999999999999999600 ) ) ;
+#4693 = ADVANCED_FACE ( 'NONE', ( #934 ), #3883, .F. ) ;
+#4694 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, -16.32049935181331100 ) ) ;
+#4695 = ORIENTED_EDGE ( 'NONE', *, *, #3037, .F. ) ;
+#4696 = PLANE ( 'NONE', #3766 ) ;
+#4697 = CYLINDRICAL_SURFACE ( 'NONE', #1965, 0.9999999999999995600 ) ;
+#4698 = ORIENTED_EDGE ( 'NONE', *, *, #4438, .F. ) ;
+#4699 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4700 = EDGE_CURVE ( 'NONE', #3098, #749, #795, .T. ) ;
+#4701 = ORIENTED_EDGE ( 'NONE', *, *, #1413, .T. ) ;
+#4702 = CARTESIAN_POINT ( 'NONE', ( 24.70769145362398700, -16.51000000000000500, 2.500000000000000000 ) ) ;
+#4703 = EDGE_CURVE ( 'NONE', #3319, #4291, #1870, .T. ) ;
+#4704 = CIRCLE ( 'NONE', #4572, 1.000000000000000900 ) ;
+#4705 = CARTESIAN_POINT ( 'NONE', ( 0.0000000000000000000, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#4706 = VERTEX_POINT ( 'NONE', #1705 ) ;
+#4707 = DIRECTION ( 'NONE', ( -0.5773502691896257300, -0.5773502691896257300, 0.5773502691896257300 ) ) ;
+#4708 = ORIENTED_EDGE ( 'NONE', *, *, #1154, .T. ) ;
+#4709 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, 19.19999999999999900, 0.0000000000000000000 ) ) ;
+#4710 = LINE ( 'NONE', #1544, #1634 ) ;
+#4711 = ORIENTED_EDGE ( 'NONE', *, *, #2085, .F. ) ;
+#4712 = CIRCLE ( 'NONE', #808, 1.000000000000000000 ) ;
+#4713 = ORIENTED_EDGE ( 'NONE', *, *, #732, .F. ) ;
+#4714 = AXIS2_PLACEMENT_3D ( 'NONE', #207, #3023, #617 ) ;
+#4715 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, 16.51000000000000200, 4.299999999999999800 ) ) ;
+#4716 = ORIENTED_EDGE ( 'NONE', *, *, #3952, .F. ) ;
+#4717 = PLANE ( 'NONE', #4313 ) ;
+#4718 = EDGE_CURVE ( 'NONE', #2154, #618, #2929, .T. ) ;
+#4719 = ORIENTED_EDGE ( 'NONE', *, *, #4473, .T. ) ;
+#4720 = EDGE_LOOP ( 'NONE', ( #3969, #983, #190, #3272 ) ) ;
+#4721 = LINE ( 'NONE', #4392, #1518 ) ;
+#4722 = VERTEX_POINT ( 'NONE', #1731 ) ;
+#4723 = CARTESIAN_POINT ( 'NONE', ( -1.076572875253784400, 20.27710678118655000, 4.299999999999999800 ) ) ;
+#4724 = ORIENTED_EDGE ( 'NONE', *, *, #345, .T. ) ;
+#4725 = CARTESIAN_POINT ( 'NONE', ( -20.77499999999999900, 22.19999999999999900, 6.000000000000000000 ) ) ;
+#4726 = AXIS2_PLACEMENT_3D ( 'NONE', #1050, #2633, #1859 ) ;
+#4727 = ADVANCED_FACE ( 'NONE', ( #4820 ), #4934, .F. ) ;
+#4728 = ORIENTED_EDGE ( 'NONE', *, *, #3011, .T. ) ;
+#4729 = LINE ( 'NONE', #4692, #1525 ) ;
+#4730 = EDGE_LOOP ( 'NONE', ( #1030, #3976 ) ) ;
+#4731 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, 0.0000000000000000000, 6.000000000000000000 ) ) ;
+#4732 = VERTEX_POINT ( 'NONE', #2505 ) ;
+#4733 = LINE ( 'NONE', #255, #1659 ) ;
+#4734 = ORIENTED_EDGE ( 'NONE', *, *, #2769, .T. ) ;
+#4735 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4736 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, 19.49289321881345000, 4.299999999999999800 ) ) ;
+#4737 = EDGE_CURVE ( 'NONE', #554, #1574, #3753, .T. ) ;
+#4738 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#4739 = EDGE_CURVE ( 'NONE', #167, #4861, #1749, .T. ) ;
+#4740 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#4741 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#4742 = AXIS2_PLACEMENT_3D ( 'NONE', #2066, #874, #3681 ) ;
+#4743 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -4.378679656440360700, 6.000000000000000000 ) ) ;
+#4744 = AXIS2_PLACEMENT_3D ( 'NONE', #1060, #4273, #4659 ) ;
+#4745 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4746 = EDGE_CURVE ( 'NONE', #4861, #4706, #671, .T. ) ;
+#4747 = ADVANCED_FACE ( 'NONE', ( #3624, #2669 ), #534, .T. ) ;
+#4748 = LINE ( 'NONE', #2378, #3680 ) ;
+#4749 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, -16.51000000000000900, 2.500000000000000000 ) ) ;
+#4750 = APPROVAL_PERSON_ORGANIZATION ( #593, #3676, #4500 ) ;
+#4751 = SECURITY_CLASSIFICATION ( '', '', #2489 ) ;
+#4752 = PLANE ( 'NONE', #4850 ) ;
+#4753 = LINE ( 'NONE', #1405, #3685 ) ;
+#4754 = LINE ( 'NONE', #2730, #4071 ) ;
+#4755 = LINE ( 'NONE', #2256, #3690 ) ;
+#4756 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4757 = LINE ( 'NONE', #2560, #1179 ) ;
+#4758 = EDGE_CURVE ( 'NONE', #2393, #1959, #1491, .T. ) ;
+#4759 = ORIENTED_EDGE ( 'NONE', *, *, #5110, .F. ) ;
+#4760 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672372400, -20.57000000000000000, 4.299999999999999800 ) ) ;
+#4761 = EDGE_CURVE ( 'NONE', #4588, #3704, #4691, .T. ) ;
+#4762 = CYLINDRICAL_SURFACE ( 'NONE', #2599, 1.600000000000000800 ) ;
+#4763 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#4764 = VECTOR ( 'NONE', #449, 1000.000000000000000 ) ;
+#4765 = ORIENTED_EDGE ( 'NONE', *, *, #1715, .F. ) ;
+#4766 = EDGE_CURVE ( 'NONE', #696, #127, #2556, .T. ) ;
+#4767 = ORIENTED_EDGE ( 'NONE', *, *, #123, .F. ) ;
+#4768 = CARTESIAN_POINT ( 'NONE', ( 23.14884572681198800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#4769 = FACE_OUTER_BOUND ( 'NONE', #4527, .T. ) ;
+#4770 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4771 = EDGE_LOOP ( 'NONE', ( #3080, #2813, #944, #1406 ) ) ;
+#4772 = ADVANCED_FACE ( 'NONE', ( #411 ), #3357, .F. ) ;
+#4773 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672371800, 20.57000000000000000, 1.000000000000000000 ) ) ;
+#4774 = ORIENTED_EDGE ( 'NONE', *, *, #3732, .T. ) ;
+#4775 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -20.57000000000000000, 4.299999999999998000 ) ) ;
+#4776 = LINE ( 'NONE', #2711, #366 ) ;
+#4777 = ORIENTED_EDGE ( 'NONE', *, *, #4802, .F. ) ;
+#4778 = ORIENTED_EDGE ( 'NONE', *, *, #1378, .T. ) ;
+#4779 = CIRCLE ( 'NONE', #1459, 4.250000000000000000 ) ;
+#4780 = EDGE_LOOP ( 'NONE', ( #1197, #1722, #641, #3850 ) ) ;
+#4781 = VERTEX_POINT ( 'NONE', #3767 ) ;
+#4782 = CARTESIAN_POINT ( 'NONE', ( -15.18053390593273700, 20.57000000000000000, 4.299999999999998000 ) ) ;
+#4783 = EDGE_LOOP ( 'NONE', ( #3155, #2629, #4657, #4658, #2227, #129, #947, #4150, #4225, #4160, #1757, #3093 ) ) ;
+#4784 = AXIS2_PLACEMENT_3D ( 'NONE', #3239, #846, #3652 ) ;
+#4785 = ORIENTED_EDGE ( 'NONE', *, *, #5024, .T. ) ;
+#4786 = CIRCLE ( 'NONE', #3264, 1.600000000000000800 ) ;
+#4787 = ORIENTED_EDGE ( 'NONE', *, *, #3955, .T. ) ;
+#4788 = ORIENTED_EDGE ( 'NONE', *, *, #4059, .T. ) ;
+#4789 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, 6.000000000000000000, -0.6999999999999992900 ) ) ;
+#4790 = ORIENTED_EDGE ( 'NONE', *, *, #3622, .F. ) ;
+#4791 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
+#4792 = LINE ( 'NONE', #945, #1588 ) ;
+#4793 = ORIENTED_EDGE ( 'NONE', *, *, #2347, .F. ) ;
+#4794 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#4795 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4796 = ORIENTED_EDGE ( 'NONE', *, *, #4941, .T. ) ;
+#4797 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672599900, -20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4798 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -19.21000000000000100, 0.0000000000000000000 ) ) ;
+#4799 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4800 = ADVANCED_FACE ( 'NONE', ( #4450, #280 ), #4577, .T. ) ;
+#4801 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, -20.19999999999999900, -0.6999999999999999600 ) ) ;
+#4802 = EDGE_CURVE ( 'NONE', #3780, #5223, #2302, .T. ) ;
+#4803 = DIRECTION ( 'NONE', ( -0.4999999999999979500, 0.8660254037844397100, 0.0000000000000000000 ) ) ;
+#4804 = VERTEX_POINT ( 'NONE', #571 ) ;
+#4805 = EDGE_CURVE ( 'NONE', #1502, #4547, #3370, .T. ) ;
+#4806 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4807 = DIRECTION ( 'NONE', ( 0.7071067811865474600, -0.7071067811865474600, 0.0000000000000000000 ) ) ;
+#4808 = CARTESIAN_POINT ( 'NONE', ( -1.860786437626899900, -19.49289321881345000, 4.299999999999999800 ) ) ;
+#4809 = LINE ( 'NONE', #3730, #1738 ) ;
+#4810 = DIRECTION ( 'NONE', ( 1.000000000000000000, -1.734723475976808700E-014, 0.0000000000000000000 ) ) ;
+#4811 = AXIS2_PLACEMENT_3D ( 'NONE', #3805, #3401, #2987 ) ;
+#4812 = LINE ( 'NONE', #4098, #2921 ) ;
+#4813 = AXIS2_PLACEMENT_3D ( 'NONE', #3528, #1119, #2449 ) ;
+#4814 = LINE ( 'NONE', #2625, #3747 ) ;
+#4815 = ORIENTED_EDGE ( 'NONE', *, *, #4567, .F. ) ;
+#4816 = VECTOR ( 'NONE', #1593, 1000.000000000000000 ) ;
+#4817 = EDGE_CURVE ( 'NONE', #5018, #4080, #3494, .T. ) ;
+#4818 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4819 = LINE ( 'NONE', #5007, #4948 ) ;
+#4820 = FACE_OUTER_BOUND ( 'NONE', #4780, .T. ) ;
+#4821 = AXIS2_PLACEMENT_3D ( 'NONE', #3723, #2531, #111 ) ;
+#4822 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, -0.0000000000000000000 ) ) ;
+#4823 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801500, -13.80999999999999500, 2.500000000000000000 ) ) ;
+#4824 = EDGE_LOOP ( 'NONE', ( #2136, #1887, #1080, #4242 ) ) ;
+#4825 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4826 = VECTOR ( 'NONE', #4365, 1000.000000000000000 ) ;
+#4827 = ADVANCED_FACE ( 'NONE', ( #1112 ), #4192, .F. ) ;
+#4828 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, 20.57000000000000000, 6.000000000000000000 ) ) ;
+#4829 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, -18.71852980579590000, -0.6999999999999999600 ) ) ;
+#4830 = VERTEX_POINT ( 'NONE', #2592 ) ;
+#4831 = LINE ( 'NONE', #5086, #2566 ) ;
+#4832 = ORIENTED_EDGE ( 'NONE', *, *, #4758, .F. ) ;
+#4833 = EDGE_CURVE ( 'NONE', #5087, #2526, #32, .T. ) ;
+#4834 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4835 = ORIENTED_EDGE ( 'NONE', *, *, #2877, .F. ) ;
+#4836 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4837 = VECTOR ( 'NONE', #5115, 1000.000000000000000 ) ;
+#4838 = VERTEX_POINT ( 'NONE', #3399 ) ;
+#4839 = LINE ( 'NONE', #4723, #1768 ) ;
+#4840 = VERTEX_POINT ( 'NONE', #991 ) ;
+#4841 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4842 = ORIENTED_EDGE ( 'NONE', *, *, #5129, .T. ) ;
+#4843 = CARTESIAN_POINT ( 'NONE', ( -23.14884572681199500, -13.81000000000000200, 0.0000000000000000000 ) ) ;
+#4844 = AXIS2_PLACEMENT_3D ( 'NONE', #661, #1474, #4289 ) ;
+#4845 = ORIENTED_EDGE ( 'NONE', *, *, #5033, .F. ) ;
+#4846 = ORIENTED_EDGE ( 'NONE', *, *, #590, .F. ) ;
+#4847 = CARTESIAN_POINT ( 'NONE', ( -7.774999999999999500, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#4848 = FACE_BOUND ( 'NONE', #3522, .T. ) ;
+#4849 = EDGE_LOOP ( 'NONE', ( #3787, #1652, #3188, #3221 ) ) ;
+#4850 = AXIS2_PLACEMENT_3D ( 'NONE', #3131, #2346, #5154 ) ;
+#4851 = ORIENTED_EDGE ( 'NONE', *, *, #1001, .T. ) ;
+#4852 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.628553204668610100E-017, 4.299999999999999800 ) ) ;
+#4853 = ADVANCED_FACE ( 'NONE', ( #1245 ), #200, .F. ) ;
+#4854 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4855 = VERTEX_POINT ( 'NONE', #1808 ) ;
+#4856 = FACE_OUTER_BOUND ( 'NONE', #2409, .T. ) ;
+#4857 = EDGE_LOOP ( 'NONE', ( #2287, #5160, #895, #893 ) ) ;
+#4858 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#4859 = ADVANCED_FACE ( 'NONE', ( #2056 ), #985, .T. ) ;
+#4860 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.330566893520345700E-016, 0.0000000000000000000 ) ) ;
+#4861 = VERTEX_POINT ( 'NONE', #1823 ) ;
+#4862 = CIRCLE ( 'NONE', #5179, 1.000000000000000900 ) ;
+#4863 = ORIENTED_EDGE ( 'NONE', *, *, #3829, .T. ) ;
+#4864 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4865 = PLANE ( 'NONE', #4585 ) ;
+#4866 = PERSON ( 'UNSPECIFIED', 'UNSPECIFIED', 'UNSPECIFIED', ('UNSPECIFIED'), ('UNSPECIFIED'), ('UNSPECIFIED') ) ;
+#4867 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4868 = EDGE_CURVE ( 'NONE', #258, #2281, #3243, .T. ) ;
+#4869 = CARTESIAN_POINT ( 'NONE', ( -28.39129536136890100, 3.262384295071445000, 6.000000000000000000 ) ) ;
+#4870 = DIRECTION ( 'NONE', ( -0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4871 = CARTESIAN_POINT ( 'NONE', ( -10.52157493058720000, -20.57000000000000000, 2.000000000000000000 ) ) ;
+#4872 = EDGE_CURVE ( 'NONE', #965, #4804, #4193, .T. ) ;
+#4873 = DIRECTION ( 'NONE', ( -1.000000000000000000, -1.224646799147352000E-016, 0.0000000000000000000 ) ) ;
+#4874 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#4875 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4876 = EDGE_CURVE ( 'NONE', #3369, #2584, #4072, .T. ) ;
+#4877 = ADVANCED_FACE ( 'NONE', ( #5001 ), #2224, .F. ) ;
+#4878 = EDGE_CURVE ( 'NONE', #3144, #3032, #3939, .T. ) ;
+#4879 = VECTOR ( 'NONE', #3170, 1000.000000000000100 ) ;
+#4880 = PLANE ( 'NONE', #4205 ) ;
+#4881 = COORDINATED_UNIVERSAL_TIME_OFFSET ( 8, 0, .BEHIND. ) ;
+#4882 = ADVANCED_FACE ( 'NONE', ( #1809 ), #4887, .F. ) ;
+#4883 = VECTOR ( 'NONE', #1793, 1000.000000000000100 ) ;
+#4884 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4885 = LINE ( 'NONE', #4335, #3812 ) ;
+#4886 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4887 = CYLINDRICAL_SURFACE ( 'NONE', #4458, 1.000000000000000000 ) ;
+#4888 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#4889 = LINE ( 'NONE', #884, #2731 ) ;
+#4890 = PERSON_AND_ORGANIZATION_ROLE ( 'design_supplier' ) ;
+#4891 = ORIENTED_EDGE ( 'NONE', *, *, #918, .T. ) ;
+#4892 = FACE_OUTER_BOUND ( 'NONE', #3715, .T. ) ;
+#4893 = CARTESIAN_POINT ( 'NONE', ( -18.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#4894 = CARTESIAN_POINT ( 'NONE', ( 18.13655776574935200, 18.98714285714286000, 2.000000000000000000 ) ) ;
+#4895 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4896 = CIRCLE ( 'NONE', #3120, 4.250000000000000000 ) ;
+#4897 = ORIENTED_EDGE ( 'NONE', *, *, #3530, .F. ) ;
+#4898 = DIRECTION ( 'NONE', ( -0.4999999999999997200, -0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#4899 = ORIENTED_EDGE ( 'NONE', *, *, #142, .F. ) ;
+#4900 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, 19.20999999999999700, 2.500000000000000000 ) ) ;
+#4901 = ADVANCED_FACE ( 'NONE', ( #2179 ), #4261, .T. ) ;
+#4902 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 73.56569754550879700 ) ) ;
+#4903 = FACE_OUTER_BOUND ( 'NONE', #440, .T. ) ;
+#4904 = ORIENTED_EDGE ( 'NONE', *, *, #3847, .F. ) ;
+#4905 = EDGE_CURVE ( 'NONE', #1582, #167, #2866, .T. ) ;
+#4906 = EDGE_LOOP ( 'NONE', ( #1090, #4142, #3651, #3654 ) ) ;
+#4907 = VECTOR ( 'NONE', #1073, 1000.000000000000000 ) ;
+#4908 = EDGE_CURVE ( 'NONE', #4107, #1952, #3814, .T. ) ;
+#4909 = VERTEX_POINT ( 'NONE', #3864 ) ;
+#4910 = VECTOR ( 'NONE', #1308, 1000.000000000000200 ) ;
+#4911 = ORIENTED_EDGE ( 'NONE', *, *, #3011, .F. ) ;
+#4912 = ORIENTED_EDGE ( 'NONE', *, *, #785, .T. ) ;
+#4913 = CARTESIAN_POINT ( 'NONE', ( -0.2394660940672604900, 19.69999999999999900, 0.0000000000000000000 ) ) ;
+#4914 = ORIENTED_EDGE ( 'NONE', *, *, #3955, .F. ) ;
+#4915 = ORIENTED_EDGE ( 'NONE', *, *, #2977, .F. ) ;
+#4916 = LINE ( 'NONE', #3584, #2758 ) ;
+#4917 = ORIENTED_EDGE ( 'NONE', *, *, #3244, .F. ) ;
+#4918 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#4919 = VERTEX_POINT ( 'NONE', #1065 ) ;
+#4920 = AXIS2_PLACEMENT_3D ( 'NONE', #1214, #4020, #1613 ) ;
+#4921 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4922 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, 19.20999999999999700, 0.0000000000000000000 ) ) ;
+#4923 = AXIS2_PLACEMENT_3D ( 'NONE', #3674, #4079, #1673 ) ;
+#4924 = ORIENTED_EDGE ( 'NONE', *, *, #720, .F. ) ;
+#4925 = LINE ( 'NONE', #931, #2765 ) ;
+#4926 = VERTEX_POINT ( 'NONE', #3878 ) ;
+#4927 = PLANE ( 'NONE', #1048 ) ;
+#4928 = PLANE ( 'NONE', #2396 ) ;
+#4929 = EDGE_CURVE ( 'NONE', #4350, #189, #4755, .T. ) ;
+#4930 = EDGE_LOOP ( 'NONE', ( #72, #549, #583, #457 ) ) ;
+#4931 = AXIS2_PLACEMENT_3D ( 'NONE', #2915, #512, #3314 ) ;
+#4932 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, -0.6999999999999992900 ) ) ;
+#4933 = EDGE_LOOP ( 'NONE', ( #4476, #4233, #4463, #904, #5039, #3972, #76, #510, #3018, #2591, #3538, #3151, #4356, #4323, #3839, #3843, #4912, #3036, #1337, #120 ) ) ;
+#4934 = PLANE ( 'NONE', #4821 ) ;
+#4935 = ORIENTED_EDGE ( 'NONE', *, *, #4700, .F. ) ;
+#4936 = EDGE_CURVE ( 'NONE', #4781, #1643, #2725, .T. ) ;
+#4937 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4938 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4939 = EDGE_CURVE ( 'NONE', #1403, #1532, #2611, .T. ) ;
+#4940 = VECTOR ( 'NONE', #911, 1000.000000000000000 ) ;
+#4941 = EDGE_CURVE ( 'NONE', #1391, #836, #601, .T. ) ;
+#4942 = LINE ( 'NONE', #3281, #535 ) ;
+#4943 = CIRCLE ( 'NONE', #3673, 0.9999999999999991100 ) ;
+#4944 = CYLINDRICAL_SURFACE ( 'NONE', #1398, 4.250000000000000000 ) ;
+#4945 = AXIS2_PLACEMENT_3D ( 'NONE', #1, #4825, #4018 ) ;
+#4946 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4947 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4948 = VECTOR ( 'NONE', #2603, 1000.000000000000000 ) ;
+#4949 = VECTOR ( 'NONE', #923, 1000.000000000000000 ) ;
+#4950 = CARTESIAN_POINT ( 'NONE', ( 4.500000000000000000, 4.500000000000000000, 4.299999999999999800 ) ) ;
+#4951 = VERTEX_POINT ( 'NONE', #5081 ) ;
+#4952 = VECTOR ( 'NONE', #1534, 1000.000000000000000 ) ;
+#4953 = ORIENTED_EDGE ( 'NONE', *, *, #2712, .T. ) ;
+#4954 = LINE ( 'NONE', #624, #682 ) ;
+#4955 = VERTEX_POINT ( 'NONE', #3891 ) ;
+#4956 = ORIENTED_EDGE ( 'NONE', *, *, #4341, .T. ) ;
+#4957 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#4958 = VECTOR ( 'NONE', #1880, 1000.000000000000000 ) ;
+#4959 = ORIENTED_EDGE ( 'NONE', *, *, #4985, .T. ) ;
+#4960 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4961 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#4962 = CYLINDRICAL_SURFACE ( 'NONE', #1622, 0.9999999999999991100 ) ;
+#4963 = ORIENTED_EDGE ( 'NONE', *, *, #1975, .T. ) ;
+#4964 = ORIENTED_EDGE ( 'NONE', *, *, #385, .F. ) ;
+#4965 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -20.57000000000000000, 6.000000000000000000 ) ) ;
+#4966 = ORIENTED_EDGE ( 'NONE', *, *, #1475, .T. ) ;
+#4967 = ORIENTED_EDGE ( 'NONE', *, *, #3824, .F. ) ;
+#4968 = VECTOR ( 'NONE', #4373, 1000.000000000000000 ) ;
+#4969 = EDGE_CURVE ( 'NONE', #2829, #1954, #3555, .T. ) ;
+#4970 = VERTEX_POINT ( 'NONE', #4305 ) ;
+#4971 = VERTEX_POINT ( 'NONE', #5105 ) ;
+#4972 = EDGE_CURVE ( 'NONE', #1519, #5096, #4514, .T. ) ;
+#4973 = ORIENTED_EDGE ( 'NONE', *, *, #1351, .F. ) ;
+#4974 = VECTOR ( 'NONE', #3070, 1000.000000000000000 ) ;
+#4975 = EDGE_LOOP ( 'NONE', ( #4796, #2078, #3007, #3150 ) ) ;
+#4976 = VERTEX_POINT ( 'NONE', #693 ) ;
+#4977 = CARTESIAN_POINT ( 'NONE', ( 27.27500000000000200, -22.19999999999999900, 6.000000000000000000 ) ) ;
+#4978 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, 22.19999999999999900, 4.299999999999999800 ) ) ;
+#4979 = LINE ( 'NONE', #1426, #837 ) ;
+#4980 = EDGE_LOOP ( 'NONE', ( #1401, #3534, #1372, #5027, #2895, #3015, #3351, #514 ) ) ;
+#4981 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4982 = CARTESIAN_POINT ( 'NONE', ( -8.852744741179039400, -19.19999999999999900, -0.6999999999999999600 ) ) ;
+#4983 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, 12.24398312239625500, 6.000000000000000000 ) ) ;
+#4984 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000100E-005 ), #2047, 'distance_accuracy_value', 'NONE');
+#4985 = EDGE_CURVE ( 'NONE', #2750, #1145, #2497, .T. ) ;
+#4986 = CARTESIAN_POINT ( 'NONE', ( -27.97669089436529800, 0.7500000000000000000, 6.000000000000000000 ) ) ;
+#4987 = AXIS2_PLACEMENT_3D ( 'NONE', #4715, #2303, #5120 ) ;
+#4988 = CARTESIAN_POINT ( 'NONE', ( -19.99000000000000200, -16.51000000000000200, 4.299999999999998000 ) ) ;
+#4989 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.57000000000000000, 4.299999999999999800 ) ) ;
+#4990 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -22.19999999999999900, -0.6999999999999999600 ) ) ;
+#4991 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801200, -19.21000000000000400, 2.500000000000000000 ) ) ;
+#4992 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 6.250000000000000000, 6.000000000000000000 ) ) ;
+#4993 = LINE ( 'NONE', #4375, #2979 ) ;
+#4994 = EDGE_CURVE ( 'NONE', #1455, #768, #1430, .T. ) ;
+#4995 = LOCAL_TIME ( 12, 52, 21.00000000000000000, #856 ) ;
+#4996 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#4997 = EDGE_CURVE ( 'NONE', #3462, #3776, #2986, .T. ) ;
+#4998 = VECTOR ( 'NONE', #3789, 1000.000000000000000 ) ;
+#4999 = ORIENTED_EDGE ( 'NONE', *, *, #3901, .T. ) ;
+#5000 = LINE ( 'NONE', #714, #861 ) ;
+#5001 = FACE_OUTER_BOUND ( 'NONE', #3220, .T. ) ;
+#5002 = AXIS2_PLACEMENT_3D ( 'NONE', #95, #2910, #500 ) ;
+#5003 = LINE ( 'NONE', #1662, #3944 ) ;
+#5004 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#5005 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#5006 = VERTEX_POINT ( 'NONE', #3110 ) ;
+#5007 = CARTESIAN_POINT ( 'NONE', ( -28.39129536136890100, -3.262384295071445000, 1.000000000000000000 ) ) ;
+#5008 = VECTOR ( 'NONE', #145, 1000.000000000000000 ) ;
+#5009 = DIRECTION ( 'NONE', ( -6.735557395310440100E-016, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#5010 = EDGE_CURVE ( 'NONE', #4838, #2203, #3303, .T. ) ;
+#5011 = DIRECTION ( 'NONE', ( 1.000000000000000000, -3.469446951953614200E-015, 0.0000000000000000000 ) ) ;
+#5012 = CC_DESIGN_PERSON_AND_ORGANIZATION_ASSIGNMENT ( #1425, #79, ( #4751 ) ) ;
+#5013 = FACE_BOUND ( 'NONE', #4217, .T. ) ;
+#5014 = ORIENTED_EDGE ( 'NONE', *, *, #4336, .F. ) ;
+#5015 = DIRECTION ( 'NONE', ( -6.735557395310440100E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#5016 = CARTESIAN_POINT ( 'NONE', ( -0.6536796564403559700, -22.19999999999999900, 4.299999999999999800 ) ) ;
+#5017 = AXIS2_PLACEMENT_3D ( 'NONE', #2658, #260, #3068 ) ;
+#5018 = VERTEX_POINT ( 'NONE', #3925 ) ;
+#5019 = VECTOR ( 'NONE', #2414, 1000.000000000000000 ) ;
+#5020 = CARTESIAN_POINT ( 'NONE', ( -14.89632034355964100, -20.57000000000000000, 4.299999999999998000 ) ) ;
+#5021 = ORIENTED_EDGE ( 'NONE', *, *, #5135, .F. ) ;
+#5022 = CARTESIAN_POINT ( 'NONE', ( -2.567893218813449800, 20.19999999999999900, 2.000000000000000000 ) ) ;
+#5023 = ORIENTED_EDGE ( 'NONE', *, *, #1154, .F. ) ;
+#5024 = EDGE_CURVE ( 'NONE', #4520, #3580, #1312, .T. ) ;
+#5025 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#5026 = FACE_OUTER_BOUND ( 'NONE', #3393, .T. ) ;
+#5027 = ORIENTED_EDGE ( 'NONE', *, *, #346, .F. ) ;
+#5028 = CARTESIAN_POINT ( 'NONE', ( 25.64999999999999900, 6.250000000000000000, 6.000000000000000000 ) ) ;
+#5029 = VERTEX_POINT ( 'NONE', #2324 ) ;
+#5030 = CYLINDRICAL_SURFACE ( 'NONE', #651, 1.600000000000000800 ) ;
+#5031 = VERTEX_POINT ( 'NONE', #4736 ) ;
+#5032 = EDGE_LOOP ( 'NONE', ( #2460, #3259, #1381, #1842 ) ) ;
+#5033 = EDGE_CURVE ( 'NONE', #1284, #3599, #4254, .T. ) ;
+#5034 = CARTESIAN_POINT ( 'NONE', ( -24.06714285714285500, -13.05655776574935000, 2.000000000000000000 ) ) ;
+#5035 = CARTESIAN_POINT ( 'NONE', ( 21.59000000000000000, -16.51000000000000200, 4.299999999999999800 ) ) ;
+#5036 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5037 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, 4.299999999999999800 ) ) ;
+#5038 = ORIENTED_EDGE ( 'NONE', *, *, #398, .T. ) ;
+#5039 = ORIENTED_EDGE ( 'NONE', *, *, #3865, .T. ) ;
+#5040 = LINE ( 'NONE', #4782, #1838 ) ;
+#5041 = ORIENTED_EDGE ( 'NONE', *, *, #4746, .F. ) ;
+#5042 = ORIENTED_EDGE ( 'NONE', *, *, #1006, .T. ) ;
+#5043 = DIRECTION ( 'NONE', ( -0.7071067811865480200, 0.7071067811865470200, 0.0000000000000000000 ) ) ;
+#5044 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, -0.0000000000000000000 ) ) ;
+#5045 = EDGE_CURVE ( 'NONE', #4175, #163, #2359, .T. ) ;
+#5046 = EDGE_LOOP ( 'NONE', ( #5218, #4369, #1456, #369 ) ) ;
+#5047 = FACE_OUTER_BOUND ( 'NONE', #755, .T. ) ;
+#5048 = ORIENTED_EDGE ( 'NONE', *, *, #3149, .T. ) ;
+#5049 = AXIS2_PLACEMENT_3D ( 'NONE', #2421, #3167, #769 ) ;
+#5050 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5051 = ORIENTED_EDGE ( 'NONE', *, *, #841, .F. ) ;
+#5052 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#5053 = CARTESIAN_POINT ( 'NONE', ( -25.64999999999999900, -10.27254486838324100, 6.000000000000000000 ) ) ;
+#5054 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5055 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318800800, 13.81000000000000200, 2.500000000000000000 ) ) ;
+#5056 = DIRECTION ( 'NONE', ( 0.5000000000000005600, -0.8660254037844382600, 0.0000000000000000000 ) ) ;
+#5057 = AXIS2_PLACEMENT_3D ( 'NONE', #2779, #3576, #3989 ) ;
+#5058 = CARTESIAN_POINT ( 'NONE', ( -17.32398312239625700, -19.57000000000000000, 4.299999999999999800 ) ) ;
+#5059 = CARTESIAN_POINT ( 'NONE', ( 20.03115427318801500, -13.80999999999999500, -0.0000000000000000000 ) ) ;
+#5060 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5061 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5062 = FACE_OUTER_BOUND ( 'NONE', #1216, .T. ) ;
+#5063 = CARTESIAN_POINT ( 'NONE', ( -15.31053390593275100, 19.69999999999999900, -0.6999999999999999600 ) ) ;
+#5064 = VECTOR ( 'NONE', #2071, 1000.000000000000000 ) ;
+#5065 = PLANE ( 'NONE', #3836 ) ;
+#5066 = VECTOR ( 'NONE', #4836, 1000.000000000000000 ) ;
+#5067 = LINE ( 'NONE', #5183, #4816 ) ;
+#5068 = DIRECTION ( 'NONE', ( 1.000000000000000000, 3.469446951953614200E-015, 0.0000000000000000000 ) ) ;
+#5069 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602300, -16.51000000000000500, 0.0000000000000000000 ) ) ;
+#5070 = EDGE_CURVE ( 'NONE', #4206, #777, #2121, .T. ) ;
+#5071 = LINE ( 'NONE', #3795, #2794 ) ;
+#5072 = LINE ( 'NONE', #4501, #4006 ) ;
+#5073 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5074 = PLANE ( 'NONE', #919 ) ;
+#5075 = EDGE_CURVE ( 'NONE', #4926, #4451, #104, .T. ) ;
+#5076 = ORIENTED_EDGE ( 'NONE', *, *, #4602, .F. ) ;
+#5077 = VECTOR ( 'NONE', #3536, 1000.000000000000000 ) ;
+#5078 = VERTEX_POINT ( 'NONE', #2728 ) ;
+#5079 = CARTESIAN_POINT ( 'NONE', ( -2.500000000000000000, 4.500000000000000000, -0.6999999999999992900 ) ) ;
+#5080 = AXIS2_PLACEMENT_3D ( 'NONE', #3679, #1296, #4092 ) ;
+#5081 = CARTESIAN_POINT ( 'NONE', ( 6.000000000000000000, -6.000000000000000000, 2.000000000000000000 ) ) ;
+#5082 = CIRCLE ( 'NONE', #2673, 0.9999999999999991100 ) ;
+#5083 = ORIENTED_EDGE ( 'NONE', *, *, #2580, .F. ) ;
+#5084 = CARTESIAN_POINT ( 'NONE', ( 23.19000000000000100, 16.51000000000000200, 4.299999999999998000 ) ) ;
+#5085 = EDGE_CURVE ( 'NONE', #3733, #2000, #1985, .T. ) ;
+#5086 = CARTESIAN_POINT ( 'NONE', ( -18.47230854637602000, 16.50999999999999400, 0.0000000000000000000 ) ) ;
+#5087 = VERTEX_POINT ( 'NONE', #3547 ) ;
+#5088 = CARTESIAN_POINT ( 'NONE', ( -20.33566017177979600, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#5089 = LINE ( 'NONE', #4296, #2006 ) ;
+#5090 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#5091 = AXIS2_PLACEMENT_3D ( 'NONE', #2542, #133, #2935 ) ;
+#5092 = ORIENTED_EDGE ( 'NONE', *, *, #2883, .F. ) ;
+#5093 = FACE_OUTER_BOUND ( 'NONE', #112, .T. ) ;
+#5094 = ADVANCED_FACE ( 'NONE', ( #3050 ), #3152, .T. ) ;
+#5095 = ORIENTED_EDGE ( 'NONE', *, *, #3037, .T. ) ;
+#5096 = VERTEX_POINT ( 'NONE', #741 ) ;
+#5097 = CARTESIAN_POINT ( 'NONE', ( 7.925134423841990700E-016, -6.000000000000000000, 4.299999999999999800 ) ) ;
+#5098 = CARTESIAN_POINT ( 'NONE', ( -12.98210678118654100, -19.19999999999999900, 2.000000000000000000 ) ) ;
+#5099 = VERTEX_POINT ( 'NONE', #2747 ) ;
+#5100 = ORIENTED_EDGE ( 'NONE', *, *, #2600, .F. ) ;
+#5101 = LINE ( 'NONE', #1160, #4968 ) ;
+#5102 = ORIENTED_EDGE ( 'NONE', *, *, #3800, .T. ) ;
+#5103 = ORIENTED_EDGE ( 'NONE', *, *, #1706, .T. ) ;
+#5104 = ORIENTED_EDGE ( 'NONE', *, *, #250, .F. ) ;
+#5105 = CARTESIAN_POINT ( 'NONE', ( -14.68921356237310100, -22.19999999999999900, 4.299999999999999800 ) ) ;
+#5106 = FACE_BOUND ( 'NONE', #2107, .T. ) ;
+#5107 = EDGE_LOOP ( 'NONE', ( #4257, #2153, #1083, #2150 ) ) ;
+#5108 = DIRECTION ( 'NONE', ( 0.7071067811865475700, -0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#5109 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5110 = EDGE_CURVE ( 'NONE', #886, #3276, #4001, .T. ) ;
+#5111 = EDGE_LOOP ( 'NONE', ( #758, #3375, #2343, #2217 ) ) ;
+#5112 = LINE ( 'NONE', #4619, #4049 ) ;
+#5113 = VERTEX_POINT ( 'NONE', #4367 ) ;
+#5114 = DIRECTION ( 'NONE', ( -0.4999999999999996700, -0.8660254037844388200, 0.0000000000000000000 ) ) ;
+#5115 = DIRECTION ( 'NONE', ( 0.0000000000000000000, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#5116 = AXIS2_PLACEMENT_3D ( 'NONE', #1894, #4699, #2292 ) ;
+#5117 = AXIS2_PLACEMENT_3D ( 'NONE', #3178, #779, #3594 ) ;
+#5118 = CARTESIAN_POINT ( 'NONE', ( -1.016249395941883900, 20.33743026049845100, 4.299999999999999800 ) ) ;
+#5119 = ADVANCED_FACE ( 'NONE', ( #1174 ), #4944, .T. ) ;
+#5120 = DIRECTION ( 'NONE', ( -0.1674394999967844500, 0.9858823529411746600, 0.0000000000000000000 ) ) ;
+#5121 = AXIS2_PLACEMENT_3D ( 'NONE', #2800, #2028, #4834 ) ;
+#5122 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5123 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5124 = CARTESIAN_POINT ( 'NONE', ( -27.27500000000000200, -6.500000000000000000, 6.000000000000000000 ) ) ;
+#5125 = LINE ( 'NONE', #1149, #4187 ) ;
+#5126 = ORGANIZATION ( 'UNSPECIFIED', 'UNSPECIFIED', '' ) ;
+#5127 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
+#5128 = CARTESIAN_POINT ( 'NONE', ( -21.59000000000000000, 16.51000000000000200, 2.500000000000002200 ) ) ;
+#5129 = EDGE_CURVE ( 'NONE', #1145, #692, #788, .T. ) ;
+#5130 = VECTOR ( 'NONE', #4575, 1000.000000000000000 ) ;
+#5131 = EDGE_LOOP ( 'NONE', ( #1124, #1940 ) ) ;
+#5132 = LINE ( 'NONE', #3497, #984 ) ;
+#5133 = VECTOR ( 'NONE', #3819, 1000.000000000000000 ) ;
+#5134 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, 4.299999999999999800 ) ) ;
+#5135 = EDGE_CURVE ( 'NONE', #2644, #1904, #2926, .T. ) ;
+#5136 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#5137 = CARTESIAN_POINT ( 'NONE', ( -28.77499999999999900, -1.543412575162920000, 1.000000000000000000 ) ) ;
+#5138 = VECTOR ( 'NONE', #2469, 1000.000000000000000 ) ;
+#5139 = EDGE_CURVE ( 'NONE', #1904, #5029, #1867, .T. ) ;
+#5140 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#5141 = ORIENTED_EDGE ( 'NONE', *, *, #3691, .T. ) ;
+#5142 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#5143 = EDGE_CURVE ( 'NONE', #2621, #179, #5067, .T. ) ;
+#5144 = LINE ( 'NONE', #1354, #5008 ) ;
+#5145 = ADVANCED_FACE ( 'NONE', ( #2663 ), #539, .T. ) ;
+#5146 = LINE ( 'NONE', #17, #2067 ) ;
+#5147 = ORIENTED_EDGE ( 'NONE', *, *, #3317, .T. ) ;
+#5148 = CARTESIAN_POINT ( 'NONE', ( -4.000000000000000000, -6.000000000000000000, 0.0000000000000000000 ) ) ;
+#5149 = ORIENTED_EDGE ( 'NONE', *, *, #1676, .F. ) ;
+#5150 = FACE_OUTER_BOUND ( 'NONE', #249, .T. ) ;
+#5151 = ORIENTED_EDGE ( 'NONE', *, *, #1053, .F. ) ;
+#5152 = CARTESIAN_POINT ( 'NONE', ( 2.000000000000000000, 6.000000000000000000, 2.000000000000000000 ) ) ;
+#5153 = ORIENTED_EDGE ( 'NONE', *, *, #2388, .F. ) ;
+#5154 = DIRECTION ( 'NONE', ( 1.330566893520345700E-016, -1.000000000000000000, 0.0000000000000000000 ) ) ;
+#5155 = ORIENTED_EDGE ( 'NONE', *, *, #1812, .T. ) ;
+#5156 = VERTEX_POINT ( 'NONE', #2786 ) ;
+#5157 = LINE ( 'NONE', #4922, #2751 ) ;
+#5158 = EDGE_LOOP ( 'NONE', ( #3981, #3783, #2698, #698 ) ) ;
+#5159 = CARTESIAN_POINT ( 'NONE', ( -1.258023919216310000E-015, 20.69999999999999900, -0.6999999999999999600 ) ) ;
+#5160 = ORIENTED_EDGE ( 'NONE', *, *, #3861, .T. ) ;
+#5161 = VERTEX_POINT ( 'NONE', #3194 ) ;
+#5162 = ORIENTED_EDGE ( 'NONE', *, *, #2577, .F. ) ;
+#5163 = LINE ( 'NONE', #4366, #4109 ) ;
+#5164 = ORIENTED_EDGE ( 'NONE', *, *, #4064, .T. ) ;
+#5165 = ORIENTED_EDGE ( 'NONE', *, *, #3456, .F. ) ;
+#5166 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5167 = ORIENTED_EDGE ( 'NONE', *, *, #3474, .T. ) ;
+#5168 = CIRCLE ( 'NONE', #396, 1.000000000000000000 ) ;
+#5169 = ORIENTED_EDGE ( 'NONE', *, *, #4477, .F. ) ;
+#5170 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#5171 = ADVANCED_FACE ( 'NONE', ( #1609 ), #3617, .T. ) ;
+#5172 = EDGE_CURVE ( 'NONE', #3719, #3479, #669, .T. ) ;
+#5173 = ORIENTED_EDGE ( 'NONE', *, *, #1715, .T. ) ;
+#5174 = EDGE_CURVE ( 'NONE', #1778, #2010, #2791, .T. ) ;
+#5175 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#5176 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5177 = CARTESIAN_POINT ( 'NONE', ( -12.91485014935554100, 18.71852980579590000, -0.6999999999999999600 ) ) ;
+#5178 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#5179 = AXIS2_PLACEMENT_3D ( 'NONE', #5058, #2642, #232 ) ;
+#5180 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.0000000000000000000, -0.0000000000000000000 ) ) ;
+#5181 = EDGE_CURVE ( 'NONE', #3195, #237, #2552, .T. ) ;
+#5182 = LINE ( 'NONE', #4493, #1043 ) ;
+#5183 = CARTESIAN_POINT ( 'NONE', ( -30.27499999999999900, 3.500000000000000000, 6.000000000000000000 ) ) ;
+#5184 = AXIS2_PLACEMENT_3D ( 'NONE', #22, #1255, #4063 ) ;
+#5185 = FACE_BOUND ( 'NONE', #2753, .T. ) ;
+#5186 = VECTOR ( 'NONE', #4456, 1000.000000000000000 ) ;
+#5187 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 0.0000000000000000000, 1.000000000000000000 ) ) ;
+#5188 = VECTOR ( 'NONE', #3164, 1000.000000000000000 ) ;
+#5189 = ADVANCED_FACE ( 'NONE', ( #4687 ), #3621, .F. ) ;
+#5190 = CIRCLE ( 'NONE', #3467, 1.000000000000000000 ) ;
+#5191 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, 1.000000000000000000 ) ) ;
+#5192 = PLANE ( 'NONE', #3034 ) ;
+#5193 = VECTOR ( 'NONE', #4197, 1000.000000000000000 ) ;
+#5194 = EDGE_CURVE ( 'NONE', #4104, #4840, #3490, .T. ) ;
+#5195 = ORIENTED_EDGE ( 'NONE', *, *, #3511, .T. ) ;
+#5196 = PLANE ( 'NONE', #3542 ) ;
+#5197 = EDGE_CURVE ( 'NONE', #2103, #3785, #1485, .T. ) ;
+#5198 = LINE ( 'NONE', #4743, #4952 ) ;
+#5199 = EDGE_CURVE ( 'NONE', #1997, #1967, #2423, .T. ) ;
+#5200 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.0000000000000000000, 0.0000000000000000000 ) ) ;
+#5201 = ORIENTED_EDGE ( 'NONE', *, *, #84, .F. ) ;
+#5202 = CARTESIAN_POINT ( 'NONE', ( -1.500000000000000000, 0.0000000000000000000, 4.299999999999999800 ) ) ;
+#5203 = CIRCLE ( 'NONE', #1079, 1.000000000000000000 ) ;
+#5204 = CARTESIAN_POINT ( 'NONE', ( -0.3694660940672371800, 19.57000000000000000, 6.000000000000000000 ) ) ;
+#5205 = ORIENTED_EDGE ( 'NONE', *, *, #3625, .F. ) ;
+#5206 = AXIS2_PLACEMENT_3D ( 'NONE', #2809, #1230, #4044 ) ;
+#5207 = CARTESIAN_POINT ( 'NONE', ( 18.47230854637602700, 16.51000000000000500, 2.500000000000000000 ) ) ;
+#5208 = ORIENTED_EDGE ( 'NONE', *, *, #616, .T. ) ;
+#5209 = CARTESIAN_POINT ( 'NONE', ( -24.06714285714285500, 13.05655776574935000, 2.000000000000000000 ) ) ;
+#5210 = CARTESIAN_POINT ( 'NONE', ( -20.03115427318800800, -19.21000000000000100, 2.500000000000000000 ) ) ;
+#5211 = LINE ( 'NONE', #4633, #949 ) ;
+#5212 = ORIENTED_EDGE ( 'NONE', *, *, #4674, .T. ) ;
+#5213 = LINE ( 'NONE', #4246, #951 ) ;
+#5214 = ORIENTED_EDGE ( 'NONE', *, *, #4010, .T. ) ;
+#5215 = ORIENTED_EDGE ( 'NONE', *, *, #3030, .T. ) ;
+#5216 = DIRECTION ( 'NONE', ( -0.0000000000000000000, -0.0000000000000000000, -1.000000000000000000 ) ) ;
+#5217 = ADVANCED_FACE ( 'NONE', ( #3365 ), #4054, .T. ) ;
+#5218 = ORIENTED_EDGE ( 'NONE', *, *, #4199, .T. ) ;
+#5219 = AXIS2_PLACEMENT_3D ( 'NONE', #4991, #3806, #1410 ) ;
+#5220 = ORIENTED_EDGE ( 'NONE', *, *, #555, .T. ) ;
+#5221 = FACE_OUTER_BOUND ( 'NONE', #4857, .T. ) ;
+#5222 = VERTEX_POINT ( 'NONE', #441 ) ;
+#5223 = VERTEX_POINT ( 'NONE', #3248 ) ;
+#5224 = DIRECTION ( 'NONE', ( -0.7071067811865475700, 0.7071067811865475700, 0.0000000000000000000 ) ) ;
+#5225 = EDGE_LOOP ( 'NONE', ( #2205, #65, #1136, #1619 ) ) ;
+#5226 = DIRECTION ( 'NONE', ( 0.0000000000000000000, 1.000000000000000000, 0.0000000000000000000 ) ) ;
+#5227 = FACE_OUTER_BOUND ( 'NONE', #1908, .T. ) ;
+#5228 = ORIENTED_EDGE ( 'NONE', *, *, #4612, .T. ) ;
+ENDSEC;
+END-ISO-10303-21;
diff --git a/hardware/enclosure/WALT_recessed_enclosure.stl b/hardware/enclosure/WALT_recessed_enclosure.stl
new file mode 100644
index 0000000..ee4ed1a
--- /dev/null
+++ b/hardware/enclosure/WALT_recessed_enclosure.stl
Binary files differ
diff --git a/hardware/kicad/.gitignore b/hardware/kicad/.gitignore
new file mode 100644
index 0000000..68bebb4
--- /dev/null
+++ b/hardware/kicad/.gitignore
@@ -0,0 +1,9 @@
+*.bak
+*.bck
+*.raw
+*.log
+*.dcm
+*.kicad_pcb-bak
+*autosave*
+*cache*
+fab
\ No newline at end of file
diff --git a/hardware/kicad/WALTsm.kicad_pcb b/hardware/kicad/WALTsm.kicad_pcb
new file mode 100644
index 0000000..a007e0f
--- /dev/null
+++ b/hardware/kicad/WALTsm.kicad_pcb
@@ -0,0 +1,1789 @@
+(kicad_pcb (version 4) (host pcbnew 4.0.4+e1-6308~48~ubuntu14.04.1-stable)
+
+ (general
+ (links 61)
+ (no_connects 1)
+ (area 65.963799 83.743799 106.756201 134.696201)
+ (thickness 1.6)
+ (drawings 24)
+ (tracks 120)
+ (zones 0)
+ (modules 26)
+ (nets 43)
+ )
+
+ (page USLetter)
+ (layers
+ (0 F.Cu signal)
+ (31 B.Cu signal hide)
+ (32 B.Adhes user)
+ (33 F.Adhes user)
+ (34 B.Paste user)
+ (35 F.Paste user)
+ (36 B.SilkS user)
+ (37 F.SilkS user)
+ (38 B.Mask user)
+ (39 F.Mask user)
+ (40 Dwgs.User user)
+ (41 Cmts.User user)
+ (42 Eco1.User user)
+ (43 Eco2.User user)
+ (44 Edge.Cuts user)
+ (45 Margin user)
+ (46 B.CrtYd user)
+ (47 F.CrtYd user)
+ (48 B.Fab user)
+ (49 F.Fab user)
+ )
+
+ (setup
+ (last_trace_width 0.3048)
+ (user_trace_width 0.1524)
+ (user_trace_width 0.2032)
+ (user_trace_width 0.3048)
+ (user_trace_width 0.508)
+ (trace_clearance 0.254)
+ (zone_clearance 0.381)
+ (zone_45_only yes)
+ (trace_min 0.1524)
+ (segment_width 0.1524)
+ (edge_width 0.1524)
+ (via_size 0.6858)
+ (via_drill 0.3302)
+ (via_min_size 0.6858)
+ (via_min_drill 0.3302)
+ (uvia_size 0.508)
+ (uvia_drill 0.127)
+ (uvias_allowed no)
+ (uvia_min_size 0.508)
+ (uvia_min_drill 0.127)
+ (pcb_text_width 0.1524)
+ (pcb_text_size 1.016 1.016)
+ (mod_edge_width 0.07)
+ (mod_text_size 0.5 0.5)
+ (mod_text_width 0.07)
+ (pad_size 5.08 10.16)
+ (pad_drill 0)
+ (pad_to_mask_clearance 0.05)
+ (pad_to_paste_clearance -0.04)
+ (aux_axis_origin 0 0)
+ (grid_origin 86.36 96.94)
+ (visible_elements 7FFFFFFF)
+ (pcbplotparams
+ (layerselection 0x010f0_80000001)
+ (usegerberextensions true)
+ (usegerberattributes true)
+ (excludeedgelayer true)
+ (linewidth 0.127000)
+ (plotframeref false)
+ (viasonmask false)
+ (mode 1)
+ (useauxorigin false)
+ (hpglpennumber 1)
+ (hpglpenspeed 20)
+ (hpglpendiameter 15)
+ (hpglpenoverlay 2)
+ (psnegative false)
+ (psa4output false)
+ (plotreference true)
+ (plotvalue true)
+ (plotinvisibletext false)
+ (padsonsilk false)
+ (subtractmaskfromsilk false)
+ (outputformat 1)
+ (mirror false)
+ (drillshape 0)
+ (scaleselection 1)
+ (outputdirectory gerbers/))
+ )
+
+ (net 0 "")
+ (net 1 "Net-(C1-Pad1)")
+ (net 2 "Net-(C1-Pad2)")
+ (net 3 /DEBUG_LED2)
+ (net 4 /DEBUG_LED1)
+ (net 5 GND)
+ (net 6 /AUDIO_PIN)
+ (net 7 /SERIAL_CTS)
+ (net 8 /SERIAL_TX)
+ (net 9 /SERIAL_RX)
+ (net 10 "Net-(P1-Pad4)")
+ (net 11 /SERIAL_RTS)
+ (net 12 /GPIO_A3)
+ (net 13 /GPIO_A4)
+ (net 14 /GPIO_A5)
+ (net 15 +3V3)
+ (net 16 /VUSB_5V)
+ (net 17 /PD_SCREEN_PIN)
+ (net 18 /MIC_PIN)
+ (net 19 "Net-(U1-Pad6)")
+ (net 20 /GXY_PIN)
+ (net 21 /GZ_PIN)
+ (net 22 "Net-(U1-Pad23)")
+ (net 23 "Net-(U2-Pad1)")
+ (net 24 "Net-(U2-Pad2)")
+ (net 25 "Net-(U2-Pad4)")
+ (net 26 "Net-(U2-Pad9)")
+ (net 27 "Net-(U2-Pad11)")
+ (net 28 "Net-(U2-Pad13)")
+ (net 29 "Net-(U2-Pad16)")
+ (net 30 "Net-(J1-Pad2)")
+ (net 31 "Net-(D1-Pad1)")
+ (net 32 "Net-(D2-Pad1)")
+ (net 33 "Net-(D3-Pad2)")
+ (net 34 "Net-(D4-Pad2)")
+ (net 35 "Net-(U1-Pad2)")
+ (net 36 "Net-(U1-Pad3)")
+ (net 37 "Net-(U1-Pad4)")
+ (net 38 "Net-(U1-Pad5)")
+ (net 39 "Net-(U1-Pad8)")
+ (net 40 "Net-(U1-Pad12)")
+ (net 41 "Net-(U1-Pad15)")
+ (net 42 "Net-(U2-Pad12)")
+
+ (net_class Default "This is the default net class."
+ (clearance 0.254)
+ (trace_width 0.254)
+ (via_dia 0.6858)
+ (via_drill 0.3302)
+ (uvia_dia 0.508)
+ (uvia_drill 0.127)
+ (add_net +3V3)
+ (add_net /AUDIO_PIN)
+ (add_net /DEBUG_LED1)
+ (add_net /DEBUG_LED2)
+ (add_net /GPIO_A3)
+ (add_net /GPIO_A4)
+ (add_net /GPIO_A5)
+ (add_net /GXY_PIN)
+ (add_net /GZ_PIN)
+ (add_net /MIC_PIN)
+ (add_net /PD_SCREEN_PIN)
+ (add_net /SERIAL_CTS)
+ (add_net /SERIAL_RTS)
+ (add_net /SERIAL_RX)
+ (add_net /SERIAL_TX)
+ (add_net /VUSB_5V)
+ (add_net GND)
+ (add_net "Net-(C1-Pad1)")
+ (add_net "Net-(C1-Pad2)")
+ (add_net "Net-(D1-Pad1)")
+ (add_net "Net-(D2-Pad1)")
+ (add_net "Net-(D3-Pad2)")
+ (add_net "Net-(D4-Pad2)")
+ (add_net "Net-(J1-Pad2)")
+ (add_net "Net-(P1-Pad4)")
+ (add_net "Net-(U1-Pad12)")
+ (add_net "Net-(U1-Pad15)")
+ (add_net "Net-(U1-Pad2)")
+ (add_net "Net-(U1-Pad23)")
+ (add_net "Net-(U1-Pad3)")
+ (add_net "Net-(U1-Pad4)")
+ (add_net "Net-(U1-Pad5)")
+ (add_net "Net-(U1-Pad6)")
+ (add_net "Net-(U1-Pad8)")
+ (add_net "Net-(U2-Pad1)")
+ (add_net "Net-(U2-Pad11)")
+ (add_net "Net-(U2-Pad12)")
+ (add_net "Net-(U2-Pad13)")
+ (add_net "Net-(U2-Pad16)")
+ (add_net "Net-(U2-Pad2)")
+ (add_net "Net-(U2-Pad4)")
+ (add_net "Net-(U2-Pad9)")
+ )
+
+ (net_class Minimal ""
+ (clearance 0.1524)
+ (trace_width 0.1524)
+ (via_dia 0.6858)
+ (via_drill 0.3302)
+ (uvia_dia 0.508)
+ (uvia_drill 0.127)
+ )
+
+ (module Capacitors_SMD:C_0805 (layer F.Cu) (tedit 58B4C36B) (tstamp 5766179B)
+ (at 83.439 99.988)
+ (descr "Capacitor SMD 0805, reflow soldering, AVX (see smccp.pdf)")
+ (tags "capacitor 0805")
+ (path /5764985B)
+ (attr smd)
+ (fp_text reference C1 (at -2.667 0.42) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 0.1uF (at 0.381 0) (layer F.Fab)
+ (effects (font (size 0.508 0.508) (thickness 0.0762)))
+ )
+ (fp_line (start -1.8 -1) (end 1.8 -1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.8 1) (end 1.8 1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.8 -1) (end -1.8 1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.8 -1) (end 1.8 1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 -0.85) (end -0.5 -0.85) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 0.85) (end 0.5 0.85) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -1 0) (size 1 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 1 "Net-(C1-Pad1)"))
+ (pad 2 smd rect (at 1 0) (size 1 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 2 "Net-(C1-Pad2)"))
+ (model Capacitors_SMD.3dshapes/C_0805.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 57B60F33) (tstamp 576617B3)
+ (at 73.66 116.078 90)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /57646D13)
+ (attr smd)
+ (fp_text reference D4 (at 0 -1.778 90) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value LED (at -0.01778 1.23444 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 2 smd rect (at 0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 34 "Net-(D4-Pad2)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 57B60F2D) (tstamp 576617AD)
+ (at 73.66 120.142 270)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /57646CD9)
+ (attr smd)
+ (fp_text reference D3 (at 0 1.778 270) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value LED (at -0.01778 1.23444 270) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 270) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 2 smd rect (at 0.85 0 270) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 33 "Net-(D3-Pad2)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 57B60ECD) (tstamp 576617FB)
+ (at 91.44 97.282 90)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /57649565)
+ (attr smd)
+ (fp_text reference R7 (at 0 -1.5494 90) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 100 (at -0.01778 1.23444 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 2 smd rect (at 0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 6 /AUDIO_PIN))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 58B4C636) (tstamp 576617F5)
+ (at 82.169 102.782 90)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /5764991F)
+ (attr smd)
+ (fp_text reference R6 (at -2.413 0.127 90) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 3.3k (at 0 0 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 2 smd rect (at 0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 1 "Net-(C1-Pad1)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 58B4C62B) (tstamp 576617EF)
+ (at 84.455 102.782 90)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /576498E4)
+ (attr smd)
+ (fp_text reference R5 (at -2.413 0.127 90) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 100 (at 0 0 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 2 smd rect (at 0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 2 "Net-(C1-Pad2)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 57B612C2) (tstamp 576617E9)
+ (at 86.487 97.448 270)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /576498AB)
+ (attr smd)
+ (fp_text reference R4 (at -0.1016 -1.6256 270) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 3,3k (at 0 0 270) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 270) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 18 /MIC_PIN))
+ (pad 2 smd rect (at 0.85 0 270) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 2 "Net-(C1-Pad2)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 57B60E47) (tstamp 576617E3)
+ (at 91.44 102.616 90)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /57648608)
+ (attr smd)
+ (fp_text reference R3 (at -2.3876 0.3302 90) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 510k (at -0.01778 1.23444 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 32 "Net-(D2-Pad1)"))
+ (pad 2 smd rect (at 0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 17 /PD_SCREEN_PIN))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 578562C6) (tstamp 576617DD)
+ (at 75.692 116.078 90)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /57646C37)
+ (attr smd)
+ (fp_text reference R2 (at 2.54 0 90) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 330 (at -0.01778 1.23444 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 4 /DEBUG_LED1))
+ (pad 2 smd rect (at 0.85 0 90) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 34 "Net-(D4-Pad2)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 578562CD) (tstamp 576617D7)
+ (at 75.692 120.142 270)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /57646BD5)
+ (attr smd)
+ (fp_text reference R1 (at 2.54 0 270) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 330 (at -0.01778 1.23444 270) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 270) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 3 /DEBUG_LED2))
+ (pad 2 smd rect (at 0.85 0 270) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 33 "Net-(D3-Pad2)"))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:Teensy_DIP-28_W15.24mm (layer F.Cu) (tedit 57683B97) (tstamp 57681E98)
+ (at 78.74 86.36)
+ (descr "28-lead dip package, row spacing 15.24 mm (600 mils)")
+ (tags "dil dip 2.54 600")
+ (path /576468C7)
+ (fp_text reference U1 (at 0 -5.22) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value TeensyLC_with_headers (at 21.082 -8.001) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -1.05 -2.45) (end -1.05 35.5) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 16.3 -2.45) (end 16.3 35.5) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.05 -2.45) (end 16.3 -2.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.05 35.5) (end 16.3 35.5) (layer F.CrtYd) (width 0.05))
+ (pad 1 thru_hole circle (at 0 0) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (pad 2 thru_hole circle (at 0 2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 35 "Net-(U1-Pad2)"))
+ (pad 3 thru_hole circle (at 0 5.08) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 36 "Net-(U1-Pad3)"))
+ (pad 4 thru_hole circle (at 0 7.62) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 37 "Net-(U1-Pad4)"))
+ (pad 5 thru_hole circle (at 0 10.16) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 38 "Net-(U1-Pad5)"))
+ (pad 6 thru_hole circle (at 0 12.7) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 19 "Net-(U1-Pad6)"))
+ (pad 7 thru_hole circle (at 0 15.24) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 11 /SERIAL_RTS))
+ (pad 8 thru_hole circle (at 0 17.78) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 39 "Net-(U1-Pad8)"))
+ (pad 9 thru_hole circle (at 0 20.32) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 9 /SERIAL_RX))
+ (pad 10 thru_hole circle (at 0 22.86) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 8 /SERIAL_TX))
+ (pad 11 thru_hole circle (at 0 25.4) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 7 /SERIAL_CTS))
+ (pad 12 thru_hole circle (at 0 27.94) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 40 "Net-(U1-Pad12)"))
+ (pad 13 thru_hole circle (at 0 30.48) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 4 /DEBUG_LED1))
+ (pad 14 thru_hole circle (at 0 33.02) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 3 /DEBUG_LED2))
+ (pad 15 thru_hole circle (at 15.24 33.02) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 41 "Net-(U1-Pad15)"))
+ (pad 16 thru_hole circle (at 15.24 30.48) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 31 "Net-(D1-Pad1)"))
+ (pad 17 thru_hole circle (at 15.24 27.94) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 20 /GXY_PIN))
+ (pad 18 thru_hole circle (at 15.24 25.4) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 21 /GZ_PIN))
+ (pad 19 thru_hole circle (at 15.24 22.86) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 12 /GPIO_A3))
+ (pad 20 thru_hole circle (at 15.24 20.32) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 13 /GPIO_A4))
+ (pad 21 thru_hole circle (at 15.24 17.78) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 14 /GPIO_A5))
+ (pad 22 thru_hole circle (at 15.24 15.24) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 17 /PD_SCREEN_PIN))
+ (pad 23 thru_hole circle (at 15.24 12.7) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 22 "Net-(U1-Pad23)"))
+ (pad 24 thru_hole circle (at 15.24 10.16) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 6 /AUDIO_PIN))
+ (pad 25 thru_hole circle (at 15.24 7.62) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 18 /MIC_PIN))
+ (pad 26 thru_hole circle (at 15.24 5.08) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 15 +3V3))
+ (pad 27 thru_hole circle (at 15.24 2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (pad 28 thru_hole circle (at 15.24 0) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 16 /VUSB_5V))
+ (model Housings_DIP.3dshapes/DIP-28_W15.24mm.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module Housings_DFN_QFN:QFN-16-1EP_4x4mm_Pitch0.65mm (layer F.Cu) (tedit 57B60F7A) (tstamp 57661833)
+ (at 88.9762 114.8842 90)
+ (descr "16-Lead Plastic Quad Flat, No Lead Package (ML) - 4x4x0.9 mm Body [QFN]; (see Microchip Packaging Specification 00000049BS.pdf)")
+ (tags "QFN 0.65")
+ (path /57647D5A)
+ (attr smd)
+ (fp_text reference U2 (at -3.9878 1.9558 180) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value ADXL335 (at 0 3.4 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -2.65 -2.65) (end -2.65 2.65) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 2.65 -2.65) (end 2.65 2.65) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -2.65 -2.65) (end 2.65 -2.65) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -2.65 2.65) (end 2.65 2.65) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 2.15 -2.15) (end 2.15 -1.375) (layer F.SilkS) (width 0.15))
+ (fp_line (start -2.15 2.15) (end -2.15 1.375) (layer F.SilkS) (width 0.15))
+ (fp_line (start 2.15 2.15) (end 2.15 1.375) (layer F.SilkS) (width 0.15))
+ (fp_line (start -2.15 -2.15) (end -1.375 -2.15) (layer F.SilkS) (width 0.15))
+ (fp_line (start -2.15 2.15) (end -1.375 2.15) (layer F.SilkS) (width 0.15))
+ (fp_line (start 2.15 2.15) (end 1.375 2.15) (layer F.SilkS) (width 0.15))
+ (fp_line (start 2.15 -2.15) (end 1.375 -2.15) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -2 -0.975 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 23 "Net-(U2-Pad1)"))
+ (pad 2 smd rect (at -2 -0.325 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 24 "Net-(U2-Pad2)"))
+ (pad 3 smd rect (at -2 0.325 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 4 smd rect (at -2 0.975 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 25 "Net-(U2-Pad4)"))
+ (pad 5 smd rect (at -0.975 2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 6 smd rect (at -0.325 2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 7 smd rect (at 0.325 2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 8 smd rect (at 0.975 2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 21 /GZ_PIN))
+ (pad 9 smd rect (at 2 0.975 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 26 "Net-(U2-Pad9)"))
+ (pad 10 smd rect (at 2 0.325 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 20 /GXY_PIN))
+ (pad 11 smd rect (at 2 -0.325 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 27 "Net-(U2-Pad11)"))
+ (pad 12 smd rect (at 2 -0.975 90) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 42 "Net-(U2-Pad12)"))
+ (pad 13 smd rect (at 0.975 -2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 28 "Net-(U2-Pad13)"))
+ (pad 14 smd rect (at 0.325 -2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 15 +3V3))
+ (pad 15 smd rect (at -0.325 -2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 15 +3V3))
+ (pad 16 smd rect (at -0.975 -2 180) (size 0.8 0.35) (layers F.Cu F.Paste F.Mask)
+ (net 29 "Net-(U2-Pad16)"))
+ (pad 17 smd rect (at 0.625 0.625 90) (size 1.25 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND) (solder_paste_margin_ratio -0.2))
+ (pad 17 smd rect (at 0.625 -0.625 90) (size 1.25 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND) (solder_paste_margin_ratio -0.2))
+ (pad 17 smd rect (at -0.625 0.625 90) (size 1.25 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND) (solder_paste_margin_ratio -0.2))
+ (pad 17 smd rect (at -0.625 -0.625 90) (size 1.25 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND) (solder_paste_margin_ratio -0.2))
+ (model Housings_DFN_QFN.3dshapes/QFN-16-1EP_4x4mm_Pitch0.65mm.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module TO_SOT_Packages_SMD:SOT-23-5 (layer F.Cu) (tedit 57B60EC7) (tstamp 5766183C)
+ (at 88.392 102.87 90)
+ (descr "5-pin SOT23 package")
+ (tags SOT-23-5)
+ (path /576483C4)
+ (attr smd)
+ (fp_text reference U3 (at 2.7686 0.8382 180) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value LM321MFX (at -0.5842 -2.0574 90) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.8 -1.6) (end 1.8 -1.6) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.8 -1.6) (end 1.8 1.6) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.8 1.6) (end -1.8 1.6) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.8 1.6) (end -1.8 -1.6) (layer F.CrtYd) (width 0.05))
+ (fp_circle (center -0.3 -1.7) (end -0.2 -1.7) (layer F.SilkS) (width 0.15))
+ (fp_line (start 0.25 -1.45) (end -0.25 -1.45) (layer F.SilkS) (width 0.15))
+ (fp_line (start 0.25 1.45) (end 0.25 -1.45) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.25 1.45) (end 0.25 1.45) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.25 -1.45) (end -0.25 1.45) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -1.1 -0.95 90) (size 1.06 0.65) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 2 smd rect (at -1.1 0 90) (size 1.06 0.65) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (pad 3 smd rect (at -1.1 0.95 90) (size 1.06 0.65) (layers F.Cu F.Paste F.Mask)
+ (net 32 "Net-(D2-Pad1)"))
+ (pad 4 smd rect (at 1.1 0.95 90) (size 1.06 0.65) (layers F.Cu F.Paste F.Mask)
+ (net 17 /PD_SCREEN_PIN))
+ (pad 5 smd rect (at 1.1 -0.95 90) (size 1.06 0.65) (layers F.Cu F.Paste F.Mask)
+ (net 15 +3V3))
+ (model TO_SOT_Packages_SMD.3dshapes/SOT-23-5.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:TRRS_SJ_43514 (layer F.Cu) (tedit 57683B26) (tstamp 576617BD)
+ (at 81.9 85.35 270)
+ (descr TRRS_SJ_43514)
+ (tags "Audio TRRS")
+ (path /5764949B)
+ (fp_text reference J1 (at 3.55 -7.254 540) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value JACK_TRRS (at -7.88 -3.19 360) (layer F.Fab) hide
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -2.9 1.905) (end -2.9 -10.795) (layer B.CrtYd) (width 0.15))
+ (fp_line (start 10.16 -10.795) (end 10.16 1.905) (layer B.CrtYd) (width 0.15))
+ (fp_line (start 10.16 1.905) (end -2.9 1.905) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -1.6 1.905) (end -1.6 -10.795) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -2.9 -10.795) (end 10.16 -10.795) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -1.6 -1.4) (end -5.1 -1.4) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -5.1 -1.4) (end -5.1 -7.55) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -5.1 -7.55) (end -1.6 -7.55) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -1.5 1.905) (end 10.16 1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start 10.16 1.905) (end 10.16 -10.795) (layer F.SilkS) (width 0.15))
+ (fp_line (start 10.16 -10.795) (end -1.5 -10.795) (layer F.SilkS) (width 0.15))
+ (pad 4 thru_hole circle (at 0 0 270) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (pad 2 thru_hole circle (at 5 -0.9 270) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 30 "Net-(J1-Pad2)"))
+ (pad 1 thru_hole circle (at 0 -9.3 270) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 1 "Net-(C1-Pad1)"))
+ (pad 3 thru_hole circle (at 8.1 -6.1 270) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 6 /AUDIO_PIN))
+ (pad "" np_thru_hole circle (at 0 -4.5 270) (size 1 1) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad "" np_thru_hole circle (at 5 -4.5 270) (size 1 1) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ )
+
+ (module Mounting_Holes:MountingHole_3.2mm_M3 (layer F.Cu) (tedit 5766F70C) (tstamp 5766BA33)
+ (at 69.85 87.63)
+ (descr "Mounting Hole 3.2mm, no annular, M3")
+ (tags "mounting hole 3.2mm no annular m3")
+ (fp_text reference REF** (at -2.54 -7.62) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value MountingHole_3.2mm_M3 (at -20.32 -1.524) (layer F.Fab) hide
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_circle (center 0 0) (end 3.2 0) (layer Cmts.User) (width 0.15))
+ (fp_circle (center 0 0) (end 3.45 0) (layer F.CrtYd) (width 0.05))
+ (pad 1 thru_hole circle (at 0 0) (size 5 5) (drill 3.2) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND))
+ )
+
+ (module Mounting_Holes:MountingHole_3.2mm_M3 (layer F.Cu) (tedit 5766F71D) (tstamp 5766BA3A)
+ (at 69.85 130.81)
+ (descr "Mounting Hole 3.2mm, no annular, M3")
+ (tags "mounting hole 3.2mm no annular m3")
+ (fp_text reference REF** (at -13.97 -2.54) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value MountingHole_3.2mm_M3 (at -6.858 28.702) (layer F.Fab) hide
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_circle (center 0 0) (end 3.2 0) (layer Cmts.User) (width 0.15))
+ (fp_circle (center 0 0) (end 3.45 0) (layer F.CrtYd) (width 0.05))
+ (pad 1 thru_hole circle (at 0 0) (size 5 5) (drill 3.2) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND))
+ )
+
+ (module Mounting_Holes:MountingHole_3.2mm_M3 (layer F.Cu) (tedit 5766F721) (tstamp 5766BA41)
+ (at 102.87 130.81)
+ (descr "Mounting Hole 3.2mm, no annular, M3")
+ (tags "mounting hole 3.2mm no annular m3")
+ (fp_text reference REF** (at 12.7 -3.81) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value MountingHole_3.2mm_M3 (at 12.7 29.21) (layer F.Fab) hide
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_circle (center 0 0) (end 3.2 0) (layer Cmts.User) (width 0.15))
+ (fp_circle (center 0 0) (end 3.45 0) (layer F.CrtYd) (width 0.05))
+ (pad 1 thru_hole circle (at 0 0) (size 5 5) (drill 3.2) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND))
+ )
+
+ (module Mounting_Holes:MountingHole_3.2mm_M3 (layer F.Cu) (tedit 5766F725) (tstamp 5766BA91)
+ (at 102.87 87.63)
+ (descr "Mounting Hole 3.2mm, no annular, M3")
+ (tags "mounting hole 3.2mm no annular m3")
+ (fp_text reference REF** (at 8.89 -8.89) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value MountingHole_3.2mm_M3 (at 17.78 -0.762) (layer F.Fab) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_circle (center 0 0) (end 3.2 0) (layer Cmts.User) (width 0.15))
+ (fp_circle (center 0 0) (end 3.45 0) (layer F.CrtYd) (width 0.05))
+ (pad 1 thru_hole circle (at 0 0) (size 5 5) (drill 3.2) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND))
+ )
+
+ (module Wire_Pads:SolderWirePad_single_SMD_5x10mm (layer F.Cu) (tedit 57855C33) (tstamp 5766BB40)
+ (at 86.61654 131.43738 90)
+ (descr "Wire Pad, Square, SMD Pad, 5mm x 10mm,")
+ (tags "MesurementPoint Square SMDPad 5mmx10mm ")
+ (attr smd)
+ (fp_text reference REF** (at -13.335 0 90) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value SolderWirePad_single_SMD_5x10mm (at -18.161 8.001 90) (layer F.Fab) hide
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start 2.75 -5.25) (end -2.75 -5.25) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 2.75 5.25) (end 2.75 -5.25) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -2.75 5.25) (end 2.75 5.25) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -2.75 -5.25) (end -2.75 5.25) (layer F.CrtYd) (width 0.05))
+ (pad 1 smd rect (at 0 0 90) (size 5.08 10.16) (layers F.Cu F.Mask)
+ (net 5 GND) (zone_connect 1))
+ )
+
+ (module Capacitors_SMD:C_0805 (layer F.Cu) (tedit 57B60F82) (tstamp 5766CA1E)
+ (at 83.312 115.57 270)
+ (descr "Capacitor SMD 0805, reflow soldering, AVX (see smccp.pdf)")
+ (tags "capacitor 0805")
+ (path /5766D3A4)
+ (attr smd)
+ (fp_text reference C2 (at 0 1.778 270) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 0.1uF (at 0.0254 -0.4064 270) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.8 -1) (end 1.8 -1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.8 1) (end 1.8 1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.8 -1) (end -1.8 1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.8 -1) (end 1.8 1) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 -0.85) (end -0.5 -0.85) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 0.85) (end 0.5 0.85) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -1 0 270) (size 1 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 15 +3V3))
+ (pad 2 smd rect (at 1 0 270) (size 1 1.25) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (model Capacitors_SMD.3dshapes/C_0805.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (module walt_footprints:BPW34_DIP2 (layer F.Cu) (tedit 57855BAC) (tstamp 576617A1)
+ (at 104.267 118.9355 90)
+ (descr BPW34_DIP2)
+ (tags BPW34)
+ (path /57646A24)
+ (fp_text reference D1 (at 2.54 -2.921 270) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value Photodiode (at 2.286 15.494 90) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start 1.27 0) (end 3.81 1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start 3.81 1.905) (end 3.81 -1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start 3.81 -1.905) (end 1.27 0) (layer F.SilkS) (width 0.15))
+ (fp_line (start 1.27 -1.905) (end 1.27 1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 -2.15) (end 6.35 -2.15) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 6.35 -2.15) (end 6.35 2.15) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 6.35 2.15) (end -1.27 2.15) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.27 2.15) (end -1.27 -2.15) (layer F.CrtYd) (width 0.05))
+ (fp_text user K (at -1.905 1.27 180) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (pad 1 smd rect (at 0 0 180) (size 4 1.8) (layers F.Cu F.Mask)
+ (net 31 "Net-(D1-Pad1)"))
+ (pad 2 smd oval (at 5.08 0 90) (size 1.8 4) (layers F.Cu F.Mask)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (model LEDs.3dshapes/LED-5MM.wrl
+ (at (xyz 0.05 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+ )
+
+ (module walt_footprints:BPW34_DIP2 (layer B.Cu) (tedit 57855A95) (tstamp 576617A7)
+ (at 88.9 108.458 180)
+ (descr BPW34_DIP2)
+ (tags BPW34)
+ (path /576487CA)
+ (fp_text reference D2 (at 2.794 0 180) (layer B.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)) (justify mirror))
+ )
+ (fp_text value Photodiode (at -29.464 1.778 180) (layer B.Fab)
+ (effects (font (size 1 1) (thickness 0.15)) (justify mirror))
+ )
+ (fp_line (start 1.27 0) (end 3.81 -1.905) (layer B.SilkS) (width 0.15))
+ (fp_line (start 3.81 -1.905) (end 3.81 1.905) (layer B.SilkS) (width 0.15))
+ (fp_line (start 3.81 1.905) (end 1.27 0) (layer B.SilkS) (width 0.15))
+ (fp_line (start 1.27 1.905) (end 1.27 -1.905) (layer B.SilkS) (width 0.15))
+ (fp_line (start -1.27 2.15) (end 6.35 2.15) (layer B.CrtYd) (width 0.05))
+ (fp_line (start 6.35 2.15) (end 6.35 -2.15) (layer B.CrtYd) (width 0.05))
+ (fp_line (start 6.35 -2.15) (end -1.27 -2.15) (layer B.CrtYd) (width 0.05))
+ (fp_line (start -1.27 -2.15) (end -1.27 2.15) (layer B.CrtYd) (width 0.05))
+ (fp_text user K (at 0.127 -1.651 180) (layer B.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)) (justify mirror))
+ )
+ (pad 1 thru_hole rect (at 0 0 90) (size 1.6 1.6) (drill 1) (layers *.Cu *.Mask B.SilkS)
+ (net 32 "Net-(D2-Pad1)"))
+ (pad 2 thru_hole circle (at 5.08 0 180) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask B.SilkS)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (model LEDs.3dshapes/LED-5MM.wrl
+ (at (xyz 0.05 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+ )
+
+ (module walt_footprints:Pin_Header_Straight_1x06 (layer F.Cu) (tedit 57855A2D) (tstamp 57681E8F)
+ (at 98.044 109.22 180)
+ (descr "Through hole pin header")
+ (tags "pin header")
+ (path /57681A8E)
+ (fp_text reference P2 (at 0 15.24 180) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value CONN_01X06 (at -20.32 1.524 180) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -1.75 -1.75) (end -1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.75 -1.75) (end 1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 -1.75) (end 1.75 -1.75) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 14.45) (end 1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.27 -1.55) (end 1.27 13.97) (layer F.SilkS) (width 0.15))
+ (fp_line (start 1.27 13.97) (end -1.27 13.97) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 13.97) (end -1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 -1.55) (end 1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (pad 1 thru_hole circle (at 0 0 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 12 /GPIO_A3))
+ (pad 2 thru_hole circle (at 0 2.54 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 13 /GPIO_A4))
+ (pad 3 thru_hole circle (at 0 5.08 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 14 /GPIO_A5))
+ (pad 4 thru_hole circle (at 0 7.62 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (pad 5 thru_hole circle (at 0 10.16 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 15 +3V3))
+ (pad 6 thru_hole circle (at 0 12.7 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 16 /VUSB_5V))
+ (model Pin_Headers.3dshapes/Pin_Header_Straight_1x06.wrl
+ (at (xyz 0 -0.25 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+ )
+
+ (module walt_footprints:Pin_Header_Straight_1x06 (layer F.Cu) (tedit 57855A3E) (tstamp 576617C7)
+ (at 75.438 109.22 180)
+ (descr "Through hole pin header")
+ (tags "pin header")
+ (path /5765C52E)
+ (fp_text reference P1 (at 0 15.24 180) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value CONN_01X06 (at 18.542 2.032 180) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -1.75 -1.75) (end -1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.75 -1.75) (end 1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 -1.75) (end 1.75 -1.75) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 14.45) (end 1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.27 -1.55) (end 1.27 13.97) (layer F.SilkS) (width 0.15))
+ (fp_line (start 1.27 13.97) (end -1.27 13.97) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 13.97) (end -1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 -1.55) (end 1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (pad 1 thru_hole circle (at 0 0 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 7 /SERIAL_CTS))
+ (pad 2 thru_hole circle (at 0 2.54 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 8 /SERIAL_TX))
+ (pad 3 thru_hole circle (at 0 5.08 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 9 /SERIAL_RX))
+ (pad 4 thru_hole circle (at 0 7.62 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 10 "Net-(P1-Pad4)"))
+ (pad 5 thru_hole circle (at 0 10.16 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 11 /SERIAL_RTS))
+ (pad 6 thru_hole circle (at 0 12.7 180) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND) (zone_connect 1) (thermal_width 0.508) (thermal_gap 0.3048))
+ (model Pin_Headers.3dshapes/Pin_Header_Straight_1x06.wrl
+ (at (xyz 0 -0.25 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+ )
+
+ (module Wire_Pads:SolderWirePad_single_0-8mmDrill (layer F.Cu) (tedit 5768598B) (tstamp 57691F94)
+ (at 86.36 119.38)
+ (fp_text reference REF** (at 29.845 4.5085) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value SolderWirePad_single_0-8mmDrill (at 49.784 2.286) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (pad 1 thru_hole circle (at 0 0) (size 1.4 1.4) (drill 1) (layers *.Cu *.Mask F.SilkS)
+ (net 5 GND))
+ )
+
+ (module walt_footprints:R_0603_pad07mm_long (layer F.Cu) (tedit 58B4C63F) (tstamp 58B4C022)
+ (at 82.042 96.94 180)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (path /58B4C350)
+ (attr smd)
+ (fp_text reference R8 (at -2.413 0.381 180) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value 100 (at 0 0 180) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0 180) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 30 "Net-(J1-Pad2)"))
+ (pad 2 smd rect (at 0.85 0 180) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask)
+ (net 5 GND))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+ )
+
+ (gr_text GND (at 71.882 96.774) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text "SCL A5" (at 103.124 104.394) (layer B.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)) (justify mirror))
+ )
+ (gr_text "SDA A4" (at 103.124 106.934) (layer B.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)) (justify mirror))
+ )
+ (gr_text A3 (at 101.346 109.474) (layer B.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)) (justify mirror))
+ )
+ (gr_text VUSB (at 102.362 96.774) (layer B.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)) (justify mirror))
+ )
+ (gr_text 3.3V (at 102.108 99.314) (layer B.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)) (justify mirror))
+ )
+ (gr_text Black (at 71.374 95.25) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text GND (at 101.854 101.854) (layer B.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)) (justify mirror))
+ )
+ (gr_text NC (at 72.39 101.854) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text "WALT Latency Timer r0.9\ngithub.com/google/walt" (at 86.6013 125.4633) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text TX-RX3 (at 70.358 104.394) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text RX-TX3 (at 70.358 106.934) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text Serial3 (at 68.072 99.568 90) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text VUSB (at 102.362 96.774) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text 3.3V (at 102.108 99.314) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text GND (at 101.854 101.854) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text A3 (at 101.346 109.474) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text "A4 SDA" (at 103.124 106.934) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text "A5 SCL" (at 103.124 104.394) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_text Green (at 71.12 109.22) (layer F.SilkS)
+ (effects (font (size 1.016 1.016) (thickness 0.1524)))
+ )
+ (gr_line (start 106.68 134.62) (end 66.04 134.62) (angle 90) (layer Edge.Cuts) (width 0.1524))
+ (gr_line (start 106.68 83.82) (end 106.68 134.62) (angle 90) (layer Edge.Cuts) (width 0.1524))
+ (gr_line (start 66.04 83.82) (end 106.68 83.82) (angle 90) (layer Edge.Cuts) (width 0.1524))
+ (gr_line (start 66.04 134.62) (end 66.04 83.82) (angle 90) (layer Edge.Cuts) (width 0.1524))
+
+ (segment (start 84.836 96.94) (end 84.836 97.448) (width 0.3048) (layer F.Cu) (net 1))
+ (segment (start 84.836 90.844) (end 84.836 96.94) (width 0.3048) (layer F.Cu) (net 1) (tstamp 58B4C616))
+ (segment (start 84.836 97.448) (end 82.439 99.845) (width 0.3048) (layer F.Cu) (net 1) (tstamp 58B4C70C))
+ (segment (start 82.439 99.845) (end 82.439 99.988) (width 0.3048) (layer F.Cu) (net 1) (tstamp 58B4C71F))
+ (segment (start 82.169 101.932) (end 82.169 100.258) (width 0.3048) (layer F.Cu) (net 1))
+ (segment (start 82.169 100.258) (end 82.439 99.988) (width 0.3048) (layer F.Cu) (net 1) (tstamp 58B4C703))
+ (segment (start 82.312 99.988) (end 82.296 99.972) (width 0.3048) (layer F.Cu) (net 1))
+ (segment (start 82.169 100.131) (end 82.312 99.988) (width 0.3048) (layer F.Cu) (net 1) (tstamp 58B4C5E9))
+ (segment (start 91.2 85.35) (end 89.148 85.35) (width 0.3048) (layer F.Cu) (net 1) (status 10))
+ (segment (start 84.836 89.662) (end 84.836 90.844) (width 0.3048) (layer F.Cu) (net 1) (tstamp 5766CDF2))
+ (segment (start 84.836 90.844) (end 84.836 90.932) (width 0.3048) (layer F.Cu) (net 1) (tstamp 58B4C241))
+ (segment (start 89.148 85.35) (end 84.836 89.662) (width 0.3048) (layer F.Cu) (net 1) (tstamp 5766CDEB))
+ (segment (start 86.487 98.425) (end 84.924 99.988) (width 0.3048) (layer F.Cu) (net 2))
+ (segment (start 84.924 99.988) (end 84.312 99.988) (width 0.3048) (layer F.Cu) (net 2) (tstamp 58B4C5F0))
+ (segment (start 84.455 101.932) (end 84.455 100.131) (width 0.3048) (layer F.Cu) (net 2))
+ (segment (start 84.455 100.131) (end 84.312 99.988) (width 0.3048) (layer F.Cu) (net 2) (tstamp 58B4C5ED))
+ (segment (start 75.692 119.292) (end 75.78 119.38) (width 0.3048) (layer F.Cu) (net 3))
+ (segment (start 75.78 119.38) (end 78.74 119.38) (width 0.3048) (layer F.Cu) (net 3) (tstamp 578559C8))
+ (segment (start 75.692 116.928) (end 75.78 116.84) (width 0.3048) (layer F.Cu) (net 4))
+ (segment (start 75.78 116.84) (end 78.74 116.84) (width 0.3048) (layer F.Cu) (net 4) (tstamp 578559D5))
+ (segment (start 89.154 118.745) (end 89.3012 118.5978) (width 0.254) (layer F.Cu) (net 5))
+ (segment (start 89.3012 116.8842) (end 89.3012 118.5978) (width 0.254) (layer F.Cu) (net 5))
+ (segment (start 89.3012 118.5978) (end 89.154 118.745) (width 0.254) (layer F.Cu) (net 5) (tstamp 5768407F))
+ (segment (start 89.3012 115.8092) (end 89.3012 115.2092) (width 0.254) (layer F.Cu) (net 5) (tstamp 5768409F))
+ (segment (start 89.3012 115.2092) (end 88.3512 114.2592) (width 0.254) (layer F.Cu) (net 5) (tstamp 576840A5))
+ (segment (start 88.3512 114.2592) (end 88.3512 115.5092) (width 0.254) (layer F.Cu) (net 5) (tstamp 576840A6))
+ (segment (start 88.3512 115.5092) (end 89.6012 115.5092) (width 0.254) (layer F.Cu) (net 5) (tstamp 576840A8))
+ (segment (start 89.3012 116.8842) (end 89.3012 115.8092) (width 0.2032) (layer F.Cu) (net 5) (status 30))
+ (segment (start 89.3012 115.8092) (end 89.6012 115.5092) (width 0.2032) (layer F.Cu) (net 5) (tstamp 576704BB) (status 30))
+ (segment (start 90.9762 115.8592) (end 92.1668 115.8592) (width 0.2032) (layer F.Cu) (net 5) (status 10))
+ (segment (start 92.1668 115.8592) (end 92.202 115.824) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766FDB9))
+ (segment (start 90.9762 115.2092) (end 92.3492 115.2092) (width 0.2032) (layer F.Cu) (net 5) (status 10))
+ (segment (start 92.3492 115.2092) (end 92.456 115.316) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766FDB6))
+ (segment (start 90.9762 114.5592) (end 92.4508 114.5592) (width 0.2032) (layer F.Cu) (net 5) (status 10))
+ (segment (start 92.4508 114.5592) (end 92.456 114.554) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766FDB2))
+ (segment (start 90.9762 115.8592) (end 89.9512 115.8592) (width 0.2032) (layer F.Cu) (net 5) (status 30))
+ (segment (start 89.9512 115.8592) (end 89.6012 115.5092) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766EBEA) (status 30))
+ (segment (start 90.9762 115.2092) (end 89.9012 115.2092) (width 0.2032) (layer F.Cu) (net 5) (status 30))
+ (segment (start 89.9012 115.2092) (end 89.6012 115.5092) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766EBE7) (status 30))
+ (segment (start 90.9762 114.5592) (end 89.9012 114.5592) (width 0.2032) (layer F.Cu) (net 5) (status 30))
+ (segment (start 89.9012 114.5592) (end 89.6012 114.2592) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766EBE2) (status 30))
+ (segment (start 89.3012 115.8092) (end 89.6012 115.5092) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766EBDC) (status 30))
+ (segment (start 89.28 116.9054) (end 89.3012 116.8842) (width 0.2032) (layer F.Cu) (net 5) (tstamp 5766EBD7) (status 30))
+ (segment (start 93.968 96.532) (end 93.98 96.52) (width 0.254) (layer F.Cu) (net 6) (tstamp 5766F946) (status 30))
+ (segment (start 93.98 96.52) (end 91.07 96.52) (width 0.3048) (layer F.Cu) (net 6) (status 30))
+ (segment (start 91.07 96.52) (end 88 93.45) (width 0.3048) (layer F.Cu) (net 6) (tstamp 5766CD81) (status 30))
+ (segment (start 75.438 109.22) (end 77.978 111.76) (width 0.3048) (layer F.Cu) (net 7))
+ (segment (start 77.978 111.76) (end 78.74 111.76) (width 0.3048) (layer F.Cu) (net 7) (tstamp 57855793))
+ (segment (start 75.438 106.68) (end 77.978 109.22) (width 0.3048) (layer F.Cu) (net 8))
+ (segment (start 77.978 109.22) (end 78.74 109.22) (width 0.3048) (layer F.Cu) (net 8) (tstamp 578557A2))
+ (segment (start 75.438 104.14) (end 77.978 106.68) (width 0.3048) (layer F.Cu) (net 9))
+ (segment (start 77.978 106.68) (end 78.74 106.68) (width 0.3048) (layer F.Cu) (net 9) (tstamp 578557AA))
+ (segment (start 75.438 99.06) (end 77.978 101.6) (width 0.3048) (layer F.Cu) (net 11))
+ (segment (start 77.978 101.6) (end 78.74 101.6) (width 0.3048) (layer F.Cu) (net 11) (tstamp 578557B3))
+ (segment (start 93.98 109.22) (end 98.552 109.22) (width 0.3048) (layer F.Cu) (net 12))
+ (segment (start 93.98 106.68) (end 98.552 106.68) (width 0.3048) (layer F.Cu) (net 13))
+ (segment (start 93.98 104.14) (end 98.552 104.14) (width 0.3048) (layer F.Cu) (net 14))
+ (segment (start 82.931 114.57) (end 85.836 114.57) (width 0.508) (layer F.Cu) (net 15))
+ (segment (start 85.852 114.586) (end 85.852 115.2092) (width 0.508) (layer F.Cu) (net 15) (tstamp 57692097))
+ (segment (start 85.836 114.57) (end 85.852 114.586) (width 0.508) (layer F.Cu) (net 15) (tstamp 57692096))
+ (segment (start 93.98 91.44) (end 91.7194 91.44) (width 0.508) (layer B.Cu) (net 15))
+ (segment (start 98.552 99.06) (end 97.282 99.06) (width 0.508) (layer F.Cu) (net 15))
+ (segment (start 94.996 91.44) (end 93.98 91.44) (width 0.508) (layer F.Cu) (net 15) (tstamp 57681EE5))
+ (segment (start 96.012 92.456) (end 94.996 91.44) (width 0.508) (layer F.Cu) (net 15) (tstamp 57681EE4))
+ (segment (start 96.012 97.79) (end 96.012 92.456) (width 0.508) (layer F.Cu) (net 15) (tstamp 57681EE3))
+ (segment (start 97.282 99.06) (end 96.012 97.79) (width 0.508) (layer F.Cu) (net 15) (tstamp 57681EE2))
+ (via (at 84.9855 114.4853) (size 0.6858) (drill 0.3302) (layers F.Cu B.Cu) (net 15))
+ (segment (start 85.0138 114.4778) (end 85.0063 114.4853) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766DAB4))
+ (segment (start 85.0063 114.4853) (end 84.9855 114.4853) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766DAB3))
+ (segment (start 86.0806 110.7948) (end 85.0138 113.2586) (width 0.508) (layer B.Cu) (net 15))
+ (segment (start 86.0806 106.2228) (end 86.0806 110.7948) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766DA76))
+ (segment (start 86.0806 103.3018) (end 86.0806 106.2228) (width 0.508) (layer B.Cu) (net 15))
+ (segment (start 85.0138 113.2586) (end 85.0138 114.4778) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766DA87))
+ (segment (start 87.4776 99.949) (end 86.0806 101.346) (width 0.508) (layer B.Cu) (net 15))
+ (segment (start 86.0806 101.346) (end 86.0806 103.3018) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766DA56))
+ (segment (start 87.4776 99.949) (end 87.4776 101.7344) (width 0.508) (layer F.Cu) (net 15) (tstamp 5766D968) (status 20))
+ (via (at 87.4776 99.949) (size 0.6858) (drill 0.3302) (layers F.Cu B.Cu) (net 15))
+ (segment (start 90.8304 96.5962) (end 87.4776 99.949) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766D901))
+ (segment (start 90.8304 92.329) (end 90.8304 96.5962) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766D8F7))
+ (segment (start 91.7194 91.44) (end 90.8304 92.329) (width 0.508) (layer B.Cu) (net 15) (tstamp 5766D8D9))
+ (segment (start 87.4776 101.7344) (end 87.442 101.77) (width 0.508) (layer F.Cu) (net 15) (tstamp 5766D969) (status 30))
+ (segment (start 84.9855 114.4853) (end 85.0594 114.5592) (width 0.2032) (layer F.Cu) (net 15) (tstamp 5766D7F0))
+ (segment (start 86.9762 114.5592) (end 85.0594 114.5592) (width 0.2032) (layer F.Cu) (net 15) (status 10))
+ (segment (start 86.9762 115.2092) (end 85.852 115.2092) (width 0.2032) (layer F.Cu) (net 15) (status 10))
+ (segment (start 98.044 96.52) (end 98.044 91.694) (width 0.508) (layer F.Cu) (net 16))
+ (segment (start 95.758 86.36) (end 93.98 86.36) (width 0.508) (layer F.Cu) (net 16) (tstamp 57681EEB))
+ (segment (start 97.028 87.63) (end 95.758 86.36) (width 0.508) (layer F.Cu) (net 16) (tstamp 57681EEA))
+ (segment (start 97.028 90.678) (end 97.028 87.63) (width 0.508) (layer F.Cu) (net 16) (tstamp 57681EE9))
+ (segment (start 98.044 91.694) (end 97.028 90.678) (width 0.508) (layer F.Cu) (net 16) (tstamp 57681EE8))
+ (segment (start 91.44 101.866) (end 91.44 101.6) (width 0.3048) (layer F.Cu) (net 17) (status 10))
+ (segment (start 93.98 101.6) (end 91.44 101.6) (width 0.3048) (layer F.Cu) (net 17) (status 10))
+ (segment (start 91.44 101.6) (end 89.512 101.6) (width 0.3048) (layer F.Cu) (net 17) (tstamp 5766CE2E) (status 20))
+ (segment (start 89.512 101.6) (end 89.342 101.77) (width 0.3048) (layer F.Cu) (net 17) (tstamp 5766CCB3) (status 30))
+ (segment (start 87.376 91.44) (end 86.36 92.456) (width 0.3048) (layer F.Cu) (net 18))
+ (segment (start 93.98 93.98) (end 91.44 93.98) (width 0.3048) (layer F.Cu) (net 18) (tstamp 5766CE13) (status 20))
+ (segment (start 88.9 91.44) (end 91.44 93.98) (width 0.3048) (layer F.Cu) (net 18) (tstamp 5766CE0F))
+ (segment (start 87.376 91.44) (end 88.9 91.44) (width 0.3048) (layer F.Cu) (net 18) (tstamp 5766CE0C))
+ (segment (start 86.36 92.456) (end 86.36 96.94) (width 0.3048) (layer F.Cu) (net 18) (tstamp 58B4C1DD))
+ (segment (start 89.3012 112.8842) (end 89.3012 111.562) (width 0.2032) (layer F.Cu) (net 20))
+ (segment (start 89.8398 114.554) (end 93.726 114.554) (width 0.2032) (layer B.Cu) (net 20) (tstamp 57B60D97))
+ (segment (start 89.3064 114.0206) (end 89.8398 114.554) (width 0.2032) (layer B.Cu) (net 20) (tstamp 57B60D93))
+ (segment (start 89.3064 111.5568) (end 89.3064 114.0206) (width 0.2032) (layer B.Cu) (net 20) (tstamp 57B60D92))
+ (via (at 89.3064 111.5568) (size 0.6858) (drill 0.3302) (layers F.Cu B.Cu) (net 20))
+ (segment (start 89.3012 111.562) (end 89.3064 111.5568) (width 0.2032) (layer F.Cu) (net 20) (tstamp 57B60D8D))
+ (segment (start 93.726 114.554) (end 93.98 114.3) (width 0.2032) (layer B.Cu) (net 20) (tstamp 57B60D9D))
+ (segment (start 90.9762 113.9092) (end 90.986 113.919) (width 0.2032) (layer F.Cu) (net 21) (status 30))
+ (segment (start 90.986 113.919) (end 91.821 113.919) (width 0.2032) (layer F.Cu) (net 21) (tstamp 5766D578) (status 10))
+ (segment (start 91.821 113.919) (end 93.98 111.76) (width 0.2032) (layer F.Cu) (net 21) (tstamp 5766D579) (status 20))
+ (segment (start 82.892 96.774) (end 82.892 90.442) (width 0.3048) (layer F.Cu) (net 30))
+ (segment (start 82.892 90.442) (end 82.8 90.35) (width 0.3048) (layer F.Cu) (net 30) (tstamp 58B4C258))
+ (segment (start 104.14 118.872) (end 102.2604 118.872) (width 0.254) (layer F.Cu) (net 31) (status 10))
+ (segment (start 102.2604 118.872) (end 100.2284 116.84) (width 0.3048) (layer F.Cu) (net 31) (tstamp 5766D5C0))
+ (segment (start 100.2284 116.84) (end 93.98 116.84) (width 0.3048) (layer F.Cu) (net 31) (tstamp 5766D5D1) (status 20))
+ (segment (start 88.9 108.458) (end 89.342 108.016) (width 0.3048) (layer F.Cu) (net 32))
+ (segment (start 89.342 108.016) (end 89.342 103.97) (width 0.3048) (layer F.Cu) (net 32) (tstamp 57683F1C))
+ (segment (start 91.44 103.366) (end 90.836 103.97) (width 0.3048) (layer F.Cu) (net 32) (tstamp 5766CCE5) (status 10))
+ (segment (start 90.836 103.97) (end 89.342 103.97) (width 0.3048) (layer F.Cu) (net 32) (tstamp 5766CCE7) (status 20))
+ (segment (start 73.66 120.992) (end 75.692 120.992) (width 0.3048) (layer F.Cu) (net 33))
+ (segment (start 73.66 115.228) (end 75.692 115.228) (width 0.3048) (layer F.Cu) (net 34))
+ (segment (start 88.0012 112.8842) (end 87.9856 112.8686) (width 0.2032) (layer F.Cu) (net 42) (status 30))
+
+ (zone (net 0) (net_name "") (layer F.Cu) (tstamp 5766EAE6) (hatch edge 0.508)
+ (connect_pads (clearance 0.508))
+ (min_thickness 0.1524)
+ (keepout (tracks allowed) (vias allowed) (copperpour not_allowed))
+ (fill (arc_segments 16) (thermal_gap 0.508) (thermal_bridge_width 0.508))
+ (polygon
+ (pts
+ (xy 105.156 111.76) (xy 105.156 121.666) (xy 108.966 121.666) (xy 108.966 111.76)
+ )
+ )
+ )
+ (zone (net 5) (net_name GND) (layer F.Cu) (tstamp 5766EB06) (hatch edge 0.508)
+ (connect_pads yes (clearance 0.381))
+ (min_thickness 0.2032)
+ (fill yes (arc_segments 16) (thermal_gap 0.508) (thermal_bridge_width 0.508))
+ (polygon
+ (pts
+ (xy 64.516 82.296) (xy 65.278 82.296) (xy 108.458 82.296) (xy 108.458 136.906) (xy 64.516 136.906)
+ (xy 64.516 134.62)
+ )
+ )
+ (filled_polygon
+ (pts
+ (xy 81.144443 84.378917) (xy 81.9 85.134474) (xy 82.655557 84.378917) (xy 82.655494 84.3788) (xy 86.177405 84.3788)
+ (xy 85.844129 84.516507) (xy 85.567478 84.792675) (xy 85.417571 85.153691) (xy 85.41723 85.544594) (xy 85.566507 85.905871)
+ (xy 85.842675 86.182522) (xy 86.203691 86.332429) (xy 86.594594 86.33277) (xy 86.955871 86.183493) (xy 87.232522 85.907325)
+ (xy 87.382429 85.546309) (xy 87.38277 85.155406) (xy 87.233493 84.794129) (xy 86.957325 84.517478) (xy 86.623351 84.3788)
+ (xy 90.215896 84.3788) (xy 90.028572 84.565797) (xy 89.966617 84.715) (xy 89.148 84.715) (xy 88.904996 84.763336)
+ (xy 88.698987 84.900987) (xy 84.386987 89.212987) (xy 84.249336 89.418996) (xy 84.201 89.662) (xy 84.201 97.184974)
+ (xy 82.515028 98.870946) (xy 81.939 98.870946) (xy 81.760159 98.904597) (xy 81.595905 99.010292) (xy 81.485713 99.171563)
+ (xy 81.446946 99.363) (xy 81.446946 100.613) (xy 81.480597 100.791841) (xy 81.534 100.874831) (xy 81.534 101.12756)
+ (xy 81.375905 101.229292) (xy 81.265713 101.390563) (xy 81.226946 101.582) (xy 81.226946 102.282) (xy 81.260597 102.460841)
+ (xy 81.366292 102.625095) (xy 81.527563 102.735287) (xy 81.719 102.774054) (xy 82.619 102.774054) (xy 82.797841 102.740403)
+ (xy 82.962095 102.634708) (xy 83.072287 102.473437) (xy 83.111054 102.282) (xy 83.111054 101.582) (xy 83.077403 101.403159)
+ (xy 82.971708 101.238905) (xy 82.810437 101.128713) (xy 82.804 101.127409) (xy 82.804 101.105054) (xy 82.939 101.105054)
+ (xy 83.117841 101.071403) (xy 83.282095 100.965708) (xy 83.392287 100.804437) (xy 83.431054 100.613) (xy 83.431054 99.750972)
+ (xy 83.446946 99.73508) (xy 83.446946 100.613) (xy 83.480597 100.791841) (xy 83.586292 100.956095) (xy 83.747563 101.066287)
+ (xy 83.82 101.080956) (xy 83.82 101.12756) (xy 83.661905 101.229292) (xy 83.551713 101.390563) (xy 83.512946 101.582)
+ (xy 83.512946 102.282) (xy 83.546597 102.460841) (xy 83.652292 102.625095) (xy 83.813563 102.735287) (xy 84.005 102.774054)
+ (xy 84.905 102.774054) (xy 85.083841 102.740403) (xy 85.248095 102.634708) (xy 85.358287 102.473437) (xy 85.397054 102.282)
+ (xy 85.397054 101.582) (xy 85.363403 101.403159) (xy 85.258413 101.24) (xy 86.624946 101.24) (xy 86.624946 102.3)
+ (xy 86.658597 102.478841) (xy 86.764292 102.643095) (xy 86.925563 102.753287) (xy 87.117 102.792054) (xy 87.767 102.792054)
+ (xy 87.945841 102.758403) (xy 88.110095 102.652708) (xy 88.220287 102.491437) (xy 88.259054 102.3) (xy 88.259054 101.24)
+ (xy 88.524946 101.24) (xy 88.524946 102.3) (xy 88.558597 102.478841) (xy 88.664292 102.643095) (xy 88.825563 102.753287)
+ (xy 89.017 102.792054) (xy 89.667 102.792054) (xy 89.845841 102.758403) (xy 90.010095 102.652708) (xy 90.120287 102.491437)
+ (xy 90.159054 102.3) (xy 90.159054 102.235) (xy 90.520337 102.235) (xy 90.531597 102.294841) (xy 90.637292 102.459095)
+ (xy 90.798563 102.569287) (xy 90.99 102.608054) (xy 91.89 102.608054) (xy 92.068841 102.574403) (xy 92.233095 102.468708)
+ (xy 92.343287 102.307437) (xy 92.357956 102.235) (xy 92.908701 102.235) (xy 92.934443 102.2973) (xy 93.280877 102.644338)
+ (xy 93.733745 102.832386) (xy 94.224104 102.832813) (xy 94.6773 102.645557) (xy 94.861198 102.461979) (xy 97.397547 102.461979)
+ (xy 97.480077 102.635452) (xy 97.919254 102.772437) (xy 98.377421 102.730929) (xy 98.607923 102.635452) (xy 98.690453 102.461979)
+ (xy 98.044 101.815526) (xy 97.397547 102.461979) (xy 94.861198 102.461979) (xy 95.024338 102.299123) (xy 95.212386 101.846255)
+ (xy 95.212709 101.475254) (xy 96.871563 101.475254) (xy 96.913071 101.933421) (xy 97.008548 102.163923) (xy 97.182021 102.246453)
+ (xy 97.828474 101.6) (xy 98.259526 101.6) (xy 98.905979 102.246453) (xy 99.079452 102.163923) (xy 99.216437 101.724746)
+ (xy 99.174929 101.266579) (xy 99.079452 101.036077) (xy 98.905979 100.953547) (xy 98.259526 101.6) (xy 97.828474 101.6)
+ (xy 97.182021 100.953547) (xy 97.008548 101.036077) (xy 96.871563 101.475254) (xy 95.212709 101.475254) (xy 95.212813 101.355896)
+ (xy 95.025557 100.9027) (xy 94.861165 100.738021) (xy 97.397547 100.738021) (xy 98.044 101.384474) (xy 98.690453 100.738021)
+ (xy 98.607923 100.564548) (xy 98.168746 100.427563) (xy 97.710579 100.469071) (xy 97.480077 100.564548) (xy 97.397547 100.738021)
+ (xy 94.861165 100.738021) (xy 94.679123 100.555662) (xy 94.226255 100.367614) (xy 93.735896 100.367187) (xy 93.2827 100.554443)
+ (xy 92.935662 100.900877) (xy 92.909036 100.965) (xy 92.084784 100.965) (xy 92.081437 100.962713) (xy 91.89 100.923946)
+ (xy 90.99 100.923946) (xy 90.811159 100.957597) (xy 90.799654 100.965) (xy 90.063526 100.965) (xy 90.019708 100.896905)
+ (xy 89.858437 100.786713) (xy 89.667 100.747946) (xy 89.017 100.747946) (xy 88.838159 100.781597) (xy 88.673905 100.887292)
+ (xy 88.563713 101.048563) (xy 88.524946 101.24) (xy 88.259054 101.24) (xy 88.225403 101.061159) (xy 88.2142 101.043749)
+ (xy 88.2142 100.327673) (xy 88.302957 100.113923) (xy 88.303243 99.785518) (xy 88.177833 99.482002) (xy 88.000245 99.304104)
+ (xy 92.747187 99.304104) (xy 92.934443 99.7573) (xy 93.280877 100.104338) (xy 93.733745 100.292386) (xy 94.224104 100.292813)
+ (xy 94.6773 100.105557) (xy 95.024338 99.759123) (xy 95.212386 99.306255) (xy 95.212813 98.815896) (xy 95.025557 98.3627)
+ (xy 94.679123 98.015662) (xy 94.226255 97.827614) (xy 93.735896 97.827187) (xy 93.2827 98.014443) (xy 92.935662 98.360877)
+ (xy 92.747614 98.813745) (xy 92.747187 99.304104) (xy 88.000245 99.304104) (xy 87.945819 99.249583) (xy 87.642523 99.123643)
+ (xy 87.314118 99.123357) (xy 87.010602 99.248767) (xy 86.778183 99.480781) (xy 86.652243 99.784077) (xy 86.651957 100.112482)
+ (xy 86.741 100.327983) (xy 86.741 100.93545) (xy 86.663713 101.048563) (xy 86.624946 101.24) (xy 85.258413 101.24)
+ (xy 85.257708 101.238905) (xy 85.096437 101.128713) (xy 85.09 101.127409) (xy 85.09 101.076642) (xy 85.117841 101.071403)
+ (xy 85.282095 100.965708) (xy 85.392287 100.804437) (xy 85.431054 100.613) (xy 85.431054 100.378972) (xy 86.669972 99.140054)
+ (xy 86.937 99.140054) (xy 87.115841 99.106403) (xy 87.280095 99.000708) (xy 87.390287 98.839437) (xy 87.429054 98.648)
+ (xy 87.429054 97.948) (xy 87.395403 97.769159) (xy 87.289708 97.604905) (xy 87.128437 97.494713) (xy 86.937 97.455946)
+ (xy 86.708841 97.455946) (xy 86.732625 97.440054) (xy 86.937 97.440054) (xy 87.115841 97.406403) (xy 87.280095 97.300708)
+ (xy 87.390287 97.139437) (xy 87.429054 96.948) (xy 87.429054 96.248) (xy 87.395403 96.069159) (xy 87.289708 95.904905)
+ (xy 87.128437 95.794713) (xy 86.995 95.767691) (xy 86.995 94.400245) (xy 87.215797 94.621428) (xy 87.723777 94.83236)
+ (xy 88.27381 94.83284) (xy 88.42312 94.771146) (xy 90.512773 96.860799) (xy 90.531597 96.960841) (xy 90.637292 97.125095)
+ (xy 90.798563 97.235287) (xy 90.99 97.274054) (xy 91.89 97.274054) (xy 92.068841 97.240403) (xy 92.20156 97.155)
+ (xy 92.908701 97.155) (xy 92.934443 97.2173) (xy 93.280877 97.564338) (xy 93.733745 97.752386) (xy 94.224104 97.752813)
+ (xy 94.6773 97.565557) (xy 95.024338 97.219123) (xy 95.212386 96.766255) (xy 95.212813 96.275896) (xy 95.025557 95.8227)
+ (xy 94.679123 95.475662) (xy 94.226255 95.287614) (xy 93.735896 95.287187) (xy 93.2827 95.474443) (xy 92.935662 95.820877)
+ (xy 92.909036 95.885) (xy 92.336718 95.885) (xy 92.242708 95.738905) (xy 92.081437 95.628713) (xy 91.89 95.589946)
+ (xy 91.037972 95.589946) (xy 89.321299 93.873273) (xy 89.38236 93.726223) (xy 89.38284 93.17619) (xy 89.172795 92.667843)
+ (xy 88.784203 92.278572) (xy 88.293948 92.075) (xy 88.636974 92.075) (xy 90.990985 94.42901) (xy 90.990987 94.429013)
+ (xy 91.11448 94.511528) (xy 91.196995 94.566664) (xy 91.44 94.615) (xy 92.908701 94.615) (xy 92.934443 94.6773)
+ (xy 93.280877 95.024338) (xy 93.733745 95.212386) (xy 94.224104 95.212813) (xy 94.6773 95.025557) (xy 95.024338 94.679123)
+ (xy 95.212386 94.226255) (xy 95.212813 93.735896) (xy 95.025557 93.2827) (xy 94.679123 92.935662) (xy 94.226255 92.747614)
+ (xy 93.735896 92.747187) (xy 93.2827 92.934443) (xy 92.935662 93.280877) (xy 92.909036 93.345) (xy 91.703025 93.345)
+ (xy 90.04213 91.684104) (xy 92.747187 91.684104) (xy 92.934443 92.1373) (xy 93.280877 92.484338) (xy 93.733745 92.672386)
+ (xy 94.224104 92.672813) (xy 94.6773 92.485557) (xy 94.838714 92.324424) (xy 95.2754 92.76111) (xy 95.2754 97.79)
+ (xy 95.33147 98.071885) (xy 95.491145 98.310855) (xy 96.761145 99.580855) (xy 96.988233 99.732591) (xy 96.998443 99.7573)
+ (xy 97.344877 100.104338) (xy 97.797745 100.292386) (xy 98.288104 100.292813) (xy 98.7413 100.105557) (xy 99.088338 99.759123)
+ (xy 99.276386 99.306255) (xy 99.276548 99.120592) (xy 99.2886 99.06) (xy 99.276653 98.999937) (xy 99.276813 98.815896)
+ (xy 99.089557 98.3627) (xy 98.743123 98.015662) (xy 98.290255 97.827614) (xy 97.799896 97.827187) (xy 97.3467 98.014443)
+ (xy 97.312397 98.048687) (xy 96.7486 97.48489) (xy 96.7486 92.456) (xy 96.69253 92.174115) (xy 96.532855 91.935145)
+ (xy 95.516855 90.919145) (xy 95.277885 90.75947) (xy 94.996 90.7034) (xy 94.986325 90.7034) (xy 94.679123 90.395662)
+ (xy 94.226255 90.207614) (xy 93.735896 90.207187) (xy 93.2827 90.394443) (xy 92.935662 90.740877) (xy 92.747614 91.193745)
+ (xy 92.747187 91.684104) (xy 90.04213 91.684104) (xy 89.349013 90.990987) (xy 89.143004 90.853336) (xy 88.9 90.805)
+ (xy 87.376 90.805) (xy 87.265919 90.826896) (xy 87.382429 90.546309) (xy 87.38277 90.155406) (xy 87.233493 89.794129)
+ (xy 87.2014 89.761979) (xy 93.333547 89.761979) (xy 93.416077 89.935452) (xy 93.855254 90.072437) (xy 94.313421 90.030929)
+ (xy 94.543923 89.935452) (xy 94.626453 89.761979) (xy 93.98 89.115526) (xy 93.333547 89.761979) (xy 87.2014 89.761979)
+ (xy 86.957325 89.517478) (xy 86.596309 89.367571) (xy 86.205406 89.36723) (xy 85.904438 89.491588) (xy 86.620772 88.775254)
+ (xy 92.807563 88.775254) (xy 92.849071 89.233421) (xy 92.944548 89.463923) (xy 93.118021 89.546453) (xy 93.764474 88.9)
+ (xy 94.195526 88.9) (xy 94.841979 89.546453) (xy 95.015452 89.463923) (xy 95.152437 89.024746) (xy 95.110929 88.566579)
+ (xy 95.015452 88.336077) (xy 94.841979 88.253547) (xy 94.195526 88.9) (xy 93.764474 88.9) (xy 93.118021 88.253547)
+ (xy 92.944548 88.336077) (xy 92.807563 88.775254) (xy 86.620772 88.775254) (xy 89.411026 85.985) (xy 89.966401 85.985)
+ (xy 90.027205 86.132157) (xy 90.415797 86.521428) (xy 90.923777 86.73236) (xy 91.47381 86.73284) (xy 91.8464 86.578889)
+ (xy 91.8464 86.614) (xy 91.853868 86.652231) (xy 91.876158 86.685842) (xy 92.892158 87.701842) (xy 92.924472 87.723595)
+ (xy 92.964 87.7316) (xy 94.060185 87.7316) (xy 93.646579 87.769071) (xy 93.416077 87.864548) (xy 93.333547 88.038021)
+ (xy 93.98 88.684474) (xy 94.626453 88.038021) (xy 94.543923 87.864548) (xy 94.117689 87.7316) (xy 94.996 87.7316)
+ (xy 95.034231 87.724132) (xy 95.067842 87.701842) (xy 95.562987 87.206697) (xy 96.2914 87.93511) (xy 96.2914 90.678)
+ (xy 96.34747 90.959885) (xy 96.507145 91.198855) (xy 97.3074 91.99911) (xy 97.3074 95.513675) (xy 96.999662 95.820877)
+ (xy 96.811614 96.273745) (xy 96.811187 96.764104) (xy 96.998443 97.2173) (xy 97.344877 97.564338) (xy 97.797745 97.752386)
+ (xy 98.288104 97.752813) (xy 98.7413 97.565557) (xy 99.088338 97.219123) (xy 99.276386 96.766255) (xy 99.276813 96.275896)
+ (xy 99.089557 95.8227) (xy 98.7806 95.513204) (xy 98.7806 91.694) (xy 98.72453 91.412115) (xy 98.564855 91.173145)
+ (xy 97.7646 90.37289) (xy 97.7646 87.63) (xy 97.70853 87.348115) (xy 97.548855 87.109145) (xy 96.278855 85.839145)
+ (xy 96.039885 85.67947) (xy 95.758 85.6234) (xy 95.6056 85.6234) (xy 95.6056 84.3788) (xy 106.1212 84.3788)
+ (xy 106.1212 111.6584) (xy 105.156 111.6584) (xy 105.119073 111.665348) (xy 105.085157 111.687172) (xy 105.062405 111.720472)
+ (xy 105.0544 111.76) (xy 105.0544 112.5491) (xy 104.4194 112.5491) (xy 104.4194 113.7031) (xy 104.4394 113.7031)
+ (xy 104.4394 114.0079) (xy 104.4194 114.0079) (xy 104.4194 115.1619) (xy 105.0544 115.1619) (xy 105.0544 117.543446)
+ (xy 102.267 117.543446) (xy 102.088159 117.577097) (xy 101.951476 117.66505) (xy 100.677413 116.390987) (xy 100.471404 116.253336)
+ (xy 100.2284 116.205) (xy 95.051299 116.205) (xy 95.025557 116.1427) (xy 94.679123 115.795662) (xy 94.226255 115.607614)
+ (xy 93.735896 115.607187) (xy 93.2827 115.794443) (xy 92.935662 116.140877) (xy 92.747614 116.593745) (xy 92.747187 117.084104)
+ (xy 92.934443 117.5373) (xy 93.280877 117.884338) (xy 93.733745 118.072386) (xy 94.224104 118.072813) (xy 94.6773 117.885557)
+ (xy 95.024338 117.539123) (xy 95.050964 117.475) (xy 99.965374 117.475) (xy 101.774946 119.284572) (xy 101.774946 119.8355)
+ (xy 101.808597 120.014341) (xy 101.914292 120.178595) (xy 102.075563 120.288787) (xy 102.267 120.327554) (xy 105.0544 120.327554)
+ (xy 105.0544 121.666) (xy 105.061348 121.702927) (xy 105.083172 121.736843) (xy 105.116472 121.759595) (xy 105.156 121.7676)
+ (xy 106.1212 121.7676) (xy 106.1212 134.0612) (xy 92.30614 134.0612) (xy 92.30614 131.74218) (xy 92.15374 131.58978)
+ (xy 86.76894 131.58978) (xy 86.76894 131.60978) (xy 86.46414 131.60978) (xy 86.46414 131.58978) (xy 81.07934 131.58978)
+ (xy 80.92694 131.74218) (xy 80.92694 134.0612) (xy 66.5988 134.0612) (xy 66.5988 128.776123) (xy 80.92694 128.776123)
+ (xy 80.92694 131.13258) (xy 81.07934 131.28498) (xy 86.46414 131.28498) (xy 86.46414 128.44018) (xy 86.76894 128.44018)
+ (xy 86.76894 131.28498) (xy 92.15374 131.28498) (xy 92.30614 131.13258) (xy 92.30614 128.776123) (xy 92.213334 128.552069)
+ (xy 92.041851 128.380586) (xy 91.817797 128.28778) (xy 86.92134 128.28778) (xy 86.76894 128.44018) (xy 86.46414 128.44018)
+ (xy 86.31174 128.28778) (xy 81.415283 128.28778) (xy 81.191229 128.380586) (xy 81.019746 128.552069) (xy 80.92694 128.776123)
+ (xy 66.5988 128.776123) (xy 66.5988 120.642) (xy 72.717946 120.642) (xy 72.717946 121.342) (xy 72.751597 121.520841)
+ (xy 72.857292 121.685095) (xy 73.018563 121.795287) (xy 73.21 121.834054) (xy 74.11 121.834054) (xy 74.288841 121.800403)
+ (xy 74.453095 121.694708) (xy 74.499358 121.627) (xy 74.851909 121.627) (xy 74.889292 121.685095) (xy 75.050563 121.795287)
+ (xy 75.242 121.834054) (xy 76.142 121.834054) (xy 76.320841 121.800403) (xy 76.485095 121.694708) (xy 76.595287 121.533437)
+ (xy 76.634054 121.342) (xy 76.634054 120.642) (xy 76.600403 120.463159) (xy 76.494708 120.298905) (xy 76.333437 120.188713)
+ (xy 76.142 120.149946) (xy 75.242 120.149946) (xy 75.063159 120.183597) (xy 74.898905 120.289292) (xy 74.852642 120.357)
+ (xy 74.500091 120.357) (xy 74.462708 120.298905) (xy 74.301437 120.188713) (xy 74.11 120.149946) (xy 73.21 120.149946)
+ (xy 73.031159 120.183597) (xy 72.866905 120.289292) (xy 72.756713 120.450563) (xy 72.717946 120.642) (xy 66.5988 120.642)
+ (xy 66.5988 118.942) (xy 74.749946 118.942) (xy 74.749946 119.642) (xy 74.783597 119.820841) (xy 74.889292 119.985095)
+ (xy 75.050563 120.095287) (xy 75.242 120.134054) (xy 76.142 120.134054) (xy 76.320841 120.100403) (xy 76.45356 120.015)
+ (xy 77.668701 120.015) (xy 77.694443 120.0773) (xy 78.040877 120.424338) (xy 78.493745 120.612386) (xy 78.984104 120.612813)
+ (xy 79.4373 120.425557) (xy 79.784338 120.079123) (xy 79.972386 119.626255) (xy 79.972387 119.624104) (xy 92.747187 119.624104)
+ (xy 92.934443 120.0773) (xy 93.280877 120.424338) (xy 93.733745 120.612386) (xy 94.224104 120.612813) (xy 94.6773 120.425557)
+ (xy 95.024338 120.079123) (xy 95.212386 119.626255) (xy 95.212813 119.135896) (xy 95.025557 118.6827) (xy 94.679123 118.335662)
+ (xy 94.226255 118.147614) (xy 93.735896 118.147187) (xy 93.2827 118.334443) (xy 92.935662 118.680877) (xy 92.747614 119.133745)
+ (xy 92.747187 119.624104) (xy 79.972387 119.624104) (xy 79.972813 119.135896) (xy 79.785557 118.6827) (xy 79.439123 118.335662)
+ (xy 78.986255 118.147614) (xy 78.495896 118.147187) (xy 78.0427 118.334443) (xy 77.695662 118.680877) (xy 77.669036 118.745)
+ (xy 76.588718 118.745) (xy 76.494708 118.598905) (xy 76.333437 118.488713) (xy 76.142 118.449946) (xy 75.242 118.449946)
+ (xy 75.063159 118.483597) (xy 74.898905 118.589292) (xy 74.788713 118.750563) (xy 74.749946 118.942) (xy 66.5988 118.942)
+ (xy 66.5988 116.578) (xy 74.749946 116.578) (xy 74.749946 117.278) (xy 74.783597 117.456841) (xy 74.889292 117.621095)
+ (xy 75.050563 117.731287) (xy 75.242 117.770054) (xy 76.142 117.770054) (xy 76.320841 117.736403) (xy 76.485095 117.630708)
+ (xy 76.591486 117.475) (xy 77.668701 117.475) (xy 77.694443 117.5373) (xy 78.040877 117.884338) (xy 78.493745 118.072386)
+ (xy 78.984104 118.072813) (xy 79.4373 117.885557) (xy 79.784338 117.539123) (xy 79.972386 117.086255) (xy 79.972813 116.595896)
+ (xy 79.785557 116.1427) (xy 79.439123 115.795662) (xy 78.986255 115.607614) (xy 78.495896 115.607187) (xy 78.0427 115.794443)
+ (xy 77.695662 116.140877) (xy 77.669036 116.205) (xy 76.450941 116.205) (xy 76.333437 116.124713) (xy 76.142 116.085946)
+ (xy 75.242 116.085946) (xy 75.063159 116.119597) (xy 74.898905 116.225292) (xy 74.788713 116.386563) (xy 74.749946 116.578)
+ (xy 66.5988 116.578) (xy 66.5988 114.878) (xy 72.717946 114.878) (xy 72.717946 115.578) (xy 72.751597 115.756841)
+ (xy 72.857292 115.921095) (xy 73.018563 116.031287) (xy 73.21 116.070054) (xy 74.11 116.070054) (xy 74.288841 116.036403)
+ (xy 74.453095 115.930708) (xy 74.499358 115.863) (xy 74.851909 115.863) (xy 74.889292 115.921095) (xy 75.050563 116.031287)
+ (xy 75.242 116.070054) (xy 76.142 116.070054) (xy 76.320841 116.036403) (xy 76.485095 115.930708) (xy 76.595287 115.769437)
+ (xy 76.634054 115.578) (xy 76.634054 114.878) (xy 76.600403 114.699159) (xy 76.500628 114.544104) (xy 77.507187 114.544104)
+ (xy 77.694443 114.9973) (xy 78.040877 115.344338) (xy 78.493745 115.532386) (xy 78.984104 115.532813) (xy 79.4373 115.345557)
+ (xy 79.784338 114.999123) (xy 79.962526 114.57) (xy 82.1944 114.57) (xy 82.194946 114.572745) (xy 82.194946 115.07)
+ (xy 82.228597 115.248841) (xy 82.334292 115.413095) (xy 82.495563 115.523287) (xy 82.687 115.562054) (xy 83.937 115.562054)
+ (xy 84.115841 115.528403) (xy 84.280095 115.422708) (xy 84.359428 115.3066) (xy 84.810807 115.3066) (xy 84.820577 115.310657)
+ (xy 85.135635 115.310931) (xy 85.17147 115.491085) (xy 85.331145 115.730055) (xy 85.570115 115.88973) (xy 85.852 115.9458)
+ (xy 86.084146 115.899624) (xy 86.084146 116.0342) (xy 86.117797 116.213041) (xy 86.223492 116.377295) (xy 86.384763 116.487487)
+ (xy 86.5124 116.513334) (xy 86.5124 117.856) (xy 86.519348 117.892927) (xy 86.541172 117.926843) (xy 86.574472 117.949595)
+ (xy 86.614 117.9576) (xy 91.948 117.9576) (xy 91.984927 117.950652) (xy 92.018843 117.928828) (xy 92.041595 117.895528)
+ (xy 92.0496 117.856) (xy 92.0496 114.455365) (xy 92.234092 114.332092) (xy 92.914611 113.651573) (xy 92.747614 114.053745)
+ (xy 92.747187 114.544104) (xy 92.934443 114.9973) (xy 93.280877 115.344338) (xy 93.733745 115.532386) (xy 94.224104 115.532813)
+ (xy 94.6773 115.345557) (xy 95.024338 114.999123) (xy 95.212386 114.546255) (xy 95.212679 114.208994) (xy 101.909335 114.208994)
+ (xy 102.135473 114.671501) (xy 102.526263 115.004135) (xy 103.0146 115.1619) (xy 104.1146 115.1619) (xy 104.1146 114.0079)
+ (xy 101.971878 114.0079) (xy 101.909335 114.208994) (xy 95.212679 114.208994) (xy 95.212813 114.055896) (xy 95.025557 113.6027)
+ (xy 94.925039 113.502006) (xy 101.909335 113.502006) (xy 101.971878 113.7031) (xy 104.1146 113.7031) (xy 104.1146 112.5491)
+ (xy 103.0146 112.5491) (xy 102.526263 112.706865) (xy 102.135473 113.039499) (xy 101.909335 113.502006) (xy 94.925039 113.502006)
+ (xy 94.679123 113.255662) (xy 94.226255 113.067614) (xy 93.735896 113.067187) (xy 93.33219 113.233994) (xy 93.620727 112.945457)
+ (xy 93.733745 112.992386) (xy 94.224104 112.992813) (xy 94.6773 112.805557) (xy 95.024338 112.459123) (xy 95.212386 112.006255)
+ (xy 95.212813 111.515896) (xy 95.025557 111.0627) (xy 94.679123 110.715662) (xy 94.226255 110.527614) (xy 93.735896 110.527187)
+ (xy 93.2827 110.714443) (xy 92.935662 111.060877) (xy 92.747614 111.513745) (xy 92.747187 112.004104) (xy 92.794706 112.11911)
+ (xy 92.0496 112.864216) (xy 92.0496 112.014) (xy 92.042652 111.977073) (xy 92.020828 111.943157) (xy 91.987528 111.920405)
+ (xy 91.948 111.9124) (xy 90.052581 111.9124) (xy 90.131757 111.721723) (xy 90.132043 111.393318) (xy 90.006633 111.089802)
+ (xy 89.774619 110.857383) (xy 89.471323 110.731443) (xy 89.142918 110.731157) (xy 88.839402 110.856567) (xy 88.606983 111.088581)
+ (xy 88.481043 111.391877) (xy 88.480757 111.720282) (xy 88.560138 111.9124) (xy 86.614 111.9124) (xy 86.577073 111.919348)
+ (xy 86.543157 111.941172) (xy 86.520405 111.974472) (xy 86.5124 112.014) (xy 86.5124 113.254151) (xy 86.397359 113.275797)
+ (xy 86.233105 113.381492) (xy 86.122913 113.542763) (xy 86.084146 113.7342) (xy 86.084146 113.882759) (xy 85.836 113.8334)
+ (xy 85.501153 113.8334) (xy 85.453719 113.785883) (xy 85.150423 113.659943) (xy 84.822018 113.659657) (xy 84.518502 113.785067)
+ (xy 84.470085 113.8334) (xy 84.358236 113.8334) (xy 84.289708 113.726905) (xy 84.128437 113.616713) (xy 83.937 113.577946)
+ (xy 82.687 113.577946) (xy 82.508159 113.611597) (xy 82.343905 113.717292) (xy 82.233713 113.878563) (xy 82.194946 114.07)
+ (xy 82.194946 114.567255) (xy 82.1944 114.57) (xy 79.962526 114.57) (xy 79.972386 114.546255) (xy 79.972813 114.055896)
+ (xy 79.785557 113.6027) (xy 79.439123 113.255662) (xy 78.986255 113.067614) (xy 78.495896 113.067187) (xy 78.0427 113.254443)
+ (xy 77.695662 113.600877) (xy 77.507614 114.053745) (xy 77.507187 114.544104) (xy 76.500628 114.544104) (xy 76.494708 114.534905)
+ (xy 76.333437 114.424713) (xy 76.142 114.385946) (xy 75.242 114.385946) (xy 75.063159 114.419597) (xy 74.898905 114.525292)
+ (xy 74.852642 114.593) (xy 74.500091 114.593) (xy 74.462708 114.534905) (xy 74.301437 114.424713) (xy 74.11 114.385946)
+ (xy 73.21 114.385946) (xy 73.031159 114.419597) (xy 72.866905 114.525292) (xy 72.756713 114.686563) (xy 72.717946 114.878)
+ (xy 66.5988 114.878) (xy 66.5988 104.384104) (xy 74.205187 104.384104) (xy 74.392443 104.8373) (xy 74.738877 105.184338)
+ (xy 75.191745 105.372386) (xy 75.682104 105.372813) (xy 75.746273 105.346299) (xy 75.963571 105.563597) (xy 75.684255 105.447614)
+ (xy 75.193896 105.447187) (xy 74.7407 105.634443) (xy 74.393662 105.980877) (xy 74.205614 106.433745) (xy 74.205187 106.924104)
+ (xy 74.392443 107.3773) (xy 74.738877 107.724338) (xy 75.191745 107.912386) (xy 75.682104 107.912813) (xy 75.746273 107.886299)
+ (xy 75.963571 108.103597) (xy 75.684255 107.987614) (xy 75.193896 107.987187) (xy 74.7407 108.174443) (xy 74.393662 108.520877)
+ (xy 74.205614 108.973745) (xy 74.205187 109.464104) (xy 74.392443 109.9173) (xy 74.738877 110.264338) (xy 75.191745 110.452386)
+ (xy 75.682104 110.452813) (xy 75.746273 110.426299) (xy 77.528985 112.20901) (xy 77.528987 112.209013) (xy 77.615829 112.267039)
+ (xy 77.694443 112.4573) (xy 78.040877 112.804338) (xy 78.493745 112.992386) (xy 78.984104 112.992813) (xy 79.4373 112.805557)
+ (xy 79.784338 112.459123) (xy 79.972386 112.006255) (xy 79.972813 111.515896) (xy 79.785557 111.0627) (xy 79.439123 110.715662)
+ (xy 78.986255 110.527614) (xy 78.495896 110.527187) (xy 78.0427 110.714443) (xy 77.936492 110.820466) (xy 76.644535 109.52851)
+ (xy 76.670386 109.466255) (xy 76.670813 108.975896) (xy 76.554591 108.694617) (xy 77.528985 109.66901) (xy 77.528987 109.669013)
+ (xy 77.615829 109.727039) (xy 77.694443 109.9173) (xy 78.040877 110.264338) (xy 78.493745 110.452386) (xy 78.984104 110.452813)
+ (xy 79.4373 110.265557) (xy 79.784338 109.919123) (xy 79.972386 109.466255) (xy 79.972418 109.429083) (xy 83.064443 109.429083)
+ (xy 83.165509 109.618108) (xy 83.659283 109.780263) (xy 84.177525 109.741115) (xy 84.474491 109.618108) (xy 84.575557 109.429083)
+ (xy 83.82 108.673526) (xy 83.064443 109.429083) (xy 79.972418 109.429083) (xy 79.972813 108.975896) (xy 79.785557 108.5227)
+ (xy 79.560533 108.297283) (xy 82.497737 108.297283) (xy 82.536885 108.815525) (xy 82.659892 109.112491) (xy 82.848917 109.213557)
+ (xy 83.604474 108.458) (xy 84.035526 108.458) (xy 84.791083 109.213557) (xy 84.980108 109.112491) (xy 85.142263 108.618717)
+ (xy 85.103115 108.100475) (xy 84.980108 107.803509) (xy 84.791083 107.702443) (xy 84.035526 108.458) (xy 83.604474 108.458)
+ (xy 82.848917 107.702443) (xy 82.659892 107.803509) (xy 82.497737 108.297283) (xy 79.560533 108.297283) (xy 79.439123 108.175662)
+ (xy 78.986255 107.987614) (xy 78.495896 107.987187) (xy 78.0427 108.174443) (xy 77.936492 108.280466) (xy 76.644535 106.98851)
+ (xy 76.670386 106.926255) (xy 76.670813 106.435896) (xy 76.554591 106.154617) (xy 77.528985 107.12901) (xy 77.528987 107.129013)
+ (xy 77.615829 107.187039) (xy 77.694443 107.3773) (xy 78.040877 107.724338) (xy 78.493745 107.912386) (xy 78.984104 107.912813)
+ (xy 79.4373 107.725557) (xy 79.676356 107.486917) (xy 83.064443 107.486917) (xy 83.82 108.242474) (xy 84.404474 107.658)
+ (xy 87.607946 107.658) (xy 87.607946 109.258) (xy 87.641597 109.436841) (xy 87.747292 109.601095) (xy 87.908563 109.711287)
+ (xy 88.1 109.750054) (xy 89.7 109.750054) (xy 89.878841 109.716403) (xy 90.043095 109.610708) (xy 90.143265 109.464104)
+ (xy 92.747187 109.464104) (xy 92.934443 109.9173) (xy 93.280877 110.264338) (xy 93.733745 110.452386) (xy 94.224104 110.452813)
+ (xy 94.6773 110.265557) (xy 95.024338 109.919123) (xy 95.050964 109.855) (xy 96.972701 109.855) (xy 96.998443 109.9173)
+ (xy 97.344877 110.264338) (xy 97.797745 110.452386) (xy 98.288104 110.452813) (xy 98.7413 110.265557) (xy 99.088338 109.919123)
+ (xy 99.276386 109.466255) (xy 99.276813 108.975896) (xy 99.089557 108.5227) (xy 98.743123 108.175662) (xy 98.290255 107.987614)
+ (xy 97.799896 107.987187) (xy 97.3467 108.174443) (xy 96.999662 108.520877) (xy 96.973036 108.585) (xy 95.051299 108.585)
+ (xy 95.025557 108.5227) (xy 94.679123 108.175662) (xy 94.226255 107.987614) (xy 93.735896 107.987187) (xy 93.2827 108.174443)
+ (xy 92.935662 108.520877) (xy 92.747614 108.973745) (xy 92.747187 109.464104) (xy 90.143265 109.464104) (xy 90.153287 109.449437)
+ (xy 90.192054 109.258) (xy 90.192054 107.658) (xy 90.158403 107.479159) (xy 90.052708 107.314905) (xy 89.977 107.263176)
+ (xy 89.977 106.924104) (xy 92.747187 106.924104) (xy 92.934443 107.3773) (xy 93.280877 107.724338) (xy 93.733745 107.912386)
+ (xy 94.224104 107.912813) (xy 94.6773 107.725557) (xy 95.024338 107.379123) (xy 95.050964 107.315) (xy 96.972701 107.315)
+ (xy 96.998443 107.3773) (xy 97.344877 107.724338) (xy 97.797745 107.912386) (xy 98.288104 107.912813) (xy 98.7413 107.725557)
+ (xy 99.088338 107.379123) (xy 99.276386 106.926255) (xy 99.276813 106.435896) (xy 99.089557 105.9827) (xy 98.743123 105.635662)
+ (xy 98.290255 105.447614) (xy 97.799896 105.447187) (xy 97.3467 105.634443) (xy 96.999662 105.980877) (xy 96.973036 106.045)
+ (xy 95.051299 106.045) (xy 95.025557 105.9827) (xy 94.679123 105.635662) (xy 94.226255 105.447614) (xy 93.735896 105.447187)
+ (xy 93.2827 105.634443) (xy 92.935662 105.980877) (xy 92.747614 106.433745) (xy 92.747187 106.924104) (xy 89.977 106.924104)
+ (xy 89.977 104.874004) (xy 90.010095 104.852708) (xy 90.120287 104.691437) (xy 90.137791 104.605) (xy 90.836 104.605)
+ (xy 91.079004 104.556664) (xy 91.285013 104.419013) (xy 91.319922 104.384104) (xy 92.747187 104.384104) (xy 92.934443 104.8373)
+ (xy 93.280877 105.184338) (xy 93.733745 105.372386) (xy 94.224104 105.372813) (xy 94.6773 105.185557) (xy 95.024338 104.839123)
+ (xy 95.050964 104.775) (xy 96.972701 104.775) (xy 96.998443 104.8373) (xy 97.344877 105.184338) (xy 97.797745 105.372386)
+ (xy 98.288104 105.372813) (xy 98.7413 105.185557) (xy 99.088338 104.839123) (xy 99.276386 104.386255) (xy 99.276813 103.895896)
+ (xy 99.089557 103.4427) (xy 98.743123 103.095662) (xy 98.290255 102.907614) (xy 97.799896 102.907187) (xy 97.3467 103.094443)
+ (xy 96.999662 103.440877) (xy 96.973036 103.505) (xy 95.051299 103.505) (xy 95.025557 103.4427) (xy 94.679123 103.095662)
+ (xy 94.226255 102.907614) (xy 93.735896 102.907187) (xy 93.2827 103.094443) (xy 92.935662 103.440877) (xy 92.747614 103.893745)
+ (xy 92.747187 104.384104) (xy 91.319922 104.384104) (xy 91.395972 104.308054) (xy 91.89 104.308054) (xy 92.068841 104.274403)
+ (xy 92.233095 104.168708) (xy 92.343287 104.007437) (xy 92.382054 103.816) (xy 92.382054 103.116) (xy 92.348403 102.937159)
+ (xy 92.242708 102.772905) (xy 92.081437 102.662713) (xy 91.89 102.623946) (xy 90.99 102.623946) (xy 90.811159 102.657597)
+ (xy 90.646905 102.763292) (xy 90.536713 102.924563) (xy 90.497946 103.116) (xy 90.497946 103.335) (xy 90.139297 103.335)
+ (xy 90.125403 103.261159) (xy 90.019708 103.096905) (xy 89.858437 102.986713) (xy 89.667 102.947946) (xy 89.017 102.947946)
+ (xy 88.838159 102.981597) (xy 88.673905 103.087292) (xy 88.563713 103.248563) (xy 88.524946 103.44) (xy 88.524946 104.5)
+ (xy 88.558597 104.678841) (xy 88.664292 104.843095) (xy 88.707 104.872276) (xy 88.707 107.165946) (xy 88.1 107.165946)
+ (xy 87.921159 107.199597) (xy 87.756905 107.305292) (xy 87.646713 107.466563) (xy 87.607946 107.658) (xy 84.404474 107.658)
+ (xy 84.575557 107.486917) (xy 84.474491 107.297892) (xy 83.980717 107.135737) (xy 83.462475 107.174885) (xy 83.165509 107.297892)
+ (xy 83.064443 107.486917) (xy 79.676356 107.486917) (xy 79.784338 107.379123) (xy 79.972386 106.926255) (xy 79.972813 106.435896)
+ (xy 79.785557 105.9827) (xy 79.439123 105.635662) (xy 78.986255 105.447614) (xy 78.495896 105.447187) (xy 78.0427 105.634443)
+ (xy 77.936492 105.740466) (xy 76.644535 104.44851) (xy 76.670386 104.386255) (xy 76.670387 104.384104) (xy 77.507187 104.384104)
+ (xy 77.694443 104.8373) (xy 78.040877 105.184338) (xy 78.493745 105.372386) (xy 78.984104 105.372813) (xy 79.4373 105.185557)
+ (xy 79.784338 104.839123) (xy 79.972386 104.386255) (xy 79.972813 103.895896) (xy 79.785557 103.4427) (xy 79.439123 103.095662)
+ (xy 78.986255 102.907614) (xy 78.495896 102.907187) (xy 78.0427 103.094443) (xy 77.695662 103.440877) (xy 77.507614 103.893745)
+ (xy 77.507187 104.384104) (xy 76.670387 104.384104) (xy 76.670813 103.895896) (xy 76.483557 103.4427) (xy 76.137123 103.095662)
+ (xy 75.684255 102.907614) (xy 75.193896 102.907187) (xy 74.7407 103.094443) (xy 74.393662 103.440877) (xy 74.205614 103.893745)
+ (xy 74.205187 104.384104) (xy 66.5988 104.384104) (xy 66.5988 99.304104) (xy 74.205187 99.304104) (xy 74.392443 99.7573)
+ (xy 74.738877 100.104338) (xy 75.191745 100.292386) (xy 75.682104 100.292813) (xy 75.746273 100.266299) (xy 75.963571 100.483597)
+ (xy 75.684255 100.367614) (xy 75.193896 100.367187) (xy 74.7407 100.554443) (xy 74.393662 100.900877) (xy 74.205614 101.353745)
+ (xy 74.205187 101.844104) (xy 74.392443 102.2973) (xy 74.738877 102.644338) (xy 75.191745 102.832386) (xy 75.682104 102.832813)
+ (xy 76.1353 102.645557) (xy 76.482338 102.299123) (xy 76.670386 101.846255) (xy 76.670813 101.355896) (xy 76.554591 101.074617)
+ (xy 77.528985 102.04901) (xy 77.528987 102.049013) (xy 77.615829 102.107039) (xy 77.694443 102.2973) (xy 78.040877 102.644338)
+ (xy 78.493745 102.832386) (xy 78.984104 102.832813) (xy 79.4373 102.645557) (xy 79.784338 102.299123) (xy 79.972386 101.846255)
+ (xy 79.972813 101.355896) (xy 79.785557 100.9027) (xy 79.439123 100.555662) (xy 78.986255 100.367614) (xy 78.495896 100.367187)
+ (xy 78.0427 100.554443) (xy 77.936492 100.660466) (xy 76.644535 99.36851) (xy 76.670386 99.306255) (xy 76.670387 99.304104)
+ (xy 77.507187 99.304104) (xy 77.694443 99.7573) (xy 78.040877 100.104338) (xy 78.493745 100.292386) (xy 78.984104 100.292813)
+ (xy 79.4373 100.105557) (xy 79.784338 99.759123) (xy 79.972386 99.306255) (xy 79.972813 98.815896) (xy 79.785557 98.3627)
+ (xy 79.439123 98.015662) (xy 78.986255 97.827614) (xy 78.495896 97.827187) (xy 78.0427 98.014443) (xy 77.695662 98.360877)
+ (xy 77.507614 98.813745) (xy 77.507187 99.304104) (xy 76.670387 99.304104) (xy 76.670813 98.815896) (xy 76.483557 98.3627)
+ (xy 76.137123 98.015662) (xy 75.684255 97.827614) (xy 75.193896 97.827187) (xy 74.7407 98.014443) (xy 74.393662 98.360877)
+ (xy 74.205614 98.813745) (xy 74.205187 99.304104) (xy 66.5988 99.304104) (xy 66.5988 97.381979) (xy 74.791547 97.381979)
+ (xy 74.874077 97.555452) (xy 75.313254 97.692437) (xy 75.771421 97.650929) (xy 76.001923 97.555452) (xy 76.084453 97.381979)
+ (xy 75.438 96.735526) (xy 74.791547 97.381979) (xy 66.5988 97.381979) (xy 66.5988 96.395254) (xy 74.265563 96.395254)
+ (xy 74.307071 96.853421) (xy 74.402548 97.083923) (xy 74.576021 97.166453) (xy 75.222474 96.52) (xy 75.653526 96.52)
+ (xy 76.299979 97.166453) (xy 76.473452 97.083923) (xy 76.573207 96.764104) (xy 77.507187 96.764104) (xy 77.694443 97.2173)
+ (xy 78.040877 97.564338) (xy 78.493745 97.752386) (xy 78.984104 97.752813) (xy 79.4373 97.565557) (xy 79.784338 97.219123)
+ (xy 79.972386 96.766255) (xy 79.972813 96.275896) (xy 79.785557 95.8227) (xy 79.439123 95.475662) (xy 78.986255 95.287614)
+ (xy 78.495896 95.287187) (xy 78.0427 95.474443) (xy 77.695662 95.820877) (xy 77.507614 96.273745) (xy 77.507187 96.764104)
+ (xy 76.573207 96.764104) (xy 76.610437 96.644746) (xy 76.568929 96.186579) (xy 76.473452 95.956077) (xy 76.299979 95.873547)
+ (xy 75.653526 96.52) (xy 75.222474 96.52) (xy 74.576021 95.873547) (xy 74.402548 95.956077) (xy 74.265563 96.395254)
+ (xy 66.5988 96.395254) (xy 66.5988 95.658021) (xy 74.791547 95.658021) (xy 75.438 96.304474) (xy 76.084453 95.658021)
+ (xy 76.001923 95.484548) (xy 75.562746 95.347563) (xy 75.104579 95.389071) (xy 74.874077 95.484548) (xy 74.791547 95.658021)
+ (xy 66.5988 95.658021) (xy 66.5988 94.224104) (xy 77.507187 94.224104) (xy 77.694443 94.6773) (xy 78.040877 95.024338)
+ (xy 78.493745 95.212386) (xy 78.984104 95.212813) (xy 79.4373 95.025557) (xy 79.784338 94.679123) (xy 79.972386 94.226255)
+ (xy 79.972813 93.735896) (xy 79.785557 93.2827) (xy 79.439123 92.935662) (xy 78.986255 92.747614) (xy 78.495896 92.747187)
+ (xy 78.0427 92.934443) (xy 77.695662 93.280877) (xy 77.507614 93.733745) (xy 77.507187 94.224104) (xy 66.5988 94.224104)
+ (xy 66.5988 91.684104) (xy 77.507187 91.684104) (xy 77.694443 92.1373) (xy 78.040877 92.484338) (xy 78.493745 92.672386)
+ (xy 78.984104 92.672813) (xy 79.4373 92.485557) (xy 79.784338 92.139123) (xy 79.972386 91.686255) (xy 79.972813 91.195896)
+ (xy 79.785557 90.7427) (xy 79.666874 90.62381) (xy 81.41716 90.62381) (xy 81.627205 91.132157) (xy 82.015797 91.521428)
+ (xy 82.257 91.621584) (xy 82.257 96.099909) (xy 82.198905 96.137292) (xy 82.088713 96.298563) (xy 82.049946 96.49)
+ (xy 82.049946 97.39) (xy 82.083597 97.568841) (xy 82.189292 97.733095) (xy 82.350563 97.843287) (xy 82.542 97.882054)
+ (xy 83.242 97.882054) (xy 83.420841 97.848403) (xy 83.585095 97.742708) (xy 83.695287 97.581437) (xy 83.734054 97.39)
+ (xy 83.734054 96.49) (xy 83.700403 96.311159) (xy 83.594708 96.146905) (xy 83.527 96.100642) (xy 83.527 91.545585)
+ (xy 83.582157 91.522795) (xy 83.971428 91.134203) (xy 84.18236 90.626223) (xy 84.18284 90.07619) (xy 83.972795 89.567843)
+ (xy 83.584203 89.178572) (xy 83.076223 88.96764) (xy 82.52619 88.96716) (xy 82.017843 89.177205) (xy 81.628572 89.565797)
+ (xy 81.41764 90.073777) (xy 81.41716 90.62381) (xy 79.666874 90.62381) (xy 79.439123 90.395662) (xy 78.986255 90.207614)
+ (xy 78.495896 90.207187) (xy 78.0427 90.394443) (xy 77.695662 90.740877) (xy 77.507614 91.193745) (xy 77.507187 91.684104)
+ (xy 66.5988 91.684104) (xy 66.5988 89.144104) (xy 77.507187 89.144104) (xy 77.694443 89.5973) (xy 78.040877 89.944338)
+ (xy 78.493745 90.132386) (xy 78.984104 90.132813) (xy 79.4373 89.945557) (xy 79.784338 89.599123) (xy 79.972386 89.146255)
+ (xy 79.972813 88.655896) (xy 79.785557 88.2027) (xy 79.439123 87.855662) (xy 78.986255 87.667614) (xy 78.495896 87.667187)
+ (xy 78.0427 87.854443) (xy 77.695662 88.200877) (xy 77.507614 88.653745) (xy 77.507187 89.144104) (xy 66.5988 89.144104)
+ (xy 66.5988 87.221979) (xy 78.093547 87.221979) (xy 78.176077 87.395452) (xy 78.615254 87.532437) (xy 79.073421 87.490929)
+ (xy 79.303923 87.395452) (xy 79.386453 87.221979) (xy 78.74 86.575526) (xy 78.093547 87.221979) (xy 66.5988 87.221979)
+ (xy 66.5988 86.235254) (xy 77.567563 86.235254) (xy 77.609071 86.693421) (xy 77.704548 86.923923) (xy 77.878021 87.006453)
+ (xy 78.524474 86.36) (xy 78.955526 86.36) (xy 79.601979 87.006453) (xy 79.775452 86.923923) (xy 79.912437 86.484746)
+ (xy 79.89761 86.321083) (xy 81.144443 86.321083) (xy 81.245509 86.510108) (xy 81.739283 86.672263) (xy 82.257525 86.633115)
+ (xy 82.554491 86.510108) (xy 82.655557 86.321083) (xy 81.9 85.565526) (xy 81.144443 86.321083) (xy 79.89761 86.321083)
+ (xy 79.870929 86.026579) (xy 79.775452 85.796077) (xy 79.601979 85.713547) (xy 78.955526 86.36) (xy 78.524474 86.36)
+ (xy 77.878021 85.713547) (xy 77.704548 85.796077) (xy 77.567563 86.235254) (xy 66.5988 86.235254) (xy 66.5988 85.498021)
+ (xy 78.093547 85.498021) (xy 78.74 86.144474) (xy 79.386453 85.498021) (xy 79.303923 85.324548) (xy 78.870261 85.189283)
+ (xy 80.577737 85.189283) (xy 80.616885 85.707525) (xy 80.739892 86.004491) (xy 80.928917 86.105557) (xy 81.684474 85.35)
+ (xy 82.115526 85.35) (xy 82.871083 86.105557) (xy 83.060108 86.004491) (xy 83.222263 85.510717) (xy 83.183115 84.992475)
+ (xy 83.060108 84.695509) (xy 82.871083 84.594443) (xy 82.115526 85.35) (xy 81.684474 85.35) (xy 80.928917 84.594443)
+ (xy 80.739892 84.695509) (xy 80.577737 85.189283) (xy 78.870261 85.189283) (xy 78.864746 85.187563) (xy 78.406579 85.229071)
+ (xy 78.176077 85.324548) (xy 78.093547 85.498021) (xy 66.5988 85.498021) (xy 66.5988 84.3788) (xy 81.144506 84.3788)
+ )
+ )
+ )
+ (zone (net 5) (net_name GND) (layer B.Cu) (tstamp 5766EB06) (hatch edge 0.508)
+ (connect_pads yes (clearance 0.381))
+ (min_thickness 0.2032)
+ (fill yes (arc_segments 16) (thermal_gap 0.508) (thermal_bridge_width 0.508))
+ (polygon
+ (pts
+ (xy 65.024 82.804) (xy 65.786 82.804) (xy 108.966 82.804) (xy 108.966 137.414) (xy 65.024 137.414)
+ (xy 65.024 135.128)
+ )
+ )
+ (filled_polygon
+ (pts
+ (xy 81.144443 84.378917) (xy 81.9 85.134474) (xy 82.655557 84.378917) (xy 82.655494 84.3788) (xy 86.177405 84.3788)
+ (xy 85.844129 84.516507) (xy 85.567478 84.792675) (xy 85.417571 85.153691) (xy 85.41723 85.544594) (xy 85.566507 85.905871)
+ (xy 85.842675 86.182522) (xy 86.203691 86.332429) (xy 86.594594 86.33277) (xy 86.955871 86.183493) (xy 87.232522 85.907325)
+ (xy 87.382429 85.546309) (xy 87.38277 85.155406) (xy 87.233493 84.794129) (xy 86.957325 84.517478) (xy 86.623351 84.3788)
+ (xy 90.215896 84.3788) (xy 90.028572 84.565797) (xy 89.81764 85.073777) (xy 89.81716 85.62381) (xy 90.027205 86.132157)
+ (xy 90.415797 86.521428) (xy 90.923777 86.73236) (xy 91.47381 86.73284) (xy 91.8464 86.578889) (xy 91.8464 86.614)
+ (xy 91.853868 86.652231) (xy 91.876158 86.685842) (xy 92.892158 87.701842) (xy 92.924472 87.723595) (xy 92.964 87.7316)
+ (xy 94.060185 87.7316) (xy 93.646579 87.769071) (xy 93.416077 87.864548) (xy 93.333547 88.038021) (xy 93.98 88.684474)
+ (xy 94.626453 88.038021) (xy 94.543923 87.864548) (xy 94.117689 87.7316) (xy 94.996 87.7316) (xy 95.034231 87.724132)
+ (xy 95.067842 87.701842) (xy 95.575842 87.193842) (xy 95.597595 87.161528) (xy 95.6056 87.122) (xy 95.6056 84.3788)
+ (xy 106.1212 84.3788) (xy 106.1212 134.0612) (xy 66.5988 134.0612) (xy 66.5988 119.624104) (xy 77.507187 119.624104)
+ (xy 77.694443 120.0773) (xy 78.040877 120.424338) (xy 78.493745 120.612386) (xy 78.984104 120.612813) (xy 79.4373 120.425557)
+ (xy 79.784338 120.079123) (xy 79.972386 119.626255) (xy 79.972387 119.624104) (xy 92.747187 119.624104) (xy 92.934443 120.0773)
+ (xy 93.280877 120.424338) (xy 93.733745 120.612386) (xy 94.224104 120.612813) (xy 94.6773 120.425557) (xy 95.024338 120.079123)
+ (xy 95.212386 119.626255) (xy 95.212813 119.135896) (xy 95.025557 118.6827) (xy 94.679123 118.335662) (xy 94.226255 118.147614)
+ (xy 93.735896 118.147187) (xy 93.2827 118.334443) (xy 92.935662 118.680877) (xy 92.747614 119.133745) (xy 92.747187 119.624104)
+ (xy 79.972387 119.624104) (xy 79.972813 119.135896) (xy 79.785557 118.6827) (xy 79.439123 118.335662) (xy 78.986255 118.147614)
+ (xy 78.495896 118.147187) (xy 78.0427 118.334443) (xy 77.695662 118.680877) (xy 77.507614 119.133745) (xy 77.507187 119.624104)
+ (xy 66.5988 119.624104) (xy 66.5988 117.084104) (xy 77.507187 117.084104) (xy 77.694443 117.5373) (xy 78.040877 117.884338)
+ (xy 78.493745 118.072386) (xy 78.984104 118.072813) (xy 79.4373 117.885557) (xy 79.784338 117.539123) (xy 79.972386 117.086255)
+ (xy 79.972387 117.084104) (xy 92.747187 117.084104) (xy 92.934443 117.5373) (xy 93.280877 117.884338) (xy 93.733745 118.072386)
+ (xy 94.224104 118.072813) (xy 94.6773 117.885557) (xy 95.024338 117.539123) (xy 95.212386 117.086255) (xy 95.212813 116.595896)
+ (xy 95.025557 116.1427) (xy 94.679123 115.795662) (xy 94.226255 115.607614) (xy 93.735896 115.607187) (xy 93.2827 115.794443)
+ (xy 92.935662 116.140877) (xy 92.747614 116.593745) (xy 92.747187 117.084104) (xy 79.972387 117.084104) (xy 79.972813 116.595896)
+ (xy 79.785557 116.1427) (xy 79.439123 115.795662) (xy 78.986255 115.607614) (xy 78.495896 115.607187) (xy 78.0427 115.794443)
+ (xy 77.695662 116.140877) (xy 77.507614 116.593745) (xy 77.507187 117.084104) (xy 66.5988 117.084104) (xy 66.5988 114.544104)
+ (xy 77.507187 114.544104) (xy 77.694443 114.9973) (xy 78.040877 115.344338) (xy 78.493745 115.532386) (xy 78.984104 115.532813)
+ (xy 79.4373 115.345557) (xy 79.784338 114.999123) (xy 79.929812 114.648782) (xy 84.159857 114.648782) (xy 84.285267 114.952298)
+ (xy 84.517281 115.184717) (xy 84.820577 115.310657) (xy 85.148982 115.310943) (xy 85.452498 115.185533) (xy 85.684917 114.953519)
+ (xy 85.810857 114.650223) (xy 85.811143 114.321818) (xy 85.7504 114.174808) (xy 85.7504 113.411223) (xy 86.48256 111.720282)
+ (xy 88.480757 111.720282) (xy 88.606167 112.023798) (xy 88.7222 112.140034) (xy 88.7222 114.0206) (xy 88.76667 114.244164)
+ (xy 88.817594 114.320377) (xy 88.893308 114.433692) (xy 89.426706 114.967089) (xy 89.426708 114.967092) (xy 89.553347 115.051709)
+ (xy 89.616237 115.093731) (xy 89.8398 115.138201) (xy 89.839805 115.1382) (xy 93.075098 115.1382) (xy 93.280877 115.344338)
+ (xy 93.733745 115.532386) (xy 94.224104 115.532813) (xy 94.6773 115.345557) (xy 95.024338 114.999123) (xy 95.212386 114.546255)
+ (xy 95.212813 114.055896) (xy 95.025557 113.6027) (xy 94.679123 113.255662) (xy 94.226255 113.067614) (xy 93.735896 113.067187)
+ (xy 93.2827 113.254443) (xy 92.935662 113.600877) (xy 92.782471 113.9698) (xy 90.081783 113.9698) (xy 89.8906 113.778616)
+ (xy 89.8906 112.140035) (xy 90.005817 112.025019) (xy 90.014501 112.004104) (xy 92.747187 112.004104) (xy 92.934443 112.4573)
+ (xy 93.280877 112.804338) (xy 93.733745 112.992386) (xy 94.224104 112.992813) (xy 94.6773 112.805557) (xy 95.024338 112.459123)
+ (xy 95.212386 112.006255) (xy 95.212813 111.515896) (xy 95.025557 111.0627) (xy 94.679123 110.715662) (xy 94.226255 110.527614)
+ (xy 93.735896 110.527187) (xy 93.2827 110.714443) (xy 92.935662 111.060877) (xy 92.747614 111.513745) (xy 92.747187 112.004104)
+ (xy 90.014501 112.004104) (xy 90.131757 111.721723) (xy 90.132043 111.393318) (xy 90.006633 111.089802) (xy 89.774619 110.857383)
+ (xy 89.471323 110.731443) (xy 89.142918 110.731157) (xy 88.839402 110.856567) (xy 88.606983 111.088581) (xy 88.481043 111.391877)
+ (xy 88.480757 111.720282) (xy 86.48256 111.720282) (xy 86.756556 111.087482) (xy 86.757813 111.081649) (xy 86.76113 111.076685)
+ (xy 86.788044 110.941376) (xy 86.817107 110.806526) (xy 86.816036 110.800653) (xy 86.8172 110.7948) (xy 86.8172 107.658)
+ (xy 87.607946 107.658) (xy 87.607946 109.258) (xy 87.641597 109.436841) (xy 87.747292 109.601095) (xy 87.908563 109.711287)
+ (xy 88.1 109.750054) (xy 89.7 109.750054) (xy 89.878841 109.716403) (xy 90.043095 109.610708) (xy 90.143265 109.464104)
+ (xy 92.747187 109.464104) (xy 92.934443 109.9173) (xy 93.280877 110.264338) (xy 93.733745 110.452386) (xy 94.224104 110.452813)
+ (xy 94.6773 110.265557) (xy 95.024338 109.919123) (xy 95.212386 109.466255) (xy 95.212387 109.464104) (xy 96.811187 109.464104)
+ (xy 96.998443 109.9173) (xy 97.344877 110.264338) (xy 97.797745 110.452386) (xy 98.288104 110.452813) (xy 98.7413 110.265557)
+ (xy 99.088338 109.919123) (xy 99.276386 109.466255) (xy 99.276813 108.975896) (xy 99.089557 108.5227) (xy 98.743123 108.175662)
+ (xy 98.290255 107.987614) (xy 97.799896 107.987187) (xy 97.3467 108.174443) (xy 96.999662 108.520877) (xy 96.811614 108.973745)
+ (xy 96.811187 109.464104) (xy 95.212387 109.464104) (xy 95.212813 108.975896) (xy 95.025557 108.5227) (xy 94.679123 108.175662)
+ (xy 94.226255 107.987614) (xy 93.735896 107.987187) (xy 93.2827 108.174443) (xy 92.935662 108.520877) (xy 92.747614 108.973745)
+ (xy 92.747187 109.464104) (xy 90.143265 109.464104) (xy 90.153287 109.449437) (xy 90.192054 109.258) (xy 90.192054 107.658)
+ (xy 90.158403 107.479159) (xy 90.052708 107.314905) (xy 89.891437 107.204713) (xy 89.7 107.165946) (xy 88.1 107.165946)
+ (xy 87.921159 107.199597) (xy 87.756905 107.305292) (xy 87.646713 107.466563) (xy 87.607946 107.658) (xy 86.8172 107.658)
+ (xy 86.8172 106.924104) (xy 92.747187 106.924104) (xy 92.934443 107.3773) (xy 93.280877 107.724338) (xy 93.733745 107.912386)
+ (xy 94.224104 107.912813) (xy 94.6773 107.725557) (xy 95.024338 107.379123) (xy 95.212386 106.926255) (xy 95.212387 106.924104)
+ (xy 96.811187 106.924104) (xy 96.998443 107.3773) (xy 97.344877 107.724338) (xy 97.797745 107.912386) (xy 98.288104 107.912813)
+ (xy 98.7413 107.725557) (xy 99.088338 107.379123) (xy 99.276386 106.926255) (xy 99.276813 106.435896) (xy 99.089557 105.9827)
+ (xy 98.743123 105.635662) (xy 98.290255 105.447614) (xy 97.799896 105.447187) (xy 97.3467 105.634443) (xy 96.999662 105.980877)
+ (xy 96.811614 106.433745) (xy 96.811187 106.924104) (xy 95.212387 106.924104) (xy 95.212813 106.435896) (xy 95.025557 105.9827)
+ (xy 94.679123 105.635662) (xy 94.226255 105.447614) (xy 93.735896 105.447187) (xy 93.2827 105.634443) (xy 92.935662 105.980877)
+ (xy 92.747614 106.433745) (xy 92.747187 106.924104) (xy 86.8172 106.924104) (xy 86.8172 104.384104) (xy 92.747187 104.384104)
+ (xy 92.934443 104.8373) (xy 93.280877 105.184338) (xy 93.733745 105.372386) (xy 94.224104 105.372813) (xy 94.6773 105.185557)
+ (xy 95.024338 104.839123) (xy 95.212386 104.386255) (xy 95.212387 104.384104) (xy 96.811187 104.384104) (xy 96.998443 104.8373)
+ (xy 97.344877 105.184338) (xy 97.797745 105.372386) (xy 98.288104 105.372813) (xy 98.7413 105.185557) (xy 99.088338 104.839123)
+ (xy 99.276386 104.386255) (xy 99.276813 103.895896) (xy 99.089557 103.4427) (xy 98.743123 103.095662) (xy 98.290255 102.907614)
+ (xy 97.799896 102.907187) (xy 97.3467 103.094443) (xy 96.999662 103.440877) (xy 96.811614 103.893745) (xy 96.811187 104.384104)
+ (xy 95.212387 104.384104) (xy 95.212813 103.895896) (xy 95.025557 103.4427) (xy 94.679123 103.095662) (xy 94.226255 102.907614)
+ (xy 93.735896 102.907187) (xy 93.2827 103.094443) (xy 92.935662 103.440877) (xy 92.747614 103.893745) (xy 92.747187 104.384104)
+ (xy 86.8172 104.384104) (xy 86.8172 101.844104) (xy 92.747187 101.844104) (xy 92.934443 102.2973) (xy 93.280877 102.644338)
+ (xy 93.733745 102.832386) (xy 94.224104 102.832813) (xy 94.6773 102.645557) (xy 94.861198 102.461979) (xy 97.397547 102.461979)
+ (xy 97.480077 102.635452) (xy 97.919254 102.772437) (xy 98.377421 102.730929) (xy 98.607923 102.635452) (xy 98.690453 102.461979)
+ (xy 98.044 101.815526) (xy 97.397547 102.461979) (xy 94.861198 102.461979) (xy 95.024338 102.299123) (xy 95.212386 101.846255)
+ (xy 95.212709 101.475254) (xy 96.871563 101.475254) (xy 96.913071 101.933421) (xy 97.008548 102.163923) (xy 97.182021 102.246453)
+ (xy 97.828474 101.6) (xy 98.259526 101.6) (xy 98.905979 102.246453) (xy 99.079452 102.163923) (xy 99.216437 101.724746)
+ (xy 99.174929 101.266579) (xy 99.079452 101.036077) (xy 98.905979 100.953547) (xy 98.259526 101.6) (xy 97.828474 101.6)
+ (xy 97.182021 100.953547) (xy 97.008548 101.036077) (xy 96.871563 101.475254) (xy 95.212709 101.475254) (xy 95.212813 101.355896)
+ (xy 95.025557 100.9027) (xy 94.861165 100.738021) (xy 97.397547 100.738021) (xy 98.044 101.384474) (xy 98.690453 100.738021)
+ (xy 98.607923 100.564548) (xy 98.168746 100.427563) (xy 97.710579 100.469071) (xy 97.480077 100.564548) (xy 97.397547 100.738021)
+ (xy 94.861165 100.738021) (xy 94.679123 100.555662) (xy 94.226255 100.367614) (xy 93.735896 100.367187) (xy 93.2827 100.554443)
+ (xy 92.935662 100.900877) (xy 92.747614 101.353745) (xy 92.747187 101.844104) (xy 86.8172 101.844104) (xy 86.8172 101.65111)
+ (xy 87.730694 100.737616) (xy 87.944598 100.649233) (xy 88.177017 100.417219) (xy 88.266437 100.201873) (xy 89.164206 99.304104)
+ (xy 92.747187 99.304104) (xy 92.934443 99.7573) (xy 93.280877 100.104338) (xy 93.733745 100.292386) (xy 94.224104 100.292813)
+ (xy 94.6773 100.105557) (xy 95.024338 99.759123) (xy 95.212386 99.306255) (xy 95.212387 99.304104) (xy 96.811187 99.304104)
+ (xy 96.998443 99.7573) (xy 97.344877 100.104338) (xy 97.797745 100.292386) (xy 98.288104 100.292813) (xy 98.7413 100.105557)
+ (xy 99.088338 99.759123) (xy 99.276386 99.306255) (xy 99.276813 98.815896) (xy 99.089557 98.3627) (xy 98.743123 98.015662)
+ (xy 98.290255 97.827614) (xy 97.799896 97.827187) (xy 97.3467 98.014443) (xy 96.999662 98.360877) (xy 96.811614 98.813745)
+ (xy 96.811187 99.304104) (xy 95.212387 99.304104) (xy 95.212813 98.815896) (xy 95.025557 98.3627) (xy 94.679123 98.015662)
+ (xy 94.226255 97.827614) (xy 93.735896 97.827187) (xy 93.2827 98.014443) (xy 92.935662 98.360877) (xy 92.747614 98.813745)
+ (xy 92.747187 99.304104) (xy 89.164206 99.304104) (xy 91.351255 97.117055) (xy 91.51093 96.878085) (xy 91.533602 96.764104)
+ (xy 92.747187 96.764104) (xy 92.934443 97.2173) (xy 93.280877 97.564338) (xy 93.733745 97.752386) (xy 94.224104 97.752813)
+ (xy 94.6773 97.565557) (xy 95.024338 97.219123) (xy 95.212386 96.766255) (xy 95.212387 96.764104) (xy 96.811187 96.764104)
+ (xy 96.998443 97.2173) (xy 97.344877 97.564338) (xy 97.797745 97.752386) (xy 98.288104 97.752813) (xy 98.7413 97.565557)
+ (xy 99.088338 97.219123) (xy 99.276386 96.766255) (xy 99.276813 96.275896) (xy 99.089557 95.8227) (xy 98.743123 95.475662)
+ (xy 98.290255 95.287614) (xy 97.799896 95.287187) (xy 97.3467 95.474443) (xy 96.999662 95.820877) (xy 96.811614 96.273745)
+ (xy 96.811187 96.764104) (xy 95.212387 96.764104) (xy 95.212813 96.275896) (xy 95.025557 95.8227) (xy 94.679123 95.475662)
+ (xy 94.226255 95.287614) (xy 93.735896 95.287187) (xy 93.2827 95.474443) (xy 92.935662 95.820877) (xy 92.747614 96.273745)
+ (xy 92.747187 96.764104) (xy 91.533602 96.764104) (xy 91.567 96.5962) (xy 91.567 94.224104) (xy 92.747187 94.224104)
+ (xy 92.934443 94.6773) (xy 93.280877 95.024338) (xy 93.733745 95.212386) (xy 94.224104 95.212813) (xy 94.6773 95.025557)
+ (xy 95.024338 94.679123) (xy 95.212386 94.226255) (xy 95.212813 93.735896) (xy 95.025557 93.2827) (xy 94.679123 92.935662)
+ (xy 94.226255 92.747614) (xy 93.735896 92.747187) (xy 93.2827 92.934443) (xy 92.935662 93.280877) (xy 92.747614 93.733745)
+ (xy 92.747187 94.224104) (xy 91.567 94.224104) (xy 91.567 92.63411) (xy 92.02451 92.1766) (xy 92.973675 92.1766)
+ (xy 93.280877 92.484338) (xy 93.733745 92.672386) (xy 94.224104 92.672813) (xy 94.6773 92.485557) (xy 95.024338 92.139123)
+ (xy 95.212386 91.686255) (xy 95.212813 91.195896) (xy 95.025557 90.7427) (xy 94.679123 90.395662) (xy 94.226255 90.207614)
+ (xy 93.735896 90.207187) (xy 93.2827 90.394443) (xy 92.973204 90.7034) (xy 91.7194 90.7034) (xy 91.437515 90.75947)
+ (xy 91.218411 90.905871) (xy 91.198545 90.919145) (xy 90.309545 91.808145) (xy 90.14987 92.047115) (xy 90.0938 92.329)
+ (xy 90.0938 96.29109) (xy 87.224506 99.160384) (xy 87.010602 99.248767) (xy 86.778183 99.480781) (xy 86.688763 99.696127)
+ (xy 85.559745 100.825145) (xy 85.40007 101.064115) (xy 85.344 101.346) (xy 85.344 110.642177) (xy 84.337844 112.965918)
+ (xy 84.336587 112.971751) (xy 84.33327 112.976715) (xy 84.306356 113.112024) (xy 84.277293 113.246874) (xy 84.278364 113.252747)
+ (xy 84.2772 113.2586) (xy 84.2772 114.038474) (xy 84.160143 114.320377) (xy 84.159857 114.648782) (xy 79.929812 114.648782)
+ (xy 79.972386 114.546255) (xy 79.972813 114.055896) (xy 79.785557 113.6027) (xy 79.439123 113.255662) (xy 78.986255 113.067614)
+ (xy 78.495896 113.067187) (xy 78.0427 113.254443) (xy 77.695662 113.600877) (xy 77.507614 114.053745) (xy 77.507187 114.544104)
+ (xy 66.5988 114.544104) (xy 66.5988 112.004104) (xy 77.507187 112.004104) (xy 77.694443 112.4573) (xy 78.040877 112.804338)
+ (xy 78.493745 112.992386) (xy 78.984104 112.992813) (xy 79.4373 112.805557) (xy 79.784338 112.459123) (xy 79.972386 112.006255)
+ (xy 79.972813 111.515896) (xy 79.785557 111.0627) (xy 79.439123 110.715662) (xy 78.986255 110.527614) (xy 78.495896 110.527187)
+ (xy 78.0427 110.714443) (xy 77.695662 111.060877) (xy 77.507614 111.513745) (xy 77.507187 112.004104) (xy 66.5988 112.004104)
+ (xy 66.5988 109.464104) (xy 74.205187 109.464104) (xy 74.392443 109.9173) (xy 74.738877 110.264338) (xy 75.191745 110.452386)
+ (xy 75.682104 110.452813) (xy 76.1353 110.265557) (xy 76.482338 109.919123) (xy 76.670386 109.466255) (xy 76.670387 109.464104)
+ (xy 77.507187 109.464104) (xy 77.694443 109.9173) (xy 78.040877 110.264338) (xy 78.493745 110.452386) (xy 78.984104 110.452813)
+ (xy 79.4373 110.265557) (xy 79.784338 109.919123) (xy 79.972386 109.466255) (xy 79.972418 109.429083) (xy 83.064443 109.429083)
+ (xy 83.165509 109.618108) (xy 83.659283 109.780263) (xy 84.177525 109.741115) (xy 84.474491 109.618108) (xy 84.575557 109.429083)
+ (xy 83.82 108.673526) (xy 83.064443 109.429083) (xy 79.972418 109.429083) (xy 79.972813 108.975896) (xy 79.785557 108.5227)
+ (xy 79.560533 108.297283) (xy 82.497737 108.297283) (xy 82.536885 108.815525) (xy 82.659892 109.112491) (xy 82.848917 109.213557)
+ (xy 83.604474 108.458) (xy 84.035526 108.458) (xy 84.791083 109.213557) (xy 84.980108 109.112491) (xy 85.142263 108.618717)
+ (xy 85.103115 108.100475) (xy 84.980108 107.803509) (xy 84.791083 107.702443) (xy 84.035526 108.458) (xy 83.604474 108.458)
+ (xy 82.848917 107.702443) (xy 82.659892 107.803509) (xy 82.497737 108.297283) (xy 79.560533 108.297283) (xy 79.439123 108.175662)
+ (xy 78.986255 107.987614) (xy 78.495896 107.987187) (xy 78.0427 108.174443) (xy 77.695662 108.520877) (xy 77.507614 108.973745)
+ (xy 77.507187 109.464104) (xy 76.670387 109.464104) (xy 76.670813 108.975896) (xy 76.483557 108.5227) (xy 76.137123 108.175662)
+ (xy 75.684255 107.987614) (xy 75.193896 107.987187) (xy 74.7407 108.174443) (xy 74.393662 108.520877) (xy 74.205614 108.973745)
+ (xy 74.205187 109.464104) (xy 66.5988 109.464104) (xy 66.5988 106.924104) (xy 74.205187 106.924104) (xy 74.392443 107.3773)
+ (xy 74.738877 107.724338) (xy 75.191745 107.912386) (xy 75.682104 107.912813) (xy 76.1353 107.725557) (xy 76.482338 107.379123)
+ (xy 76.670386 106.926255) (xy 76.670387 106.924104) (xy 77.507187 106.924104) (xy 77.694443 107.3773) (xy 78.040877 107.724338)
+ (xy 78.493745 107.912386) (xy 78.984104 107.912813) (xy 79.4373 107.725557) (xy 79.676356 107.486917) (xy 83.064443 107.486917)
+ (xy 83.82 108.242474) (xy 84.575557 107.486917) (xy 84.474491 107.297892) (xy 83.980717 107.135737) (xy 83.462475 107.174885)
+ (xy 83.165509 107.297892) (xy 83.064443 107.486917) (xy 79.676356 107.486917) (xy 79.784338 107.379123) (xy 79.972386 106.926255)
+ (xy 79.972813 106.435896) (xy 79.785557 105.9827) (xy 79.439123 105.635662) (xy 78.986255 105.447614) (xy 78.495896 105.447187)
+ (xy 78.0427 105.634443) (xy 77.695662 105.980877) (xy 77.507614 106.433745) (xy 77.507187 106.924104) (xy 76.670387 106.924104)
+ (xy 76.670813 106.435896) (xy 76.483557 105.9827) (xy 76.137123 105.635662) (xy 75.684255 105.447614) (xy 75.193896 105.447187)
+ (xy 74.7407 105.634443) (xy 74.393662 105.980877) (xy 74.205614 106.433745) (xy 74.205187 106.924104) (xy 66.5988 106.924104)
+ (xy 66.5988 104.384104) (xy 74.205187 104.384104) (xy 74.392443 104.8373) (xy 74.738877 105.184338) (xy 75.191745 105.372386)
+ (xy 75.682104 105.372813) (xy 76.1353 105.185557) (xy 76.482338 104.839123) (xy 76.670386 104.386255) (xy 76.670387 104.384104)
+ (xy 77.507187 104.384104) (xy 77.694443 104.8373) (xy 78.040877 105.184338) (xy 78.493745 105.372386) (xy 78.984104 105.372813)
+ (xy 79.4373 105.185557) (xy 79.784338 104.839123) (xy 79.972386 104.386255) (xy 79.972813 103.895896) (xy 79.785557 103.4427)
+ (xy 79.439123 103.095662) (xy 78.986255 102.907614) (xy 78.495896 102.907187) (xy 78.0427 103.094443) (xy 77.695662 103.440877)
+ (xy 77.507614 103.893745) (xy 77.507187 104.384104) (xy 76.670387 104.384104) (xy 76.670813 103.895896) (xy 76.483557 103.4427)
+ (xy 76.137123 103.095662) (xy 75.684255 102.907614) (xy 75.193896 102.907187) (xy 74.7407 103.094443) (xy 74.393662 103.440877)
+ (xy 74.205614 103.893745) (xy 74.205187 104.384104) (xy 66.5988 104.384104) (xy 66.5988 101.844104) (xy 74.205187 101.844104)
+ (xy 74.392443 102.2973) (xy 74.738877 102.644338) (xy 75.191745 102.832386) (xy 75.682104 102.832813) (xy 76.1353 102.645557)
+ (xy 76.482338 102.299123) (xy 76.670386 101.846255) (xy 76.670387 101.844104) (xy 77.507187 101.844104) (xy 77.694443 102.2973)
+ (xy 78.040877 102.644338) (xy 78.493745 102.832386) (xy 78.984104 102.832813) (xy 79.4373 102.645557) (xy 79.784338 102.299123)
+ (xy 79.972386 101.846255) (xy 79.972813 101.355896) (xy 79.785557 100.9027) (xy 79.439123 100.555662) (xy 78.986255 100.367614)
+ (xy 78.495896 100.367187) (xy 78.0427 100.554443) (xy 77.695662 100.900877) (xy 77.507614 101.353745) (xy 77.507187 101.844104)
+ (xy 76.670387 101.844104) (xy 76.670813 101.355896) (xy 76.483557 100.9027) (xy 76.137123 100.555662) (xy 75.684255 100.367614)
+ (xy 75.193896 100.367187) (xy 74.7407 100.554443) (xy 74.393662 100.900877) (xy 74.205614 101.353745) (xy 74.205187 101.844104)
+ (xy 66.5988 101.844104) (xy 66.5988 99.304104) (xy 74.205187 99.304104) (xy 74.392443 99.7573) (xy 74.738877 100.104338)
+ (xy 75.191745 100.292386) (xy 75.682104 100.292813) (xy 76.1353 100.105557) (xy 76.482338 99.759123) (xy 76.670386 99.306255)
+ (xy 76.670387 99.304104) (xy 77.507187 99.304104) (xy 77.694443 99.7573) (xy 78.040877 100.104338) (xy 78.493745 100.292386)
+ (xy 78.984104 100.292813) (xy 79.4373 100.105557) (xy 79.784338 99.759123) (xy 79.972386 99.306255) (xy 79.972813 98.815896)
+ (xy 79.785557 98.3627) (xy 79.439123 98.015662) (xy 78.986255 97.827614) (xy 78.495896 97.827187) (xy 78.0427 98.014443)
+ (xy 77.695662 98.360877) (xy 77.507614 98.813745) (xy 77.507187 99.304104) (xy 76.670387 99.304104) (xy 76.670813 98.815896)
+ (xy 76.483557 98.3627) (xy 76.137123 98.015662) (xy 75.684255 97.827614) (xy 75.193896 97.827187) (xy 74.7407 98.014443)
+ (xy 74.393662 98.360877) (xy 74.205614 98.813745) (xy 74.205187 99.304104) (xy 66.5988 99.304104) (xy 66.5988 97.381979)
+ (xy 74.791547 97.381979) (xy 74.874077 97.555452) (xy 75.313254 97.692437) (xy 75.771421 97.650929) (xy 76.001923 97.555452)
+ (xy 76.084453 97.381979) (xy 75.438 96.735526) (xy 74.791547 97.381979) (xy 66.5988 97.381979) (xy 66.5988 96.395254)
+ (xy 74.265563 96.395254) (xy 74.307071 96.853421) (xy 74.402548 97.083923) (xy 74.576021 97.166453) (xy 75.222474 96.52)
+ (xy 75.653526 96.52) (xy 76.299979 97.166453) (xy 76.473452 97.083923) (xy 76.573207 96.764104) (xy 77.507187 96.764104)
+ (xy 77.694443 97.2173) (xy 78.040877 97.564338) (xy 78.493745 97.752386) (xy 78.984104 97.752813) (xy 79.4373 97.565557)
+ (xy 79.784338 97.219123) (xy 79.972386 96.766255) (xy 79.972813 96.275896) (xy 79.785557 95.8227) (xy 79.439123 95.475662)
+ (xy 78.986255 95.287614) (xy 78.495896 95.287187) (xy 78.0427 95.474443) (xy 77.695662 95.820877) (xy 77.507614 96.273745)
+ (xy 77.507187 96.764104) (xy 76.573207 96.764104) (xy 76.610437 96.644746) (xy 76.568929 96.186579) (xy 76.473452 95.956077)
+ (xy 76.299979 95.873547) (xy 75.653526 96.52) (xy 75.222474 96.52) (xy 74.576021 95.873547) (xy 74.402548 95.956077)
+ (xy 74.265563 96.395254) (xy 66.5988 96.395254) (xy 66.5988 95.658021) (xy 74.791547 95.658021) (xy 75.438 96.304474)
+ (xy 76.084453 95.658021) (xy 76.001923 95.484548) (xy 75.562746 95.347563) (xy 75.104579 95.389071) (xy 74.874077 95.484548)
+ (xy 74.791547 95.658021) (xy 66.5988 95.658021) (xy 66.5988 94.224104) (xy 77.507187 94.224104) (xy 77.694443 94.6773)
+ (xy 78.040877 95.024338) (xy 78.493745 95.212386) (xy 78.984104 95.212813) (xy 79.4373 95.025557) (xy 79.784338 94.679123)
+ (xy 79.972386 94.226255) (xy 79.972813 93.735896) (xy 79.96782 93.72381) (xy 86.61716 93.72381) (xy 86.827205 94.232157)
+ (xy 87.215797 94.621428) (xy 87.723777 94.83236) (xy 88.27381 94.83284) (xy 88.782157 94.622795) (xy 89.171428 94.234203)
+ (xy 89.38236 93.726223) (xy 89.38284 93.17619) (xy 89.172795 92.667843) (xy 88.784203 92.278572) (xy 88.276223 92.06764)
+ (xy 87.72619 92.06716) (xy 87.217843 92.277205) (xy 86.828572 92.665797) (xy 86.61764 93.173777) (xy 86.61716 93.72381)
+ (xy 79.96782 93.72381) (xy 79.785557 93.2827) (xy 79.439123 92.935662) (xy 78.986255 92.747614) (xy 78.495896 92.747187)
+ (xy 78.0427 92.934443) (xy 77.695662 93.280877) (xy 77.507614 93.733745) (xy 77.507187 94.224104) (xy 66.5988 94.224104)
+ (xy 66.5988 91.684104) (xy 77.507187 91.684104) (xy 77.694443 92.1373) (xy 78.040877 92.484338) (xy 78.493745 92.672386)
+ (xy 78.984104 92.672813) (xy 79.4373 92.485557) (xy 79.784338 92.139123) (xy 79.972386 91.686255) (xy 79.972813 91.195896)
+ (xy 79.785557 90.7427) (xy 79.666874 90.62381) (xy 81.41716 90.62381) (xy 81.627205 91.132157) (xy 82.015797 91.521428)
+ (xy 82.523777 91.73236) (xy 83.07381 91.73284) (xy 83.582157 91.522795) (xy 83.971428 91.134203) (xy 84.18236 90.626223)
+ (xy 84.182431 90.544594) (xy 85.41723 90.544594) (xy 85.566507 90.905871) (xy 85.842675 91.182522) (xy 86.203691 91.332429)
+ (xy 86.594594 91.33277) (xy 86.955871 91.183493) (xy 87.232522 90.907325) (xy 87.382429 90.546309) (xy 87.38277 90.155406)
+ (xy 87.233493 89.794129) (xy 87.2014 89.761979) (xy 93.333547 89.761979) (xy 93.416077 89.935452) (xy 93.855254 90.072437)
+ (xy 94.313421 90.030929) (xy 94.543923 89.935452) (xy 94.626453 89.761979) (xy 93.98 89.115526) (xy 93.333547 89.761979)
+ (xy 87.2014 89.761979) (xy 86.957325 89.517478) (xy 86.596309 89.367571) (xy 86.205406 89.36723) (xy 85.844129 89.516507)
+ (xy 85.567478 89.792675) (xy 85.417571 90.153691) (xy 85.41723 90.544594) (xy 84.182431 90.544594) (xy 84.18284 90.07619)
+ (xy 83.972795 89.567843) (xy 83.584203 89.178572) (xy 83.076223 88.96764) (xy 82.52619 88.96716) (xy 82.017843 89.177205)
+ (xy 81.628572 89.565797) (xy 81.41764 90.073777) (xy 81.41716 90.62381) (xy 79.666874 90.62381) (xy 79.439123 90.395662)
+ (xy 78.986255 90.207614) (xy 78.495896 90.207187) (xy 78.0427 90.394443) (xy 77.695662 90.740877) (xy 77.507614 91.193745)
+ (xy 77.507187 91.684104) (xy 66.5988 91.684104) (xy 66.5988 89.144104) (xy 77.507187 89.144104) (xy 77.694443 89.5973)
+ (xy 78.040877 89.944338) (xy 78.493745 90.132386) (xy 78.984104 90.132813) (xy 79.4373 89.945557) (xy 79.784338 89.599123)
+ (xy 79.972386 89.146255) (xy 79.972709 88.775254) (xy 92.807563 88.775254) (xy 92.849071 89.233421) (xy 92.944548 89.463923)
+ (xy 93.118021 89.546453) (xy 93.764474 88.9) (xy 94.195526 88.9) (xy 94.841979 89.546453) (xy 95.015452 89.463923)
+ (xy 95.152437 89.024746) (xy 95.110929 88.566579) (xy 95.015452 88.336077) (xy 94.841979 88.253547) (xy 94.195526 88.9)
+ (xy 93.764474 88.9) (xy 93.118021 88.253547) (xy 92.944548 88.336077) (xy 92.807563 88.775254) (xy 79.972709 88.775254)
+ (xy 79.972813 88.655896) (xy 79.785557 88.2027) (xy 79.439123 87.855662) (xy 78.986255 87.667614) (xy 78.495896 87.667187)
+ (xy 78.0427 87.854443) (xy 77.695662 88.200877) (xy 77.507614 88.653745) (xy 77.507187 89.144104) (xy 66.5988 89.144104)
+ (xy 66.5988 87.221979) (xy 78.093547 87.221979) (xy 78.176077 87.395452) (xy 78.615254 87.532437) (xy 79.073421 87.490929)
+ (xy 79.303923 87.395452) (xy 79.386453 87.221979) (xy 78.74 86.575526) (xy 78.093547 87.221979) (xy 66.5988 87.221979)
+ (xy 66.5988 86.235254) (xy 77.567563 86.235254) (xy 77.609071 86.693421) (xy 77.704548 86.923923) (xy 77.878021 87.006453)
+ (xy 78.524474 86.36) (xy 78.955526 86.36) (xy 79.601979 87.006453) (xy 79.775452 86.923923) (xy 79.912437 86.484746)
+ (xy 79.89761 86.321083) (xy 81.144443 86.321083) (xy 81.245509 86.510108) (xy 81.739283 86.672263) (xy 82.257525 86.633115)
+ (xy 82.554491 86.510108) (xy 82.655557 86.321083) (xy 81.9 85.565526) (xy 81.144443 86.321083) (xy 79.89761 86.321083)
+ (xy 79.870929 86.026579) (xy 79.775452 85.796077) (xy 79.601979 85.713547) (xy 78.955526 86.36) (xy 78.524474 86.36)
+ (xy 77.878021 85.713547) (xy 77.704548 85.796077) (xy 77.567563 86.235254) (xy 66.5988 86.235254) (xy 66.5988 85.498021)
+ (xy 78.093547 85.498021) (xy 78.74 86.144474) (xy 79.386453 85.498021) (xy 79.303923 85.324548) (xy 78.870261 85.189283)
+ (xy 80.577737 85.189283) (xy 80.616885 85.707525) (xy 80.739892 86.004491) (xy 80.928917 86.105557) (xy 81.684474 85.35)
+ (xy 82.115526 85.35) (xy 82.871083 86.105557) (xy 83.060108 86.004491) (xy 83.222263 85.510717) (xy 83.183115 84.992475)
+ (xy 83.060108 84.695509) (xy 82.871083 84.594443) (xy 82.115526 85.35) (xy 81.684474 85.35) (xy 80.928917 84.594443)
+ (xy 80.739892 84.695509) (xy 80.577737 85.189283) (xy 78.870261 85.189283) (xy 78.864746 85.187563) (xy 78.406579 85.229071)
+ (xy 78.176077 85.324548) (xy 78.093547 85.498021) (xy 66.5988 85.498021) (xy 66.5988 84.3788) (xy 81.144506 84.3788)
+ )
+ )
+ )
+ (zone (net 0) (net_name "") (layer F.Cu) (tstamp 5766FD87) (hatch edge 0.508)
+ (connect_pads (clearance 0.508))
+ (min_thickness 0.2032)
+ (keepout (tracks allowed) (vias allowed) (copperpour not_allowed))
+ (fill (arc_segments 16) (thermal_gap 0.508) (thermal_bridge_width 0.508))
+ (polygon
+ (pts
+ (xy 86.614 112.014) (xy 91.948 112.014) (xy 91.948 117.856) (xy 86.614 117.856) (xy 86.614 117.602)
+ )
+ )
+ )
+ (zone (net 0) (net_name "") (layer B.Cu) (tstamp 57B61157) (hatch edge 0.508)
+ (connect_pads yes (clearance 0.381))
+ (min_thickness 0.2032)
+ (keepout (tracks allowed) (vias allowed) (copperpour not_allowed))
+ (fill (arc_segments 16) (thermal_gap 0.508) (thermal_bridge_width 0.508))
+ (polygon
+ (pts
+ (xy 95.504 84.328) (xy 95.504 87.122) (xy 94.996 87.63) (xy 92.964 87.63) (xy 91.948 86.614)
+ (xy 91.948 86.36) (xy 92.456 85.852) (xy 92.456 84.328)
+ )
+ )
+ )
+ (zone (net 0) (net_name "") (layer F.Cu) (tstamp 57B6118A) (hatch edge 0.508)
+ (connect_pads yes (clearance 0.381))
+ (min_thickness 0.2032)
+ (keepout (tracks allowed) (vias allowed) (copperpour not_allowed))
+ (fill (arc_segments 16) (thermal_gap 0.508) (thermal_bridge_width 0.508))
+ (polygon
+ (pts
+ (xy 95.504 84.328) (xy 95.504 87.122) (xy 94.996 87.63) (xy 92.964 87.63) (xy 91.948 86.614)
+ (xy 91.948 86.36) (xy 92.456 85.852) (xy 92.456 84.328)
+ )
+ )
+ )
+)
diff --git a/hardware/kicad/WALTsm.net b/hardware/kicad/WALTsm.net
new file mode 100644
index 0000000..8548c7e
--- /dev/null
+++ b/hardware/kicad/WALTsm.net
@@ -0,0 +1,442 @@
+(export (version D)
+ (design
+ (source /usr/local/google/home/kamrik/src/walt/hardware/kicad/WALTsm.sch)
+ (date "Mon 27 Feb 2017 07:09:15 PM EST")
+ (tool "Eeschema 4.0.4+e1-6308~48~ubuntu14.04.1-stable")
+ (sheet (number 1) (name /) (tstamps /)
+ (title_block
+ (title "WALT Latency Timer")
+ (company)
+ (rev)
+ (date)
+ (source WALTsm.sch)
+ (comment (number 1) (value ""))
+ (comment (number 2) (value ""))
+ (comment (number 3) (value ""))
+ (comment (number 4) (value "")))))
+ (components
+ (comp (ref U1)
+ (value TeensyLC_with_headers)
+ (footprint Housings_DIP:DIP-28_W15.24mm)
+ (datasheet https://www.pjrc.com/teensy/teensyLC.html)
+ (libsource (lib teensy) (part TeensyLC_with_headers))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 576468C7))
+ (comp (ref D1)
+ (value Photodiode)
+ (footprint walt_footprints:BPW34_DIP2)
+ (libsource (lib walt_misc) (part Photodiode))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57646A24))
+ (comp (ref R1)
+ (value 330)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57646BD5))
+ (comp (ref R2)
+ (value 330)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57646C37))
+ (comp (ref D3)
+ (value LED)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part LED))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57646CD9))
+ (comp (ref D4)
+ (value LED)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part LED))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57646D13))
+ (comp (ref U2)
+ (value ADXL335)
+ (footprint Housings_DFN_QFN:QFN-16-1EP_4x4mm_Pitch0.65mm)
+ (libsource (lib walt_misc) (part ADXL335))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57647D5A))
+ (comp (ref U3)
+ (value LM321MFX)
+ (footprint TO_SOT_Packages_SMD:SOT-23-5)
+ (libsource (lib walt_misc) (part LM321MFX))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 576483C4))
+ (comp (ref R3)
+ (value 510k)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57648608))
+ (comp (ref D2)
+ (value Photodiode)
+ (footprint walt_footprints:BPW34_DIP2)
+ (libsource (lib walt_misc) (part Photodiode))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 576487CA))
+ (comp (ref J1)
+ (value JACK_TRRS)
+ (footprint walt_footprints:TRRS_SJ_43514)
+ (libsource (lib walt_misc) (part JACK_TRRS))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 5764949B))
+ (comp (ref R7)
+ (value 100)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57649565))
+ (comp (ref C1)
+ (value 0.1uF)
+ (footprint Capacitors_SMD:C_0805)
+ (libsource (lib device) (part C))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 5764985B))
+ (comp (ref R4)
+ (value 3,3k)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 576498AB))
+ (comp (ref R5)
+ (value 100)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 576498E4))
+ (comp (ref R6)
+ (value 3.3k)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 5764991F))
+ (comp (ref P1)
+ (value CONN_01X06)
+ (footprint walt_footprints:Pin_Header_Straight_1x06)
+ (libsource (lib conn) (part CONN_01X06))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 5765C52E))
+ (comp (ref C2)
+ (value 0.1uF)
+ (footprint Capacitors_SMD:C_0805)
+ (libsource (lib device) (part C))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 5766D3A4))
+ (comp (ref P2)
+ (value CONN_01X06)
+ (footprint walt_footprints:Pin_Header_Straight_1x06)
+ (libsource (lib conn) (part CONN_01X06))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 57681A8E))
+ (comp (ref R8)
+ (value 100)
+ (footprint walt_footprints:R_0603_pad07mm_long)
+ (libsource (lib device) (part R))
+ (sheetpath (names /) (tstamps /))
+ (tstamp 58B4C350)))
+ (libparts
+ (libpart (lib walt_misc) (part ADXL335)
+ (footprints
+ (fp QFN-16*))
+ (fields
+ (field (name Reference) U)
+ (field (name Value) ADXL335))
+ (pins
+ (pin (num 1) (name NC) (type passive))
+ (pin (num 2) (name ST) (type passive))
+ (pin (num 3) (name COM) (type passive))
+ (pin (num 4) (name NC) (type passive))
+ (pin (num 5) (name COM) (type passive))
+ (pin (num 6) (name COM) (type passive))
+ (pin (num 7) (name COM) (type passive))
+ (pin (num 8) (name Zout) (type output))
+ (pin (num 9) (name NC) (type passive))
+ (pin (num 10) (name Yout) (type output))
+ (pin (num 11) (name NC) (type BiDi))
+ (pin (num 12) (name Xout) (type output))
+ (pin (num 13) (name NC) (type passive))
+ (pin (num 14) (name VCC) (type power_in))
+ (pin (num 15) (name VCC) (type power_in))
+ (pin (num 16) (name NC) (type passive))
+ (pin (num 17) (name EP) (type passive))))
+ (libpart (lib device) (part C)
+ (description "Unpolarized capacitor")
+ (footprints
+ (fp C?)
+ (fp C_????_*)
+ (fp C_????)
+ (fp SMD*_c)
+ (fp Capacitor*))
+ (fields
+ (field (name Reference) C)
+ (field (name Value) C))
+ (pins
+ (pin (num 1) (name ~) (type passive))
+ (pin (num 2) (name ~) (type passive))))
+ (libpart (lib conn) (part CONN_01X06)
+ (description "Connector 01x06")
+ (footprints
+ (fp Pin_Header_Straight_1X06)
+ (fp Pin_Header_Angled_1X06)
+ (fp Socket_Strip_Straight_1X06)
+ (fp Socket_Strip_Angled_1X06))
+ (fields
+ (field (name Reference) P)
+ (field (name Value) CONN_01X06))
+ (pins
+ (pin (num 1) (name P1) (type passive))
+ (pin (num 2) (name P2) (type passive))
+ (pin (num 3) (name P3) (type passive))
+ (pin (num 4) (name P4) (type passive))
+ (pin (num 5) (name P5) (type passive))
+ (pin (num 6) (name P6) (type passive))))
+ (libpart (lib walt_misc) (part JACK_TRRS)
+ (fields
+ (field (name Reference) J)
+ (field (name Value) JACK_TRRS))
+ (pins
+ (pin (num 1) (name S) (type passive))
+ (pin (num 2) (name T) (type passive))
+ (pin (num 3) (name R) (type passive))
+ (pin (num 4) (name R) (type passive))))
+ (libpart (lib device) (part LED)
+ (footprints
+ (fp LED-3MM)
+ (fp LED-5MM)
+ (fp LED-10MM)
+ (fp LED-0603)
+ (fp LED-0805)
+ (fp LED-1206)
+ (fp LEDV))
+ (fields
+ (field (name Reference) D)
+ (field (name Value) LED))
+ (pins
+ (pin (num 1) (name K) (type passive))
+ (pin (num 2) (name A) (type passive))))
+ (libpart (lib walt_misc) (part LM321MFX)
+ (footprints
+ (fp SOT-23*)
+ (fp SC-70-5*))
+ (fields
+ (field (name Reference) U)
+ (field (name Value) LM321MFX)
+ (field (name Footprint) SC70-5))
+ (pins
+ (pin (num 1) (name +) (type input))
+ (pin (num 2) (name V-) (type input))
+ (pin (num 3) (name -) (type input))
+ (pin (num 4) (name ~) (type output))
+ (pin (num 5) (name V+) (type input))))
+ (libpart (lib walt_misc) (part Photodiode)
+ (footprints
+ (fp LED-3MM)
+ (fp LED-5MM)
+ (fp LED-10MM)
+ (fp LED-0603)
+ (fp LED-0805)
+ (fp LED-1206)
+ (fp LEDV))
+ (fields
+ (field (name Reference) D)
+ (field (name Value) Photodiode))
+ (pins
+ (pin (num 1) (name K) (type passive))
+ (pin (num 2) (name A) (type passive))))
+ (libpart (lib device) (part R)
+ (description Resistor)
+ (footprints
+ (fp R_*)
+ (fp Resistor_*))
+ (fields
+ (field (name Reference) R)
+ (field (name Value) R))
+ (pins
+ (pin (num 1) (name ~) (type passive))
+ (pin (num 2) (name ~) (type passive))))
+ (libpart (lib teensy) (part TeensyLC_with_headers)
+ (footprints
+ (fp Housings_DIP:DIP-28_W15.24mm_LongPads)
+ (fp Housings_DIP:DIP-28_W15.24mm))
+ (fields
+ (field (name Reference) U)
+ (field (name Value) TeensyLC_with_headers)
+ (field (name Footprint) Housings_DIP:DIP-28_W15.24mm_LongPads)
+ (field (name Datasheet) https://www.pjrc.com/teensy/teensyLC.html))
+ (pins
+ (pin (num 1) (name GND) (type passive))
+ (pin (num 2) (name D0) (type BiDi))
+ (pin (num 3) (name D1) (type BiDi))
+ (pin (num 4) (name D2) (type BiDi))
+ (pin (num 5) (name D3) (type BiDi))
+ (pin (num 6) (name D4) (type BiDi))
+ (pin (num 7) (name D5) (type BiDi))
+ (pin (num 8) (name D6) (type BiDi))
+ (pin (num 9) (name D7) (type BiDi))
+ (pin (num 10) (name D8) (type BiDi))
+ (pin (num 11) (name D9) (type BiDi))
+ (pin (num 12) (name D10) (type BiDi))
+ (pin (num 13) (name D11) (type BiDi))
+ (pin (num 14) (name D12) (type BiDi))
+ (pin (num 15) (name D13_LED) (type BiDi))
+ (pin (num 16) (name D14_A0) (type BiDi))
+ (pin (num 17) (name D15_A1) (type BiDi))
+ (pin (num 18) (name D16_A2) (type BiDi))
+ (pin (num 19) (name D17_A3) (type BiDi))
+ (pin (num 20) (name D18_A4) (type BiDi))
+ (pin (num 21) (name D19_A5) (type BiDi))
+ (pin (num 22) (name D20_A6) (type BiDi))
+ (pin (num 23) (name D21_A7) (type BiDi))
+ (pin (num 24) (name D22_A8) (type BiDi))
+ (pin (num 25) (name D23_A9) (type BiDi))
+ (pin (num 26) (name 3V3) (type power_in))
+ (pin (num 27) (name GND) (type passive))
+ (pin (num 28) (name VCC) (type passive)))))
+ (libraries
+ (library (logical device)
+ (uri /usr/share/kicad/library/device.lib))
+ (library (logical conn)
+ (uri /usr/share/kicad/library/conn.lib))
+ (library (logical walt_misc)
+ (uri /usr/local/google/home/kamrik/src/walt/hardware/kicad/walt_misc.lib))
+ (library (logical teensy)
+ (uri /usr/local/google/home/kamrik/src/walt/hardware/kicad/teensy.lib)))
+ (nets
+ (net (code 1) (name "Net-(C1-Pad1)")
+ (node (ref C1) (pin 1))
+ (node (ref J1) (pin 1))
+ (node (ref R6) (pin 2)))
+ (net (code 2) (name "Net-(C1-Pad2)")
+ (node (ref C1) (pin 2))
+ (node (ref R5) (pin 2))
+ (node (ref R4) (pin 2)))
+ (net (code 3) (name "Net-(D2-Pad1)")
+ (node (ref U3) (pin 3))
+ (node (ref D2) (pin 1))
+ (node (ref R3) (pin 1)))
+ (net (code 4) (name "Net-(U1-Pad6)")
+ (node (ref U1) (pin 6)))
+ (net (code 5) (name /GPIO_A3)
+ (node (ref U1) (pin 19))
+ (node (ref P2) (pin 1)))
+ (net (code 6) (name /GPIO_A4)
+ (node (ref P2) (pin 2))
+ (node (ref U1) (pin 20)))
+ (net (code 7) (name /GPIO_A5)
+ (node (ref U1) (pin 21))
+ (node (ref P2) (pin 3)))
+ (net (code 8) (name "Net-(U1-Pad23)")
+ (node (ref U1) (pin 23)))
+ (net (code 9) (name /SERIAL_CTS)
+ (node (ref P1) (pin 1))
+ (node (ref U1) (pin 11)))
+ (net (code 10) (name /SERIAL_RTS)
+ (node (ref P1) (pin 5))
+ (node (ref U1) (pin 7)))
+ (net (code 11) (name "Net-(P1-Pad4)")
+ (node (ref P1) (pin 4)))
+ (net (code 12) (name "Net-(U2-Pad2)")
+ (node (ref U2) (pin 2)))
+ (net (code 13) (name +3V3)
+ (node (ref U3) (pin 5))
+ (node (ref P2) (pin 5))
+ (node (ref C2) (pin 1))
+ (node (ref U1) (pin 26))
+ (node (ref U2) (pin 14))
+ (node (ref U2) (pin 15)))
+ (net (code 14) (name /DEBUG_LED1)
+ (node (ref U1) (pin 13))
+ (node (ref R2) (pin 1)))
+ (net (code 15) (name /VUSB_5V)
+ (node (ref P2) (pin 6))
+ (node (ref U1) (pin 28)))
+ (net (code 16) (name GND)
+ (node (ref U2) (pin 6))
+ (node (ref D1) (pin 2))
+ (node (ref U2) (pin 5))
+ (node (ref U2) (pin 3))
+ (node (ref R7) (pin 1))
+ (node (ref D4) (pin 1))
+ (node (ref U2) (pin 7))
+ (node (ref D3) (pin 1))
+ (node (ref J1) (pin 4))
+ (node (ref U1) (pin 1))
+ (node (ref D2) (pin 2))
+ (node (ref R6) (pin 1))
+ (node (ref R5) (pin 1))
+ (node (ref U1) (pin 27))
+ (node (ref P1) (pin 6))
+ (node (ref U3) (pin 2))
+ (node (ref U3) (pin 1))
+ (node (ref U2) (pin 17))
+ (node (ref R8) (pin 2))
+ (node (ref C2) (pin 2))
+ (node (ref P2) (pin 4)))
+ (net (code 17) (name /DEBUG_LED2)
+ (node (ref R1) (pin 1))
+ (node (ref U1) (pin 14)))
+ (net (code 18) (name "Net-(D4-Pad2)")
+ (node (ref D4) (pin 2))
+ (node (ref R2) (pin 2)))
+ (net (code 19) (name "Net-(U1-Pad15)")
+ (node (ref U1) (pin 15)))
+ (net (code 20) (name "Net-(J1-Pad2)")
+ (node (ref J1) (pin 2))
+ (node (ref R8) (pin 1)))
+ (net (code 21) (name "Net-(U2-Pad12)")
+ (node (ref U2) (pin 12)))
+ (net (code 22) (name "Net-(D3-Pad2)")
+ (node (ref D3) (pin 2))
+ (node (ref R1) (pin 2)))
+ (net (code 23) (name "Net-(U1-Pad8)")
+ (node (ref U1) (pin 8)))
+ (net (code 24) (name "Net-(U1-Pad2)")
+ (node (ref U1) (pin 2)))
+ (net (code 25) (name "Net-(U1-Pad3)")
+ (node (ref U1) (pin 3)))
+ (net (code 26) (name "Net-(U1-Pad4)")
+ (node (ref U1) (pin 4)))
+ (net (code 27) (name "Net-(U1-Pad5)")
+ (node (ref U1) (pin 5)))
+ (net (code 28) (name "Net-(U1-Pad12)")
+ (node (ref U1) (pin 12)))
+ (net (code 29) (name /SERIAL_TX)
+ (node (ref P1) (pin 2))
+ (node (ref U1) (pin 10)))
+ (net (code 30) (name "Net-(D1-Pad1)")
+ (node (ref U1) (pin 16))
+ (node (ref D1) (pin 1)))
+ (net (code 31) (name /GXY_PIN)
+ (node (ref U1) (pin 17))
+ (node (ref U2) (pin 10)))
+ (net (code 32) (name /GZ_PIN)
+ (node (ref U1) (pin 18))
+ (node (ref U2) (pin 8)))
+ (net (code 33) (name /PD_SCREEN_PIN)
+ (node (ref U3) (pin 4))
+ (node (ref U1) (pin 22))
+ (node (ref R3) (pin 2)))
+ (net (code 34) (name "Net-(U2-Pad4)")
+ (node (ref U2) (pin 4)))
+ (net (code 35) (name "Net-(U2-Pad11)")
+ (node (ref U2) (pin 11)))
+ (net (code 36) (name "Net-(U2-Pad9)")
+ (node (ref U2) (pin 9)))
+ (net (code 37) (name /AUDIO_PIN)
+ (node (ref J1) (pin 3))
+ (node (ref U1) (pin 24))
+ (node (ref R7) (pin 2)))
+ (net (code 38) (name "Net-(U2-Pad1)")
+ (node (ref U2) (pin 1)))
+ (net (code 39) (name /MIC_PIN)
+ (node (ref U1) (pin 25))
+ (node (ref R4) (pin 1)))
+ (net (code 40) (name "Net-(U2-Pad13)")
+ (node (ref U2) (pin 13)))
+ (net (code 41) (name "Net-(U2-Pad16)")
+ (node (ref U2) (pin 16)))
+ (net (code 42) (name /SERIAL_RX)
+ (node (ref U1) (pin 9))
+ (node (ref P1) (pin 3)))))
\ No newline at end of file
diff --git a/hardware/kicad/WALTsm.pro b/hardware/kicad/WALTsm.pro
new file mode 100644
index 0000000..eb45c89
--- /dev/null
+++ b/hardware/kicad/WALTsm.pro
@@ -0,0 +1,72 @@
+update=Mon 19 Sep 2016 11:55:35 AM EDT
+version=1
+last_client=kicad
+[pcbnew]
+version=1
+LastNetListRead=
+UseCmpFile=1
+PadDrill=0.600000000000
+PadDrillOvalY=0.600000000000
+PadSizeH=1.500000000000
+PadSizeV=1.500000000000
+PcbTextSizeV=1.500000000000
+PcbTextSizeH=1.500000000000
+PcbTextThickness=0.300000000000
+ModuleTextSizeV=1.000000000000
+ModuleTextSizeH=1.000000000000
+ModuleTextSizeThickness=0.150000000000
+SolderMaskClearance=0.000000000000
+SolderMaskMinWidth=0.000000000000
+DrawSegmentWidth=0.200000000000
+BoardOutlineThickness=0.100000000000
+ModuleOutlineThickness=0.150000000000
+[cvpcb]
+version=1
+NetIExt=net
+[general]
+version=1
+[eeschema]
+version=1
+LibDir=
+[eeschema/libraries]
+LibName1=power
+LibName2=device
+LibName3=transistors
+LibName4=conn
+LibName5=linear
+LibName6=regul
+LibName7=74xx
+LibName8=cmos4000
+LibName9=adc-dac
+LibName10=memory
+LibName11=xilinx
+LibName12=microcontrollers
+LibName13=dsp
+LibName14=microchip
+LibName15=analog_switches
+LibName16=motorola
+LibName17=texas
+LibName18=intel
+LibName19=audio
+LibName20=interface
+LibName21=digital-audio
+LibName22=philips
+LibName23=display
+LibName24=cypress
+LibName25=siliconi
+LibName26=opto
+LibName27=atmel
+LibName28=contrib
+LibName29=valves
+LibName30=walt_misc
+LibName31=teensy
+[schematic_editor]
+version=1
+PageLayoutDescrFile=
+PlotDirectoryName=
+SubpartIdSeparator=0
+SubpartFirstId=65
+NetFmtName=
+SpiceForceRefPrefix=0
+SpiceUseNetNumbers=0
+LabSize=60
diff --git a/hardware/kicad/WALTsm.sch b/hardware/kicad/WALTsm.sch
new file mode 100644
index 0000000..682d79d
--- /dev/null
+++ b/hardware/kicad/WALTsm.sch
@@ -0,0 +1,782 @@
+EESchema Schematic File Version 2
+LIBS:power
+LIBS:device
+LIBS:transistors
+LIBS:conn
+LIBS:linear
+LIBS:regul
+LIBS:74xx
+LIBS:cmos4000
+LIBS:adc-dac
+LIBS:memory
+LIBS:xilinx
+LIBS:microcontrollers
+LIBS:dsp
+LIBS:microchip
+LIBS:analog_switches
+LIBS:motorola
+LIBS:texas
+LIBS:intel
+LIBS:audio
+LIBS:interface
+LIBS:digital-audio
+LIBS:philips
+LIBS:display
+LIBS:cypress
+LIBS:siliconi
+LIBS:opto
+LIBS:atmel
+LIBS:contrib
+LIBS:valves
+LIBS:walt_misc
+LIBS:teensy
+LIBS:WALTsm-cache
+EELAYER 25 0
+EELAYER END
+$Descr USLetter 11000 8500
+encoding utf-8
+Sheet 1 1
+Title "WALT Latency Timer"
+Date ""
+Rev ""
+Comp ""
+Comment1 ""
+Comment2 ""
+Comment3 ""
+Comment4 ""
+$EndDescr
+$Comp
+L TeensyLC_with_headers U1
+U 1 1 576468C7
+P 2650 2750
+F 0 "U1" H 2300 3500 50 0000 C CNN
+F 1 "TeensyLC_with_headers" V 2550 2750 50 0000 C CNN
+F 2 "Housings_DIP:DIP-28_W15.24mm" V 2950 2750 50 0001 C CNN
+F 3 "https://www.pjrc.com/teensy/teensyLC.html" V 2850 2750 50 0001 C CNN
+ 1 2650 2750
+ 1 0 0 -1
+$EndComp
+$Comp
+L GND #PWR01
+U 1 1 57646954
+P 1650 2000
+F 0 "#PWR01" H 1650 1750 50 0001 C CNN
+F 1 "GND" H 1650 1850 50 0000 C CNN
+F 2 "" H 1650 2000 50 0000 C CNN
+F 3 "" H 1650 2000 50 0000 C CNN
+ 1 1650 2000
+ -1 0 0 1
+$EndComp
+$Comp
+L GND #PWR02
+U 1 1 5764697D
+P 3950 2100
+F 0 "#PWR02" H 3950 1850 50 0001 C CNN
+F 1 "GND" H 3950 1950 50 0000 C CNN
+F 2 "" H 3950 2100 50 0000 C CNN
+F 3 "" H 3950 2100 50 0000 C CNN
+ 1 3950 2100
+ -1 0 0 1
+$EndComp
+$Comp
+L +3V3 #PWR03
+U 1 1 57646997
+P 4200 2100
+F 0 "#PWR03" H 4200 1950 50 0001 C CNN
+F 1 "+3V3" H 4200 2240 50 0000 C CNN
+F 2 "" H 4200 2100 50 0000 C CNN
+F 3 "" H 4200 2100 50 0000 C CNN
+ 1 4200 2100
+ 1 0 0 -1
+$EndComp
+Text Label 4050 2400 0 60 ~ 0
+MIC_PIN
+Text Label 3950 2500 0 60 ~ 0
+AUDIO_PIN
+Text Label 3700 2700 0 60 ~ 0
+PD_SCREEN_PIN
+Text Label 4100 3100 0 60 ~ 0
+GZ_PIN
+Text Label 4050 3200 0 60 ~ 0
+GXY_PIN
+$Comp
+L Photodiode D1
+U 1 1 57646A24
+P 4400 3600
+F 0 "D1" H 4400 3700 50 0000 C CNN
+F 1 "Photodiode" H 4394 3471 50 0001 C CNN
+F 2 "walt_footprints:BPW34_DIP2" H 4400 3600 50 0001 C CNN
+F 3 "" H 4400 3600 50 0000 C CNN
+ 1 4400 3600
+ 0 1 1 0
+$EndComp
+$Comp
+L GND #PWR04
+U 1 1 57646A4B
+P 4400 3900
+F 0 "#PWR04" H 4400 3650 50 0001 C CNN
+F 1 "GND" H 4400 3750 50 0000 C CNN
+F 2 "" H 4400 3900 50 0000 C CNN
+F 3 "" H 4400 3900 50 0000 C CNN
+ 1 4400 3900
+ 1 0 0 -1
+$EndComp
+$Comp
+L R R1
+U 1 1 57646BD5
+P 2450 800
+F 0 "R1" V 2530 800 50 0000 C CNN
+F 1 "330" V 2450 800 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 2380 800 50 0001 C CNN
+F 3 "" H 2450 800 50 0000 C CNN
+ 1 2450 800
+ 0 -1 -1 0
+$EndComp
+$Comp
+L R R2
+U 1 1 57646C37
+P 2450 1200
+F 0 "R2" V 2530 1200 50 0000 C CNN
+F 1 "330" V 2450 1200 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 2380 1200 50 0001 C CNN
+F 3 "" H 2450 1200 50 0000 C CNN
+ 1 2450 1200
+ 0 -1 -1 0
+$EndComp
+$Comp
+L GND #PWR05
+U 1 1 57646C65
+P 3400 800
+F 0 "#PWR05" H 3400 550 50 0001 C CNN
+F 1 "GND" H 3400 650 50 0000 C CNN
+F 2 "" H 3400 800 50 0000 C CNN
+F 3 "" H 3400 800 50 0000 C CNN
+ 1 3400 800
+ 0 -1 -1 0
+$EndComp
+$Comp
+L LED D3
+U 1 1 57646CD9
+P 3000 800
+F 0 "D3" H 3000 900 50 0000 C CNN
+F 1 "LED" H 3000 700 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" H 3000 800 50 0001 C CNN
+F 3 "" H 3000 800 50 0000 C CNN
+ 1 3000 800
+ -1 0 0 1
+$EndComp
+$Comp
+L LED D4
+U 1 1 57646D13
+P 3000 1200
+F 0 "D4" H 3000 1300 50 0000 C CNN
+F 1 "LED" H 3000 1100 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" H 3000 1200 50 0001 C CNN
+F 3 "" H 3000 1200 50 0000 C CNN
+ 1 3000 1200
+ -1 0 0 1
+$EndComp
+$Comp
+L GND #PWR06
+U 1 1 57646D3D
+P 3400 1200
+F 0 "#PWR06" H 3400 950 50 0001 C CNN
+F 1 "GND" H 3400 1050 50 0000 C CNN
+F 2 "" H 3400 1200 50 0000 C CNN
+F 3 "" H 3400 1200 50 0000 C CNN
+ 1 3400 1200
+ 0 -1 -1 0
+$EndComp
+Text Label 1600 1200 0 60 ~ 0
+DEBUG_LED1
+Text Label 1600 800 0 60 ~ 0
+DEBUG_LED2
+$Comp
+L +3V3 #PWR07
+U 1 1 57647B1C
+P 2750 5100
+F 0 "#PWR07" H 2750 4950 50 0001 C CNN
+F 1 "+3V3" H 2750 5240 50 0000 C CNN
+F 2 "" H 2750 5100 50 0000 C CNN
+F 3 "" H 2750 5100 50 0000 C CNN
+ 1 2750 5100
+ 1 0 0 -1
+$EndComp
+$Comp
+L ADXL335 U2
+U 1 1 57647D5A
+P 3550 5800
+F 0 "U2" H 2950 6450 50 0000 L CNN
+F 1 "ADXL335" H 3800 6450 50 0000 L CNN
+F 2 "Housings_DFN_QFN:QFN-16-1EP_4x4mm_Pitch0.65mm" H 3550 5500 50 0001 C CNN
+F 3 "" H 3550 5500 50 0000 C CNN
+ 1 3550 5800
+ 1 0 0 -1
+$EndComp
+$Comp
+L GND #PWR08
+U 1 1 57647DD6
+P 2750 6500
+F 0 "#PWR08" H 2750 6250 50 0001 C CNN
+F 1 "GND" H 2750 6350 50 0000 C CNN
+F 2 "" H 2750 6500 50 0000 C CNN
+F 3 "" H 2750 6500 50 0000 C CNN
+ 1 2750 6500
+ 1 0 0 -1
+$EndComp
+Text Label 4650 5600 0 60 ~ 0
+GZ_PIN
+Text Label 4550 5500 0 60 ~ 0
+GXY_PIN
+$Comp
+L GND #PWR09
+U 1 1 5764825A
+P 6200 3300
+F 0 "#PWR09" H 6200 3050 50 0001 C CNN
+F 1 "GND" H 6200 3150 50 0000 C CNN
+F 2 "" H 6200 3300 50 0000 C CNN
+F 3 "" H 6200 3300 50 0000 C CNN
+ 1 6200 3300
+ 0 -1 -1 0
+$EndComp
+$Comp
+L +3V3 #PWR010
+U 1 1 576482AD
+P 6000 2400
+F 0 "#PWR010" H 6000 2250 50 0001 C CNN
+F 1 "+3V3" H 6000 2540 50 0000 C CNN
+F 2 "" H 6000 2400 50 0000 C CNN
+F 3 "" H 6000 2400 50 0000 C CNN
+ 1 6000 2400
+ 1 0 0 -1
+$EndComp
+$Comp
+L GND #PWR011
+U 1 1 57648307
+P 5600 2600
+F 0 "#PWR011" H 5600 2350 50 0001 C CNN
+F 1 "GND" H 5600 2450 50 0000 C CNN
+F 2 "" H 5600 2600 50 0000 C CNN
+F 3 "" H 5600 2600 50 0000 C CNN
+ 1 5600 2600
+ -1 0 0 1
+$EndComp
+$Comp
+L LM321MFX U3
+U 1 1 576483C4
+P 6100 2900
+F 0 "U3" H 6150 3100 50 0000 C CNN
+F 1 "LM321MFX" H 6300 2700 50 0000 C CNN
+F 2 "TO_SOT_Packages_SMD:SOT-23-5" H 6050 2600 50 0001 L CNN
+F 3 "" H 6150 3100 50 0000 C CNN
+ 1 6100 2900
+ 1 0 0 -1
+$EndComp
+$Comp
+L R R3
+U 1 1 57648608
+P 6150 3600
+F 0 "R3" V 6230 3600 50 0000 C CNN
+F 1 "510k" V 6150 3600 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 6080 3600 50 0001 C CNN
+F 3 "" H 6150 3600 50 0000 C CNN
+ 1 6150 3600
+ 0 -1 -1 0
+$EndComp
+$Comp
+L GND #PWR012
+U 1 1 57648798
+P 5600 4300
+F 0 "#PWR012" H 5600 4050 50 0001 C CNN
+F 1 "GND" H 5600 4150 50 0000 C CNN
+F 2 "" H 5600 4300 50 0000 C CNN
+F 3 "" H 5600 4300 50 0000 C CNN
+ 1 5600 4300
+ 1 0 0 -1
+$EndComp
+$Comp
+L Photodiode D2
+U 1 1 576487CA
+P 5600 3950
+F 0 "D2" H 5600 4050 50 0000 C CNN
+F 1 "Photodiode" H 5594 3821 50 0001 C CNN
+F 2 "walt_footprints:BPW34_DIP2" H 5600 3950 50 0001 C CNN
+F 3 "" H 5600 3950 50 0000 C CNN
+ 1 5600 3950
+ 0 1 1 0
+$EndComp
+$Comp
+L JACK_TRRS J1
+U 1 1 5764949B
+P 10000 1500
+F 0 "J1" H 10200 1850 50 0000 C CNN
+F 1 "JACK_TRRS" H 9950 1250 50 0000 C CNN
+F 2 "walt_footprints:TRRS_SJ_43514" H 9800 1350 50 0001 C CNN
+F 3 "" H 9800 1350 50 0000 C CNN
+ 1 10000 1500
+ 1 0 0 -1
+$EndComp
+$Comp
+L R R7
+U 1 1 57649565
+P 9200 2250
+F 0 "R7" V 9280 2250 50 0000 C CNN
+F 1 "100" V 9200 2250 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 9130 2250 50 0001 C CNN
+F 3 "" H 9200 2250 50 0000 C CNN
+ 1 9200 2250
+ -1 0 0 1
+$EndComp
+$Comp
+L GND #PWR013
+U 1 1 57649633
+P 9200 2600
+F 0 "#PWR013" H 9200 2350 50 0001 C CNN
+F 1 "GND" H 9200 2450 50 0000 C CNN
+F 2 "" H 9200 2600 50 0000 C CNN
+F 3 "" H 9200 2600 50 0000 C CNN
+ 1 9200 2600
+ 1 0 0 -1
+$EndComp
+Text Label 8600 1900 0 60 ~ 0
+AUDIO_PIN
+$Comp
+L C C1
+U 1 1 5764985B
+P 9050 1300
+F 0 "C1" H 9075 1400 50 0000 L CNN
+F 1 "0.1uF" H 9075 1200 50 0000 L CNN
+F 2 "Capacitors_SMD:C_0805" H 9088 1150 50 0001 C CNN
+F 3 "" H 9050 1300 50 0000 C CNN
+ 1 9050 1300
+ 0 1 1 0
+$EndComp
+$Comp
+L R R4
+U 1 1 576498AB
+P 8550 1300
+F 0 "R4" V 8630 1300 50 0000 C CNN
+F 1 "3,3k" V 8550 1300 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 8480 1300 50 0001 C CNN
+F 3 "" H 8550 1300 50 0000 C CNN
+ 1 8550 1300
+ 0 -1 -1 0
+$EndComp
+$Comp
+L R R5
+U 1 1 576498E4
+P 8800 1050
+F 0 "R5" V 8880 1050 50 0000 C CNN
+F 1 "100" V 8800 1050 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 8730 1050 50 0001 C CNN
+F 3 "" H 8800 1050 50 0000 C CNN
+ 1 8800 1050
+ 1 0 0 -1
+$EndComp
+$Comp
+L R R6
+U 1 1 5764991F
+P 9300 1050
+F 0 "R6" V 9380 1050 50 0000 C CNN
+F 1 "3.3k" V 9300 1050 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 9230 1050 50 0001 C CNN
+F 3 "" H 9300 1050 50 0000 C CNN
+ 1 9300 1050
+ 1 0 0 -1
+$EndComp
+$Comp
+L GND #PWR014
+U 1 1 57649B4B
+P 9300 800
+F 0 "#PWR014" H 9300 550 50 0001 C CNN
+F 1 "GND" H 9300 650 50 0000 C CNN
+F 2 "" H 9300 800 50 0000 C CNN
+F 3 "" H 9300 800 50 0000 C CNN
+ 1 9300 800
+ -1 0 0 1
+$EndComp
+$Comp
+L GND #PWR015
+U 1 1 57649B86
+P 8800 800
+F 0 "#PWR015" H 8800 550 50 0001 C CNN
+F 1 "GND" H 8800 650 50 0000 C CNN
+F 2 "" H 8800 800 50 0000 C CNN
+F 3 "" H 8800 800 50 0000 C CNN
+ 1 8800 800
+ -1 0 0 1
+$EndComp
+Text Label 7800 1300 0 60 ~ 0
+MIC_PIN
+NoConn ~ 4250 5800
+NoConn ~ 4250 5900
+NoConn ~ 4250 6000
+NoConn ~ 4250 6100
+NoConn ~ 4250 6200
+NoConn ~ 4250 6300
+Text Label 7000 2900 0 60 ~ 0
+PD_SCREEN_PIN
+$Comp
+L +3V3 #PWR016
+U 1 1 5764A52C
+P 800 800
+F 0 "#PWR016" H 800 650 50 0001 C CNN
+F 1 "+3V3" H 800 940 50 0000 C CNN
+F 2 "" H 800 800 50 0000 C CNN
+F 3 "" H 800 800 50 0000 C CNN
+ 1 800 800
+ 1 0 0 -1
+$EndComp
+$Comp
+L GND #PWR017
+U 1 1 5764A560
+P 800 1700
+F 0 "#PWR017" H 800 1450 50 0001 C CNN
+F 1 "GND" H 800 1550 50 0000 C CNN
+F 2 "" H 800 1700 50 0000 C CNN
+F 3 "" H 800 1700 50 0000 C CNN
+ 1 800 1700
+ 1 0 0 -1
+$EndComp
+$Comp
+L PWR_FLAG #FLG018
+U 1 1 5764A594
+P 800 1500
+F 0 "#FLG018" H 800 1595 50 0001 C CNN
+F 1 "PWR_FLAG" H 800 1680 50 0000 C CNN
+F 2 "" H 800 1500 50 0000 C CNN
+F 3 "" H 800 1500 50 0000 C CNN
+ 1 800 1500
+ 1 0 0 -1
+$EndComp
+$Comp
+L PWR_FLAG #FLG019
+U 1 1 5764A5D6
+P 800 1000
+F 0 "#FLG019" H 800 1095 50 0001 C CNN
+F 1 "PWR_FLAG" H 800 1180 50 0000 C CNN
+F 2 "" H 800 1000 50 0000 C CNN
+F 3 "" H 800 1000 50 0000 C CNN
+ 1 800 1000
+ -1 0 0 1
+$EndComp
+Text Label 1300 2900 0 60 ~ 0
+SERIAL_RX
+Text Label 1300 3000 0 60 ~ 0
+SERIAL_TX
+$Comp
+L CONN_01X06 P1
+U 1 1 5765C52E
+P 900 4250
+F 0 "P1" H 900 4600 50 0000 C CNN
+F 1 "CONN_01X06" V 1000 4250 50 0000 C CNN
+F 2 "walt_footprints:Pin_Header_Straight_1x06" H 900 4250 50 0001 C CNN
+F 3 "" H 900 4250 50 0000 C CNN
+ 1 900 4250
+ -1 0 0 1
+$EndComp
+Text Label 1200 4400 0 60 ~ 0
+SERIAL_TX
+Text Label 1200 4300 0 60 ~ 0
+SERIAL_RX
+$Comp
+L GND #PWR020
+U 1 1 5765C7B4
+P 1300 3850
+F 0 "#PWR020" H 1300 3600 50 0001 C CNN
+F 1 "GND" H 1300 3700 50 0000 C CNN
+F 2 "" H 1300 3850 50 0000 C CNN
+F 3 "" H 1300 3850 50 0000 C CNN
+ 1 1300 3850
+ -1 0 0 1
+$EndComp
+NoConn ~ 1100 4200
+Text Label 1300 2700 0 60 ~ 0
+SERIAL_RTS
+Text Label 1300 3100 0 60 ~ 0
+SERIAL_CTS
+Text Label 1200 4100 0 60 ~ 0
+SERIAL_RTS
+Text Label 1200 4500 0 60 ~ 0
+SERIAL_CTS
+Text Label 3400 2100 0 60 ~ 0
+VUSB_5V
+Text Label 1300 5100 0 60 ~ 0
+VUSB_5V
+$Comp
+L GND #PWR021
+U 1 1 5765DB92
+P 1700 5300
+F 0 "#PWR021" H 1700 5050 50 0001 C CNN
+F 1 "GND" H 1700 5150 50 0000 C CNN
+F 2 "" H 1700 5300 50 0000 C CNN
+F 3 "" H 1700 5300 50 0000 C CNN
+ 1 1700 5300
+ 0 -1 -1 0
+$EndComp
+Text Label 1200 5400 0 60 ~ 0
+GPIO_A5
+Text Label 1200 5500 0 60 ~ 0
+GPIO_A4
+Text Label 1200 5600 0 60 ~ 0
+GPIO_A3
+Text Label 3700 2800 0 60 ~ 0
+GPIO_A5
+Text Label 3700 2900 0 60 ~ 0
+GPIO_A4
+Text Label 3700 3000 0 60 ~ 0
+GPIO_A3
+NoConn ~ 1900 2600
+NoConn ~ 3400 2600
+NoConn ~ 2850 5600
+Wire Wire Line
+ 1650 2100 1900 2100
+Wire Wire Line
+ 3400 2400 4350 2400
+Wire Wire Line
+ 4350 2500 3400 2500
+Wire Wire Line
+ 4400 2700 3400 2700
+Wire Wire Line
+ 4400 3000 3400 3000
+Wire Wire Line
+ 4400 3100 3400 3100
+Wire Wire Line
+ 4400 3200 3400 3200
+Wire Wire Line
+ 4400 3900 4400 3750
+Wire Wire Line
+ 3400 1200 3200 1200
+Wire Wire Line
+ 3200 800 3400 800
+Wire Wire Line
+ 1600 800 2300 800
+Wire Wire Line
+ 1600 1200 2300 1200
+Wire Wire Line
+ 2750 5100 2750 5400
+Wire Wire Line
+ 2450 5400 2850 5400
+Wire Wire Line
+ 2850 5300 2750 5300
+Connection ~ 2750 5300
+Wire Wire Line
+ 2750 5800 2750 6500
+Wire Wire Line
+ 2450 5800 2850 5800
+Wire Wire Line
+ 2850 5900 2750 5900
+Connection ~ 2750 5900
+Wire Wire Line
+ 2750 6000 2850 6000
+Connection ~ 2750 6000
+Wire Wire Line
+ 2850 6100 2750 6100
+Connection ~ 2750 6100
+Wire Wire Line
+ 2750 6300 2850 6300
+Connection ~ 2750 6300
+Wire Wire Line
+ 4950 5600 4250 5600
+Wire Wire Line
+ 4950 5500 4250 5500
+Wire Wire Line
+ 6000 2400 6000 2600
+Wire Wire Line
+ 5600 2600 5600 2800
+Wire Wire Line
+ 5600 2800 5800 2800
+Wire Wire Line
+ 6200 3300 6000 3300
+Wire Wire Line
+ 6000 3300 6000 3200
+Wire Wire Line
+ 5600 4300 5600 4100
+Wire Wire Line
+ 5600 3000 5600 3800
+Wire Wire Line
+ 5600 3000 5800 3000
+Wire Wire Line
+ 6000 3600 5600 3600
+Connection ~ 5600 3600
+Wire Wire Line
+ 6300 3600 6700 3600
+Wire Wire Line
+ 6700 3600 6700 2900
+Wire Wire Line
+ 6400 2900 7700 2900
+Wire Wire Line
+ 9200 2600 9200 2400
+Wire Wire Line
+ 9500 1500 9200 1500
+Wire Wire Line
+ 9200 1500 9200 2100
+Wire Wire Line
+ 8600 1900 9200 1900
+Connection ~ 9200 1900
+Wire Wire Line
+ 8700 1300 8900 1300
+Wire Wire Line
+ 8800 1300 8800 1200
+Connection ~ 8800 1300
+Wire Wire Line
+ 9300 1300 9300 1200
+Connection ~ 9300 1300
+Wire Wire Line
+ 7800 1300 8400 1300
+Wire Wire Line
+ 8800 900 8800 800
+Wire Wire Line
+ 9300 800 9300 900
+Connection ~ 6700 2900
+Wire Wire Line
+ 800 800 800 1000
+Wire Wire Line
+ 800 1500 800 1700
+Wire Wire Line
+ 1650 2000 1650 2100
+Wire Wire Line
+ 1300 2900 1900 2900
+Wire Wire Line
+ 1900 3000 1300 3000
+Wire Wire Line
+ 1700 4400 1100 4400
+Wire Wire Line
+ 1700 4300 1100 4300
+Wire Wire Line
+ 1300 4000 1100 4000
+Wire Wire Line
+ 1300 2700 1900 2700
+Wire Wire Line
+ 1900 3100 1300 3100
+Wire Wire Line
+ 1700 4100 1100 4100
+Wire Wire Line
+ 1300 4000 1300 3850
+Wire Wire Line
+ 1700 4500 1100 4500
+Wire Wire Line
+ 1700 5100 1100 5100
+Wire Wire Line
+ 1700 5400 1100 5400
+Wire Wire Line
+ 1700 5500 1100 5500
+Wire Wire Line
+ 1700 5600 1100 5600
+Wire Wire Line
+ 3400 2300 4200 2300
+Wire Wire Line
+ 4200 2300 4200 2100
+Wire Wire Line
+ 3400 2200 3950 2200
+Wire Wire Line
+ 3950 2200 3950 2100
+Wire Wire Line
+ 3800 2100 3400 2100
+Wire Wire Line
+ 1700 5300 1100 5300
+Wire Wire Line
+ 4400 2900 3400 2900
+Wire Wire Line
+ 4400 2800 3400 2800
+Wire Wire Line
+ 3400 3300 4400 3300
+Wire Wire Line
+ 4400 3300 4400 3450
+Wire Wire Line
+ 9200 1300 9500 1300
+$Comp
+L GND #PWR022
+U 1 1 5766B49C
+P 9400 1400
+F 0 "#PWR022" H 9400 1150 50 0001 C CNN
+F 1 "GND" H 9400 1250 50 0000 C CNN
+F 2 "" H 9400 1400 50 0000 C CNN
+F 3 "" H 9400 1400 50 0000 C CNN
+ 1 9400 1400
+ 0 1 1 0
+$EndComp
+Wire Wire Line
+ 9400 1400 9500 1400
+Wire Wire Line
+ 2800 800 2600 800
+Wire Wire Line
+ 2600 1200 2800 1200
+$Comp
+L C C2
+U 1 1 5766D3A4
+P 2450 5600
+F 0 "C2" H 2475 5700 50 0000 L CNN
+F 1 "0.1uF" H 2475 5500 50 0000 L CNN
+F 2 "Capacitors_SMD:C_0805" H 2488 5450 50 0001 C CNN
+F 3 "" H 2450 5600 50 0000 C CNN
+ 1 2450 5600
+ 1 0 0 -1
+$EndComp
+Wire Wire Line
+ 2450 5750 2450 5800
+Connection ~ 2750 5800
+Wire Wire Line
+ 2450 5400 2450 5450
+Connection ~ 2750 5400
+$Comp
+L +3V3 #PWR023
+U 1 1 5766F66D
+P 1550 5200
+F 0 "#PWR023" H 1550 5050 50 0001 C CNN
+F 1 "+3V3" H 1550 5340 50 0000 C CNN
+F 2 "" H 1550 5200 50 0000 C CNN
+F 3 "" H 1550 5200 50 0000 C CNN
+ 1 1550 5200
+ 0 1 1 0
+$EndComp
+$Comp
+L CONN_01X06 P2
+U 1 1 57681A8E
+P 900 5350
+F 0 "P2" H 900 5700 50 0000 C CNN
+F 1 "CONN_01X06" V 1000 5350 50 0000 C CNN
+F 2 "walt_footprints:Pin_Header_Straight_1x06" H 900 5350 50 0001 C CNN
+F 3 "" H 900 5350 50 0000 C CNN
+ 1 900 5350
+ -1 0 0 1
+$EndComp
+Wire Wire Line
+ 1550 5200 1100 5200
+Wire Wire Line
+ 1300 3300 1900 3300
+Wire Wire Line
+ 1300 3400 1900 3400
+Text Label 1300 3300 0 60 ~ 0
+DEBUG_LED1
+Text Label 1300 3400 0 60 ~ 0
+DEBUG_LED2
+NoConn ~ 1900 3200
+NoConn ~ 1900 2500
+NoConn ~ 1900 2400
+NoConn ~ 1900 2300
+NoConn ~ 1900 2200
+NoConn ~ 1900 2800
+NoConn ~ 3400 3400
+NoConn ~ 4250 5400
+$Comp
+L R R8
+U 1 1 58B4C350
+P 9400 2250
+F 0 "R8" V 9480 2250 50 0000 C CNN
+F 1 "100" V 9400 2250 50 0000 C CNN
+F 2 "walt_footprints:R_0603_pad07mm_long" V 9330 2250 50 0001 C CNN
+F 3 "" H 9400 2250 50 0000 C CNN
+ 1 9400 2250
+ 1 0 0 -1
+$EndComp
+Wire Wire Line
+ 9500 1600 9400 1600
+Wire Wire Line
+ 9400 1600 9400 2100
+Wire Wire Line
+ 9400 2400 9400 2500
+Wire Wire Line
+ 9400 2500 9200 2500
+Connection ~ 9200 2500
+$EndSCHEMATC
diff --git a/hardware/kicad/teensy.lib b/hardware/kicad/teensy.lib
new file mode 100644
index 0000000..c8e5dc1
--- /dev/null
+++ b/hardware/kicad/teensy.lib
@@ -0,0 +1,48 @@
+EESchema-LIBRARY Version 2.3
+#encoding utf-8
+#
+# TeensyLC_with_headers
+#
+DEF TeensyLC_with_headers U 0 0 N Y 1 F N
+F0 "U" -350 750 50 H V C CNN
+F1 "TeensyLC_with_headers" -100 0 50 V V C CNN
+F2 "Housings_DIP:DIP-28_W15.24mm_LongPads" 300 0 50 V I C CNN
+F3 "https://www.pjrc.com/teensy/teensyLC.html" 200 0 50 V I C CNN
+$FPLIST
+ Housings_DIP:DIP-28_W15.24mm_LongPads
+ Housings_DIP:DIP-28_W15.24mm
+$ENDFPLIST
+DRAW
+S -450 -850 450 850 1 0 0 N
+X GND 1 -750 650 300 R 50 50 1 1 P
+X D0 2 -750 550 300 R 50 50 1 1 B
+X D1 3 -750 450 300 R 50 50 1 1 B
+X D2 4 -750 350 300 R 50 50 1 1 B
+X D3 5 -750 250 300 R 50 50 1 1 B
+X D4 6 -750 150 300 R 50 50 1 1 B
+X D5 7 -750 50 300 R 50 50 1 1 B
+X D6 8 -750 -50 300 R 50 50 1 1 B
+X D7 9 -750 -150 300 R 50 50 1 1 B
+X D8 10 -750 -250 300 R 50 50 1 1 B
+X D18_A4 20 750 -150 300 L 50 50 1 1 B
+X D9 11 -750 -350 300 R 50 50 1 1 B
+X D19_A5 21 750 -50 300 L 50 50 1 1 B
+X D10 12 -750 -450 300 R 50 50 1 1 B
+X D20_A6 22 750 50 300 L 50 50 1 1 B
+X D11 13 -750 -550 300 R 50 50 1 1 B
+X D21_A7 23 750 150 300 L 50 50 1 1 B
+X D12 14 -750 -650 300 R 50 50 1 1 B
+X D22_A8 24 750 250 300 L 50 50 1 1 B
+X D13_LED 15 750 -650 300 L 50 50 1 1 B
+X D23_A9 25 750 350 300 L 50 50 1 1 B
+X D14_A0 16 750 -550 300 L 50 50 1 1 B
+X 3V3 26 750 450 300 L 50 50 1 1 W
+X D15_A1 17 750 -450 300 L 50 50 1 1 B
+X GND 27 750 550 300 L 50 50 1 1 P
+X D16_A2 18 750 -350 300 L 50 50 1 1 B
+X VCC 28 750 650 300 L 50 50 1 1 P
+X D17_A3 19 750 -250 300 L 50 50 1 1 B
+ENDDRAW
+ENDDEF
+#
+#End Library
diff --git a/hardware/kicad/walt_footprints.pretty/BMI160.kicad_mod b/hardware/kicad/walt_footprints.pretty/BMI160.kicad_mod
new file mode 100644
index 0000000..40fb6ab
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/BMI160.kicad_mod
@@ -0,0 +1,27 @@
+(module teensy32:BMI160 (layer F.Cu) (tedit 56F22C5B)
+ (fp_text reference U4 (at 0 2.75) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value BMI160 (at 1.59 4.3675) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -2 1.75) (end -2 -1.75) (layer F.SilkS) (width 0.15))
+ (fp_line (start 2 1.75) (end -2 1.75) (layer F.SilkS) (width 0.15))
+ (fp_line (start 2 -1.75) (end 2 1.75) (layer F.SilkS) (width 0.15))
+ (fp_line (start -2 -1.75) (end 2 -1.75) (layer F.SilkS) (width 0.15))
+ (fp_circle (center -2 -1.75) (end -1.7 -1.7) (layer F.SilkS) (width 0.15))
+ (pad 6 smd rect (at 0 1.0125 90) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 13 smd rect (at 0 -1.0125 90) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 7 smd rect (at 0.5 1.0125 90) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 5 smd rect (at -0.5 1.0125 90) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 12 smd rect (at 0.5 -1.0125 90) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 14 smd rect (at -0.5 -1.0125 90) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 2 smd rect (at -1.2625 -0.25 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 1 smd rect (at -1.2625 -0.75 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 3 smd rect (at -1.2625 0.25 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 4 smd rect (at -1.2625 0.75 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 8 smd rect (at 1.2625 0.75 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 10 smd rect (at 1.2625 -0.25 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 9 smd rect (at 1.2625 0.25 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+ (pad 11 smd rect (at 1.2625 -0.75 180) (size 0.675 0.25) (layers F.Cu F.Paste F.Mask))
+)
diff --git a/hardware/kicad/walt_footprints.pretty/BPW34_DIP2.kicad_mod b/hardware/kicad/walt_footprints.pretty/BPW34_DIP2.kicad_mod
new file mode 100644
index 0000000..d1f6552
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/BPW34_DIP2.kicad_mod
@@ -0,0 +1,28 @@
+(module BPW34_DIP2 (layer F.Cu) (tedit 571545A8)
+ (descr BPW34_DIP2)
+ (tags BPW34)
+ (fp_text reference REF** (at 1.905 3.175) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value Photodiode (at 2.52 -3.15) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start 1.27 0) (end 3.81 1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start 3.81 1.905) (end 3.81 -1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start 3.81 -1.905) (end 1.27 0) (layer F.SilkS) (width 0.15))
+ (fp_line (start 1.27 -1.905) (end 1.27 1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 -2.15) (end 6.35 -2.15) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 6.35 -2.15) (end 6.35 2.15) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 6.35 2.15) (end -1.27 2.15) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.27 2.15) (end -1.27 -2.15) (layer F.CrtYd) (width 0.05))
+ (fp_text user K (at -1.905 1.27) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (pad 1 thru_hole rect (at 0 0 90) (size 2 1.9) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 2 thru_hole circle (at 5.08 0) (size 1.9 1.9) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (model LEDs.3dshapes/LED-5MM.wrl
+ (at (xyz 0.05 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+)
diff --git a/hardware/kicad/walt_footprints.pretty/LED-5MM.kicad_mod b/hardware/kicad/walt_footprints.pretty/LED-5MM.kicad_mod
new file mode 100644
index 0000000..5bbd221
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/LED-5MM.kicad_mod
@@ -0,0 +1,25 @@
+(module LEDs:LED-5MM (layer F.Cu) (tedit 5570F7EA)
+ (descr "LED 5mm round vertical")
+ (tags "LED 5mm round vertical")
+ (fp_text reference D4 (at 1.524 4.064) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value Photodiode (at 1.524 -3.937) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -1.5 -1.55) (end -1.5 1.55) (layer F.CrtYd) (width 0.05))
+ (fp_arc (start 1.3 0) (end -1.5 1.55) (angle -302) (layer F.CrtYd) (width 0.05))
+ (fp_arc (start 1.27 0) (end -1.23 -1.5) (angle 297.5) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.23 1.5) (end -1.23 -1.5) (layer F.SilkS) (width 0.15))
+ (fp_circle (center 1.27 0) (end 0.97 -2.5) (layer F.SilkS) (width 0.15))
+ (fp_text user K (at -1.905 1.905) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (pad 1 thru_hole rect (at 0 0 90) (size 2 1.9) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 2 thru_hole circle (at 2.54 0) (size 1.9 1.9) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (model LEDs.3dshapes/LED-5MM.wrl
+ (at (xyz 0.05 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+)
diff --git a/hardware/kicad/walt_footprints.pretty/Pin_Header_Straight_1x05_small_pads.kicad_mod b/hardware/kicad/walt_footprints.pretty/Pin_Header_Straight_1x05_small_pads.kicad_mod
new file mode 100644
index 0000000..a1e0faf
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/Pin_Header_Straight_1x05_small_pads.kicad_mod
@@ -0,0 +1,28 @@
+(module Pin_Header_Straight_1x05_small_pads (layer F.Cu) (tedit 5766F4A2)
+ (descr "Through hole pin header")
+ (tags "pin header")
+ (fp_text reference P2 (at 0 -2.54) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value CONN_01X05 (at -2.54 5.08 90) (layer F.Fab) hide
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.75 -1.75) (end -1.75 11.656) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.75 -1.75) (end 1.75 11.656) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 -1.75) (end 1.75 -1.75) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 11.656) (end 1.75 11.656) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.27 -1.55) (end 1.27 11.43) (layer F.SilkS) (width 0.15))
+ (fp_line (start 1.27 11.43) (end -1.27 11.43) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 11.43) (end -1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 -1.55) (end 1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (pad 1 thru_hole circle (at 0 0) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 2 thru_hole circle (at 0 2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 3 thru_hole circle (at 0 5.08) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 4 thru_hole circle (at 0 7.62) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 5 thru_hole circle (at 0 10.16) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (model Pin_Headers.3dshapes/Pin_Header_Straight_1x06.wrl
+ (at (xyz 0 -0.25 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+)
diff --git a/hardware/kicad/walt_footprints.pretty/Pin_Header_Straight_1x06.kicad_mod b/hardware/kicad/walt_footprints.pretty/Pin_Header_Straight_1x06.kicad_mod
new file mode 100644
index 0000000..d1ce85e
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/Pin_Header_Straight_1x06.kicad_mod
@@ -0,0 +1,29 @@
+(module Pin_Header_Straight_1x06 (layer F.Cu) (tedit 5766B543)
+ (descr "Through hole pin header")
+ (tags "pin header")
+ (fp_text reference P1 (at 0 -5.1) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value CONN_01X06 (at 0 -3.1) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -1.75 -1.75) (end -1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.75 -1.75) (end 1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 -1.75) (end 1.75 -1.75) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.75 14.45) (end 1.75 14.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.27 -1.55) (end 1.27 13.97) (layer F.SilkS) (width 0.15))
+ (fp_line (start 1.27 13.97) (end -1.27 13.97) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 13.97) (end -1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.27 -1.55) (end 1.27 -1.55) (layer F.SilkS) (width 0.15))
+ (pad 1 thru_hole circle (at 0 0) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 2 thru_hole circle (at 0 2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 3 thru_hole circle (at 0 5.08) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 4 thru_hole circle (at 0 7.62) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 5 thru_hole circle (at 0 10.16) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 6 thru_hole circle (at 0 12.7) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (model Pin_Headers.3dshapes/Pin_Header_Straight_1x06.wrl
+ (at (xyz 0 -0.25 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 90))
+ )
+)
diff --git a/hardware/kicad/walt_footprints.pretty/R_0603_pad07mm_long.kicad_mod b/hardware/kicad/walt_footprints.pretty/R_0603_pad07mm_long.kicad_mod
new file mode 100644
index 0000000..7b387df
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/R_0603_pad07mm_long.kicad_mod
@@ -0,0 +1,24 @@
+(module R_0603_pad07mm_long (layer F.Cu) (tedit 578554A0)
+ (descr "Resistor SMD 0603, reflow soldering, Vishay (see dcrcw.pdf)")
+ (tags "resistor 0603")
+ (attr smd)
+ (fp_text reference REF** (at 0 -1.9) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value R_0603 (at -0.01778 1.23444) (layer F.Fab)
+ (effects (font (size 0.5 0.5) (thickness 0.07)))
+ )
+ (fp_line (start -1.3 -0.8) (end 1.3 -0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.3 -0.8) (end -1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 1.3 -0.8) (end 1.3 0.8) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 0.5 0.675) (end -0.5 0.675) (layer F.SilkS) (width 0.15))
+ (fp_line (start -0.5 -0.675) (end 0.5 -0.675) (layer F.SilkS) (width 0.15))
+ (pad 1 smd rect (at -0.85 0) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask))
+ (pad 2 smd rect (at 0.85 0) (size 0.7 0.9) (layers F.Cu F.Paste F.Mask))
+ (model Resistors_SMD.3dshapes/R_0603.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+)
diff --git a/hardware/kicad/walt_footprints.pretty/SJ-43514-SMT-TR.kicad_mod b/hardware/kicad/walt_footprints.pretty/SJ-43514-SMT-TR.kicad_mod
new file mode 100644
index 0000000..67039b9
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/SJ-43514-SMT-TR.kicad_mod
@@ -0,0 +1,28 @@
+(module Footprints:SJ-43514-SMT-TR (layer F.Cu) (tedit 57436713)
+ (fp_text reference J2 (at -0.54 5.36) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value Audio-TRRS (at 6.53 -3.83) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text user IN (at -9.8 0.36) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start 8.64 -2.75) (end 8.64 3.84) (layer F.SilkS) (width 0.15))
+ (fp_line (start 5.42 3.83) (end 8.65 3.83) (layer F.SilkS) (width 0.15))
+ (fp_line (start -3.24 3.84) (end 2.15 3.84) (layer F.SilkS) (width 0.15))
+ (fp_line (start -11 3.92) (end -8.75 3.92) (layer F.SilkS) (width 0.15))
+ (fp_line (start -11 0) (end -11 3.92) (layer F.SilkS) (width 0.15))
+ (fp_line (start -1.33 -2.74) (end 8.67 -2.74) (layer F.SilkS) (width 0.15))
+ (fp_line (start -11.02 -2.66) (end -6.81 -2.66) (layer F.SilkS) (width 0.15))
+ (fp_line (start -11.02 -2) (end -11.02 -2.66) (layer F.SilkS) (width 0.15))
+ (fp_line (start -11.02 0.01) (end -11.02 -2) (layer F.SilkS) (width 0.15))
+ (pad ~ thru_hole circle (at -3.5 0) (size 1.8 1.8) (drill 1.7) (layers *.Cu *.Mask F.SilkS))
+ (pad ~ thru_hole circle (at 3.5 0) (size 1.8 1.8) (drill 1.7) (layers *.Cu *.Mask F.SilkS))
+ (pad 3 smd rect (at -2.7 -3.7) (size 2.2 2.8) (layers F.Cu F.Paste F.Mask))
+ (pad 6 smd rect (at -5.5 -3.7) (size 2 2.8) (layers F.Cu F.Paste F.Mask))
+ (pad 1 smd rect (at -7.4 3.7) (size 2 2.8) (layers F.Cu F.Paste F.Mask))
+ (pad 4 smd rect (at -4.6 3.7) (size 2.2 2.8) (layers F.Cu F.Paste F.Mask))
+ (pad 2 smd rect (at 3.8 3.7) (size 2.8 2.8) (layers F.Cu F.Paste F.Mask))
+ (pad 5 smd rect (at 10.3 0) (size 2.8 2.8) (layers F.Cu F.Paste F.Mask))
+)
diff --git a/hardware/kicad/walt_footprints.pretty/TRRS_SJ_43514.kicad_mod b/hardware/kicad/walt_footprints.pretty/TRRS_SJ_43514.kicad_mod
new file mode 100644
index 0000000..a4bdfff
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/TRRS_SJ_43514.kicad_mod
@@ -0,0 +1,27 @@
+(module TRRS_SJ_43514 (layer F.Cu) (tedit 5766AF92)
+ (descr TRRS_SJ_43514)
+ (tags "Audio TRRS")
+ (fp_text reference REF** (at 3.81 3.81 180) (layer F.SilkS)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value "TRRS JACK" (at 11.43 -4.445 90) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -2.9 1.905) (end -2.9 -10.795) (layer B.CrtYd) (width 0.15))
+ (fp_line (start 10.16 -10.795) (end 10.16 1.905) (layer B.CrtYd) (width 0.15))
+ (fp_line (start 10.16 1.905) (end -2.9 1.905) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -1.6 1.905) (end -1.6 -10.795) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -2.9 -10.795) (end 10.16 -10.795) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -1.6 -1.4) (end -5.1 -1.4) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -5.1 -1.4) (end -5.1 -7.55) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -5.1 -7.55) (end -1.6 -7.55) (layer B.CrtYd) (width 0.15))
+ (fp_line (start -1.5 1.905) (end 10.16 1.905) (layer F.SilkS) (width 0.15))
+ (fp_line (start 10.16 1.905) (end 10.16 -10.795) (layer F.SilkS) (width 0.15))
+ (fp_line (start 10.16 -10.795) (end -1.5 -10.795) (layer F.SilkS) (width 0.15))
+ (pad 4 thru_hole circle (at 0 0) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 2 thru_hole circle (at 5 -0.9) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 1 thru_hole circle (at 0 -9.3) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 3 thru_hole circle (at 8.1 -6.1) (size 1.8 1.8) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad "" np_thru_hole circle (at 0 -4.5) (size 1 1) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad "" np_thru_hole circle (at 5 -4.5) (size 1 1) (drill 1) (layers *.Cu *.Mask F.SilkS))
+)
diff --git a/hardware/kicad/walt_footprints.pretty/Teensy_DIP-28_W15.24mm.kicad_mod b/hardware/kicad/walt_footprints.pretty/Teensy_DIP-28_W15.24mm.kicad_mod
new file mode 100644
index 0000000..5c64f0c
--- /dev/null
+++ b/hardware/kicad/walt_footprints.pretty/Teensy_DIP-28_W15.24mm.kicad_mod
@@ -0,0 +1,47 @@
+(module Teensy_DIP-28_W15.24mm (layer F.Cu) (tedit 5768161F)
+ (descr "28-lead dip package, row spacing 15.24 mm (600 mils)")
+ (tags "dil dip 2.54 600")
+ (fp_text reference U1 (at 0 -5.22) (layer F.SilkS) hide
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value TeensyLC_with_headers (at 21.082 -8.001) (layer F.Fab)
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_line (start -1.05 -2.45) (end -1.05 35.5) (layer F.CrtYd) (width 0.05))
+ (fp_line (start 16.3 -2.45) (end 16.3 35.5) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.05 -2.45) (end 16.3 -2.45) (layer F.CrtYd) (width 0.05))
+ (fp_line (start -1.05 35.5) (end 16.3 35.5) (layer F.CrtYd) (width 0.05))
+ (pad 1 thru_hole circle (at 0 0) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 2 thru_hole circle (at 0 2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 3 thru_hole circle (at 0 5.08) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 4 thru_hole circle (at 0 7.62) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 5 thru_hole circle (at 0 10.16) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 6 thru_hole circle (at 0 12.7) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 7 thru_hole circle (at 0 15.24) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 8 thru_hole circle (at 0 17.78) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 9 thru_hole circle (at 0 20.32) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 10 thru_hole circle (at 0 22.86) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 11 thru_hole circle (at 0 25.4) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 12 thru_hole circle (at 0 27.94) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 13 thru_hole circle (at 0 30.48) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 14 thru_hole circle (at 0 33.02) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 15 thru_hole circle (at 15.24 33.02) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 16 thru_hole circle (at 15.24 30.48) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 17 thru_hole circle (at 15.24 27.94) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 18 thru_hole circle (at 15.24 25.4) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 19 thru_hole circle (at 15.24 22.86) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 20 thru_hole circle (at 15.24 20.32) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 21 thru_hole circle (at 15.24 17.78) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 22 thru_hole circle (at 15.24 15.24) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 23 thru_hole circle (at 15.24 12.7) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 24 thru_hole circle (at 15.24 10.16) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 25 thru_hole circle (at 15.24 7.62) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 26 thru_hole circle (at 15.24 5.08) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 27 thru_hole circle (at 15.24 2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (pad 28 thru_hole circle (at 15.24 0) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask F.SilkS))
+ (model Housings_DIP.3dshapes/DIP-28_W15.24mm.wrl
+ (at (xyz 0 0 0))
+ (scale (xyz 1 1 1))
+ (rotate (xyz 0 0 0))
+ )
+)
diff --git a/hardware/kicad/walt_misc.lib b/hardware/kicad/walt_misc.lib
new file mode 100644
index 0000000..b5041a4
--- /dev/null
+++ b/hardware/kicad/walt_misc.lib
@@ -0,0 +1,136 @@
+EESchema-LIBRARY Version 2.3
+#encoding utf-8
+#
+# ADXL335
+#
+DEF ADXL335 U 0 40 Y Y 1 F N
+F0 "U" -600 650 50 H V L CNN
+F1 "ADXL335" 250 650 50 H V L CNN
+F2 "" 0 -300 50 H V C CNN
+F3 "" 0 -300 50 H V C CNN
+$FPLIST
+ QFN-16*
+$ENDFPLIST
+DRAW
+S -600 600 600 -600 0 1 10 f
+X NC 1 700 0 100 L 50 50 1 1 P
+X ST 2 -700 200 100 R 50 50 1 1 P
+X COM 3 -700 0 100 R 50 50 1 1 P
+X NC 4 700 -100 100 L 50 50 1 1 P
+X COM 5 -700 -100 100 R 50 50 1 1 P
+X COM 6 -700 -200 100 R 50 50 1 1 P
+X COM 7 -700 -300 100 R 50 50 1 1 P
+X Zout 8 700 200 100 L 50 50 1 1 O
+X NC 9 700 -200 100 L 50 50 1 1 P
+X Yout 10 700 300 100 L 50 50 1 1 O
+X NC 11 700 -300 100 L 50 50 1 1 B
+X Xout 12 700 400 100 L 50 50 1 1 O
+X NC 13 700 -400 100 L 50 50 1 1 P
+X VCC 14 -700 400 100 R 50 50 1 1 W
+X VCC 15 -700 500 100 R 50 50 1 1 W
+X NC 16 700 -500 100 L 50 50 1 1 P
+X EP 17 -700 -500 100 R 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# BMI160
+#
+DEF BMI160 U 0 40 Y Y 1 F N
+F0 "U" -350 550 60 H V C CNN
+F1 "BMI160" -250 -650 60 H V C CNN
+F2 "" 0 -300 60 H V C CNN
+F3 "" 0 -300 60 H V C CNN
+DRAW
+S -400 500 500 -600 0 1 0 f
+X SDO 1 -600 0 200 R 50 50 1 1 O
+X ASDx 2 -600 -200 200 R 50 50 1 1 B
+X ASCx 3 -600 -300 200 R 50 50 1 1 O
+X INT1 4 700 -200 200 L 50 50 1 1 B
+X VDDIO 5 -600 300 200 R 50 50 1 1 W
+X GNDIO 6 -600 -400 200 R 50 50 1 1 W
+X GND 7 -600 -500 200 R 50 50 1 1 W
+X VDD 8 -600 400 200 R 50 50 1 1 W
+X INT2 9 700 -300 200 L 50 50 1 1 B
+X OSCB 10 700 -400 200 L 50 50 1 1 B
+X OSDO 11 700 -500 200 L 50 50 1 1 B
+X CSB 12 -600 -100 200 R 50 50 1 1 I
+X SCx/SCK/SCL 13 -600 100 200 R 50 50 1 1 I
+X SDx/SDA/MOSI/SISO 14 -600 200 200 R 50 50 1 1 B
+ENDDRAW
+ENDDEF
+#
+# JACK_TRRS
+#
+DEF JACK_TRRS J 0 30 Y Y 1 F N
+F0 "J" 200 350 50 H V C CNN
+F1 "JACK_TRRS" -50 -250 50 H V C CNN
+F2 "" -200 -150 50 H V C CNN
+F3 "" -200 -150 50 H V C CNN
+DRAW
+S 200 -100 200 -100 0 1 0 N
+S 200 100 200 100 0 1 0 N
+S 250 100 200 -100 0 1 0 N
+P 2 0 1 0 -250 -100 -400 -100 N
+P 2 0 1 0 -150 0 -400 0 N
+P 3 0 1 0 -400 200 200 200 200 100 N
+P 3 0 1 0 -150 0 -100 -50 -50 0 N
+P 4 0 1 0 -400 100 -50 100 0 50 50 100 N
+P 4 0 1 0 -250 -100 -200 -50 -150 -100 -150 -100 N
+S -400 -300 300 300 1 1 0 N
+X S 1 -500 200 100 R 50 50 1 1 P
+X T 2 -500 -100 100 R 50 50 1 1 P
+X R 3 -500 0 100 R 50 50 1 1 P
+X R 4 -500 100 100 R 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# LM321MFX
+#
+DEF LM321MFX U 0 20 Y Y 1 F N
+F0 "U" 50 200 50 H V C CNN
+F1 "LM321MFX" 200 -200 50 H V C CNN
+F2 "SC70-5" -50 -300 50 H I L CNN
+F3 "" 50 200 50 H V C CNN
+$FPLIST
+ SOT-23*
+ SC-70-5*
+$ENDFPLIST
+DRAW
+P 4 0 1 10 -200 200 200 0 -200 -200 -200 200 f
+X + 1 -300 100 100 R 50 50 1 1 I
+X V- 2 -100 -300 150 U 50 50 1 1 I
+X - 3 -300 -100 100 R 50 50 1 1 I
+X ~ 4 300 0 100 L 50 50 1 1 O
+X V+ 5 -100 300 150 D 50 50 1 1 I
+ENDDRAW
+ENDDEF
+#
+# Photodiode
+#
+DEF ~Photodiode D 0 40 N N 1 F N
+F0 "D" 0 100 50 H V C CNN
+F1 "Photodiode" -6 -129 50 H I C CNN
+F2 "" 0 0 50 H V C CNN
+F3 "" 0 0 50 H V C CNN
+$FPLIST
+ LED-3MM
+ LED-5MM
+ LED-10MM
+ LED-0603
+ LED-0805
+ LED-1206
+ LEDV
+$ENDFPLIST
+DRAW
+P 2 0 1 0 -62 -54 -87 -59 N
+P 2 0 1 0 -50 50 -50 -50 N
+P 2 0 1 0 -26 -56 -51 -61 N
+P 3 0 1 0 -106 -95 -61 -55 -66 -80 N
+P 3 0 1 0 -70 -97 -25 -57 -30 -82 N
+P 3 0 1 0 50 50 -50 0 50 -50 F
+X K 1 -150 0 100 R 40 40 1 1 P
+X A 2 150 0 100 L 40 40 1 1 P
+ENDDRAW
+ENDDEF
+#
+#End Library
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..916c7c5
--- /dev/null
+++ b/ios/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+xcuserdata/
+
diff --git a/ios/WALT.xcodeproj/project.pbxproj b/ios/WALT.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..e6458b3
--- /dev/null
+++ b/ios/WALT.xcodeproj/project.pbxproj
@@ -0,0 +1,441 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 932918081D8376BE0029432C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 932918071D8376BE0029432C /* main.m */; };
+ 9329180B1D8376BE0029432C /* WALTAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9329180A1D8376BE0029432C /* WALTAppDelegate.m */; };
+ 9329180E1D8376BE0029432C /* MenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9329180D1D8376BE0029432C /* MenuController.m */; };
+ 932918111D8376BE0029432C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9329180F1D8376BE0029432C /* Main.storyboard */; };
+ 932918131D8376BE0029432C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 932918121D8376BE0029432C /* Assets.xcassets */; };
+ 9329181F1D837A3E0029432C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9329181D1D837A3E0029432C /* LaunchScreen.storyboard */; };
+ 932B1C6A1D861108008F3025 /* TapLatencyController.m in Sources */ = {isa = PBXBuildFile; fileRef = 932B1C691D861108008F3025 /* TapLatencyController.m */; };
+ 932B1C761D865AE2008F3025 /* WALTLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 932B1C751D865AE2008F3025 /* WALTLogger.m */; };
+ 93B35DE31D8CA7D000BE2E58 /* DebugLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B35DE21D8CA7D000BE2E58 /* DebugLogController.m */; };
+ 93B35DEC1D90639500BE2E58 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B35DEB1D90639500BE2E58 /* CoreMIDI.framework */; };
+ 93B35DEF1D90B4C500BE2E58 /* MIDIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B35DEE1D90B4C500BE2E58 /* MIDIClient.m */; };
+ 93B35DF31D90B7C200BE2E58 /* MIDIEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B35DF21D90B7C200BE2E58 /* MIDIEndpoint.m */; };
+ 93B35E081D91F1FE00BE2E58 /* MIDIMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B35E071D91F1FE00BE2E58 /* MIDIMessage.m */; };
+ 93B35E0C1D92FA6200BE2E58 /* WALTClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B35E0B1D92FA6200BE2E58 /* WALTClient.m */; };
+ 93B44C781D9AF97600D9BEC9 /* WALTTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B44C771D9AF97600D9BEC9 /* WALTTouch.m */; };
+ 93B44C7D1D9C433A00D9BEC9 /* SettingsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B44C7C1D9C433A00D9BEC9 /* SettingsController.m */; };
+ 93B44C801D9C4DE300D9BEC9 /* ScreenResponseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B44C7F1D9C4DE300D9BEC9 /* ScreenResponseController.m */; };
+ 93B44C831D9D9E1F00D9BEC9 /* NSArray+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B44C821D9D9E1F00D9BEC9 /* NSArray+Extensions.m */; };
+ 93B44C881D9EE33400D9BEC9 /* UIAlertView+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B44C871D9EE33400D9BEC9 /* UIAlertView+Extensions.m */; };
+ 93B44C8B1D9F1D3700D9BEC9 /* DragLatencyController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 93B44C8A1D9F1D3700D9BEC9 /* DragLatencyController.mm */; };
+ 93B44C901DA8562E00D9BEC9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B44C8F1DA8562E00D9BEC9 /* UIKit.framework */; };
+ 93B44C921DA8563900D9BEC9 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B44C911DA8563900D9BEC9 /* CoreGraphics.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 932918031D8376BE0029432C /* WALT.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WALT.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 932918071D8376BE0029432C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 932918091D8376BE0029432C /* WALTAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WALTAppDelegate.h; sourceTree = "<group>"; };
+ 9329180A1D8376BE0029432C /* WALTAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WALTAppDelegate.m; sourceTree = "<group>"; };
+ 9329180C1D8376BE0029432C /* MenuController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MenuController.h; sourceTree = "<group>"; };
+ 9329180D1D8376BE0029432C /* MenuController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuController.m; sourceTree = "<group>"; };
+ 932918101D8376BE0029432C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ 932918121D8376BE0029432C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ 932918171D8376BE0029432C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ 9329181E1D837A3E0029432C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ 932B1C681D861108008F3025 /* TapLatencyController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TapLatencyController.h; sourceTree = "<group>"; };
+ 932B1C691D861108008F3025 /* TapLatencyController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TapLatencyController.m; sourceTree = "<group>"; };
+ 932B1C741D865AE2008F3025 /* WALTLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WALTLogger.h; sourceTree = "<group>"; };
+ 932B1C751D865AE2008F3025 /* WALTLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WALTLogger.m; sourceTree = "<group>"; };
+ 93B35DE11D8CA7D000BE2E58 /* DebugLogController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugLogController.h; sourceTree = "<group>"; };
+ 93B35DE21D8CA7D000BE2E58 /* DebugLogController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogController.m; sourceTree = "<group>"; };
+ 93B35DEB1D90639500BE2E58 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; };
+ 93B35DED1D90B4C500BE2E58 /* MIDIClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIClient.h; sourceTree = "<group>"; };
+ 93B35DEE1D90B4C500BE2E58 /* MIDIClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIClient.m; sourceTree = "<group>"; };
+ 93B35DF11D90B7C200BE2E58 /* MIDIEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIEndpoint.h; sourceTree = "<group>"; };
+ 93B35DF21D90B7C200BE2E58 /* MIDIEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIEndpoint.m; sourceTree = "<group>"; };
+ 93B35E061D91F1FE00BE2E58 /* MIDIMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIMessage.h; sourceTree = "<group>"; };
+ 93B35E071D91F1FE00BE2E58 /* MIDIMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIMessage.m; sourceTree = "<group>"; };
+ 93B35E0A1D92FA6200BE2E58 /* WALTClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WALTClient.h; sourceTree = "<group>"; };
+ 93B35E0B1D92FA6200BE2E58 /* WALTClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WALTClient.m; sourceTree = "<group>"; };
+ 93B44C761D9AF97600D9BEC9 /* WALTTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WALTTouch.h; sourceTree = "<group>"; };
+ 93B44C771D9AF97600D9BEC9 /* WALTTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WALTTouch.m; sourceTree = "<group>"; };
+ 93B44C7B1D9C433A00D9BEC9 /* SettingsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsController.h; sourceTree = "<group>"; };
+ 93B44C7C1D9C433A00D9BEC9 /* SettingsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsController.m; sourceTree = "<group>"; };
+ 93B44C7E1D9C4DE300D9BEC9 /* ScreenResponseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreenResponseController.h; sourceTree = "<group>"; };
+ 93B44C7F1D9C4DE300D9BEC9 /* ScreenResponseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenResponseController.m; sourceTree = "<group>"; };
+ 93B44C811D9D9E1F00D9BEC9 /* NSArray+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Extensions.h"; sourceTree = "<group>"; };
+ 93B44C821D9D9E1F00D9BEC9 /* NSArray+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Extensions.m"; sourceTree = "<group>"; };
+ 93B44C861D9EE33400D9BEC9 /* UIAlertView+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+Extensions.h"; sourceTree = "<group>"; };
+ 93B44C871D9EE33400D9BEC9 /* UIAlertView+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+Extensions.m"; sourceTree = "<group>"; };
+ 93B44C891D9F1D3700D9BEC9 /* DragLatencyController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DragLatencyController.h; sourceTree = "<group>"; };
+ 93B44C8A1D9F1D3700D9BEC9 /* DragLatencyController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DragLatencyController.mm; sourceTree = "<group>"; };
+ 93B44C8F1DA8562E00D9BEC9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 93B44C911DA8563900D9BEC9 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 932918001D8376BD0029432C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 93B44C921DA8563900D9BEC9 /* CoreGraphics.framework in Frameworks */,
+ 93B44C901DA8562E00D9BEC9 /* UIKit.framework in Frameworks */,
+ 93B35DEC1D90639500BE2E58 /* CoreMIDI.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 932917FA1D8376BD0029432C = {
+ isa = PBXGroup;
+ children = (
+ 932918051D8376BE0029432C /* WALT */,
+ 932918041D8376BE0029432C /* Products */,
+ 932B1C211D83B14A008F3025 /* Frameworks */,
+ );
+ sourceTree = "<group>";
+ };
+ 932918041D8376BE0029432C /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 932918031D8376BE0029432C /* WALT.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 932918051D8376BE0029432C /* WALT */ = {
+ isa = PBXGroup;
+ children = (
+ 932918091D8376BE0029432C /* WALTAppDelegate.h */,
+ 9329180A1D8376BE0029432C /* WALTAppDelegate.m */,
+ 932B1C741D865AE2008F3025 /* WALTLogger.h */,
+ 932B1C751D865AE2008F3025 /* WALTLogger.m */,
+ 93B44C761D9AF97600D9BEC9 /* WALTTouch.h */,
+ 93B44C771D9AF97600D9BEC9 /* WALTTouch.m */,
+ 93B44C811D9D9E1F00D9BEC9 /* NSArray+Extensions.h */,
+ 93B44C821D9D9E1F00D9BEC9 /* NSArray+Extensions.m */,
+ 93B44C861D9EE33400D9BEC9 /* UIAlertView+Extensions.h */,
+ 93B44C871D9EE33400D9BEC9 /* UIAlertView+Extensions.m */,
+ 932B1C6C1D861803008F3025 /* Connectivity */,
+ 932B1C6D1D861809008F3025 /* UI */,
+ 932918061D8376BE0029432C /* Supporting Files */,
+ );
+ path = WALT;
+ sourceTree = "<group>";
+ };
+ 932918061D8376BE0029432C /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 932918071D8376BE0029432C /* main.m */,
+ 932918171D8376BE0029432C /* Info.plist */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ 932B1C211D83B14A008F3025 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 93B44C911DA8563900D9BEC9 /* CoreGraphics.framework */,
+ 93B44C8F1DA8562E00D9BEC9 /* UIKit.framework */,
+ 93B35DEB1D90639500BE2E58 /* CoreMIDI.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 932B1C6C1D861803008F3025 /* Connectivity */ = {
+ isa = PBXGroup;
+ children = (
+ 93B35DED1D90B4C500BE2E58 /* MIDIClient.h */,
+ 93B35DEE1D90B4C500BE2E58 /* MIDIClient.m */,
+ 93B35DF11D90B7C200BE2E58 /* MIDIEndpoint.h */,
+ 93B35DF21D90B7C200BE2E58 /* MIDIEndpoint.m */,
+ 93B35E061D91F1FE00BE2E58 /* MIDIMessage.h */,
+ 93B35E071D91F1FE00BE2E58 /* MIDIMessage.m */,
+ 93B35E0A1D92FA6200BE2E58 /* WALTClient.h */,
+ 93B35E0B1D92FA6200BE2E58 /* WALTClient.m */,
+ );
+ name = Connectivity;
+ sourceTree = "<group>";
+ };
+ 932B1C6D1D861809008F3025 /* UI */ = {
+ isa = PBXGroup;
+ children = (
+ 9329180C1D8376BE0029432C /* MenuController.h */,
+ 9329180D1D8376BE0029432C /* MenuController.m */,
+ 93B35DE11D8CA7D000BE2E58 /* DebugLogController.h */,
+ 93B35DE21D8CA7D000BE2E58 /* DebugLogController.m */,
+ 93B44C891D9F1D3700D9BEC9 /* DragLatencyController.h */,
+ 93B44C8A1D9F1D3700D9BEC9 /* DragLatencyController.mm */,
+ 93B44C7B1D9C433A00D9BEC9 /* SettingsController.h */,
+ 93B44C7C1D9C433A00D9BEC9 /* SettingsController.m */,
+ 93B44C7E1D9C4DE300D9BEC9 /* ScreenResponseController.h */,
+ 93B44C7F1D9C4DE300D9BEC9 /* ScreenResponseController.m */,
+ 932B1C681D861108008F3025 /* TapLatencyController.h */,
+ 932B1C691D861108008F3025 /* TapLatencyController.m */,
+ 9329180F1D8376BE0029432C /* Main.storyboard */,
+ 9329181D1D837A3E0029432C /* LaunchScreen.storyboard */,
+ 932918121D8376BE0029432C /* Assets.xcassets */,
+ );
+ name = UI;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 932918021D8376BD0029432C /* WALT */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9329181A1D8376BE0029432C /* Build configuration list for PBXNativeTarget "WALT" */;
+ buildPhases = (
+ 932917FF1D8376BD0029432C /* Sources */,
+ 932918001D8376BD0029432C /* Frameworks */,
+ 932918011D8376BD0029432C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = WALT;
+ productName = WALT;
+ productReference = 932918031D8376BE0029432C /* WALT.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 932917FB1D8376BD0029432C /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0810;
+ ORGANIZATIONNAME = "Google Inc.";
+ TargetAttributes = {
+ 932918021D8376BD0029432C = {
+ CreatedOnToolsVersion = 7.3.1;
+ DevelopmentTeam = M624B4DA33;
+ SystemCapabilities = {
+ com.apple.BackgroundModes = {
+ enabled = 1;
+ };
+ com.apple.WAC = {
+ enabled = 0;
+ };
+ };
+ };
+ };
+ };
+ buildConfigurationList = 932917FE1D8376BD0029432C /* Build configuration list for PBXProject "WALT" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 932917FA1D8376BD0029432C;
+ productRefGroup = 932918041D8376BE0029432C /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 932918021D8376BD0029432C /* WALT */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 932918011D8376BD0029432C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9329181F1D837A3E0029432C /* LaunchScreen.storyboard in Resources */,
+ 932918131D8376BE0029432C /* Assets.xcassets in Resources */,
+ 932918111D8376BE0029432C /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 932917FF1D8376BD0029432C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 93B44C7D1D9C433A00D9BEC9 /* SettingsController.m in Sources */,
+ 93B35E081D91F1FE00BE2E58 /* MIDIMessage.m in Sources */,
+ 93B44C881D9EE33400D9BEC9 /* UIAlertView+Extensions.m in Sources */,
+ 93B44C831D9D9E1F00D9BEC9 /* NSArray+Extensions.m in Sources */,
+ 93B44C8B1D9F1D3700D9BEC9 /* DragLatencyController.mm in Sources */,
+ 93B35DE31D8CA7D000BE2E58 /* DebugLogController.m in Sources */,
+ 9329180E1D8376BE0029432C /* MenuController.m in Sources */,
+ 93B35DEF1D90B4C500BE2E58 /* MIDIClient.m in Sources */,
+ 93B35DF31D90B7C200BE2E58 /* MIDIEndpoint.m in Sources */,
+ 9329180B1D8376BE0029432C /* WALTAppDelegate.m in Sources */,
+ 932B1C6A1D861108008F3025 /* TapLatencyController.m in Sources */,
+ 932918081D8376BE0029432C /* main.m in Sources */,
+ 93B44C801D9C4DE300D9BEC9 /* ScreenResponseController.m in Sources */,
+ 93B35E0C1D92FA6200BE2E58 /* WALTClient.m in Sources */,
+ 932B1C761D865AE2008F3025 /* WALTLogger.m in Sources */,
+ 93B44C781D9AF97600D9BEC9 /* WALTTouch.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 9329180F1D8376BE0029432C /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 932918101D8376BE0029432C /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+ 9329181D1D837A3E0029432C /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 9329181E1D837A3E0029432C /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 932918181D8376BE0029432C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = c11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 932918191D8376BE0029432C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = c11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 9329181B1D8376BE0029432C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ INFOPLIST_FILE = WALT/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = org.chromium.latency.WALT;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ };
+ name = Debug;
+ };
+ 9329181C1D8376BE0029432C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ INFOPLIST_FILE = WALT/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = org.chromium.latency.WALT;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 932917FE1D8376BD0029432C /* Build configuration list for PBXProject "WALT" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 932918181D8376BE0029432C /* Debug */,
+ 932918191D8376BE0029432C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9329181A1D8376BE0029432C /* Build configuration list for PBXNativeTarget "WALT" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9329181B1D8376BE0029432C /* Debug */,
+ 9329181C1D8376BE0029432C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 932917FB1D8376BD0029432C /* Project object */;
+}
diff --git a/ios/WALT.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/WALT.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..51432ed
--- /dev/null
+++ b/ios/WALT.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:WALT.xcodeproj">
+ </FileRef>
+</Workspace>
diff --git a/ios/WALT/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/WALT/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..ba51812
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,95 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "83.5x83.5",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/AppIcon.appiconset/icon@2x.png b/ios/WALT/Assets.xcassets/AppIcon.appiconset/icon@2x.png
new file mode 100644
index 0000000..cf5db2b
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/AppIcon.appiconset/icon@2x.png
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/AppIcon.appiconset/icon@3x.png b/ios/WALT/Assets.xcassets/AppIcon.appiconset/icon@3x.png
new file mode 100644
index 0000000..754e0b8
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/AppIcon.appiconset/icon@3x.png
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/Contents.json b/ios/WALT/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_brightness_medium_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_brightness_medium_24dp.imageset/Contents.json
new file mode 100644
index 0000000..175b130
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_brightness_medium_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_brightness_medium_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_brightness_medium_24dp.imageset/ic_brightness_medium_24dp.pdf b/ios/WALT/Assets.xcassets/ic_brightness_medium_24dp.imageset/ic_brightness_medium_24dp.pdf
new file mode 100644
index 0000000..2b34316
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_brightness_medium_24dp.imageset/ic_brightness_medium_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_radio_button_checked_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_radio_button_checked_24dp.imageset/Contents.json
new file mode 100644
index 0000000..d2fdd6f
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_radio_button_checked_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_radio_button_checked_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_radio_button_checked_24dp.imageset/ic_radio_button_checked_24dp.pdf b/ios/WALT/Assets.xcassets/ic_radio_button_checked_24dp.imageset/ic_radio_button_checked_24dp.pdf
new file mode 100644
index 0000000..0dbad84
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_radio_button_checked_24dp.imageset/ic_radio_button_checked_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_receipt_black_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_receipt_black_24dp.imageset/Contents.json
new file mode 100644
index 0000000..10b20cc
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_receipt_black_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_receipt_black_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_receipt_black_24dp.imageset/ic_receipt_black_24dp.pdf b/ios/WALT/Assets.xcassets/ic_receipt_black_24dp.imageset/ic_receipt_black_24dp.pdf
new file mode 100644
index 0000000..dc7033b
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_receipt_black_24dp.imageset/ic_receipt_black_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_schedule_black_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_schedule_black_24dp.imageset/Contents.json
new file mode 100644
index 0000000..ad6335c
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_schedule_black_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_schedule_black_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_schedule_black_24dp.imageset/ic_schedule_black_24dp.pdf b/ios/WALT/Assets.xcassets/ic_schedule_black_24dp.imageset/ic_schedule_black_24dp.pdf
new file mode 100644
index 0000000..ab3cca1
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_schedule_black_24dp.imageset/ic_schedule_black_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_settings_black_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_settings_black_24dp.imageset/Contents.json
new file mode 100644
index 0000000..5087db5
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_settings_black_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_settings_black_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_settings_black_24dp.imageset/ic_settings_black_24dp.pdf b/ios/WALT/Assets.xcassets/ic_settings_black_24dp.imageset/ic_settings_black_24dp.pdf
new file mode 100644
index 0000000..a20e463
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_settings_black_24dp.imageset/ic_settings_black_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_swap_horiz_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_swap_horiz_24dp.imageset/Contents.json
new file mode 100644
index 0000000..8161920
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_swap_horiz_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_swap_horiz_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_swap_horiz_24dp.imageset/ic_swap_horiz_24dp.pdf b/ios/WALT/Assets.xcassets/ic_swap_horiz_24dp.imageset/ic_swap_horiz_24dp.pdf
new file mode 100644
index 0000000..50bfc96
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_swap_horiz_24dp.imageset/ic_swap_horiz_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_swap_vert_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_swap_vert_24dp.imageset/Contents.json
new file mode 100644
index 0000000..d7d49f0
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_swap_vert_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_swap_vert_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_swap_vert_24dp.imageset/ic_swap_vert_24dp.pdf b/ios/WALT/Assets.xcassets/ic_swap_vert_24dp.imageset/ic_swap_vert_24dp.pdf
new file mode 100644
index 0000000..36647b4
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_swap_vert_24dp.imageset/ic_swap_vert_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Assets.xcassets/ic_timelapse_black_24dp.imageset/Contents.json b/ios/WALT/Assets.xcassets/ic_timelapse_black_24dp.imageset/Contents.json
new file mode 100644
index 0000000..4ec5a1a
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_timelapse_black_24dp.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_timelapse_black_24dp.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ios/WALT/Assets.xcassets/ic_timelapse_black_24dp.imageset/ic_timelapse_black_24dp.pdf b/ios/WALT/Assets.xcassets/ic_timelapse_black_24dp.imageset/ic_timelapse_black_24dp.pdf
new file mode 100644
index 0000000..eacbc18
--- /dev/null
+++ b/ios/WALT/Assets.xcassets/ic_timelapse_black_24dp.imageset/ic_timelapse_black_24dp.pdf
Binary files differ
diff --git a/ios/WALT/Base.lproj/LaunchScreen.storyboard b/ios/WALT/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..cc0fbd6
--- /dev/null
+++ b/ios/WALT/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ZVE-EP-b8v">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="NNW-W4-Efl">
+ <objects>
+ <viewController id="ZVE-EP-b8v" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="s0l-pW-AVn"/>
+ <viewControllerLayoutGuide type="bottom" id="e0H-b7-IdX"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="aTj-Tt-xEN">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="uIO-hR-lzl" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="474" y="302"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/ios/WALT/Base.lproj/Main.storyboard b/ios/WALT/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..0f44ef7
--- /dev/null
+++ b/ios/WALT/Base.lproj/Main.storyboard
@@ -0,0 +1,502 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="eQU-I2-GD8">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ <capability name="Navigation items with more than one left or right bar item" minToolsVersion="7.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--Menu-->
+ <scene sceneID="xfn-MN-fso">
+ <objects>
+ <tableViewController title="Menu" id="VPe-PS-rc7" customClass="MenuController" sceneMemberID="viewController">
+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="Nnj-XQ-ITE">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
+ <sections>
+ <tableViewSection headerTitle="Connection" id="5LD-wg-Xzb">
+ <cells>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="checkmark" indentationWidth="10" id="Yz9-7O-DAz">
+ <rect key="frame" x="0.0" y="120" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Yz9-7O-DAz" id="iAK-6i-7rL">
+ <frame key="frameInset" width="281" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_schedule_black_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="m9l-fT-TFO">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Clock Sync" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cQe-pS-WYx">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ </tableViewCell>
+ </cells>
+ </tableViewSection>
+ <tableViewSection headerTitle="Measure Latency" id="Xz6-jL-dfU">
+ <cells>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="JQX-fF-HrG">
+ <rect key="frame" x="0.0" y="220" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="JQX-fF-HrG" id="hvq-GP-UvE">
+ <frame key="frameInset" width="287" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_radio_button_checked_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="2Jk-he-kof">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Tap Latency" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ksj-bS-5ef">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ <connections>
+ <segue destination="h21-iB-JdX" kind="show" id="SB0-CR-bdX"/>
+ </connections>
+ </tableViewCell>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="imo-Vh-mdV">
+ <rect key="frame" x="0.0" y="264" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="imo-Vh-mdV" id="rjR-UK-RhN">
+ <frame key="frameInset" width="287" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_swap_vert_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="8pB-A2-BtL">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Drag Latency" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l4F-Iu-ak1">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ <connections>
+ <segue destination="Iye-5w-RWd" kind="show" id="Cz1-x2-JfO"/>
+ </connections>
+ </tableViewCell>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="hEB-ci-IiA">
+ <rect key="frame" x="0.0" y="308" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hEB-ci-IiA" id="JMe-la-M9R">
+ <frame key="frameInset" width="287" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_brightness_medium_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="Osy-BJ-byS">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Screen Response" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CDC-fL-NyR">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ <connections>
+ <segue destination="Fkf-5e-wh6" kind="show" id="5k3-vY-CoW"/>
+ </connections>
+ </tableViewCell>
+ </cells>
+ </tableViewSection>
+ <tableViewSection headerTitle="Configuration" id="b19-yD-3Nr">
+ <cells>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="WbU-am-NQX">
+ <rect key="frame" x="0.0" y="408" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="WbU-am-NQX" id="Poi-2P-Xzl">
+ <frame key="frameInset" width="287" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_receipt_black_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="oYC-Td-Xiq">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="r9U-Nu-T4s">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ <connections>
+ <segue destination="guL-f1-O7a" kind="show" id="HYc-2d-b1V"/>
+ </connections>
+ </tableViewCell>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="3zc-KF-CGB">
+ <rect key="frame" x="0.0" y="452" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3zc-KF-CGB" id="lWb-4z-t2t">
+ <frame key="frameInset" width="287" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_settings_black_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="PFH-yZ-k7o">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qKV-2v-Mof">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ <connections>
+ <segue destination="KXp-FX-gyD" kind="show" id="zW3-IK-73l"/>
+ </connections>
+ </tableViewCell>
+ </cells>
+ </tableViewSection>
+ </sections>
+ <connections>
+ <outlet property="dataSource" destination="VPe-PS-rc7" id="zUh-Cn-OUV"/>
+ <outlet property="delegate" destination="VPe-PS-rc7" id="apU-y3-EWH"/>
+ </connections>
+ </tableView>
+ <navigationItem key="navigationItem" title="WALT" id="cMy-Nl-GuX">
+ <barButtonItem key="rightBarButtonItem" systemItem="action" id="CNR-8L-zzo">
+ <connections>
+ <action selector="shareLog:" destination="VPe-PS-rc7" id="uOw-PR-xfd"/>
+ </connections>
+ </barButtonItem>
+ </navigationItem>
+ <connections>
+ <outlet property="syncCell" destination="Yz9-7O-DAz" id="wd3-Q6-3f4"/>
+ </connections>
+ </tableViewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="M86-GS-6VT" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="297.5" y="451.5"/>
+ </scene>
+ <!--Tap Latency-->
+ <scene sceneID="3uc-Py-LUu">
+ <objects>
+ <viewController id="h21-iB-JdX" customClass="TapLatencyController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Kje-tJ-2UI"/>
+ <viewControllerLayoutGuide type="bottom" id="8ba-If-sGm"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="sap-lR-8gs">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="N ↓%d (%d) ↑%d (%d) ⇄ %d" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CKp-gK-Prj">
+ <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="12"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <textView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" text="Tap log" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aVF-f1-M68">
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="12"/>
+ <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+ </textView>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstItem="aVF-f1-M68" firstAttribute="leading" secondItem="sap-lR-8gs" secondAttribute="leadingMargin" id="GJR-qH-82B"/>
+ <constraint firstItem="aVF-f1-M68" firstAttribute="trailing" secondItem="sap-lR-8gs" secondAttribute="trailingMargin" id="H63-xH-0g1"/>
+ <constraint firstItem="CKp-gK-Prj" firstAttribute="trailing" secondItem="sap-lR-8gs" secondAttribute="trailingMargin" id="IJc-zq-skT"/>
+ <constraint firstItem="8ba-If-sGm" firstAttribute="top" secondItem="aVF-f1-M68" secondAttribute="bottom" constant="20" id="XK1-Td-yd9"/>
+ <constraint firstItem="aVF-f1-M68" firstAttribute="top" secondItem="CKp-gK-Prj" secondAttribute="bottom" constant="8" id="YQn-DQ-Crg"/>
+ <constraint firstItem="CKp-gK-Prj" firstAttribute="top" secondItem="Kje-tJ-2UI" secondAttribute="bottom" constant="8" id="Zk4-8b-9t6"/>
+ <constraint firstItem="CKp-gK-Prj" firstAttribute="leading" secondItem="sap-lR-8gs" secondAttribute="leadingMargin" id="cu4-I8-gXQ"/>
+ </constraints>
+ </view>
+ <navigationItem key="navigationItem" title="Tap Latency" id="tft-Mo-BGM">
+ <rightBarButtonItems>
+ <barButtonItem systemItem="action" id="QRN-YN-FDB">
+ <connections>
+ <action selector="computeStatistics:" destination="h21-iB-JdX" id="MTm-4g-4QE"/>
+ </connections>
+ </barButtonItem>
+ <barButtonItem systemItem="refresh" id="qgs-da-ABq">
+ <connections>
+ <action selector="reset:" destination="h21-iB-JdX" id="APG-Ef-yHQ"/>
+ </connections>
+ </barButtonItem>
+ </rightBarButtonItems>
+ </navigationItem>
+ <connections>
+ <outlet property="countLabel" destination="CKp-gK-Prj" id="W9S-au-8R2"/>
+ <outlet property="logView" destination="aVF-f1-M68" id="Wyz-CU-4mT"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="W9i-HC-abY" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="1123" y="-183"/>
+ </scene>
+ <!--Drag Latency-->
+ <scene sceneID="4bt-KJ-0Wm">
+ <objects>
+ <viewController id="Iye-5w-RWd" customClass="DragLatencyController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="fEb-9K-y6d"/>
+ <viewControllerLayoutGuide type="bottom" id="Enu-n5-RxV"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="0Ud-kj-BK3">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cBY-fS-a2g">
+ <color key="backgroundColor" red="0.050980392156862744" green="0.37254901960784315" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="1" id="DwU-Fu-L9W"/>
+ </constraints>
+ </view>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="N ✛ %d ⇄ %d" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="71e-I5-3db">
+ <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="12"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GyK-xM-A9Q">
+ <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="12"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstItem="Enu-n5-RxV" firstAttribute="top" secondItem="cBY-fS-a2g" secondAttribute="bottom" constant="100" id="1vV-Pu-RuO"/>
+ <constraint firstItem="GyK-xM-A9Q" firstAttribute="top" secondItem="71e-I5-3db" secondAttribute="bottom" constant="4" id="Hju-xu-Q8g"/>
+ <constraint firstItem="GyK-xM-A9Q" firstAttribute="leading" secondItem="0Ud-kj-BK3" secondAttribute="leadingMargin" id="Pbs-9G-d6W"/>
+ <constraint firstAttribute="trailingMargin" secondItem="cBY-fS-a2g" secondAttribute="trailing" constant="48" id="WCL-lT-JAo"/>
+ <constraint firstItem="71e-I5-3db" firstAttribute="top" secondItem="fEb-9K-y6d" secondAttribute="bottom" constant="8" id="Whk-y2-L1A"/>
+ <constraint firstItem="cBY-fS-a2g" firstAttribute="leading" secondItem="0Ud-kj-BK3" secondAttribute="leadingMargin" constant="47" id="bNo-cM-ETZ"/>
+ <constraint firstItem="71e-I5-3db" firstAttribute="leading" secondItem="0Ud-kj-BK3" secondAttribute="leadingMargin" id="ez0-ku-0wH"/>
+ <constraint firstItem="71e-I5-3db" firstAttribute="trailing" secondItem="0Ud-kj-BK3" secondAttribute="trailingMargin" id="wvD-NG-Tmd"/>
+ <constraint firstItem="GyK-xM-A9Q" firstAttribute="trailing" secondItem="0Ud-kj-BK3" secondAttribute="trailingMargin" id="zH0-EP-Iao"/>
+ </constraints>
+ </view>
+ <navigationItem key="navigationItem" title="Drag Latency" id="nkK-4I-dD4">
+ <rightBarButtonItems>
+ <barButtonItem systemItem="action" id="pwk-sy-UTR">
+ <connections>
+ <action selector="computeStatistics:" destination="Iye-5w-RWd" id="XSc-C0-Ipw"/>
+ </connections>
+ </barButtonItem>
+ <barButtonItem systemItem="play" id="ox2-eT-993">
+ <connections>
+ <action selector="start:" destination="Iye-5w-RWd" id="grt-Pk-vaH"/>
+ </connections>
+ </barButtonItem>
+ <barButtonItem systemItem="refresh" id="gjM-TD-UIa">
+ <connections>
+ <action selector="reset:" destination="Iye-5w-RWd" id="XLB-Zo-bxB"/>
+ </connections>
+ </barButtonItem>
+ </rightBarButtonItems>
+ </navigationItem>
+ <connections>
+ <outlet property="countLabel" destination="71e-I5-3db" id="2Hd-cJ-mJq"/>
+ <outlet property="goalpostView" destination="cBY-fS-a2g" id="ye8-n4-scw"/>
+ <outlet property="statusLabel" destination="GyK-xM-A9Q" id="PUC-gN-nFr"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="xKv-dC-lRO" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="1123" y="479"/>
+ </scene>
+ <!--Log-->
+ <scene sceneID="nPK-4w-VMQ">
+ <objects>
+ <viewController id="guL-f1-O7a" customClass="DebugLogController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Eeq-Eg-acb"/>
+ <viewControllerLayoutGuide type="bottom" id="KWd-BQ-T6A"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="L9F-Pn-wlS">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ieg-Za-vhc">
+ <frame key="frameInset" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="12"/>
+ <textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no"/>
+ </textView>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </view>
+ <navigationItem key="navigationItem" title="Log" id="O0W-gg-5jB">
+ <barButtonItem key="rightBarButtonItem" systemItem="refresh" id="Jkt-ZT-dbp">
+ <connections>
+ <action selector="reset:" destination="guL-f1-O7a" id="iq1-NB-I55"/>
+ </connections>
+ </barButtonItem>
+ </navigationItem>
+ <connections>
+ <outlet property="textView" destination="Ieg-Za-vhc" id="cpW-FM-oGN"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="sVI-Tk-Oeq" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-486" y="1241"/>
+ </scene>
+ <!--Settings-->
+ <scene sceneID="AXy-Me-DP3">
+ <objects>
+ <tableViewController id="KXp-FX-gyD" customClass="SettingsController" sceneMemberID="viewController">
+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="UHk-hm-yk8">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
+ <sections>
+ <tableViewSection headerTitle="Diagnostics" id="D5K-Gr-zPc">
+ <cells>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="VEI-kW-Bog">
+ <rect key="frame" x="0.0" y="120" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="VEI-kW-Bog" id="xAH-6e-SbQ">
+ <frame key="frameInset" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_swap_horiz_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="pi9-AT-sUn">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Ping" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a4d-MA-jEA">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ </tableViewCell>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="Gz9-j5-8xW">
+ <rect key="frame" x="0.0" y="164" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Gz9-j5-8xW" id="r4F-Bs-7ud">
+ <frame key="frameInset" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ic_timelapse_black_24dp" translatesAutoresizingMaskIntoConstraints="NO" id="bTu-Fo-JNE">
+ <frame key="frameInset" minX="11" minY="4" width="34" height="34"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Check Clock Drift" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EEb-uw-LMh">
+ <frame key="frameInset" minX="53" minY="11" width="289" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </tableViewCellContentView>
+ </tableViewCell>
+ </cells>
+ </tableViewSection>
+ </sections>
+ <connections>
+ <outlet property="dataSource" destination="KXp-FX-gyD" id="avQ-Qu-gD4"/>
+ <outlet property="delegate" destination="KXp-FX-gyD" id="e0U-YI-bn6"/>
+ </connections>
+ </tableView>
+ <navigationItem key="navigationItem" title="Settings" id="bOg-hs-cZX"/>
+ </tableViewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iTV-IX-dfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="281" y="1240"/>
+ </scene>
+ <!--Screen Response-->
+ <scene sceneID="xRB-KS-fQF">
+ <objects>
+ <viewController id="Fkf-5e-wh6" customClass="ScreenResponseController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="fLM-tb-4JW"/>
+ <viewControllerLayoutGuide type="bottom" id="o3i-Ce-bdu"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="mUy-JP-aUq">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="A0k-c3-ite">
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </view>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="%2f s" textAlignment="right" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sBN-Rv-s0v">
+ <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="12"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstAttribute="trailingMargin" secondItem="sBN-Rv-s0v" secondAttribute="trailing" constant="4" id="4r7-cz-Seh"/>
+ <constraint firstItem="o3i-Ce-bdu" firstAttribute="top" secondItem="sBN-Rv-s0v" secondAttribute="bottom" constant="20" id="6nT-mv-2OZ"/>
+ <constraint firstItem="A0k-c3-ite" firstAttribute="leading" secondItem="mUy-JP-aUq" secondAttribute="leading" id="95R-9H-9dg"/>
+ <constraint firstItem="A0k-c3-ite" firstAttribute="bottom" secondItem="o3i-Ce-bdu" secondAttribute="top" id="EO5-zp-wxl"/>
+ <constraint firstItem="sBN-Rv-s0v" firstAttribute="top" secondItem="fLM-tb-4JW" secondAttribute="bottom" constant="8" id="Okz-Er-W3e"/>
+ <constraint firstAttribute="trailing" secondItem="A0k-c3-ite" secondAttribute="trailing" id="TO4-EE-mpl"/>
+ <constraint firstItem="A0k-c3-ite" firstAttribute="top" secondItem="fLM-tb-4JW" secondAttribute="bottom" id="Yav-6g-gqR"/>
+ <constraint firstItem="sBN-Rv-s0v" firstAttribute="leading" secondItem="mUy-JP-aUq" secondAttribute="leadingMargin" constant="4" id="g05-ai-aW1"/>
+ </constraints>
+ </view>
+ <navigationItem key="navigationItem" title="Screen Response" id="CyH-dP-ZeE">
+ <barButtonItem key="rightBarButtonItem" systemItem="play" id="ovk-ji-96i">
+ <connections>
+ <action selector="start:" destination="Fkf-5e-wh6" id="Xzm-yg-vpv"/>
+ </connections>
+ </barButtonItem>
+ </navigationItem>
+ <connections>
+ <outlet property="flasherView" destination="A0k-c3-ite" id="Rzq-mg-51J"/>
+ <outlet property="responseLabel" destination="sBN-Rv-s0v" id="yl2-9u-Hho"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="2TJ-Zy-sEu" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="1121" y="1240"/>
+ </scene>
+ <!--Navigation Controller-->
+ <scene sceneID="OTd-lv-DGm">
+ <objects>
+ <navigationController id="eQU-I2-GD8" sceneMemberID="viewController">
+ <navigationBar key="navigationBar" contentMode="scaleToFill" id="hSL-5Q-cVe">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </navigationBar>
+ <connections>
+ <segue destination="VPe-PS-rc7" kind="relationship" relationship="rootViewController" id="Sex-vh-uAb"/>
+ </connections>
+ </navigationController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="cgc-Xe-8Tr" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-510" y="451"/>
+ </scene>
+ </scenes>
+ <resources>
+ <image name="ic_brightness_medium_24dp" width="180" height="180"/>
+ <image name="ic_radio_button_checked_24dp" width="180" height="180"/>
+ <image name="ic_receipt_black_24dp" width="180" height="180"/>
+ <image name="ic_schedule_black_24dp" width="180" height="180"/>
+ <image name="ic_settings_black_24dp" width="180" height="180"/>
+ <image name="ic_swap_horiz_24dp" width="180" height="180"/>
+ <image name="ic_swap_vert_24dp" width="180" height="180"/>
+ <image name="ic_timelapse_black_24dp" width="180" height="180"/>
+ </resources>
+</document>
diff --git a/ios/WALT/DebugLogController.h b/ios/WALT/DebugLogController.h
new file mode 100644
index 0000000..4b2c30e
--- /dev/null
+++ b/ios/WALT/DebugLogController.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface DebugLogController : UIViewController
+@property (assign) IBOutlet UITextView *textView;
+
+- (IBAction)reset:(id)sender;
+@end
diff --git a/ios/WALT/DebugLogController.m b/ios/WALT/DebugLogController.m
new file mode 100644
index 0000000..400c262
--- /dev/null
+++ b/ios/WALT/DebugLogController.m
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "DebugLogController.h"
+
+#import "WALTLogger.h"
+
+@implementation DebugLogController
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+ self.textView.text = [[WALTLogger sessionLogger] stringValue];
+}
+
+- (IBAction)reset:(id)sender {
+ [[WALTLogger sessionLogger] clear];
+ self.textView.text = [[WALTLogger sessionLogger] stringValue];
+ // TODO(pquinn): Reprint DEVICE information?
+}
+@end
diff --git a/ios/WALT/DragLatencyController.h b/ios/WALT/DragLatencyController.h
new file mode 100644
index 0000000..6454fe7
--- /dev/null
+++ b/ios/WALT/DragLatencyController.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface DragLatencyController : UIViewController
+@property (assign, nonatomic) IBOutlet UILabel *countLabel;
+@property (assign, nonatomic) IBOutlet UILabel *statusLabel;
+@property (assign, nonatomic) IBOutlet UIView *goalpostView;
+
+- (IBAction)start:(id)sender;
+- (IBAction)reset:(id)sender;
+- (IBAction)computeStatistics:(id)sender;
+@end
diff --git a/ios/WALT/DragLatencyController.mm b/ios/WALT/DragLatencyController.mm
new file mode 100644
index 0000000..5b6b9b4
--- /dev/null
+++ b/ios/WALT/DragLatencyController.mm
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "DragLatencyController.h"
+
+#import <dispatch/dispatch.h>
+#import <math.h>
+#import <numeric>
+#import <vector>
+
+#import "UIAlertView+Extensions.h"
+#import "WALTAppDelegate.h"
+#import "WALTClient.h"
+#import "WALTLogger.h"
+#import "WALTTouch.h"
+
+static const NSTimeInterval kGoalpostFrequency = 0.55; // TODO(pquinn): User-configurable settings.
+static const NSUInteger kMinTouchEvents = 100;
+static const NSUInteger kMinLaserEvents = 8;
+static const char kWALTLaserTag = 'L';
+
+@interface WALTLaserEvent : NSObject
+@property (assign) NSTimeInterval t;
+@property (assign) int value;
+@end
+
+@implementation WALTLaserEvent
+@end
+
+/** Linear interpolation between x0 and x1 at alpha. */
+template <typename T>
+static T Lerp(const T& x0, const T& x1, double alpha) {
+ NSCAssert(alpha >= 0 && alpha <= 1, @"alpha must be between 0 and 1 (%f)", alpha);
+ return ((1 - alpha) * x0) + (alpha * x1);
+}
+
+/** Linear interpolation of (xp, yp) at x. */
+template <typename S, typename T>
+static std::vector<T> Interpolate(const std::vector<S>& x,
+ const std::vector<S>& xp,
+ const std::vector<T>& yp) {
+ NSCAssert(xp.size(), @"xp must contain at least one value.");
+ NSCAssert(xp.size() == yp.size(), @"xp and yp must have matching lengths.");
+
+ std::vector<T> y;
+ y.reserve(x.size());
+
+ size_t i = 0; // Index into x.
+
+ for (; i < x.size() && x[i] < xp.front(); ++i) {
+ y.push_back(yp.front()); // Pad out y with yp.front() for x values before xp.front().
+ }
+
+ size_t ip = 0; // Index into xp/yp.
+
+ for (; ip < xp.size() && i < x.size(); ++i) {
+ while (ip < xp.size() && xp[ip] <= x[i]) { // Find an xp[ip] greater than x[i].
+ ++ip;
+ }
+ if (ip >= xp.size()) {
+ break; // Ran out of values.
+ }
+
+ const double alpha = (x[i] - xp[ip - 1]) / static_cast<double>(xp[ip] - xp[ip - 1]);
+ y.push_back(Lerp(yp[ip - 1], yp[ip], alpha));
+ }
+
+ for (; i < x.size(); ++i) {
+ y.push_back(yp.back()); // Pad out y with yp.back() for values after xp.back().
+ }
+
+ return y;
+}
+
+/** Extracts the values of y where the corresponding value in x is equal to value. */
+template <typename S, typename T>
+static std::vector<S> Extract(const std::vector<T>& x, const std::vector<S>& y, const T& value) {
+ NSCAssert(x.size() == y.size(), @"x and y must have matching lengths.");
+ std::vector<S> extracted;
+
+ for (size_t i = 0; i < x.size(); ++i) {
+ if (x[i] == value) {
+ extracted.push_back(y[i]);
+ }
+ }
+
+ return extracted;
+}
+
+/** Returns the standard deviation of the values in x. */
+template <typename T>
+static T StandardDeviation(const std::vector<T>& x) {
+ NSCAssert(x.size() > 0, @"x must have at least one value.");
+ const T sum = std::accumulate(x.begin(), x.end(), T{});
+ const T mean = sum / x.size();
+ const T ss = std::accumulate(x.begin(), x.end(), T{}, ^(T accum, T value){
+ return accum + ((value - mean) * (value - mean));
+ });
+ return sqrt(ss / (x.size() - 1));
+}
+
+/** Returns the index of the smallest value in x. */
+template <typename T>
+static size_t ArgMin(const std::vector<T>& x) {
+ NSCAssert(x.size() > 0, @"x must have at least one value.");
+ size_t imin = 0;
+ for (size_t i = 1; i < x.size(); ++i) {
+ if (x[i] < x[imin]) {
+ imin = i;
+ }
+ }
+ return imin;
+}
+
+/**
+ * Finds a positive time value that shifting laserTs by will minimise the standard deviation of
+ * interpolated touchYs.
+ */
+static NSTimeInterval FindBestShift(const std::vector<NSTimeInterval>& laserTs,
+ const std::vector<NSTimeInterval>& touchTs,
+ const std::vector<CGFloat>& touchYs) {
+ NSCAssert(laserTs.size() > 0, @"laserTs must have at least one value.");
+ NSCAssert(touchTs.size() == touchYs.size(), @"touchTs and touchYs must have matching lengths.");
+
+ const NSTimeInterval kSearchCoverage = 0.15;
+ const int kSteps = 1500;
+ const NSTimeInterval kShiftStep = kSearchCoverage / kSteps;
+
+ std::vector<NSTimeInterval> deviations;
+ deviations.reserve(kSteps);
+
+ std::vector<NSTimeInterval> ts(laserTs.size());
+ for (int i = 0; i < kSteps; ++i) {
+ for (size_t j = 0; j < laserTs.size(); ++j) {
+ ts[j] = laserTs[j] + (kShiftStep * i);
+ }
+
+ std::vector<CGFloat> laserYs = Interpolate(ts, touchTs, touchYs);
+ deviations.push_back(StandardDeviation(laserYs));
+ }
+
+ return ArgMin(deviations) * kShiftStep;
+}
+
+@interface DragLatencyController ()
+- (void)updateCountDisplay;
+- (void)processEvent:(UIEvent *)event;
+- (void)receiveTriggers:(id)context;
+- (void)stopReceiver;
+@end
+
+@implementation DragLatencyController {
+ WALTClient *_client;
+ WALTLogger *_logger;
+
+ NSMutableArray<WALTTouch *> *_touchEvents;
+ NSMutableArray<WALTLaserEvent *> *_laserEvents;
+
+ NSThread *_triggerReceiver;
+ dispatch_semaphore_t _receiverComplete;
+}
+
+- (void)dealloc {
+ [self stopReceiver];
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client;
+ _logger = [WALTLogger sessionLogger];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ [self updateCountDisplay];
+
+ [_logger appendString:@"DRAGLATENCY\n"];
+}
+
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+ [self processEvent:event];
+}
+
+- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+ [self processEvent:event];
+}
+
+- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+ [self processEvent:event];
+}
+
+- (void)processEvent:(UIEvent *)event {
+ // TODO(pquinn): Pull out coalesced touches.
+
+ WALTTouch *touch = [[WALTTouch alloc] initWithEvent:event];
+ [_touchEvents addObject:touch];
+ [_logger appendFormat:@"TOUCH\t%.3f\t%.2f\t%.2f\n",
+ touch.kernelTime, touch.location.x, touch.location.y];
+ [self updateCountDisplay];
+}
+
+- (void)updateCountDisplay {
+ NSString *counts = [NSString stringWithFormat:@"N ✛ %lu ⇄ %lu",
+ (unsigned long)_laserEvents.count, (unsigned long)_touchEvents.count];
+ self.countLabel.text = counts;
+}
+
+- (IBAction)start:(id)sender {
+ [self reset:sender];
+
+ self.goalpostView.hidden = NO;
+ self.statusLabel.text = @"";
+
+ [UIView beginAnimations:@"Goalpost" context:NULL];
+ [UIView setAnimationDuration:kGoalpostFrequency];
+ [UIView setAnimationBeginsFromCurrentState:NO];
+ [UIView setAnimationRepeatCount:FLT_MAX];
+ [UIView setAnimationRepeatAutoreverses:YES];
+
+ self.goalpostView.transform =
+ CGAffineTransformMakeTranslation(0.0, -CGRectGetHeight(self.view.frame) + 300);
+
+ [UIView commitAnimations];
+
+ _receiverComplete = dispatch_semaphore_create(0);
+ _triggerReceiver = [[NSThread alloc] initWithTarget:self
+ selector:@selector(receiveTriggers:)
+ object:nil];
+ [_triggerReceiver start];
+}
+
+- (IBAction)reset:(id)sender {
+ [self stopReceiver];
+
+ self.goalpostView.transform = CGAffineTransformMakeTranslation(0.0, 0.0);
+ self.goalpostView.hidden = YES;
+
+ _touchEvents = [[NSMutableArray<WALTTouch *> alloc] init];
+ _laserEvents = [[NSMutableArray<WALTLaserEvent *> alloc] init];
+
+ [self updateCountDisplay];
+
+ NSError *error = nil;
+ if (![_client syncClocksWithError:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ }
+
+ [_logger appendString:@"RESET\n"];
+}
+
+- (void)receiveTriggers:(id)context {
+ // Turn on laser change notifications.
+ NSError *error = nil;
+ if (![_client sendCommand:WALTLaserOnCommand error:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ dispatch_semaphore_signal(_receiverComplete);
+ return;
+ }
+
+ NSData *response = [_client readResponseWithTimeout:kWALTReadTimeout];
+ if (![_client checkResponse:response forCommand:WALTLaserOnCommand]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error"
+ message:@"Failed to start laser probe."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ dispatch_semaphore_signal(_receiverComplete);
+ return;
+ }
+
+ while (!NSThread.currentThread.isCancelled) {
+ WALTTrigger response = [_client readTriggerWithTimeout:kWALTReadTimeout];
+ if (response.tag == kWALTLaserTag) {
+ WALTLaserEvent *event = [[WALTLaserEvent alloc] init];
+ event.t = response.t;
+ event.value = response.value;
+ [_laserEvents addObject:event];
+ [_logger appendFormat:@"LASER\t%.3f\t%d\n", event.t, event.value];
+ } else if (response.tag != '\0') { // Don't fail for timeout errors.
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error"
+ message:@"Failed to read laser probe."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ }
+ }
+
+ // Turn off laser change notifications.
+ [_client sendCommand:WALTLaserOffCommand error:nil];
+ [_client readResponseWithTimeout:kWALTReadTimeout];
+
+ dispatch_semaphore_signal(_receiverComplete);
+}
+
+- (void)stopReceiver {
+ // TODO(pquinn): This will deadlock if called in rapid succession -- there is a small delay
+ // between dispatch_semaphore_signal() and -[NSThread isExecuting] changing.
+ // Unfortunately, NSThread is not joinable...
+ if (_triggerReceiver.isExecuting) {
+ [_triggerReceiver cancel];
+ dispatch_semaphore_wait(_receiverComplete, DISPATCH_TIME_FOREVER);
+ }
+}
+
+- (IBAction)computeStatistics:(id)sender {
+ if (_touchEvents.count < kMinTouchEvents) {
+ self.statusLabel.text =
+ [NSString stringWithFormat:@"Too few touch events (%lu/%lu).",
+ (unsigned long)_touchEvents.count, (unsigned long)kMinTouchEvents];
+ [self reset:sender];
+ return;
+ }
+
+ // Timestamps are reset to be relative to t0 to make the output easier to read.
+ const NSTimeInterval t0 = _touchEvents.firstObject.kernelTime;
+ const NSTimeInterval tF = _touchEvents.lastObject.kernelTime;
+
+ std::vector<NSTimeInterval> ft(_touchEvents.count);
+ std::vector<CGFloat> fy(_touchEvents.count);
+ for (NSUInteger i = 0; i < _touchEvents.count; ++i) {
+ ft[i] = _touchEvents[i].kernelTime - t0;
+ fy[i] = _touchEvents[i].location.y;
+ }
+
+ // Remove laser events that have a timestamp outside [t0, tF].
+ [_laserEvents filterUsingPredicate:[NSPredicate predicateWithBlock:
+ ^BOOL(WALTLaserEvent *evaluatedObject, NSDictionary<NSString *, id> *bindings) {
+ return evaluatedObject.t >= t0 && evaluatedObject.t <= tF;
+ }]];
+
+ if (_laserEvents.count < kMinLaserEvents) {
+ self.statusLabel.text =
+ [NSString stringWithFormat:@"Too few laser events (%lu/%lu).",
+ (unsigned long)_laserEvents.count, (unsigned long)kMinLaserEvents];
+ [self reset:sender];
+ return;
+ }
+
+ if (_laserEvents.firstObject.value != 0) {
+ self.statusLabel.text = @"First laser crossing was not into the beam.";
+ [self reset:sender];
+ return;
+ }
+
+ std::vector<NSTimeInterval> lt(_laserEvents.count);
+ std::vector<int> lv(_laserEvents.count);
+ for (NSUInteger i = 0; i < _laserEvents.count; ++i) {
+ lt[i] = _laserEvents[i].t - t0;
+ lv[i] = _laserEvents[i].value;
+ }
+
+ // Calculate interpolated touch y positions at each laser event.
+ std::vector<CGFloat> ly = Interpolate(lt, ft, fy);
+
+ // Labels for each laser event to denote those above/below the beam.
+ // The actual side is irrelevant, but events on the same side should have the same label. The
+ // vector will look like [0, 1, 1, 0, 0, 1, 1, 0, 0, ...].
+ std::vector<int> sideLabels(lt.size());
+ for (size_t i = 0; i < lt.size(); ++i) {
+ sideLabels[i] = ((i + 1) / 2) % 2;
+ }
+
+ NSTimeInterval averageBestShift = 0;
+ for (int side = 0; side < 2; ++side) {
+ std::vector<NSTimeInterval> lts = Extract(sideLabels, lt, side);
+ NSTimeInterval bestShift = FindBestShift(lts, ft, fy);
+ averageBestShift += bestShift / 2;
+ }
+
+ self.statusLabel.text = [NSString stringWithFormat:@"%.3f s", averageBestShift];
+
+ [self reset:sender];
+}
+@end
diff --git a/ios/WALT/Info.plist b/ios/WALT/Info.plist
new file mode 100644
index 0000000..459291f
--- /dev/null
+++ b/ios/WALT/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIBackgroundModes</key>
+ <array>
+ <string>audio</string>
+ </array>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UIRequiresFullScreen</key>
+ <true/>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ </array>
+</dict>
+</plist>
diff --git a/ios/WALT/MIDIClient.h b/ios/WALT/MIDIClient.h
new file mode 100644
index 0000000..b4e10e3
--- /dev/null
+++ b/ios/WALT/MIDIClient.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class MIDIClient;
+@class MIDIDestination;
+@class MIDISource;
+
+extern NSString * const MIDIClientErrorDomain;
+
+/**
+ * Callbacks for MIDIClient changes.
+ *
+ * Note that these methods may not be called on the main thread.
+ */
+@protocol MIDIClientDelegate <NSObject>
+/** Called when a MIDIClient receives data from a connected source. */
+- (void)MIDIClient:(MIDIClient *)client receivedData:(NSData *)message;
+
+@optional
+/** Called when a MIDI I/O error occurs on the client's endpoints. */
+- (void)MIDIClient:(MIDIClient *)client receivedError:(NSError *)error;
+
+/** Called when a MIDI endpoint has been added to the system. */
+- (void)MIDIClientEndpointAdded:(MIDIClient *)client;
+
+/** Called when a MIDI endpoint has been removed to the system. */
+- (void)MIDIClientEndpointRemoved:(MIDIClient *)client;
+
+/** Called when the configuration of a MIDI object attached to the system has changed. */
+- (void)MIDIClientConfigurationChanged:(MIDIClient *)client;
+@end
+
+/** A MIDI client that can read data from a MIDI source and write data to a MIDI destination. */
+@interface MIDIClient : NSObject
+/** The source attached by -connectToSource:error:. */
+@property (readonly, nonatomic) MIDISource *source;
+
+/** The destination attached by -connectToDestination:error:. */
+@property (readonly, nonatomic) MIDIDestination *destination;
+
+@property (nonatomic, weak) id<MIDIClientDelegate> delegate;
+
+/**
+ * Creates a new MIDI client with a friendly name.
+ *
+ * If an error occurs, nil is returned and the error is populated with a description of the issue.
+ */
+- (instancetype)initWithName:(NSString *)name error:(NSError **)error;
+
+/** Attaches an input source to the client. */
+- (BOOL)connectToSource:(MIDISource *)source error:(NSError **)error;
+
+/** Attaches an output destination to the client. */
+- (BOOL)connectToDestination:(MIDIDestination *)destination error:(NSError **)error;
+
+/** Sends a MIDI packet of data to the client's output destination. */
+- (BOOL)sendData:(NSData *)data error:(NSError **)error;
+@end
diff --git a/ios/WALT/MIDIClient.m b/ios/WALT/MIDIClient.m
new file mode 100644
index 0000000..1c51f5f
--- /dev/null
+++ b/ios/WALT/MIDIClient.m
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "MIDIClient.h"
+
+#include <CoreMIDI/CoreMIDI.h>
+
+#import "MIDIEndpoint.h"
+#import "MIDIMessage.h"
+
+NSString * const MIDIClientErrorDomain = @"MIDIClientErrorDomain";
+
+@interface MIDIClient ()
+@property (readwrite, nonatomic) MIDISource *source;
+@property (readwrite, nonatomic) MIDIDestination *destination;
+// Used by midiRead() for SysEx messages spanning multiple packets.
+@property (readwrite, nonatomic) NSMutableData *sysExBuffer;
+
+/** Returns whether the client's source or destination is attached to a particular device. */
+- (BOOL)attachedToDevice:(MIDIDeviceRef)device;
+@end
+
+// Note: These functions (midiStateChanged and midiRead) are not called on the main thread!
+static void midiStateChanged(const MIDINotification *message, void *context) {
+ MIDIClient *client = (__bridge MIDIClient *)context;
+
+ switch (message->messageID) {
+ case kMIDIMsgObjectAdded: {
+ const MIDIObjectAddRemoveNotification *notification =
+ (const MIDIObjectAddRemoveNotification *)message;
+
+ @autoreleasepool {
+ if ((notification->childType & (kMIDIObjectType_Source|kMIDIObjectType_Destination)) != 0 &&
+ [client.delegate respondsToSelector:@selector(MIDIClientEndpointAdded:)]) {
+ [client.delegate MIDIClientEndpointAdded:client];
+ }
+ }
+ break;
+ }
+
+ case kMIDIMsgObjectRemoved: {
+ const MIDIObjectAddRemoveNotification *notification =
+ (const MIDIObjectAddRemoveNotification *)message;
+
+ @autoreleasepool {
+ if ((notification->childType & (kMIDIObjectType_Source|kMIDIObjectType_Destination)) != 0 &&
+ [client.delegate respondsToSelector:@selector(MIDIClientEndpointRemoved:)]) {
+ [client.delegate MIDIClientEndpointRemoved:client];
+ }
+ }
+ break;
+ }
+
+ case kMIDIMsgSetupChanged:
+ case kMIDIMsgPropertyChanged:
+ case kMIDIMsgSerialPortOwnerChanged:
+ case kMIDIMsgThruConnectionsChanged: {
+ @autoreleasepool {
+ if ([client.delegate respondsToSelector:@selector(MIDIClientConfigurationChanged:)]) {
+ [client.delegate MIDIClientConfigurationChanged:client];
+ }
+ }
+ break;
+ }
+
+ case kMIDIMsgIOError: {
+ const MIDIIOErrorNotification *notification = (const MIDIIOErrorNotification *)message;
+
+ if ([client attachedToDevice:notification->driverDevice]) {
+ @autoreleasepool {
+ NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain
+ code:notification->errorCode
+ userInfo:nil];
+ if ([client.delegate respondsToSelector:@selector(MIDIClient:receivedError:)]) {
+ [client.delegate MIDIClient:client receivedError:error];
+ }
+ }
+ }
+ break;
+ }
+
+ default: {
+ NSLog(@"Unhandled MIDI state change: %d", (int)message->messageID);
+ }
+ }
+}
+
+static void midiRead(const MIDIPacketList *packets, void *portContext, void *sourceContext) {
+ MIDIClient *client = (__bridge MIDIClient *)portContext;
+
+ // Read the data out of each packet and forward it to the client's delegate.
+ // Each MIDIPacket will contain either some MIDI commands, or the start/continuation of a SysEx
+ // command. The start of a command is detected with a byte greater than or equal to 0x80 (all data
+ // must be 7-bit friendly). The end of a SysEx command is marked with 0x7F.
+
+ // TODO(pquinn): Should something be done with the timestamp data?
+
+ UInt32 packetCount = packets->numPackets;
+ const MIDIPacket *packet = &packets->packet[0];
+ @autoreleasepool {
+ while (packetCount--) {
+ if (packet->length == 0) {
+ continue;
+ }
+
+ const Byte firstByte = packet->data[0];
+ const Byte lastByte = packet->data[packet->length - 1];
+
+ if (firstByte >= 0x80 && firstByte != MIDIMessageSysEx && firstByte != MIDIMessageSysExEnd) {
+ // Packet describes non-SysEx MIDI messages.
+ NSMutableData *data = nil;
+ for (UInt16 i = 0; i < packet->length; ++i) {
+ // Packets can contain multiple MIDI messages.
+ if (packet->data[i] >= 0x80) {
+ if (data.length > 0) { // Tell the delegate about the last extracted command.
+ [client.delegate MIDIClient:client receivedData:data];
+ }
+ data = [[NSMutableData alloc] init];
+ }
+ [data appendBytes:&packet->data[i] length:1];
+ }
+
+ if (data.length > 0) {
+ [client.delegate MIDIClient:client receivedData:data];
+ }
+ }
+
+ if (firstByte == MIDIMessageSysEx) {
+ // The start of a SysEx message; collect data into sysExBuffer.
+ client.sysExBuffer = [[NSMutableData alloc] initWithBytes:packet->data
+ length:packet->length];
+ } else if (firstByte < 0x80 || firstByte == MIDIMessageSysExEnd) {
+ // Continuation or end of a SysEx message.
+ [client.sysExBuffer appendBytes:packet->data length:packet->length];
+ }
+
+ if (lastByte == MIDIMessageSysExEnd) {
+ // End of a SysEx message.
+ [client.delegate MIDIClient:client receivedData:client.sysExBuffer];
+ client.sysExBuffer = nil;
+ }
+
+ packet = MIDIPacketNext(packet);
+ }
+ }
+}
+
+@implementation MIDIClient {
+ NSString *_name;
+ MIDIClientRef _client;
+ MIDIPortRef _input;
+ MIDIPortRef _output;
+}
+
+- (instancetype)initWithName:(NSString *)name error:(NSError **)error {
+ if ((self = [super init])) {
+ _name = name; // Hold onto the name because MIDIClientCreate() doesn't retain it.
+ OSStatus result = MIDIClientCreate((__bridge CFStringRef)name,
+ midiStateChanged,
+ (__bridge void *)self,
+ &_client);
+ if (result != noErr) {
+ if (error) {
+ *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
+ }
+ self = nil;
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ MIDIClientDispose(_client); // Automatically disposes of the ports too.
+}
+
+- (BOOL)connectToSource:(MIDISource *)source error:(NSError **)error {
+ OSStatus result = noErr;
+ if (!_input) { // Lazily create the input port.
+ result = MIDIInputPortCreate(_client,
+ (__bridge CFStringRef)_name,
+ midiRead,
+ (__bridge void *)self,
+ &_input);
+ if (result != noErr) {
+ if (error) {
+ *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
+ }
+ return NO;
+ }
+ }
+
+ // Connect the source to the port.
+ result = MIDIPortConnectSource(_input, source.endpoint, (__bridge void *)self);
+ if (result != noErr) {
+ if (error) {
+ *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
+ }
+ return NO;
+ }
+
+ self.source = source;
+ return YES;
+}
+
+- (BOOL)connectToDestination:(MIDIDestination *)destination error:(NSError **)error {
+ if (!_output) { // Lazily create the output port.
+ OSStatus result = MIDIOutputPortCreate(_client,
+ (__bridge CFStringRef)_name,
+ &_output);
+ if (result != noErr) {
+ if (error) {
+ *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
+ }
+ return NO;
+ }
+ }
+
+ self.destination = destination;
+ return YES;
+}
+
+- (BOOL)sendData:(NSData *)data error:(NSError **)error {
+ if (data.length > sizeof(((MIDIPacket *)0)->data)) {
+ // TODO(pquinn): Dynamically allocate a buffer.
+ if (error) {
+ *error = [NSError errorWithDomain:MIDIClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ @"Too much data for a basic MIDIPacket."}];
+ }
+ return NO;
+ }
+
+ MIDIPacketList packetList;
+ MIDIPacket *packet = MIDIPacketListInit(&packetList);
+ packet = MIDIPacketListAdd(&packetList, sizeof(packetList), packet, 0, data.length, data.bytes);
+ if (!packet) {
+ if (error) {
+ *error = [NSError errorWithDomain:MIDIClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ @"Packet too large for buffer."}];
+ }
+ return NO;
+ }
+
+ OSStatus result = MIDISend(_output, self.destination.endpoint, &packetList);
+ if (result != noErr) {
+ if (error) {
+ *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
+ }
+ return NO;
+ }
+ return YES;
+}
+
+- (BOOL)attachedToDevice:(MIDIDeviceRef)device {
+ MIDIDeviceRef sourceDevice = 0, destinationDevice = 0;
+ MIDIEntityGetDevice(self.source.endpoint, &sourceDevice);
+ MIDIEntityGetDevice(self.destination.endpoint, &destinationDevice);
+
+ SInt32 sourceID = 0, destinationID = 0, deviceID = 0;
+ MIDIObjectGetIntegerProperty(sourceDevice, kMIDIPropertyUniqueID, &sourceID);
+ MIDIObjectGetIntegerProperty(destinationDevice, kMIDIPropertyUniqueID, &destinationID);
+ MIDIObjectGetIntegerProperty(device, kMIDIPropertyUniqueID, &deviceID);
+
+ return (deviceID == sourceID || deviceID == destinationID);
+}
+@end
diff --git a/ios/WALT/MIDIEndpoint.h b/ios/WALT/MIDIEndpoint.h
new file mode 100644
index 0000000..ec43d1c
--- /dev/null
+++ b/ios/WALT/MIDIEndpoint.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <CoreMIDI/CoreMIDI.h>
+#import <Foundation/Foundation.h>
+
+/** An abstract MIDI endpoint (input source or output destination). */
+@interface MIDIEndpoint : NSObject
+@property (readonly, nonatomic) MIDIEndpointRef endpoint;
+@property (readonly, nonatomic) NSString *name;
+@property (readonly, nonatomic, getter=isOnline) BOOL online;
+@end
+
+@interface MIDIDestination : MIDIEndpoint
+/** Returns an NSArray of all MIDI output destinations currently available on the system. */
++ (NSArray<MIDIDestination *> *)allDestinations;
+@end
+
+@interface MIDISource : MIDIEndpoint
+/** Returns an NSArray of all MIDI input sources currently available on the system. */
++ (NSArray<MIDISource *> *)allSources;
+@end
diff --git a/ios/WALT/MIDIEndpoint.m b/ios/WALT/MIDIEndpoint.m
new file mode 100644
index 0000000..9fe7163
--- /dev/null
+++ b/ios/WALT/MIDIEndpoint.m
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "MIDIEndpoint.h"
+
+@interface MIDIEndpoint ()
+@property (readwrite, nonatomic, assign) MIDIEndpointRef endpoint;
+@end
+
+@implementation MIDIEndpoint
+- (NSString *)name {
+ CFStringRef result = CFSTR("");
+ MIDIObjectGetStringProperty(self.endpoint, kMIDIPropertyDisplayName, &result);
+ return CFBridgingRelease(result);
+}
+
+- (BOOL)isOnline {
+ SInt32 result = 1;
+ MIDIObjectGetIntegerProperty(self.endpoint, kMIDIPropertyOffline, &result);
+ return (result == 0 ? YES : NO);
+}
+@end
+
+@implementation MIDIDestination
++ (NSArray *)allDestinations {
+ NSMutableArray<MIDIDestination *> *destinations =
+ [[NSMutableArray<MIDIDestination *> alloc] init];
+
+ ItemCount destinationCount = MIDIGetNumberOfDestinations();
+ for (ItemCount i = 0; i < destinationCount; ++i) {
+ MIDIEndpointRef endpoint = MIDIGetDestination(i);
+ if (endpoint) {
+ MIDIDestination *destination = [[MIDIDestination alloc] init];
+ destination.endpoint = endpoint;
+ [destinations addObject:destination];
+ } else {
+ NSLog(@"Error getting destination at index %lud, skipping.", i);
+ }
+ }
+
+ return destinations;
+}
+@end
+
+@implementation MIDISource
++ (NSArray *)allSources {
+ NSMutableArray<MIDISource *> *sources = [[NSMutableArray<MIDISource *> alloc] init];
+
+ ItemCount sourceCount = MIDIGetNumberOfSources();
+ for (ItemCount i = 0; i < sourceCount; ++i) {
+ MIDIEndpointRef endpoint = MIDIGetSource(i);
+ if (endpoint) {
+ MIDISource *source = [[MIDISource alloc] init];
+ source.endpoint = endpoint;
+ [sources addObject:source];
+ } else {
+ NSLog(@"Error getting source at index %lud, skipping.", i);
+ }
+ }
+
+ return sources;
+}
+@end
diff --git a/ios/WALT/MIDIMessage.h b/ios/WALT/MIDIMessage.h
new file mode 100644
index 0000000..2577ee6
--- /dev/null
+++ b/ios/WALT/MIDIMessage.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * A MIDI channel number.
+ *
+ * Note that the first channel is '1'.
+ */
+typedef uint8_t MIDIChannel;
+typedef uint8_t MIDIByte;
+
+typedef NS_ENUM(MIDIByte, MIDIMessageType) {
+ // Channel messages
+ MIDIMessageNoteOff = 0x08,
+ MIDIMessageNoteOn = 0x09,
+ MIDIMessageKeyPressure = 0x0A,
+ MIDIMessageControlChange = 0x0B,
+ MIDIMessageProgramChange = 0x0C,
+ MIDIMessageChannelPressure = 0x0D,
+ MIDIMessagePitchBend = 0x0E,
+
+ // System messages
+ MIDIMessageSysEx = 0xF0,
+ MIDIMessageQuarterFrame = 0xF1,
+ MIDIMessageSongPosition = 0xF2,
+ MIDIMessageSongSelect = 0xF3,
+ MIDIMessageTuneRequest = 0xF6,
+ MIDIMessageSysExEnd = 0xF7,
+ MIDIMessageTimingClock = 0xF8,
+ MIDIMessageStart = 0xFA,
+ MIDIMessageContinue = 0xFB,
+ MIDIMessageStop = 0xFC,
+ MIDIMessageActiveSensing = 0xFE,
+ MIDIMessageReset = 0xFF,
+};
+
+extern const MIDIChannel kMIDINoChannel;
+
+#pragma mark Message Parsing
+
+/** Returns the MIDIMessageType for a given status byte. */
+MIDIMessageType MIDIMessageTypeFromStatus(MIDIByte status);
+
+/**
+ * Returns the MIDIChannel for a given status byte, or kMIDINoChannel if the status byte does not
+ * describe a channel message.
+ */
+MIDIChannel MIDIChannelFromStatus(MIDIByte status);
+
+/**
+ * Returns the body portion from a complete MIDI message (i.e., without leading or trailing data).
+ */
+NSData *MIDIMessageBody(NSData *message);
+
+#pragma mark Message Building
+
+/** Returns the MIDI status byte for a message type sent to a particular channel. */
+MIDIByte MIDIStatusByte(MIDIMessageType type, MIDIChannel channel);
+
+/** Creates a complete MIDI message packet for a given message type, channel, and its body. */
+NSData *MIDIMessageCreate(MIDIMessageType type, MIDIChannel channel, NSData *body);
+
+/** Creates a complete MIDI message packet for a simple message containing one data byte. */
+NSData *MIDIMessageCreateSimple1(MIDIMessageType type, MIDIChannel channel, MIDIByte first);
+
+/** Creates a complete MIDI message packet for a simple message containing two data bytes. */
+NSData *MIDIMessageCreateSimple2(MIDIMessageType type,
+ MIDIChannel channel,
+ MIDIByte first,
+ MIDIByte second);
diff --git a/ios/WALT/MIDIMessage.m b/ios/WALT/MIDIMessage.m
new file mode 100644
index 0000000..75c6a3b
--- /dev/null
+++ b/ios/WALT/MIDIMessage.m
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "MIDIMessage.h"
+
+const uint8_t kMIDINoChannel = -1;
+
+MIDIMessageType MIDIMessageTypeFromStatus(MIDIByte status) {
+ if (status < MIDIMessageSysEx) {
+ return (status & 0xF0) >> 4;
+ } else {
+ return status;
+ }
+}
+
+MIDIChannel MIDIChannelFromStatus(MIDIByte status) {
+ if (status < MIDIMessageSysEx) {
+ return (status & 0x0F) + 1;
+ } else {
+ return -1;
+ }
+}
+
+NSData *MIDIMessageBody(NSData *message) {
+ if (message.length == 0) {
+ return nil;
+ }
+
+ const MIDIByte *bytes = (const MIDIByte *)message.bytes;
+
+ // Slice off any header/trailer bytes.
+ if (MIDIMessageTypeFromStatus(bytes[0]) == MIDIMessageSysEx) {
+ NSCAssert(bytes[message.length - 1] == MIDIMessageSysExEnd, @"SysEx message without trailer.");
+ return [message subdataWithRange:NSMakeRange(1, message.length - 2)];
+ } else {
+ return [message subdataWithRange:NSMakeRange(1, message.length - 1)];
+ }
+}
+
+MIDIByte MIDIStatusByte(MIDIMessageType type, MIDIChannel channel) {
+ if (type >= MIDIMessageSysEx) {
+ return type;
+ } else {
+ return (type << 4) | (channel - 1);
+ }
+}
+
+NSData *MIDIChannelMessageCreate(MIDIMessageType type, MIDIChannel channel, NSData *body) {
+ NSMutableData *message =
+ [[NSMutableData alloc] initWithCapacity:body.length + 2]; // +2 for status and SysEx trailer
+
+ const MIDIByte status = MIDIStatusByte(type, channel);
+ [message appendBytes:&status length:1];
+ [message appendData:body];
+
+ if (type == MIDIMessageSysEx) {
+ const MIDIByte trailer = MIDIMessageSysEx;
+ [message appendBytes:&trailer length:1];
+ }
+
+ return message;
+}
+
+NSData *MIDIMessageCreateSimple1(MIDIMessageType type, MIDIChannel channel, MIDIByte first) {
+ NSCAssert(type != MIDIMessageSysEx, @"MIDIMessageCreateSimple1 cannot create SysEx messages.");
+
+ NSMutableData *message = [[NSMutableData alloc] initWithCapacity:2]; // Status + Data
+
+ const MIDIByte status = MIDIStatusByte(type, channel);
+ [message appendBytes:&status length:1];
+ [message appendBytes:&first length:1];
+
+ return message;
+}
+
+NSData *MIDIMessageCreateSimple2(MIDIMessageType type,
+ MIDIChannel channel,
+ MIDIByte first,
+ MIDIByte second) {
+ NSCAssert(type != MIDIMessageSysEx, @"MIDIMessageCreateSimple2 cannot create SysEx messages.");
+
+ NSMutableData *message = [[NSMutableData alloc] initWithCapacity:3]; // Status + Data + Data
+
+ const MIDIByte status = MIDIStatusByte(type, channel);
+ [message appendBytes:&status length:1];
+ [message appendBytes:&first length:1];
+ [message appendBytes:&second length:1];
+
+ return message;
+}
diff --git a/ios/WALT/MenuController.h b/ios/WALT/MenuController.h
new file mode 100644
index 0000000..559392f
--- /dev/null
+++ b/ios/WALT/MenuController.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface MenuController : UITableViewController
+@property (nonatomic, assign) IBOutlet UITableViewCell *syncCell;
+
+- (IBAction)shareLog:(id)sender;
+@end
+
diff --git a/ios/WALT/MenuController.m b/ios/WALT/MenuController.m
new file mode 100644
index 0000000..98b84e9
--- /dev/null
+++ b/ios/WALT/MenuController.m
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "MenuController.h"
+
+#import "UIAlertView+Extensions.h"
+#import "WALTLogger.h"
+#import "WALTAppDelegate.h"
+#import "WALTClient.h"
+
+@implementation MenuController {
+ WALTClient *_client;
+ UIActivityIndicatorView *_spinner;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ _spinner =
+ [[UIActivityIndicatorView alloc]
+ initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
+
+ _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client;
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+ [_client addObserver:self
+ forKeyPath:@"connected"
+ options:NSKeyValueObservingOptionInitial
+ context:NULL];
+}
+
+- (void)dealloc {
+ [_client removeObserver:self forKeyPath:@"connected"];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ if (_client.isConnected) {
+ [_spinner stopAnimating];
+ self.syncCell.accessoryView = nil; // Display a checkmark.
+ [[WALTLogger sessionLogger] appendString:@"WALT\tCONNECTED\n"];
+ [[WALTLogger sessionLogger] appendFormat:@"SYNC\t%lld\t%lld\n",
+ _client.minError, _client.maxError];
+ } else {
+ self.syncCell.accessoryView = _spinner;
+ [_spinner startAnimating];
+ [[WALTLogger sessionLogger] appendString:@"WALT\tDISCONNECTED\n"];
+
+ // Return to this view controller.
+ UINavigationController *navigationController = self.navigationController;
+ if (navigationController.visibleViewController != self) {
+ [navigationController popToRootViewControllerAnimated:YES];
+
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error"
+ message:@"WALT disconnected."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ }
+ }
+
+ [self.tableView reloadData]; // Update accessory types.
+}
+
+- (void)shareLog:(id)sender {
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSArray *urls = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
+
+ if (urls.count > 0) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.dateFormat = @"yyyy-MM-dd'T'HH-mm-ss";
+
+ // Save the log to a file (which also allows it to be retrieved in iTunes/Xcode).
+ NSString *logName = [NSString stringWithFormat:@"walt_%@.log",
+ [formatter stringFromDate:[NSDate date]]];
+ NSURL *logURL = [urls.firstObject URLByAppendingPathComponent:logName];
+
+ WALTLogger *logger = [WALTLogger sessionLogger];
+ NSError *error = nil;
+ if ([logger writeToURL:logURL error:&error]) {
+ // Open a share sheet for the URL.
+ UIActivityViewController *activityController =
+ [[UIActivityViewController alloc] initWithActivityItems:@[logURL]
+ applicationActivities:nil];
+ [self presentViewController:activityController animated:YES completion:NULL];
+ } else {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Log Write Error"
+ error:error];
+ [alert show];
+ }
+ } else {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Log Write Error"
+ message:@"Could not locate document directory."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ }
+}
+
+#pragma mark - UITableView Delegate
+
+- (void)tableView:(UITableView *)tableView
+ willDisplayCell:(UITableViewCell *)cell
+forRowAtIndexPath:(NSIndexPath *)indexPath {
+ if (indexPath.section == 1) {
+ // Show/hide the disclosure indicator on the "Measure Latency" cells.
+ cell.accessoryType = (_client.isConnected ?
+ UITableViewCellAccessoryDisclosureIndicator :
+ UITableViewCellAccessoryNone);
+ }
+}
+
+- (NSIndexPath *)tableView:(UITableView *)tableView
+ willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+ if (indexPath.section == 0 && indexPath.row == 0) {
+ // "Clock Sync"
+ NSError *error = nil;
+ if (![_client checkConnectionWithError:&error] ||
+ ![_client syncClocksWithError:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error"
+ error:error];
+ [alert show];
+ }
+ [[WALTLogger sessionLogger] appendFormat:@"SYNC\t%lld\t%lld\n",
+ _client.minError, _client.maxError];
+ return nil;
+ } else if (indexPath.section == 1 && !_client.isConnected) {
+ // "Measure Latency"
+ return nil;
+ }
+
+ return indexPath;
+}
+@end
diff --git a/ios/WALT/NSArray+Extensions.h b/ios/WALT/NSArray+Extensions.h
new file mode 100644
index 0000000..cb8a4d1
--- /dev/null
+++ b/ios/WALT/NSArray+Extensions.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface NSArray (WALTExtensions)
+- (NSNumber *)medianValue;
+@end
diff --git a/ios/WALT/NSArray+Extensions.m b/ios/WALT/NSArray+Extensions.m
new file mode 100644
index 0000000..2217bc4
--- /dev/null
+++ b/ios/WALT/NSArray+Extensions.m
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "NSArray+Extensions.h"
+
+@implementation NSArray (WALTExtensions)
+- (NSNumber *)medianValue {
+ NSArray<NSNumber *> *sorted = [self sortedArrayUsingSelector:@selector(compare:)];
+ const NSUInteger count = sorted.count;
+ if (count == 0) {
+ return nil;
+ }
+
+ if (count % 2) {
+ return [sorted objectAtIndex:count / 2];
+ } else {
+ return [NSNumber numberWithDouble:0.5 * ([sorted objectAtIndex:count / 2].doubleValue +
+ [sorted objectAtIndex:count / 2 - 1].doubleValue)];
+ }
+}
+@end
diff --git a/ios/WALT/ScreenResponseController.h b/ios/WALT/ScreenResponseController.h
new file mode 100644
index 0000000..33d36a1
--- /dev/null
+++ b/ios/WALT/ScreenResponseController.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface ScreenResponseController : UIViewController
+@property (nonatomic, assign) IBOutlet UIView *flasherView;
+@property (nonatomic, assign) IBOutlet UILabel *responseLabel;
+
+- (IBAction)start:(id)sender;
+- (IBAction)reset:(id)sender;
+- (IBAction)computeStatistics:(id)sender;
+@end
diff --git a/ios/WALT/ScreenResponseController.m b/ios/WALT/ScreenResponseController.m
new file mode 100644
index 0000000..c88236c
--- /dev/null
+++ b/ios/WALT/ScreenResponseController.m
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "ScreenResponseController.h"
+
+#include <stdatomic.h>
+
+#import "NSArray+Extensions.h"
+#import "UIAlertView+Extensions.h"
+#import "WALTAppDelegate.h"
+#import "WALTClient.h"
+#import "WALTLogger.h"
+
+static const NSUInteger kMaxFlashes = 20; // TODO(pquinn): Make this user-configurable.
+static const NSTimeInterval kFlashingInterval = 0.1;
+static const char kWALTScreenTag = 'S';
+
+@interface ScreenResponseController ()
+- (void)setFlashTimer;
+- (void)flash:(NSTimer *)timer;
+@end
+
+@implementation ScreenResponseController {
+ WALTClient *_client;
+ WALTLogger *_logger;
+
+ NSTimer *_flashTimer;
+ NSOperationQueue *_readOperations;
+
+ // Statistics
+ NSUInteger _initiatedFlashes;
+ NSUInteger _detectedFlashes;
+
+ _Atomic NSTimeInterval _lastFlashTime;
+ NSMutableArray<NSNumber *> *_deltas;
+}
+
+- (void)dealloc {
+ [_readOperations cancelAllOperations];
+ [_flashTimer invalidate];
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client;
+ _logger = [WALTLogger sessionLogger];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ [_logger appendString:@"SCREENRESPONSE\n"];
+ [self reset:nil];
+}
+
+- (IBAction)start:(id)sender {
+ [self reset:nil];
+
+ // Clear the screen trigger on the WALT.
+ NSError *error = nil;
+ if (![_client sendCommand:WALTSendLastScreenCommand error:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ return;
+ }
+
+ WALTTrigger trigger = [_client readTriggerWithTimeout:kWALTReadTimeout];
+ if (trigger.tag != kWALTScreenTag) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error"
+ message:@"Failed to read last screen trigger."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ return;
+ }
+
+ // Create a queue for work blocks to read WALT trigger responses.
+ _readOperations = [[NSOperationQueue alloc] init];
+ _readOperations.maxConcurrentOperationCount = 1;
+
+ // Start the flash timer and spawn a thread to check for responses.
+ [self setFlashTimer];
+}
+
+- (void)setFlashTimer {
+ _flashTimer = [NSTimer scheduledTimerWithTimeInterval:kFlashingInterval
+ target:self
+ selector:@selector(flash:)
+ userInfo:nil
+ repeats:NO];
+}
+
+- (IBAction)computeStatistics:(id)sender {
+ self.flasherView.hidden = YES;
+ self.responseLabel.hidden = NO;
+
+ NSMutableString *results = [[NSMutableString alloc] init];
+ for (NSNumber *delta in _deltas) {
+ [results appendFormat:@"%.3f s\n", delta.doubleValue];
+ }
+
+ [results appendFormat:@"Median: %.3f s\n", [_deltas medianValue].doubleValue];
+ self.responseLabel.text = results;
+}
+
+- (IBAction)reset:(id)sender {
+ _initiatedFlashes = 0;
+ _detectedFlashes = 0;
+ _deltas = [[NSMutableArray<NSNumber *> alloc] init];
+
+ [_readOperations cancelAllOperations];
+ [_flashTimer invalidate];
+
+ self.flasherView.hidden = NO;
+ self.flasherView.backgroundColor = [UIColor whiteColor];
+ self.responseLabel.hidden = YES;
+
+ NSError *error = nil;
+ if (![_client syncClocksWithError:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ }
+
+ [_logger appendString:@"RESET\n"];
+}
+
+- (void)flash:(NSTimer *)timer {
+ if (_initiatedFlashes == 0) {
+ // First flash.
+ // Turn on brightness change notifications.
+ NSError *error = nil;
+ if (![_client sendCommand:WALTScreenOnCommand error:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ return;
+ }
+
+ NSData *response = [_client readResponseWithTimeout:kWALTReadTimeout];
+ if (![_client checkResponse:response forCommand:WALTScreenOnCommand]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error"
+ message:@"Failed to start screen probe."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ return;
+ }
+ }
+
+ if (_initiatedFlashes != kMaxFlashes) {
+ // Swap the background colour and record the time.
+ self.flasherView.backgroundColor =
+ ([self.flasherView.backgroundColor isEqual:[UIColor blackColor]] ?
+ [UIColor whiteColor] :
+ [UIColor blackColor]);
+ atomic_store(&_lastFlashTime, _client.currentTime);
+ ++_initiatedFlashes;
+
+ // Queue an operation to read the trigger.
+ [_readOperations addOperationWithBlock:^{
+ // NB: The timeout here should be much greater than the expected screen response time.
+ WALTTrigger response = [_client readTriggerWithTimeout:kWALTReadTimeout];
+ if (response.tag == kWALTScreenTag) {
+ ++_detectedFlashes;
+
+ // Record the delta between the trigger and the flash time.
+ NSTimeInterval lastFlash = atomic_load(&_lastFlashTime);
+ NSTimeInterval delta = response.t - lastFlash;
+ if (delta > 0) { // Sanity check
+ [_deltas addObject:[NSNumber numberWithDouble:delta]];
+ [_logger appendFormat:@"O\t%f\n", delta];
+ } else {
+ [_logger appendFormat:@"X\tbogus delta\t%f\t%f\n", lastFlash, response.t];
+ }
+
+ // Queue up another flash.
+ [self performSelectorOnMainThread:@selector(setFlashTimer)
+ withObject:nil
+ waitUntilDone:NO];
+ } else {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error"
+ message:@"Failed to read screen probe."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ }
+ }];
+ }
+
+ if (_initiatedFlashes == kMaxFlashes) {
+ // Queue an operation (after the read trigger above) to turn off brightness notifications.
+ [_readOperations addOperationWithBlock:^{
+ [_client sendCommand:WALTScreenOffCommand error:nil];
+ [_client readResponseWithTimeout:kWALTReadTimeout];
+ [self performSelectorOnMainThread:@selector(computeStatistics:)
+ withObject:nil
+ waitUntilDone:NO];
+ }];
+ }
+}
+@end
diff --git a/ios/WALT/SettingsController.h b/ios/WALT/SettingsController.h
new file mode 100644
index 0000000..85f3e79
--- /dev/null
+++ b/ios/WALT/SettingsController.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface SettingsController : UITableViewController
+- (IBAction)ping:(id)sender;
+- (IBAction)checkDrift:(id)sender;
+@end
diff --git a/ios/WALT/SettingsController.m b/ios/WALT/SettingsController.m
new file mode 100644
index 0000000..2ed5afc
--- /dev/null
+++ b/ios/WALT/SettingsController.m
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "SettingsController.h"
+
+#import "UIAlertView+Extensions.h"
+#import "WALTAppDelegate.h"
+#import "WALTClient.h"
+
+@implementation SettingsController {
+ WALTClient *_client;
+ NSString *_status;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ _status = [NSString string];
+ [self.tableView reloadData];
+}
+
+- (IBAction)ping:(id)sender {
+ NSTimeInterval start = _client.currentTime;
+ NSError *error = nil;
+ if (![_client sendCommand:WALTPingCommand error:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ } else {
+ NSData *response = [_client readResponseWithTimeout:kWALTReadTimeout];
+ if (!response) {
+ _status = @"Timed out waiting for ping response.";
+ } else {
+ NSTimeInterval delta = _client.currentTime - start;
+ _status = [NSString stringWithFormat:@"Ping response in %.2f ms.", delta * 1000];
+ }
+ }
+ [self.tableView reloadData];
+}
+
+- (IBAction)checkDrift:(id)sender {
+ NSError *error = nil;
+ if (![_client updateSyncBoundsWithError:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ } else {
+ _status = [NSString stringWithFormat:@"Remote clock delayed between %lld and %lld µs.",
+ _client.minError, _client.maxError];
+ }
+ [self.tableView reloadData];
+}
+
+- (NSIndexPath *)tableView:(UITableView *)tableView
+ willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+ if (indexPath.section == 0 && indexPath.row == 0) {
+ // "Ping"
+ [self ping:tableView];
+ return nil;
+ } else if (indexPath.section == 0 && indexPath.row == 1) {
+ // "Check Drift"
+ [self checkDrift:tableView];
+ return nil;
+ }
+ return indexPath;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+ return (section == 0 ? _status : nil);
+}
+@end
diff --git a/ios/WALT/TapLatencyController.h b/ios/WALT/TapLatencyController.h
new file mode 100644
index 0000000..dea25a5
--- /dev/null
+++ b/ios/WALT/TapLatencyController.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface TapLatencyController : UIViewController
+@property(nonatomic, assign) IBOutlet UILabel* countLabel;
+@property(nonatomic, assign) IBOutlet UITextView* logView;
+
+- (IBAction)reset:(id)sender;
+- (IBAction)computeStatistics:(id)sender;
+@end
diff --git a/ios/WALT/TapLatencyController.m b/ios/WALT/TapLatencyController.m
new file mode 100644
index 0000000..f4590b3
--- /dev/null
+++ b/ios/WALT/TapLatencyController.m
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "TapLatencyController.h"
+
+#import "NSArray+Extensions.h"
+#import "UIAlertView+Extensions.h"
+#import "WALTAppDelegate.h"
+#import "WALTClient.h"
+#import "WALTLogger.h"
+#import "WALTTouch.h"
+
+@interface TapLatencyController ()
+- (void)updateCountDisplay;
+- (void)processEvent:(UIEvent *)event;
+- (void)appendToLogView:(NSString *)string;
+- (void)computeStatisticsForPhase:(UITouchPhase)phase;
+@end
+
+@implementation TapLatencyController {
+ WALTClient *_client;
+ WALTLogger *_logger;
+
+ // Statistics
+ unsigned int _downCount;
+ unsigned int _downCountRecorded;
+ unsigned int _upCount;
+ unsigned int _upCountRecorded;
+
+ NSMutableArray<WALTTouch *> *_touches;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.logView.selectable = YES;
+ self.logView.text = [NSString string];
+ self.logView.selectable = NO;
+
+ _logger = [WALTLogger sessionLogger];
+ _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ [_logger appendString:@"TAPLATENCY\n"];
+ [self reset:nil];
+}
+
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+ [self processEvent:event];
+ [self updateCountDisplay];
+}
+
+- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+ [self processEvent:event];
+ [self updateCountDisplay];
+}
+
+- (void)updateCountDisplay {
+ NSString *counts = [NSString stringWithFormat:@"N ↓%u (%u) ↑%u (%u)",
+ _downCountRecorded, _downCount, _upCountRecorded, _upCount];
+ self.countLabel.text = counts;
+}
+
+- (void)processEvent:(UIEvent *)event {
+ // TODO(pquinn): Pick first/last coalesced touch?
+
+ NSTimeInterval kernelTime = event.timestamp;
+ NSTimeInterval callbackTime = _client.currentTime;
+
+ NSError *error = nil;
+ NSTimeInterval physicalTime = [_client lastShockTimeWithError:&error];
+ if (physicalTime == -1) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ return;
+ }
+
+ WALTTouch *touch = [[WALTTouch alloc] initWithEvent:event];
+ touch.callbackTime = callbackTime;
+ touch.physicalTime = physicalTime;
+
+ NSString *actionString = nil;
+ if (touch.phase == UITouchPhaseBegan) {
+ _downCount += 1;
+ actionString = @"ACTION_DOWN";
+ } else {
+ _upCount += 1;
+ actionString = @"ACTION_UP";
+ }
+
+ if (physicalTime == 0) {
+ [_logger appendFormat:@"%@\tX\tno shock\n", actionString];
+ [self appendToLogView:[NSString stringWithFormat:@"%@: No shock detected\n", actionString]];
+ return;
+ }
+
+ NSTimeInterval physicalToKernel = kernelTime - physicalTime;
+ NSTimeInterval kernelToCallback = callbackTime - kernelTime;
+
+ if (physicalToKernel < 0 || physicalToKernel > 0.2) {
+ [_logger appendFormat:@"%@\tX\tbogus kernelTime\t%f\n", actionString, physicalToKernel];
+ [self appendToLogView:
+ [NSString stringWithFormat:@"%@: Bogus P → K: %.3f s\n", actionString, physicalToKernel]];
+ return;
+ }
+
+ [_logger appendFormat:@"%@\tO\t%f\t%f\t%f\n",
+ actionString, _client.baseTime, physicalToKernel, kernelToCallback];
+
+ [self appendToLogView:
+ [NSString stringWithFormat:@"%@: P → K: %.3f s; K → C: %.3f s\n",
+ actionString, physicalToKernel, kernelToCallback]];
+
+ [_touches addObject:touch];
+ if (touch.phase == UITouchPhaseBegan) {
+ _downCountRecorded += 1;
+ } else {
+ _upCountRecorded += 1;
+ }
+}
+
+- (IBAction)reset:(id)sender {
+ _downCount = 0;
+ _downCountRecorded = 0;
+ _upCount = 0;
+ _upCountRecorded = 0;
+ [self updateCountDisplay];
+
+ _touches = [[NSMutableArray<WALTTouch *> alloc] init];
+
+ NSError *error = nil;
+ if (![_client syncClocksWithError:&error]) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ }
+
+ [_logger appendString:@"RESET\n"];
+ [self appendToLogView:@"===========================================\n"];
+}
+
+- (IBAction)computeStatistics:(id)sender {
+ [self appendToLogView:@"-------------------------------------------\n"];
+ [self appendToLogView:@"Medians:\n"];
+ [self computeStatisticsForPhase:UITouchPhaseBegan];
+ [self computeStatisticsForPhase:UITouchPhaseEnded];
+
+ [self reset:sender];
+}
+
+- (void)computeStatisticsForPhase:(UITouchPhase)phase {
+ NSMutableArray<NSNumber *> *p2k = [[NSMutableArray<NSNumber *> alloc] init];
+ NSMutableArray<NSNumber *> *k2c = [[NSMutableArray<NSNumber *> alloc] init];
+
+ for (WALTTouch *touch in _touches) {
+ if (touch.phase != phase) {
+ continue;
+ }
+
+ [p2k addObject:[NSNumber numberWithDouble:touch.kernelTime - touch.physicalTime]];
+ [k2c addObject:[NSNumber numberWithDouble:touch.callbackTime - touch.kernelTime]];
+ }
+
+ NSNumber *p2kMedian = [p2k medianValue];
+ NSNumber *k2cMedian = [k2c medianValue];
+
+ NSString *actionString = (phase == UITouchPhaseBegan ? @"ACTION_DOWN" : @"ACTION_UP");
+ [self appendToLogView:
+ [NSString stringWithFormat:@"%@: P → K: %.3f s; K → C: %.3f s\n",
+ actionString, p2kMedian.doubleValue, k2cMedian.doubleValue]];
+}
+
+- (void)appendToLogView:(NSString*)string {
+ self.logView.selectable = YES;
+ self.logView.text = [self.logView.text stringByAppendingString:string];
+ [self.logView scrollRangeToVisible:NSMakeRange(self.logView.text.length - 2, 1)];
+ self.logView.selectable = NO;
+}
+@end
diff --git a/ios/WALT/UIAlertView+Extensions.h b/ios/WALT/UIAlertView+Extensions.h
new file mode 100644
index 0000000..03b7a7b
--- /dev/null
+++ b/ios/WALT/UIAlertView+Extensions.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface UIAlertView (WALTExtensions)
+- (instancetype)initWithTitle:(NSString *)title error:(NSError *)error;
+@end
diff --git a/ios/WALT/UIAlertView+Extensions.m b/ios/WALT/UIAlertView+Extensions.m
new file mode 100644
index 0000000..50ad605
--- /dev/null
+++ b/ios/WALT/UIAlertView+Extensions.m
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "UIAlertView+Extensions.h"
+
+@implementation UIAlertView (WALTExtensions)
+- (instancetype)initWithTitle:(NSString *)title error:(NSError *)error {
+ return [self initWithTitle:title
+ message:error.localizedDescription
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+}
+@end
diff --git a/ios/WALT/WALTAppDelegate.h b/ios/WALT/WALTAppDelegate.h
new file mode 100644
index 0000000..b34df0d
--- /dev/null
+++ b/ios/WALT/WALTAppDelegate.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@class WALTClient;
+
+@interface WALTAppDelegate : UIResponder <UIApplicationDelegate>
+@property (nonatomic) UIWindow *window;
+@property (readonly, nonatomic) WALTClient *client;
+@end
diff --git a/ios/WALT/WALTAppDelegate.m b/ios/WALT/WALTAppDelegate.m
new file mode 100644
index 0000000..8756723
--- /dev/null
+++ b/ios/WALT/WALTAppDelegate.m
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "WALTAppDelegate.h"
+
+#include <sys/utsname.h>
+
+#import "UIAlertView+Extensions.h"
+#import "WALTClient.h"
+#import "WALTLogger.h"
+
+@interface WALTAppDelegate ()
+@property (readwrite, nonatomic) WALTClient *client;
+@end
+
+@implementation WALTAppDelegate
+- (BOOL)application:(UIApplication *)application
+ willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ struct utsname systemInfo;
+ if (uname(&systemInfo) != 0) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"System Error"
+ message:@"Cannot identify system."
+ delegate:nil
+ cancelButtonTitle:@"Dismiss"
+ otherButtonTitles:nil];
+ [alert show];
+ } else {
+ [[WALTLogger sessionLogger] appendFormat:@"DEVICE\t%s\t%s\t%s\t%s\t%s\n",
+ systemInfo.machine,
+ systemInfo.sysname,
+ systemInfo.release,
+ systemInfo.nodename,
+ systemInfo.version];
+ }
+
+ NSError *error = nil;
+ self.client = [[WALTClient alloc] initWithError:&error];
+ if (!self.client) {
+ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error];
+ [alert show];
+ }
+
+ return YES;
+}
+@end
diff --git a/ios/WALT/WALTClient.h b/ios/WALT/WALTClient.h
new file mode 100644
index 0000000..df4c1e0
--- /dev/null
+++ b/ios/WALT/WALTClient.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "MIDIClient.h"
+#import "MIDIMessage.h"
+
+extern NSString * const WALTClientErrorDomain;
+
+/** A reasonable timeout to use when reading from the WALT. */
+extern const NSTimeInterval kWALTReadTimeout;
+
+typedef NS_ENUM(MIDIByte, WALTCommand) {
+ WALTDelayedPingCommand = 'D', // Ping with a delay
+ WALTResetCommand = 'F', // Reset all vars
+ WALTSendSyncCommand = 'I', // Send some digits for clock sync
+ WALTPingCommand = 'P', // Ping with a single byte
+ WALTVersionCommand = 'V', // Determine WALT's firmware version
+ WALTReadoutSyncCommand = 'R', // Read out sync times
+ WALTGShockCommand = 'G', // Send last shock time and watch for another
+ WALTTimeCommand = 'T', // Current time
+ WALTZeroSyncCommand = 'Z', // Initial zero
+ WALTScreenOnCommand = 'C', // Send a message on screen color change
+ WALTScreenOffCommand = 'c',
+ WALTSendLastScreenCommand = 'E', // Send info about last screen color change
+ WALTBrightnessCurveCommand = 'U', // Probe screen for brightness vs time curve
+ WALTLaserOnCommand = 'L', // Send messages on state change of the laser
+ WALTLaserOffCommand = 'l',
+ WALTSendLastLaserCommand = 'J',
+ WALTAudioCommand = 'A', // Start watching for signal on audio out line
+ WALTBeepCommand = 'B', // Generate a tone into the mic and send timestamp
+ WALTBeepStopCommand = 'S', // Stop generating tone
+ WALTMIDICommand = 'M', // Start listening for a MIDI message
+ WALTNoteCommand = 'N', // Generate a MIDI NoteOn message
+};
+
+typedef struct {
+ char tag;
+ NSTimeInterval t;
+ int value;
+ unsigned int count;
+} WALTTrigger;
+
+/**
+ * A client for a WALT device.
+ *
+ * The client will automatically try to connect to any available WALT device, and monitor the system
+ * for device connections/disconnections. Users should observe the "connected" key to be notified of
+ * connection changes.
+ *
+ * Most commands produce a corresponding response from the WALT. The -sendCommand:error: method
+ * should be used to send the command, and -readResponse used to collect the response.
+ */
+@interface WALTClient : NSObject <MIDIClientDelegate>
+@property (readonly, nonatomic, getter=isConnected) BOOL connected;
+
+/**
+ * Returns the base time of the WALT device.
+ *
+ * The time value is an adjusted version of -currentTime.
+ */
+@property (readonly, nonatomic) NSTimeInterval baseTime;
+
+/** Returns the number of seconds the system has been awake since it was last restarted. */
+@property (readonly, nonatomic) NSTimeInterval currentTime;
+
+/** Initialises the client and attempts to connect to any available WALT device. */
+- (instancetype)initWithError:(NSError **)error;
+
+/** Sends a command to the WALT. */
+- (BOOL)sendCommand:(WALTCommand)command error:(NSError **)error;
+
+/** Reads a response from the WALT, blocking up to timeout until one becomes available. */
+- (NSData *)readResponseWithTimeout:(NSTimeInterval)timeout;
+
+/**
+ * Reads a trigger response from the WALT.
+ *
+ * If an error occurs, the trigger's tag will be '\0'.
+ */
+- (WALTTrigger)readTriggerWithTimeout:(NSTimeInterval)timeout;
+
+/** Returns YES if the response data contains a valid acknowledgement for a command. */
+- (BOOL)checkResponse:(NSData *)response forCommand:(WALTCommand)command;
+
+/** Forces a complete clock synchronisation with the WALT. */
+- (BOOL)syncClocksWithError:(NSError **)error;
+
+/** Refreshes the min/max error synchronisation bounds. */
+- (BOOL)updateSyncBoundsWithError:(NSError **)error;
+
+@property (readonly, nonatomic) int64_t minError;
+@property (readonly, nonatomic) int64_t maxError;
+
+/**
+ * Confirms the connection with the WALT (by setting -isConnected).
+ *
+ * Note that this method will only return NO if there is an error in the connection process. The
+ * absence of a device is not such an error.
+ */
+- (BOOL)checkConnectionWithError:(NSError **)error;
+
+/** Returns the time of the last shock detected by the WALT. */
+- (NSTimeInterval)lastShockTimeWithError:(NSError **)error;
+@end
diff --git a/ios/WALT/WALTClient.m b/ios/WALT/WALTClient.m
new file mode 100644
index 0000000..ed69349
--- /dev/null
+++ b/ios/WALT/WALTClient.m
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "WALTClient.h"
+
+#include <ctype.h>
+#include <dispatch/dispatch.h>
+#include <mach/clock.h>
+#include <mach/mach.h>
+#include <mach/mach_host.h>
+#include <stdlib.h>
+#include <time.h>
+
+#import "MIDIEndpoint.h"
+#import "MIDIMessage.h"
+
+NSString * const WALTClientErrorDomain = @"WALTClientErrorDomain";
+
+static NSString * const kWALTVersion = @"v 4";
+
+static const MIDIChannel kWALTChannel = 1;
+static const MIDIByte kWALTSerialOverMIDIProgram = 1;
+static const MIDIMessageType kWALTCommandType = MIDIMessageChannelPressure;
+
+const NSTimeInterval kWALTReadTimeout = 0.2;
+static const NSTimeInterval kWALTDuplicateTimeout = 0.01;
+
+static const int kWALTSyncIterations = 7;
+#define kWALTSyncDigitMax 9 // #define to avoid variable length array warnings.
+
+/** Similar to atoll(), but only reads a maximum of n characters from s. */
+static unsigned long long antoull(const char *s, size_t n) {
+ unsigned long long result = 0;
+ while (s && n-- && *s && isdigit(*s)) {
+ result = result * 10 + (*s - '0');
+ ++s;
+ }
+ return result;
+}
+
+/** Converts a mach_timespec_t to its equivalent number of microseconds. */
+static int64_t TimespecToMicroseconds(const mach_timespec_t ts) {
+ return ((int64_t)ts.tv_sec) * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC;
+}
+
+/** Returns the current time (in microseconds) on a clock. */
+static int64_t CurrentTime(clock_serv_t clock) {
+ mach_timespec_t time = {0};
+ clock_get_time(clock, &time);
+ return TimespecToMicroseconds(time);
+}
+
+/** Sleeps the current thread for us microseconds. */
+static void Sleep(int64_t us) {
+ const struct timespec ts = {
+ .tv_sec = (long)(us / USEC_PER_SEC),
+ .tv_nsec = (us % USEC_PER_SEC) * NSEC_PER_USEC,
+ };
+ nanosleep(&ts, NULL);
+}
+
+@interface WALTClient ()
+@property (readwrite, nonatomic, getter=isConnected) BOOL connected;
+
+- (void)drainResponseQueue;
+- (BOOL)improveSyncBoundsWithError:(NSError **)error;
+- (BOOL)improveMinBoundWithError:(NSError **)error;
+- (BOOL)improveMaxBoundWithError:(NSError **)error;
+- (BOOL)readRemoteTimestamps:(uint64_t[kWALTSyncDigitMax])times error:(NSError **)error;
+- (WALTTrigger)readTrigger:(NSData *)response;
+@end
+
+@implementation WALTClient {
+ MIDIClient *_client;
+
+ // Responses from the MIDIClient are queued up here with a signal to the semaphore.
+ NSMutableArray<NSData *> *_responseQueue; // TODO(pquinn): Lock-free circular buffer?
+ dispatch_semaphore_t _responseSemaphore;
+
+ BOOL _syncCompleted;
+
+ clock_serv_t _clock;
+
+ NSData *_lastData;
+ NSTimeInterval _lastDataTimestamp;
+
+ struct {
+ // All microseconds.
+ int64_t base;
+ int64_t minError;
+ int64_t maxError;
+ } _sync;
+}
+
+- (instancetype)initWithError:(NSError **)error {
+ if ((self = [super init])) {
+ _responseQueue = [[NSMutableArray<NSData *> alloc] init];
+ _responseSemaphore = dispatch_semaphore_create(0);
+
+ // NB: It's important that this is the same clock used as the base for UIEvent's -timestamp.
+ kern_return_t result = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &_clock);
+
+ if (result != KERN_SUCCESS || ![self checkConnectionWithError:error]) {
+ self = nil;
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self drainResponseQueue];
+ mach_port_deallocate(mach_task_self(), _clock);
+}
+
+// Ensure only one KVO notification is sent when the connection state is changed.
++ (BOOL)automaticallyNotifiesObserversOfConnected {
+ return NO;
+}
+
+- (void)setConnected:(BOOL)connected {
+ if (_connected != connected) {
+ [self willChangeValueForKey:@"connected"];
+ _connected = connected;
+ [self didChangeValueForKey:@"connected"];
+ }
+}
+
+- (BOOL)checkConnectionWithError:(NSError **)error {
+ if (_client.source.isOnline && _client.destination.isOnline && _syncCompleted) {
+ self.connected = YES;
+ return YES; // Everything's fine.
+ }
+
+ _syncCompleted = NO; // Reset the sync state.
+ [self drainResponseQueue];
+
+ // Create a new client.
+ // This probably isn't strictly necessary, but solves some of the flakiness on iOS.
+ _client.delegate = nil;
+ _client = [[MIDIClient alloc] initWithName:@"WALT" error:error];
+ _client.delegate = self;
+ if (!_client) {
+ self.connected = NO;
+ return NO;
+ }
+
+ if (!_client.source.isOnline) {
+ // Try to connect to the first available input source.
+ // TODO(pquinn): Make this user-configurable.
+ NSArray<MIDISource *> *sources = [MIDISource allSources];
+ if (sources.count) {
+ if (![_client connectToSource:sources.firstObject error:error]) {
+ self.connected = NO;
+ return NO;
+ }
+ }
+ }
+
+ if (!_client.destination.isOnline) {
+ // Try to connect to the first available input source.
+ // TODO(pquinn): Make this user-configurable.
+ NSArray<MIDIDestination *> *destinations = [MIDIDestination allDestinations];
+ if (destinations.count) {
+ if (![_client connectToDestination:destinations.firstObject error:error]) {
+ self.connected = NO;
+ return NO;
+ }
+ }
+
+ if (_client.destination.isOnline) {
+ // Switch to Serial-over-MIDI mode.
+ NSData *message = MIDIMessageCreateSimple1(MIDIMessageProgramChange,
+ kWALTChannel,
+ kWALTSerialOverMIDIProgram);
+ if (![_client sendData:message error:error]) {
+ self.connected = NO;
+ return NO;
+ }
+
+ // Make sure it's using a known protocol version.
+ message = MIDIMessageCreateSimple1(kWALTCommandType, kWALTChannel, WALTVersionCommand);
+ if (![_client sendData:message error:error]) {
+ self.connected = NO;
+ return NO;
+ }
+
+ NSData *response = [self readResponseWithTimeout:kWALTReadTimeout];
+ NSString *versionString = [[NSString alloc] initWithData:response
+ encoding:NSASCIIStringEncoding];
+ if (![versionString isEqualToString:kWALTVersion]) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [@"Unknown WALT version: "
+ stringByAppendingString:versionString]}];
+ }
+ self.connected = NO;
+ return NO;
+ }
+
+ if (![self syncClocksWithError:error]) {
+ self.connected = NO;
+ return NO;
+ }
+
+ _syncCompleted = YES;
+ }
+ }
+
+ self.connected = (_client.source.isOnline && _client.destination.isOnline && _syncCompleted);
+ return YES;
+}
+
+#pragma mark - Clock Synchronisation
+
+- (BOOL)syncClocksWithError:(NSError **)error {
+ _sync.base = CurrentTime(_clock);
+
+ if (![self sendCommand:WALTZeroSyncCommand error:error]) {
+ return NO;
+ }
+
+ NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
+ if (![self checkResponse:data forCommand:WALTZeroSyncCommand]) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Bad acknowledgement for WALTZeroSyncCommand: %@", data]}];
+ }
+ return NO;
+ }
+
+ _sync.maxError = CurrentTime(_clock) - _sync.base;
+ _sync.minError = 0;
+
+ for (int i = 0; i < kWALTSyncIterations; ++i) {
+ if (![self improveSyncBoundsWithError:error]) {
+ return NO;
+ }
+ }
+
+ // Shift the time base so minError == 0
+ _sync.base += _sync.minError;
+ _sync.maxError -= _sync.minError;
+ _sync.minError = 0;
+ return YES;
+}
+
+- (BOOL)updateSyncBoundsWithError:(NSError **)error {
+ // Reset the bounds to unrealistic values
+ _sync.minError = -1e7;
+ _sync.maxError = 1e7;
+
+ for (int i = 0; i < kWALTSyncIterations; ++i) {
+ if (![self improveSyncBoundsWithError:error]) {
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+- (int64_t)minError {
+ return _sync.minError;
+}
+
+- (int64_t)maxError {
+ return _sync.maxError;
+}
+
+- (BOOL)improveSyncBoundsWithError:(NSError **)error {
+ return ([self improveMinBoundWithError:error] && [self improveMaxBoundWithError:error]);
+}
+
+- (BOOL)improveMinBoundWithError:(NSError **)error {
+ if (![self sendCommand:WALTResetCommand error:error]) {
+ return NO;
+ }
+
+ NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
+ if (![self checkResponse:data forCommand:WALTResetCommand]) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Bad acknowledgement for WALTResetCommand: %@", data]}];
+ }
+ return NO;
+ }
+
+ const uint64_t kMaxSleepTime = 700; // µs
+ const uint64_t kMinSleepTime = 70; // µs
+ const uint64_t kSleepTimeDivider = 10;
+
+ uint64_t sleepTime = (_sync.maxError - _sync.minError) / kSleepTimeDivider;
+ if (sleepTime > kMaxSleepTime) { sleepTime = kMaxSleepTime; }
+ if (sleepTime < kMinSleepTime) { sleepTime = kMinSleepTime; }
+
+ struct {
+ uint64_t local[kWALTSyncDigitMax];
+ uint64_t remote[kWALTSyncDigitMax];
+ } digitTimes = {0};
+
+ // Send the digits 1 through 9 and record the times they were sent in digitTimes.local.
+ for (int i = 0; i < kWALTSyncDigitMax; ++i) {
+ digitTimes.local[i] = CurrentTime(_clock) - _sync.base;
+
+ char c = '1' + i;
+ if (![self sendCommand:c error:error]) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Error sending digit %d", i + 1],
+ NSUnderlyingErrorKey: *error}];
+ }
+ return NO;
+ }
+ // Sleep between digits
+ Sleep(sleepTime);
+ }
+
+ if (![self readRemoteTimestamps:digitTimes.remote error:error]) {
+ return NO;
+ }
+
+ // Adjust minError to be the largest delta between local and remote.
+ for (int i = 0; i < kWALTSyncDigitMax; ++i) {
+ int64_t delta = digitTimes.local[i] - digitTimes.remote[i];
+ if (digitTimes.local[i] != 0 && digitTimes.remote[i] != 0 && delta > _sync.minError) {
+ _sync.minError = delta;
+ }
+ }
+ return YES;
+}
+
+- (BOOL)improveMaxBoundWithError:(NSError **)error {
+ struct {
+ uint64_t local[kWALTSyncDigitMax];
+ uint64_t remote[kWALTSyncDigitMax];
+ } digitTimes = {0};
+
+ // Ask the WALT to send the digits 1 through 9, and record the times they are received in
+ // digitTimes.local.
+ if (![self sendCommand:WALTSendSyncCommand error:error]) {
+ return NO;
+ }
+
+ for (int i = 0; i < kWALTSyncDigitMax; ++i) {
+ NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
+ if (data.length != 1) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Error receiving digit %d: %@", i + 1, data]}];
+ }
+ return NO;
+ }
+
+ char c = ((const char *)data.bytes)[0];
+ if (!isdigit(c)) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Error parsing digit response: %c", c]}];
+ }
+ return NO;
+ }
+
+ int digit = c - '0';
+ digitTimes.local[digit - 1] = CurrentTime(_clock) - _sync.base;
+ }
+
+ if (![self readRemoteTimestamps:digitTimes.remote error:error]) {
+ return NO;
+ }
+
+ // Adjust maxError to be the smallest delta between local and remote
+ for (int i = 0; i < kWALTSyncDigitMax; ++i) {
+ int64_t delta = digitTimes.local[i] - digitTimes.remote[i];
+ if (digitTimes.local[i] != 0 && digitTimes.remote[i] != 0 && delta < _sync.maxError) {
+ _sync.maxError = delta;
+ }
+ }
+ return YES;
+}
+
+- (BOOL)readRemoteTimestamps:(uint64_t [9])times error:(NSError **)error {
+ for (int i = 0; i < kWALTSyncDigitMax; ++i) {
+ // Ask the WALT for each digit's recorded timestamp
+ if (![self sendCommand:WALTReadoutSyncCommand error:error]) {
+ return NO;
+ }
+
+ NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
+ if (data.length < 3) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Error receiving sync digit %d: %@", i + 1, data]}];
+ }
+ return NO;
+ }
+
+ // The reply data is formatted as n:xxxx, where n is a digit between 1 and 9, and xxxx
+ // is a microsecond timestamp.
+ int digit = (int)antoull(data.bytes, 1);
+ uint64_t timestamp = antoull(((const char *)data.bytes) + 2, data.length - 2);
+
+ if (digit != (i + 1) || timestamp == 0) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ [NSString stringWithFormat:@"Error parsing remote time response for %d: %@", i, data]}];
+ }
+ return NO;
+ }
+ times[digit - 1] = timestamp;
+ }
+ return YES;
+}
+
+#pragma mark - MIDIClient Delegate
+
+// TODO(pquinn): Errors from these callbacks aren't propoagated anywhere.
+
+- (void)MIDIClientEndpointAdded:(MIDIClient *)client {
+ [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
+ withObject:nil
+ waitUntilDone:NO];
+}
+
+- (void)MIDIClientEndpointRemoved:(MIDIClient *)client {
+ [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
+ withObject:nil
+ waitUntilDone:NO];
+}
+
+- (void)MIDIClientConfigurationChanged:(MIDIClient *)client {
+ [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
+ withObject:nil
+ waitUntilDone:NO];
+}
+
+- (void)MIDIClient:(MIDIClient *)client receivedError:(NSError *)error {
+ // TODO(pquinn): What's the scope of these errors?
+ NSLog(@"WALTClient received unhandled error: %@", error);
+}
+
+- (void)MIDIClient:(MIDIClient *)client receivedData:(NSData *)message {
+ NSData *body = MIDIMessageBody(message);
+ @synchronized (_responseQueue) {
+ // Sometimes a message will be received twice in quick succession. It's not clear where the bug
+ // is (the WALT, CoreMIDI, or this application), and it cannot be reliably reproduced. As a
+ // hack, simply ignore messages that appear to be duplicates and arrive within
+ // kWALTDuplicateTimeout seconds.
+ if (self.currentTime - _lastDataTimestamp <= kWALTDuplicateTimeout &&
+ [body isEqualToData:_lastData]) {
+ NSLog(@"Ignoring duplicate response within kWALTDuplicateTimeout: %@", message);
+ return;
+ }
+
+ [_responseQueue addObject:body];
+ _lastData = body;
+ _lastDataTimestamp = self.currentTime;
+ }
+ dispatch_semaphore_signal(_responseSemaphore);
+}
+
+#pragma mark - Send/Receive
+
+- (void)drainResponseQueue {
+ @synchronized (_responseQueue) {
+ // Drain out any stale responses or the semaphore destructor will assert.
+ while (_responseQueue.count) {
+ dispatch_semaphore_wait(_responseSemaphore, DISPATCH_TIME_FOREVER);
+ [_responseQueue removeObjectAtIndex:0];
+ }
+ }
+}
+
+- (NSData *)readResponseWithTimeout:(NSTimeInterval)timeout {
+ if (dispatch_semaphore_wait(_responseSemaphore,
+ dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
+ return nil;
+ }
+
+ @synchronized (_responseQueue) {
+ NSAssert(_responseQueue.count > 0, @"_responseQueue is empty!");
+ NSData *response = _responseQueue.firstObject;
+ [_responseQueue removeObjectAtIndex:0];
+ return response;
+ }
+}
+
+- (BOOL)sendCommand:(WALTCommand)command error:(NSError **)error {
+ NSData *message = MIDIMessageCreateSimple1(kWALTCommandType, kWALTChannel, command);
+ [self drainResponseQueue];
+ return [_client sendData:message error:error];
+}
+
+- (BOOL)checkResponse:(NSData *)response forCommand:(WALTCommand)command {
+ const WALTCommand flipped = isupper(command) ? tolower(command) : toupper(command);
+ if (response.length < 1 || ((const char *)response.bytes)[0] != flipped) {
+ return NO;
+ } else {
+ return YES;
+ }
+}
+
+#pragma mark - Specific Commands
+
+- (NSTimeInterval)lastShockTimeWithError:(NSError **)error {
+ if (![self sendCommand:WALTGShockCommand error:error]) {
+ return -1;
+ }
+
+ NSData *response = [self readResponseWithTimeout:kWALTReadTimeout];
+ if (!response) {
+ if (error) {
+ *error = [NSError errorWithDomain:WALTClientErrorDomain
+ code:0
+ userInfo:@{NSLocalizedDescriptionKey:
+ @"Error receiving shock response."}];
+ }
+ return -1;
+ }
+
+ uint64_t microseconds = antoull(response.bytes, response.length);
+ return ((NSTimeInterval)microseconds + _sync.base) / USEC_PER_SEC;
+}
+
+- (WALTTrigger)readTrigger:(NSData *)response {
+ NSString *responseString =
+ [[NSString alloc] initWithData:response encoding:NSASCIIStringEncoding];
+ NSArray<NSString *> *components = [responseString componentsSeparatedByString:@" "];
+
+ WALTTrigger result = {0};
+
+ if (components.count != 5 ||
+ ![[components objectAtIndex:0] isEqualToString:@"G"] ||
+ [components objectAtIndex:1].length != 1) {
+ return result;
+ }
+
+ result.tag = [[components objectAtIndex:1] characterAtIndex:0];
+
+ uint64_t microseconds = atoll([components objectAtIndex:2].UTF8String);
+ result.t = ((NSTimeInterval)microseconds + _sync.base) / USEC_PER_SEC;
+ result.value = (int)atoll([components objectAtIndex:3].UTF8String);
+ result.count = (unsigned int)atoll([components objectAtIndex:4].UTF8String);
+ return result;
+}
+
+- (WALTTrigger)readTriggerWithTimeout:(NSTimeInterval)timeout {
+ return [self readTrigger:[self readResponseWithTimeout:timeout]];
+}
+
+#pragma mark - Time
+
+- (NSTimeInterval)baseTime {
+ return ((NSTimeInterval)_sync.base) / USEC_PER_SEC;
+}
+
+- (NSTimeInterval)currentTime {
+ return ((NSTimeInterval)CurrentTime(_clock)) / USEC_PER_SEC;
+}
+@end
diff --git a/ios/WALT/WALTLogger.h b/ios/WALT/WALTLogger.h
new file mode 100644
index 0000000..436aed7
--- /dev/null
+++ b/ios/WALT/WALTLogger.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+/** A basic buffer for log text. */
+@interface WALTLogger : NSObject
++ (instancetype)sessionLogger;
+
+- (void)appendString:(NSString *)string;
+- (void)appendFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
+- (void)clear;
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error;
+- (NSString *)stringValue;
+@end
diff --git a/ios/WALT/WALTLogger.m b/ios/WALT/WALTLogger.m
new file mode 100644
index 0000000..28ecb30
--- /dev/null
+++ b/ios/WALT/WALTLogger.m
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "WALTLogger.h"
+
+@implementation WALTLogger {
+ NSMutableString *_buffer;
+}
+
++ (instancetype)sessionLogger {
+ static WALTLogger *sessionLogger = nil;
+ static dispatch_once_t token;
+ dispatch_once(&token, ^{
+ sessionLogger = [[self alloc] init];
+ });
+ return sessionLogger;
+}
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _buffer = [[NSMutableString alloc] init];
+ }
+ return self;
+}
+
+- (void)appendString:(NSString *)string {
+ @synchronized (_buffer) {
+ [_buffer appendString:string];
+ }
+}
+
+- (void)appendFormat:(NSString *)format, ... {
+ va_list args;
+ va_start(args, format);
+ NSString *formatted = [[NSString alloc] initWithFormat:format arguments:args];
+ [_buffer appendString:formatted];
+ va_end(args);
+}
+
+- (void)clear {
+ @synchronized (_buffer) {
+ [_buffer setString:[NSString string]];
+ }
+}
+
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error {
+ @synchronized (_buffer) {
+ return [_buffer writeToURL:url
+ atomically:YES
+ encoding:NSUTF8StringEncoding
+ error:error];
+ }
+}
+
+- (NSString *)stringValue {
+ @synchronized (_buffer) {
+ return [NSString stringWithString:_buffer];
+ }
+}
+@end
diff --git a/ios/WALT/WALTTouch.h b/ios/WALT/WALTTouch.h
new file mode 100644
index 0000000..b780096
--- /dev/null
+++ b/ios/WALT/WALTTouch.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@interface WALTTouch : NSObject
+@property (nonatomic, assign) UITouchPhase phase;
+@property (nonatomic, assign) NSTimeInterval physicalTime;
+@property (nonatomic, assign) NSTimeInterval kernelTime;
+@property (nonatomic, assign) NSTimeInterval callbackTime;
+@property (nonatomic, assign) CGPoint location;
+
+- (instancetype)initWithEvent:(UIEvent *)event;
+@end
diff --git a/ios/WALT/WALTTouch.m b/ios/WALT/WALTTouch.m
new file mode 100644
index 0000000..1f69686
--- /dev/null
+++ b/ios/WALT/WALTTouch.m
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "WALTTouch.h"
+
+@implementation WALTTouch
+- (instancetype)initWithEvent:(UIEvent *)event {
+ if ((self = [super init])) {
+ if ([event allTouches].count > 1) {
+ NSLog(@"Multiple touches in event; taking any.");
+ }
+ self.kernelTime = event.timestamp;
+
+ UITouch *touch = [[event allTouches] anyObject];
+ self.phase = touch.phase;
+ if ([touch respondsToSelector:@selector(preciseLocationInView:)]) { // iOS 9.1+
+ self.location = [touch preciseLocationInView:nil];
+ } else {
+ self.location = [touch locationInView:nil];
+ }
+ }
+ return self;
+}
+@end
diff --git a/ios/WALT/main.m b/ios/WALT/main.m
new file mode 100644
index 0000000..90429a8
--- /dev/null
+++ b/ios/WALT/main.m
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+#import "WALTAppDelegate.h"
+
+int main(int argc, char * argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([WALTAppDelegate class]));
+ }
+}
diff --git a/pywalt/.gitignore b/pywalt/.gitignore
new file mode 100644
index 0000000..72364f9
--- /dev/null
+++ b/pywalt/.gitignore
@@ -0,0 +1,89 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# virtualenv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
diff --git a/pywalt/LICENSE b/pywalt/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/pywalt/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/pywalt/README.md b/pywalt/README.md
new file mode 100644
index 0000000..2d5031e
--- /dev/null
+++ b/pywalt/README.md
@@ -0,0 +1,87 @@
+# PyWALT
+Python scripts for [WALT Latency Timer](https://github.com/google/walt) on Linux and ChromeOS.
+
+ * Based on [ChromeOS scroll test implementation](https://chromium.googlesource.com/chromiumos/platform/touchbot/+/master/quickstep/)
+ * Currently supprots tap and drag (scroll) latency measurements
+ * For tests using evetest or drm (all touch and screen tests) pywalt needs to run as root
+ * In order to find the name/number of your touch device run `evtest`. It will list available input devices and once you enter a device number it will show incoming events when you touch that touchpad / touchscreen.
+
+
+Synopsis:
+```
+$ ./walt.py --help
+usage: walt.py [-h] [-i INPUT] [-s SERIAL] [-t TYPE] [-l LOGDIR] [-n N]
+ [-p PORT] [-d]
+
+Run the touchpad drag latency test using WALT Latency Timer
+
+optional arguments:
+ -h, --help show this help message and exit
+ -i INPUT, --input INPUT
+ input device, e.g: 6 or /dev/input/event6 (default: )
+ -s SERIAL, --serial SERIAL
+ WALT serial port (default: /dev/ttyACM0)
+ -t TYPE, --type TYPE Test type: drag|tap|screen|sanity|curve|bridge
+ (default: drag)
+ -l LOGDIR, --logdir LOGDIR
+ where to store logs (default: /tmp)
+ -n N Number of laser toggles to read (default: 40)
+ -p PORT, --port PORT Port to listen on for the TCP bridge (default: 50007)
+ -d, --debug talk more (default: False)
+ ```
+
+
+## Tap Latency ##
+See the [tap latency section](../docs/usage/WALT_usage.md#tap-latency) in Android app usage doc.
+
+Below is output from an example run of a tap latency test that reads touch events from `/dev/input/event4` (in this case a touchpad). After 40 events (20 down and 20 up) are detected, the script prints median delays and exits.
+
+The input device option is mandatory since several touch devices might be preset (e.g. touchpad and touch screen). You can use a shorthand notation `-i 4` which is expanded to `-i /dev/input/event4`.
+
+The following must be run as root.
+
+```
+$ ./walt.py -t tap -n 40 -i /dev/input/event4
+
+Starting tap latency test
+Clock zeroed at 1487105210 (rt 0.250 ms)
+Event: time 1487105212.048997, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
+
+shock t 1990338, tap t 1487105212.048997, tap val 1. dt=63738.9
+Event: time 1487105212.262449, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 0
+
+shock t 2219992, tap t 1487105212.262449, tap val 0. dt=47537.0
+Event: time 1487105212.702711, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
+
+...
+
+Processing data...
+dt_down = [63.74, 26.96, 27.14 ...
+dt_up = [47.54, 47.03, 41.52...
+
+Median latency, down: 23.9, up: 47.1
+```
+
+## Drag / Scroll Latency ##
+See the [drag latency section](../docs/usage/WALT_usage.md#dragscroll-latency) in Android app usage doc.
+
+Below is a drag latency measurement of the trackpad on Asus Flip. The trackpad input device is `/dev/input/event4` which was selected using the `-i 4` argument.
+
+The `-n 20` option tells the script to record 20 laser events. Any change in laser sensor reading counts as one event, therefore one crossing of the beam counts as two events (laser goes off and back on), and a full cycle of the finger going up and down counts as 4 events. This measurement recorded 20/4 = 5 full cycles of the finger moving up and down.
+
+In addition to moving your finger up and down please also move it slowly along the beam. The calculation used by pywalt needs some spread of the x coordinates for better precision.
+
+Drag latency uses evtest and must therefore be run as root.
+
+```
+#./walt.py -t drag -i 4 -n 20
+Starting drag latency test
+Input device : /dev/input/event4
+Serial device : /dev/ttyACM1
+Laser log file : /tmp/WALT_2017_03_07__1532_12_laser.log
+evtest log file: /tmp/WALT_2017_03_07__1532_12_evtest.log
+Clock zeroed at 1488918733 (rt 0.306 ms)
+....................
+Processing data, may take a minute or two...
+Drag latency (min method) = 21.07 ms
+```
diff --git a/pywalt/plothist.py b/pywalt/plothist.py
new file mode 100644
index 0000000..59c559b
--- /dev/null
+++ b/pywalt/plothist.py
@@ -0,0 +1,31 @@
+#
+# Utility script to plot a histogram of tap latency results
+# run in iPython pylab mode using the -i flag
+# ipython --pylab
+# %run -i plothist.py
+# show()
+
+# Copy / paste data from walt.py output here
+dt_down += [32.97, 21.91, 23.06, 21.72, 67.31, 22.71, 29.52, 39.83, 38.55, 24.35, 30.14, 30.52, 38.66, 21.43, 28.69, 29.57, 21.93, 22.64, 38.77]
+dt_up += [15.86, 17.36, 15.93, 21.32, 20.16, 18.95, 14.13, 6.05, 17.58, 12.21, 13.23, 16.17, 19.14, 11.35]
+
+
+dt_down = array(dt_down)
+dt_up = array(dt_up)
+
+
+# Filter out number that are definitely garbage
+
+dt_down = dt_down[dt_down < 150]
+dt_up = dt_up[dt_up < 150]
+
+hist([dt_down, dt_up])
+
+xlabel('tap latency [ms]')
+ylabel('Count')
+grid(True)
+legend([
+ 'tap down N=%d med=%0.1f' % (len(dt_down), median(dt_down)),
+ 'tap up N=%d med=%0.1f' % (len(dt_up), median(dt_up)),
+ ])
+title('Tap latency')
\ No newline at end of file
diff --git a/pywalt/pywalt/__init__.py b/pywalt/pywalt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pywalt/pywalt/__init__.py
diff --git a/pywalt/pywalt/evparser.py b/pywalt/pywalt/evparser.py
new file mode 100644
index 0000000..19b3387
--- /dev/null
+++ b/pywalt/pywalt/evparser.py
@@ -0,0 +1,33 @@
+import re
+from numpy import array
+
+TIME = 'time'
+VALUE = 'value'
+AXIS = 'axis'
+
+re_xy = re.compile(r'.*time (?P<time>\d+\.\d+), type \d+ \(EV_ABS\), code \d+ \(ABS_(?P<axis>[XY])\), value (?P<value>\d+)')
+re_tap = re.compile(r'.*time (?P<time>\d+\.\d+), type \d+ \(EV_KEY\), code \d+ \(BTN_TOUCH\), value (?P<value>\d+)')
+
+
+def load_xy(fname):
+ with open(fname, 'rt') as f:
+ match_iter = (re_xy.search(line) for line in f)
+ events = [m.groupdict() for m in match_iter if m]
+
+ x = array([int(e[VALUE]) for e in events if e[AXIS] == 'X'])
+ tx = array([float(e[TIME]) for e in events if e[AXIS] == 'X'])
+
+ y = array([int(e[VALUE]) for e in events if e[AXIS] == 'Y'])
+ ty = array([float(e[TIME]) for e in events if e[AXIS] == 'Y'])
+
+ return (tx, x, ty, y)
+
+
+def parse_tap_line(line):
+ m = re_tap.search(line)
+ if not m:
+ return None
+
+ t = float(m.group(TIME))
+ val = int(m.group(VALUE))
+ return (t, val)
diff --git a/pywalt/pywalt/minimization.py b/pywalt/pywalt/minimization.py
new file mode 100644
index 0000000..dc5c0be
--- /dev/null
+++ b/pywalt/pywalt/minimization.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Module for computing drag latency given logs of touchpad positions and
+QuickStep laser crossing timestamps
+"""
+
+import numpy
+import evparser
+
+debug_mode = False
+
+
+def load_laser_data(fname_laser):
+ laser_data = numpy.loadtxt(fname_laser)
+ t = laser_data[:, 0]
+ transition = laser_data[:, 1].astype(int)
+ if transition[0] != 0:
+ print('WARNING: First laser transition should be from light to dark')
+ return t, transition
+
+
+def calc_ssr(x, y):
+ """Return sum of squared residuals (SSR) of a linear least square fit"""
+ p = numpy.polyfit(x, y, 1, full=True)
+ r = p[1][0]
+ return r
+
+
+def minimize_lsq(tx, x, ty, y, tl, min_shift, max_shift, step):
+ """Find best time shift so that the shifted laser crossing events fit nicely
+ on a straight line. Upper and lower side are treated separately.
+
+ """
+
+ # generate an array of all shifts to try
+ shifts = numpy.arange(min_shift, max_shift, step)
+
+ # side = [0, 1, 1, 0, 0, 1, 1 ...
+ # this is an indicator of which side of the beam the crossing belongs to
+ side = ((numpy.arange(len(tl)) + 1) / 2) % 2
+
+ residuals0 = []
+ residuals1 = []
+ for shift in shifts:
+ # Find the locations of the finger at the shifted laser timestamps
+ yl = numpy.interp(tl + shift, ty, y)
+ xl = numpy.interp(tl + shift, tx, x)
+ # Fit a line to each side separately and save the SSR for this fit
+ residuals0.append(calc_ssr(xl[side == 0], yl[side == 0]))
+ residuals1.append(calc_ssr(xl[side == 1], yl[side == 1]))
+
+ # Find the shift with lower SSR for each side
+ best_shift0 = shifts[numpy.argmin(residuals0)]
+ best_shift1 = shifts[numpy.argmin(residuals1)]
+
+ # Use average of the two sides
+ best_shift = (best_shift0 + best_shift1) / 2
+ return best_shift
+
+
+def minimize(fname_evtest, fname_laser):
+
+ # Load all the data
+ tl, transition = load_laser_data(fname_laser)
+ (tx, x, ty, y) = evparser.load_xy(fname_evtest)
+
+ # Shift time so that first time point is 0
+ t0 = min(tx[0], ty[0])
+ tx = tx - t0
+ ty = ty - t0
+ tl = tl - t0
+
+ # Sanity checks
+ if numpy.std(x)*2 < numpy.std(y):
+ print('WARNING: Not enough motion in X axis')
+
+ # Search for minimum with coarse step of 1 ms in range of 0 to 200 ms
+ coarse_step = 1e-3 # Seconds
+ best_shift_coarse = minimize_lsq(tx, x, ty, y, tl, 0, 0.2, coarse_step)
+ # Run another search with 0.02 ms step within +-3 ms of the previous result
+ lmts = numpy.array([-1, 1]) * 3 * coarse_step + best_shift_coarse
+ fine_step = 2e-5 # seconds
+ best_shift_fine = minimize_lsq(tx, x, ty, y, tl, lmts[0], lmts[1], fine_step)
+
+ print("Drag latency (min method) = %.2f ms" % (best_shift_fine*1000))
+ if debug_mode:
+ debug_plot(tx, x, ty, y, tl, best_shift_fine)
+
+ return best_shift_fine
+
+
+def debug_plot(tx, x, ty, y, tl, shift):
+ """Plot the XY data with time-shifted laser events
+
+ Note: this is a utility function used for offline debugging. It needs
+ matplotlib which is not installed on CrOS images.
+
+ """
+ import matplotlib.pyplot as plt
+ xx = numpy.interp(ty, tx, x)
+ plt.plot(xx, y, '.b')
+
+ yl = numpy.interp(tl + shift, ty, y)
+ xl = numpy.interp(tl + shift, tx, x)
+ sides = (((numpy.arange(len(tl)) + 1) / 2) % 2)
+ colors = ['g', 'm']
+ x_linear = numpy.array([min(x), max(x)])
+ for side in [0, 1]:
+ xls = xl[sides == side]
+ yls = yl[sides == side]
+ plt.plot(xls, yls, 'o' + colors[side])
+ a, c = numpy.polyfit(xls, yls, 1)
+ plt.plot(x_linear, a * x_linear + c, colors[side])
+ plt.xlabel('X')
+ plt.ylabel('Y')
+ plt.title('Laser events shifted %.2f ms' % (shift*1000))
+ plt.show()
+
+# Debug & test
+if __name__ == '__main__':
+
+ fname = '/tmp/WALT_2016_06_22__1739_21_'
+ fname_evtest = fname + 'evtest.log'
+ fname_laser = fname + 'laser.log'
+
+ minimize(fname_evtest, fname_laser)
+
diff --git a/pywalt/pywalt/screen_stats.py b/pywalt/pywalt/screen_stats.py
new file mode 100644
index 0000000..1930e6d
--- /dev/null
+++ b/pywalt/pywalt/screen_stats.py
@@ -0,0 +1,54 @@
+import numpy
+
+
+def screen_stats(blinker_file_name, sensor_file_name):
+
+ sensor_data = numpy.loadtxt(sensor_file_name)
+ blinker_data = numpy.loadtxt(blinker_file_name)
+
+ # Convert all times to milliseconds
+ t_sensor = sensor_data[:, 0] * 1e3
+ t_vsync = blinker_data / 1e3
+
+ # Throw away any sensor timestamps earlier than the first blink
+ # this may happen if the operator attached the sensor after
+ # running the command. But this should be avoided.
+ skip_sensor = sum(t_sensor < t_vsync[0])
+ if(skip_sensor):
+ t_sensor = t_sensor[skip_sensor:]
+ print('Skipped first %d readings from the sensor' % skip_sensor)
+
+ # Get only the common size and skip the first blink, it's often weird.
+ length = min(len(t_sensor), len(t_vsync))
+ t_sensor = t_sensor[1:length]
+ t_vsync = t_vsync[1:length]
+
+ # Shift time so that first time point is 0
+ t0 = min(t_vsync)
+ t_sensor = t_sensor - t0
+ t_vsync = t_vsync - t0
+
+ dt = t_sensor - t_vsync
+
+ # Look at even and odd transitions separately - black <-> white.
+ dt_even = dt[0::2]
+ dt_odd = dt[1::2]
+
+ print('')
+ print('dt = array([' + ', '.join('%0.2f' % x for x in dt) + '])')
+ print('')
+ print('Screen response times [ms]')
+ print('Even: median %0.1f ms, stdev %0.2f ms' %
+ (numpy.median(dt_even), numpy.std(dt_even)))
+ print('Odd: median %0.1f ms, stdev %0.2f ms' %
+ (numpy.median(dt_odd), numpy.std(dt_odd)))
+
+
+# Debug & test
+if __name__ == '__main__':
+
+ fname = '/tmp/WALT_2016_06_22__1739_21_'
+ blinker_file_name = fname + 'evtest.log'
+ sensor_file_name = fname + 'laser.log'
+
+ screen_stats(blinker_file_name, sensor_file_name)
diff --git a/pywalt/pywalt/walt.py b/pywalt/pywalt/walt.py
new file mode 100644
index 0000000..9987f4f
--- /dev/null
+++ b/pywalt/pywalt/walt.py
@@ -0,0 +1,799 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Runs the touchpad drag latency test using WALT Latency Timer
+Usage example:
+ $ python walt.py 11
+ Input device : /dev/input/event11
+ Serial device : /dev/ttyACM1
+ Laser log file : /tmp/WALT_2016_06_23__1714_51_laser.log
+ evtest log file: /tmp/WALT_2016_06_23__1714_51_evtest.log
+ Clock zeroed at 1466716492 (rt 0.284ms)
+ ........................................
+ Processing data, may take a minute or two...
+ Drag latency (min method) = 15.37 ms
+
+Note, before running this script, check that evtest can grab the device.
+On some systems it requires running as root.
+"""
+
+import argparse
+import contextlib
+import glob
+import os
+import random
+import re
+import socket
+import subprocess
+import sys
+import tempfile
+import threading
+import time
+
+import serial
+import numpy
+
+import evparser
+import minimization
+import screen_stats
+
+
+# Time units
+MS = 1e-3 # MS = 0.001 seconds
+US = 1e-6 # US = 10^-6 seconds
+
+# Globals
+debug_mode = True
+
+
+def log(msg):
+ if debug_mode:
+ print(msg)
+
+
+class Walt(object):
+ """ A class for communicating with Walt device
+
+ Usage:
+ with Walt('/dev/ttyUSB0') as walt:
+ body....
+
+
+ """
+
+ # Teensy commands, always singe char. Defined in WALT.ino
+ # github.com/google/walt/blob/master/arduino/walt/walt.ino
+ CMD_RESET = 'F'
+ CMD_PING = 'P'
+ CMD_SYNC_ZERO = 'Z'
+ CMD_SYNC_SEND = 'I'
+ CMD_SYNC_READOUT = 'R'
+ CMD_TIME_NOW = 'T'
+ CMD_AUTO_LASER_ON = 'L'
+ CMD_AUTO_LASER_OFF = 'l'
+ CMD_AUTO_SCREEN_ON = 'C'
+ CMD_AUTO_SCREEN_OFF = 'c'
+ CMD_GSHOCK = 'G'
+ CMD_VERSION = 'V'
+ CMD_SAMPLE_ALL = 'Q'
+ CMD_BRIGHTNESS_CURVE = 'U'
+ CMD_AUDIO = 'A'
+
+
+ def __init__(self, serial_dev, timeout=None, encoding='utf-8'):
+ self.encoding = encoding
+ self.serial_dev = serial_dev
+ self.ser = serial.Serial(serial_dev, baudrate=115200, timeout=timeout)
+ self.base_time = None
+ self.min_lag = None
+ self.max_lag = None
+ self.median_latency = None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ try:
+ self.ser.close()
+ except:
+ pass
+
+ def close(self):
+ self.ser.close()
+
+ def readline(self):
+ return self.ser.readline().decode(self.encoding)
+
+ def sndrcv(self, data):
+ """ Send a 1-char command.
+ Return the reply and how long it took.
+
+ """
+ t0 = time.time()
+ self.ser.write(data.encode(self.encoding))
+ reply = self.ser.readline()
+ reply = reply.decode(self.encoding)
+ t1 = time.time()
+ dt = (t1 - t0)
+ log('sndrcv(): round trip %.3fms, reply=%s' % (dt / MS, reply.strip()))
+ return dt, reply
+
+ def read_shock_time(self):
+ dt, s = self.sndrcv(Walt.CMD_GSHOCK)
+ t_us = int(s.strip())
+ return t_us
+
+
+ def run_comm_stats(self, N=100):
+ """
+ Measure the USB serial round trip time.
+ Send CMD_TIME_NOW to the Teensy N times measuring the round trip each time.
+ Prints out stats (min, median, max).
+
+ """
+ log('Running USB comm stats...')
+ self.ser.flushInput()
+ self.sndrcv(Walt.CMD_SYNC_ZERO)
+ tstart = time.time()
+ times = numpy.zeros((N, 1))
+ for i in range(N):
+ dt, _ = self.sndrcv(Walt.CMD_TIME_NOW)
+ times[i] = dt
+ t_total = time.time() - tstart
+
+ median = numpy.median(times)
+ stats = (times.min() / MS, median / MS, times.max() / MS, N)
+ self.median_latency = median
+ log('USB comm round trip stats:')
+ log('min=%.2fms, median=%.2fms, max=%.2fms N=%d' % stats)
+ if (median > 2):
+ print('ERROR: the median round trip is too high: %.2f ms' % (median / MS) )
+ sys.exit(2)
+
+ def zero_clock(self, max_delay=0.001, retries=10):
+ """
+ Tell the TeensyUSB to zero its clock (CMD_SYNC_ZERO).
+ Returns the time when the command was sent.
+ Verify that the response arrived within max_delay seconds.
+
+ This is the simple zeroing used when the round trip is fast.
+ It does not employ the same method as Android clock sync.
+ """
+
+ # Check that we get reasonable ping time with Teensy
+ # this also 'warms up' the comms, first msg is often slower
+ self.run_comm_stats(N=10)
+
+ self.ser.flushInput()
+
+ for i in range(retries):
+ t0 = time.time()
+ dt, _ = self.sndrcv(Walt.CMD_SYNC_ZERO)
+ if dt < max_delay:
+ print('Clock zeroed at %.0f (rt %.3f ms)' % (t0, dt / MS))
+ self.base_time = t0
+ self.max_lag = dt
+ self.min_lag = 0
+ return t0
+ print('Error, failed to zero the clock after %d retries')
+ return -1
+
+ def read_remote_times(self):
+ """ Helper func, see doc string in estimate_lage()
+ Read out the timestamps taken recorded by the Teensy.
+ """
+ times = numpy.zeros(9)
+ for i in range(9):
+ dt, reply = self.sndrcv(Walt.CMD_SYNC_READOUT)
+ num, tstamp = reply.strip().split(':')
+ # TODO: verify that num is what we expect it to be
+ log('read_remote_times() CMD_SYNC_READOUT > w > = %s' % reply)
+ t = float(tstamp) * US # WALT sends timestamps in microseconds
+ times[i] = t
+ return times
+
+ def estimate_lag(self):
+ """ Estimate the difference between local and remote clocks
+
+ This is based on:
+ github.com/google/walt/blob/master/android/WALT/app/src/main/jni/README.md
+
+ self.base_time needs to be set using self.zero_clock() before running
+ this function.
+
+ The result is saved as self.min_lag and self.max_lag. Assume that the
+ remote clock lags behind the local by `lag` That is, at a given moment
+ local_time = remote_time + lag
+ where local_time = time.time() - self.base_time
+
+ Immediately after this function completes the lag is guaranteed to be
+ between min_lag and max_lag. But the lag change (drift) away with time.
+ """
+ self.ser.flushInput()
+
+ # remote -> local
+ times_local_received = numpy.zeros(9)
+ self.ser.write(Walt.CMD_SYNC_SEND)
+ for i in range(9):
+ reply = self.ser.readline()
+ times_local_received[i] = time.time() - self.base_time
+
+ times_remote_sent = self.read_remote_times()
+ max_lag = (times_local_received - times_remote_sent).min()
+
+ # local -> remote
+ times_local_sent = numpy.zeros(9)
+ for i in range(9):
+ s = '%d' % (i + 1)
+ # Sleep between the messages to combat buffering
+ t_sleep = US * random.randint(70, 700)
+ time.sleep(t_sleep)
+ times_local_sent[i] = time.time() - self.base_time
+ self.ser.write(s)
+
+ times_remote_received = self.read_remote_times()
+ min_lag = (times_local_sent - times_remote_received).max()
+
+ self.min_lag = min_lag
+ self.max_lag = max_lag
+
+ def parse_trigger(self, trigger_line):
+ """ Parse a trigger line from WALT.
+
+ Trigger events look like this: "G L 12902345 1 1"
+ The parts:
+ * G - common for all trigger events
+ * L - means laser
+ * 12902345 is timestamp in us since zeroed
+ * 1st 1 or 0 is trigger value. 0 = changed to dark, 1 = changed to light,
+ * 2nd 1 is counter of how many times this trigger happened since last
+ readout, should always be 1 in our case
+
+ """
+
+ parts = trigger_line.strip().split()
+ if len(parts) != 5:
+ raise Exception('Malformed trigger line: "%s"\n' % trigger_line)
+ t_us = int(parts[2])
+ val = int(parts[3])
+ return (t_us / 1e6, val)
+
+
+def array2str(a):
+ a_strs = ['%0.2f' % x for x in a]
+ s = ', '.join(a_strs)
+ return '[' + s + ']'
+
+
+def parse_args(argv):
+ temp_dir = tempfile.gettempdir()
+ serial = '/dev/ttyACM0'
+
+ # Try to autodetect the WALT serial port
+ ls_ttyACM = glob.glob('/dev/ttyACM*')
+ if len(ls_ttyACM) > 0:
+ serial = ls_ttyACM[0]
+
+ description = "Run a latency test using WALT Latency Timer"
+ parser = argparse.ArgumentParser(
+ description=description,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+ parser.add_argument('-i', '--input',
+ help='input device, e.g: 6 or /dev/input/event6')
+ parser.add_argument('-s', '--serial', default=serial,
+ help='WALT serial port')
+ parser.add_argument('-t', '--type',
+ help='Test type: drag|tap|screen|sanity|curve|bridge|'
+ 'tapaudio|tapblink')
+ parser.add_argument('-l', '--logdir', default=temp_dir,
+ help='where to store logs')
+ parser.add_argument('-n', default=40, type=int,
+ help='Number of laser toggles to read')
+ parser.add_argument('-p', '--port', default=50007, type=int,
+ help='port to listen on for the TCP bridge')
+ parser.add_argument('-d', '--debug', action='store_true',
+ help='talk more')
+ args = parser.parse_args(argv)
+
+ if not args.type:
+ parser.print_usage()
+ sys.exit(0)
+
+ global debug_mode
+ debug_mode = args.debug
+
+ if args.input and args.input.isalnum():
+ args.input = '/dev/input/event' + args.input
+
+ return args
+
+
+def run_drag_latency_test(args):
+
+ if not args.input:
+ print('Error: --input argument is required for drag latency test')
+ sys.exit(1)
+
+ # Create names for log files
+ prefix = time.strftime('WALT_%Y_%m_%d__%H%M_%S')
+ laser_file_name = os.path.join(args.logdir, prefix + '_laser.log')
+ evtest_file_name = os.path.join(args.logdir, prefix + '_evtest.log')
+
+ print('Starting drag latency test')
+ print('Input device : ' + args.input)
+ print('Serial device : ' + args.serial)
+ print('Laser log file : ' + laser_file_name)
+ print('evtest log file: ' + evtest_file_name)
+
+ with Walt(args.serial) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+ tstart = time.time()
+ t_zero = walt.zero_clock()
+ if t_zero < 0:
+ print('Error: Couldn\'t zero clock, exiting')
+ sys.exit(1)
+
+ # Fire up the evtest process
+ cmd = 'evtest %s > %s' % (args.input, evtest_file_name)
+ evtest = subprocess.Popen(cmd, shell=True)
+
+ # Turn on laser trigger auto-sending
+ walt.sndrcv(Walt.CMD_AUTO_LASER_ON)
+ trigger_count = 0
+ while trigger_count < args.n:
+ # The following line blocks until a message from WALT arrives
+ trigger_line = walt.readline()
+ trigger_count += 1
+ log('#%d/%d - ' % (trigger_count, args.n) +
+ trigger_line.strip())
+
+ if not debug_mode:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+ t, val = walt.parse_trigger(trigger_line)
+ t += t_zero
+ with open(laser_file_name, 'at') as flaser:
+ flaser.write('%.3f %d\n' % (t, val))
+ walt.sndrcv(Walt.CMD_AUTO_LASER_OFF)
+
+ # Send SIGTERM to evtest process
+ evtest.terminate()
+
+ print("\nProcessing data, may take a minute or two...")
+ # lm.main(evtest_file_name, laser_file_name)
+ minimization.minimize(evtest_file_name, laser_file_name)
+
+
+def run_screen_curve(args):
+
+ with Walt(args.serial, timeout=1) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+
+ t_zero = walt.zero_clock()
+ if t_zero < 0:
+ print('Error: Couldn\'t zero clock, exiting')
+ sys.exit(1)
+
+ # Fire up the walt_blinker process
+ cmd = 'blink_test 1'
+ blinker = subprocess.Popen(cmd, shell=True)
+
+ # Request screen brightness data
+ walt.sndrcv(Walt.CMD_BRIGHTNESS_CURVE)
+ s = 'dummy'
+ while s:
+ s = walt.readline()
+ print(s.strip())
+
+
+def run_screen_latency_test(args):
+
+ # Create names for log files
+ prefix = time.strftime('WALT_%Y_%m_%d__%H%M_%S')
+ sensor_file_name = os.path.join(args.logdir, prefix + '_screen_sensor.log')
+ blinker_file_name = os.path.join(args.logdir, prefix + '_blinker.log')
+
+ print('Starting screen latency test')
+ print('Serial device : ' + args.serial)
+ print('Sensor log file : ' + sensor_file_name)
+ print('Blinker log file: ' + blinker_file_name)
+
+ with Walt(args.serial, timeout=1) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+
+ t_zero = walt.zero_clock()
+ if t_zero < 0:
+ print('Error: Couldn\'t zero clock, exiting')
+ sys.exit(1)
+
+ # Fire up the walt_blinker process
+ cmd = 'blink_test %d > %s' % (args.n, blinker_file_name, )
+ blinker = subprocess.Popen(cmd, shell=True)
+
+ # Turn on screen trigger auto-sending
+ walt.sndrcv(Walt.CMD_AUTO_SCREEN_ON)
+ trigger_count = 0
+
+ # Iterate while the blinker process is alive
+ # TODO: re-sync clocks every once in a while
+ while blinker.poll() is None:
+ # The following line blocks until a message from WALT arrives
+ trigger_line = walt.readline()
+ if not trigger_line:
+ # This usually happens when readline timeouts on last iteration
+ continue
+ trigger_count += 1
+ log('#%d/%d - ' % (trigger_count, args.n) +
+ trigger_line.strip())
+
+ if not debug_mode:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+ t, val = walt.parse_trigger(trigger_line)
+ t += t_zero
+ with open(sensor_file_name, 'at') as flaser:
+ flaser.write('%.3f %d\n' % (t, val))
+ walt.sndrcv(Walt.CMD_AUTO_SCREEN_OFF)
+ screen_stats.screen_stats(blinker_file_name, sensor_file_name)
+
+
+def run_tap_audio_test(args):
+ print('Starting tap-to-audio latency test')
+ with Walt(args.serial) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+ t_zero = walt.zero_clock()
+ if t_zero < 0:
+ print('Error: Couldn\'t zero clock, exiting')
+ sys.exit(1)
+
+ walt.sndrcv(Walt.CMD_GSHOCK)
+ deltas = []
+ while len(deltas) < args.n:
+ sys.stdout.write('\rWAIT ')
+ sys.stdout.flush()
+ time.sleep(1) # Wait for previous beep to stop playing
+ while walt.read_shock_time() != 0:
+ pass # skip shocks during sleep
+ sys.stdout.write('\rTAP NOW')
+ sys.stdout.flush()
+ walt.sndrcv(Walt.CMD_AUDIO)
+ trigger_line = walt.readline()
+ beep_time_seconds, val = walt.parse_trigger(trigger_line)
+ beep_time_ms = beep_time_seconds * 1e3
+ shock_time_ms = walt.read_shock_time() / 1e3
+ if shock_time_ms == 0:
+ print("\rNo shock detected, skipping this event")
+ continue
+ dt = beep_time_ms - shock_time_ms
+ deltas.append(dt)
+ print("\rdt=%0.1f ms" % dt)
+ print('Median tap-to-audio latency: %0.1f ms' % numpy.median(deltas))
+
+
+def run_tap_blink_test(args):
+ print('Starting tap-to-blink latency test')
+ with Walt(args.serial) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+ t_zero = walt.zero_clock()
+ if t_zero < 0:
+ print('Error: Couldn\'t zero clock, exiting')
+ sys.exit(1)
+
+ walt.sndrcv(Walt.CMD_GSHOCK)
+ walt.sndrcv(Walt.CMD_AUTO_SCREEN_ON)
+ deltas = []
+ while len(deltas) < args.n:
+ trigger_line = walt.readline()
+ blink_time_seconds, val = walt.parse_trigger(trigger_line)
+ blink_time_ms = blink_time_seconds * 1e3
+ shock_time_ms = walt.read_shock_time() / 1e3
+ if shock_time_ms == 0:
+ print("No shock detected, skipping this event")
+ continue
+ dt = blink_time_ms - shock_time_ms
+ deltas.append(dt)
+ print("dt=%0.1f ms" % dt)
+ print('Median tap-to-blink latency: %0.1f ms' % numpy.median(deltas))
+
+
+def run_tap_latency_test(args):
+
+ if not args.input:
+ print('Error: --input argument is required for tap latency test')
+ sys.exit(1)
+
+ print('Starting tap latency test')
+
+ with Walt(args.serial) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+ t_zero = walt.zero_clock()
+ if t_zero < 0:
+ print('Error: Couldn\'t zero clock, exiting')
+ sys.exit(1)
+
+ # Fire up the evtest process
+ cmd = 'evtest ' + args.input
+ evtest = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True)
+ walt.sndrcv(Walt.CMD_GSHOCK)
+
+ taps_detected = 0
+ taps = []
+ while taps_detected < args.n:
+ ev_line = evtest.stdout.readline()
+ tap_info = evparser.parse_tap_line(ev_line)
+ if not tap_info:
+ continue
+
+ # Just received a tap event from evtest
+ taps_detected += 1
+
+ t_tap_epoch, direction = tap_info
+ shock_time_us = walt.read_shock_time()
+ dt_tap_us = 1e6 * (t_tap_epoch - t_zero) - shock_time_us
+
+ print(ev_line.strip())
+ print("shock t %d, tap t %f, tap val %d. dt=%0.1f" % (shock_time_us, t_tap_epoch, direction, dt_tap_us))
+
+ if shock_time_us == 0:
+ print("No shock detected, skipping this event")
+ continue
+
+ taps.append((dt_tap_us, direction))
+
+ evtest.terminate()
+
+ # Process data
+ print("\nProcessing data...")
+ dt_down = numpy.array([t[0] for t in taps if t[1] == 1]) / 1e3
+ dt_up = numpy.array([t[0] for t in taps if t[1] == 0]) / 1e3
+
+ print('dt_down = ' + array2str(dt_down))
+ print('dt_up = ' + array2str(dt_up))
+
+ median_down_ms = numpy.median(dt_down)
+ median_up_ms = numpy.median(dt_up)
+
+ print('Median latency, down: %0.1f, up: %0.1f' % (median_down_ms, median_up_ms))
+
+
+def run_walt_sanity_test(args):
+ print('Starting sanity test')
+
+ with Walt(args.serial) as walt:
+ walt.sndrcv(Walt.CMD_RESET)
+
+ not_digit = re.compile('\D+')
+ lows = numpy.zeros(3) + 1024
+ highs = numpy.zeros(3)
+ while True:
+ t, s = walt.sndrcv(Walt.CMD_SAMPLE_ALL)
+ nums = not_digit.sub(' ', s).strip().split()
+ if not nums:
+ continue
+ ints = numpy.array([int(x) for x in nums])
+ lows = numpy.array([lows, ints]).min(axis=0)
+ highs = numpy.array([highs, ints]).max(axis=0)
+
+ minmax = ' '.join(['%d-%d' % (lows[i], highs[i]) for i in range(3)])
+ print(s.strip() + '\tmin-max: ' + minmax)
+ time.sleep(0.1)
+
+
+class TcpServer:
+ """
+
+
+ """
+ def __init__(self, walt, port=50007, host=''):
+ self.running = threading.Event()
+ self.paused = threading.Event()
+ self.net = None
+ self.walt = walt
+ self.port = port
+ self.host = host
+ self.last_zero = 0.
+
+ def ser2net(self, data):
+ print('w>: ' + repr(data))
+ return data
+
+ def net2ser(self, data):
+ print('w<: ' + repr(data))
+ # Discard any empty data
+ if not data or len(data) == 0:
+ print('o<: discarded empty data')
+ return
+
+ # Get a string version of the data for checking longer commands
+ s = data.decode(self.walt.encoding)
+ bridge_command = None
+ while len(s) > 0:
+ if not bridge_command:
+ bridge_command = re.search(r'bridge (sync|update)', s)
+ # If a "bridge" command does not exist, send everything to the WALT
+ if not bridge_command:
+ self.walt.ser.write(s.encode(self.walt.encoding))
+ break
+ # If a "bridge" command is preceded by WALT commands, send those
+ # first
+ if bridge_command.start() > 0:
+ before_command = s[:bridge_command.start()]
+ log('found bridge command after "%s"' % before_command)
+ s = s[bridge_command.start():]
+ self.walt.ser.write(before_command.encode(self.walt.encoding))
+ continue
+ # Otherwise, reply directly to the command
+ log('bridge command: %s, pausing ser2net thread...' %
+ bridge_command.group(0))
+ self.pause()
+ is_sync = bridge_command.group(1) == 'sync' or not self.walt.base_time
+ if is_sync:
+ self.walt.zero_clock()
+
+ self.walt.estimate_lag()
+ if is_sync:
+ # shift the base so that min_lag is 0
+ self.walt.base_time += self.walt.min_lag
+ self.walt.max_lag -= self.walt.min_lag
+ self.walt.min_lag = 0
+
+ min_lag = self.walt.min_lag * 1e6
+ max_lag = self.walt.max_lag * 1e6
+ # Send the time difference between now and when the clock was zeroed
+ dt0 = (time.time() - self.walt.base_time) * 1e6
+ reply = 'clock %d %d %d\n' % (dt0, min_lag, max_lag)
+ self.net.sendall(reply)
+ print('|custom-reply>: ' + repr(reply))
+ self.resume()
+ s = s[bridge_command.end():]
+ bridge_command = None
+
+ def connections_loop(self):
+ with contextlib.closing(socket.socket(
+ socket.AF_INET, socket.SOCK_STREAM)) as sock:
+ self.sock = sock
+ # SO_REUSEADDR is supposed to prevent the "Address already in use" error
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ sock.bind((self.host, self.port))
+ sock.listen(1)
+ while True:
+ print('Listening on port %d' % self.port)
+ net, addr = sock.accept()
+ self.net = net
+ try:
+ print('Connected by: ' + str(addr))
+ self.net2ser_loop()
+ except socket.error as e:
+ # IO errors with the socket, not sure what they are
+ print('Error: %s' % e)
+ break
+ finally:
+ net.close()
+ self.net = None
+
+ def net2ser_loop(self):
+ while True:
+ data = self.net.recv(1024)
+ if not data:
+ break # got disconnected
+ self.net2ser(data)
+
+ def ser2net_loop(self):
+ while True:
+ self.running.wait()
+ data = self.walt.readline()
+ if self.net and self.running.is_set():
+ data = self.ser2net(data)
+ data = data.encode(self.walt.encoding)
+ self.net.sendall(data)
+ if not self.running.is_set():
+ self.paused.set()
+
+ def serve(self):
+ t = self.ser2net_thread = threading.Thread(
+ target=self.ser2net_loop,
+ name='ser2net_thread'
+ )
+ t.daemon = True
+ t.start()
+ self.paused.clear()
+ self.running.set()
+ self.connections_loop()
+
+ def pause(self):
+ """ Pause serial -> net forwarding
+
+ The ser2net_thread stays running, but won't read any incoming data
+ from the serial port.
+ """
+
+ self.running.clear()
+ # Send a ping to break out of the blocking read on serial port and get
+ # blocked on running.wait() instead. The ping response is discarded.
+ self.walt.ser.write(Walt.CMD_PING)
+ # Wait until the ping response comes in and we are sure we are no longer
+ # blocked on ser.read()
+ self.paused.wait()
+ print("Paused ser2net thread")
+
+ def resume(self):
+ self.running.set()
+ self.paused.clear()
+ print("Resuming ser2net thread")
+
+ def close(self):
+ try:
+ self.sock.close()
+ except:
+ pass
+
+ try:
+ self.walt.close()
+ except:
+ pass
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
+ def __enter__(self):
+ return self
+
+
+def run_tcp_bridge(args):
+
+ print('Starting TCP bridge')
+ print('You may need to run the following to allow traffic from the android container:')
+ print('iptables -A INPUT -p tcp --dport %d -j ACCEPT' % args.port)
+
+ try:
+ with Walt(args.serial) as walt:
+ with TcpServer(walt, port=args.port) as srv:
+ walt.sndrcv(Walt.CMD_RESET)
+ srv.serve()
+ except KeyboardInterrupt:
+ print(' KeyboardInterrupt, exiting...')
+
+
+def main(argv=sys.argv[1:]):
+ args = parse_args(argv)
+ if args.type == 'drag':
+ run_drag_latency_test(args)
+ if args.type == 'tap':
+ run_tap_latency_test(args)
+ elif args.type == 'screen':
+ run_screen_latency_test(args)
+ elif args.type == 'sanity':
+ run_walt_sanity_test(args)
+ elif args.type == 'curve':
+ run_screen_curve(args)
+ elif args.type == 'bridge':
+ run_tcp_bridge(args)
+ elif args.type == 'tapaudio':
+ run_tap_audio_test(args)
+ elif args.type == 'tapblink':
+ run_tap_blink_test(args)
+ else:
+ print('Unknown test type: "%s"' % args.type)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pywalt/setup.py b/pywalt/setup.py
new file mode 100755
index 0000000..e7e1acd
--- /dev/null
+++ b/pywalt/setup.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+from setuptools import setup, find_packages
+
+setup(
+ name='pywalt',
+ entry_points={
+ 'console_scripts': (
+ 'walt = pywalt.walt:main',
+ ),
+ },
+ install_requires=['pyserial'],
+ packages=find_packages(),
+ description='WALT Latency Timer',
+ license='Apache 2.0',
+ url='https://github.com/google/walt',
+)
diff --git a/pywalt/walt b/pywalt/walt
new file mode 100755
index 0000000..a5af890
--- /dev/null
+++ b/pywalt/walt
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+import sys
+from pywalt.walt import main
+sys.exit(main(sys.argv[1:]))
diff --git a/server/main.py b/server/main.py
new file mode 100644
index 0000000..0f2fb53
--- /dev/null
+++ b/server/main.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Runs a server that receives log uploads from the WALT app
+Usage example:
+ $ python main.py
+"""
+
+import time
+import os
+
+try:
+ from bottle import route, template, run, request, static_file
+except:
+ print('Could not import bottle! Please install bottle, e.g. pip install bottle')
+ raise
+
+@route('/')
+def index():
+ if not os.path.isdir('logs/'):
+ return 'No files uploaded yet'
+ filenames = []
+ for file in os.listdir('logs/'):
+ if file.endswith('.txt'):
+ filenames.append(file)
+ return template('make_table', filenames=filenames)
+
+
+@route('/logs/<filename>')
+def static(filename):
+ return static_file(filename, root='logs')
+
+
+@route('/upload', method='POST')
+def upload():
+ body = request.body.getvalue()
+ request.body.close()
+ filename = 'logs/' + str(int(time.time()*1000)) + '.txt'
+ if not os.path.exists(os.path.dirname(filename)):
+ try:
+ os.makedirs(os.path.dirname(filename))
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+ with open(filename, 'w') as file:
+ file.write(body)
+ return 'success'
+
+
+run(host='localhost', port=8080)
+
diff --git a/server/make_table.tpl b/server/make_table.tpl
new file mode 100644
index 0000000..b2e2972
--- /dev/null
+++ b/server/make_table.tpl
@@ -0,0 +1,7 @@
+<h1>Logs</h1>
+<ul>
+%for filename in filenames:
+ <li><a href={{'/logs/' + filename}}>{{filename}}</a></li>
+%end
+</ul>
+