关于autodesk-forge:Forge账户试用期结束或试用云币用完后怎么办

(因为Forge费用政策可能变动,所有以官网的最终阐明为准 https://forge.autodesk.com/pr...) 每个Forge账户注册后,会主动获取100个收费的试用云币,有效期3个月。当应用Forge服务,某些端口将扣除云币,例如模型转换成SVF的端口。如果试用期完结,或云币用完,您可能会看到如下集中景象: 明确告知账户曾经试用完结 获取Token时,返回如下谬误 提交转换服务时,返回如下谬误 这些都阐明,您必须开始订阅和购买Forge云币了。如果您正式思考订阅,大陆用户请按如下格局填写后,发送到 forge.orders@autodesk.com , 并抄送 Xianhua.tang@autodesk.com 或 xiaodong.liang@autodesk.com 。保留表格中的英文,以便国外共事查看。表格内容能够是中文,或中英文。咱们财务将发送洽购清单给您,提供领取账号和Swift码,贵方从公司账户执行后续操作以及电汇过程。若公司账户确有艰难,亦可用个人账户领取,并及时分割Autodesk财务提供缴纳的凭据以供查验。 目前中国大陆还不反对间接信用卡购买。咱们正向无关部门催促提供更便捷的领取形式。 公司名(Company Name):公司地址(Company Address):公司城市(City):国家(Country):电话(Contact Phone):Forge用户的名称(Forge  account  name):Forge用户的电子邮件地址 (Forge account email):须要洽购的云币数量(cloud credit to purchase):以下是中国大陆用户Forge云币批量购买目前的阶梯价格表(以美元为计价单位)。依据您的理论可能须要来购买适当的用量。 留神: 根底费用能够了解为订阅的资格费。必须缴纳。其余的云币包按业务须要购买。购买的云币若未应用,1年后主动生效。在订阅时,请依据您的业务具体情况抉择适当的云币数量。举荐有对外电汇服务的银行,例如中国银行。 Autodesk收款账户是新加坡花旗银行。各银行可能有不同的手续费,倡议先征询一下若有特地问题,须要中文沟通,敬请致信 adn-china-admin@autodesk.com , 或您若在微信群的话,可 @  Autodesk的无关人员 。

March 29, 2021 · 1 min · jiezi

在-Revit-里重现-Forge-Viewer相机的状态

最近,我收到一个客户的需求,希望可以把Viewer的相机状态通过Revit API还原到Revit里。所以我们来看看要如何实现这个要求。在开始之前,你要先知道一些有关于Revit相机的事情: Revit预设的相机FOV值大约为50度,焦距为38.6mm,片幅尺寸为36mm。Revit默认的渲染图片尺寸为6英吋。为了调整Revit相机的FOV值,我们必须利用修改3D视图的裁剪尺寸来完成。因为Revit API没有直接的方法可以修改相机的FOV值。Viewer的相机视角比Revit的相机视角宽。注意:上述关于Revit的相机参数皆为我反复测试得出,Revit没有确切的值,即皆为近似值。 好的,我们转换过程的总思路如下(注意:接下来的步骤适用透视相机模式): Forge Viewer的部分: 从当前视图的 Viewer 相机获取焦距,目标,位置和上向量。 调用 Viewer3D#getFocalLength 以取得焦距。调用 Viewer3D#getState({ viewport: true }) 以取得当前视图必要的相机状态,例如: { "viewport": { "name": "", "eye": [ -14.870469093323, 36.571562767029, -1.2129259109497 ], "target": [ -14.770469665527, 36.571967124939, -1.2129259109497 ], "up": [ 0, 0, 1 ], "worldUpVector": [ 0, 0, 1 ], "pivotPoint": [ -14.770469665527, 36.571967124939, -1.2129259109497 ], "distanceToOrbit": 0.10000024532334, "aspectRatio": 3.1789297658863, "projection": "perspective", "isOrthographic": false, "fieldOfView": 90.68087674208 }}获取当前加载模型的 global offset(注意:Viewer 默认使用 global offset 来调整加载模型的位置,以避免浮点运算精度和 z-buffer fighting的问题): ...

July 11, 2019 · 2 min · jiezi

在Forge-Viewer中模拟不加载任何模型

不久前,我收到一个关于如何在不加载任何模型的情况下初始化Forge viewer的请求。据我所知,viewer的一些初始化设定是在加载模型时才被决定的,所以基本上是无法以这种方式初始化viewer的。然而,我在StackOverflow上针对此需求提供了一种暂时的解决方法: 加载一个包含两个极小点的虚拟模型(例如放置在bounding box的最小点和最大点),让Viewer看起来像没有加载任何模型。若要加载新的模型,必须先卸除此虚拟模型,并使用Viewer3D.tearDown()释放一些内存。现在让我们来看看该如何完成(以下步骤使用了Revit的通用族样板和3D视图的bounding box): 一、我们必须先建立一个幽灵点群代表上述提到的极小点。它的外观如下: 二、放置两个幽灵点族群的实体,并将其放置在一个非常非常接近Revit3D视图默认bounding box最大和最小点的位置,如下图所示: 为了简化工作,我写了一段Dynamo的程序来加快速度:然后根据我测试的结果,需要将这两个幽灵点的偏移调整为+30000mm(最大点)和-30000mm(最小点)。 (如果这两个点太靠近3D视图bounding box的最大点或最小点,它们会在Forge转档之后消失。) 三、将此Revit档案上传到Forge 服务器转档,并像往常一样使用viewer打开,您将会看到空的viewer。以下是视频演示:https://www.youtube.com/watch... 是不是很簡單呢?更多程序代码的细节请查看这里的源码和范例:https://github.com/yiskang/fo...

May 13, 2019 · 1 min · jiezi

添加自定义属性到Viewer的属性面板

这是一个常被提起的问题,第一次是在「添加自定义属性到Viewer的属性面板」这篇博客被提出,但因自V4开始,Viewer的CSS有重大变更,导致该方法在新版的 Viewer已无法使用。另一个相关的是「Viewer属性检查器」这篇博客,其是通过建立一个工具栏按钮的方式来新增面板,它是可以正常运作的,但它的做法并非取代现有的属性面板,不在今天要讨论的范围。 那我们该怎么做才能取代现有的属性面板呢? 事实上,我们有一个接受内建面板 ViewerPropertyPanel 对象的方法叫 viewer.setPropertyPanel 可以使用。因此,最简单的操作方式是使用 setPropertyPanel 这个方法,再替换 setProperties 这方法的内容。为了新增更多我们想要的属性数据,让我们也复写setNodeProperties 这个接受dbId作为函数变量的方法,以下是部分程序代码: // *******************************************// Custom Property Panel// *******************************************function CustomPropertyPanel(viewer, options) { this.viewer = viewer; this.options = options; this.nodeId = -1; // dbId of the current element showing properties Autodesk.Viewing.Extensions.ViewerPropertyPanel.call(this, this.viewer);}CustomPropertyPanel.prototype = Object.create(Autodesk.Viewing.Extensions.ViewerPropertyPanel.prototype);CustomPropertyPanel.prototype.constructor = CustomPropertyPanel;CustomPropertyPanel.prototype.setProperties = function (properties, options) { Autodesk.Viewing.Extensions.ViewerPropertyPanel.prototype.setProperties.call(this, properties, options); // add your custom properties here // for example, let's show the dbId and externalId var _this = this; // dbId is right here as nodeId this.addProperty('dbId', this.nodeId, 'Custom Properties'); // externalId is under all properties, let's get it! this.viewer.getProperties(this.nodeId, function(props){ _this.addProperty('externalId', props.externalId, 'Custom Properties'); })}CustomPropertyPanel.prototype.setNodeProperties = function (nodeId) { Autodesk.Viewing.Extensions.ViewerPropertyPanel.prototype.setNodeProperties.call(this, nodeId); this.nodeId = nodeId; // store the dbId for later use};为了方便使用,我们将之包装在Viewer扩展中。这里有篇博客「扩展概念:工具栏与面板」在讨论扩充展架的基本概念,这里我们只使用它创建扩展的部分(不是工具栏或面板的部分)。以下扩展代码使用刚才建立的面板,并通过 setPropertyPanel 使之与 Viewer 连结。(这个扩充将负责注册和取消注册我们的自定义属性面板。) ...

April 30, 2019 · 1 min · jiezi

Revit 自定义材质图档转换

最近有两个不同的台湾客户都反映材质图档消失的问题。当Forge Viewer加载模型时,他们看不到自定义的材质图档。经过一番研究,我发现这个问题相当容易,本文章将会以简单的方式描述该如何解决此问题,就让我们开始吧!在开始之前,有一些关于Revit材质转档的事你必须先知道:Revit 3D视图中的视觉樣式必须是真实。以下项目应仅包含ASCII字符或应以ASCII字符命名:材质图档的文档名,例如: red-brick.jpg。材质图档路径(即为Revit材质浏览器中自定义材质的外观选项中的材质图档),例如:C:adntexturesred-brick.jpg。RVT文档名(Revit项目名称)。所有被使用的自定义材质图档应该要跟RVT文档放在一起,即JPG、PNG和RVT的所有文档应放置于文件夹的同一层。所有材质图档和RVT档案应该要被打包成一个 ZIP 压缩包里。让我们开始准备要转换的模型吧!一、 将3D视图中的视觉樣式切换成真实:二、 确定所有文档名及路径皆为ASCII字符:三、 选择视图并加入Revit的发布设置(Publish Settings)窗口的视图集中:四、 将所有自定义义材质图档跟RVT档案放在同一层文件夹:五、 将步骤4打包成 ZIP 压缩包:六、 将ZIP上传到Forge OSS 桶(Bucket)并使用此设置开始文档转换作业:{ “input”: { “urn”: “{BASE64_URN_OF_THE_ZIP}”, //!<<< e.g. dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bXlidWNrZXQvRm9yZ2UgVGV4dHVyZSBEZW1vLnppcA “compressedUrn”: true, “rootFilename”: “{THE_HOST_RVT_FILENAME}” //!<<< e.g. adn-brick-test.rvt }, “output”: { “formats”: [ { “type”: “svf”, “views”: [ “2d”, “3d” ] } ] } }七、 在Forge Viewer上加载转换好的模型,并会看到屏幕上的自定义材质:此篇文章同步发表在 Forge 官方博客:https://forge.autodesk.com/bl…

March 28, 2019 · 1 min · jiezi

「重大更新」Autodesk Forge IFC 模型转换提取服务管线变更

当前 IFC文档通过 Forge 模型转换提服务 (Model Derivative API) 进行转换时是使用 Navisworks 进行 IFC 文档解析,同时一个更好的、基于 Revit 引擎的 IFC 文档解析也上线了。文档解析器的变更对于 IFC 转换数据格式、内容有重大影响 (例如,结构树结构、模型转向等),所以请更新即刻的您代码来应对这些变更。这个转换将会分成两个阶段:阶段一:自今日(美国时间 2019/03/26)开始的几个月内,IFC 文档的模型转换提取管线仍默认使用 Navisworks 进行 IFC 文档解析,但建议您开始依下面的样例使用 Revit 管线进行测试:curl -X ‘POST’ \ -H ‘Content-Type: application/json; charset=utf-8’ \ -H ‘Authorization: Bearer PtnrvrtSRpWwUi3407QhgvqdUVKL’ -v ‘https://developer.api.autodesk.com/modelderivative/v2/designdata/job' \ -d ‘{ “input”: { “urn”: “dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bW9kZWxkZXJpdmF0aXZlL21vZGVsLmlmYw”, }, “output”: { “formats”: [ { “type”: “svf”, “views”: [ “3d” ], “advanced”: { “switchLoader”: true } } ] } }‘阶段二:在几个月之后,Forge 模型转换提取服务将默认使用 Revit 管线处理 IFC 文档的转换工作,所以我们强列建议您尽快开始使用 Revit 管线测试您的模型。注意1: 转换后的数据结构变更是可以预期的,所以请您尽快因应此变更来更新您的项目代码。注意2: 请密切关注Forge官方博客的公告,待 Revit IFC 管线的验证工作完成后,我们将发对此发布公告注意3:切换至阶段二后,旧有的 Navisworks 管线可以使用下面的方式进行调用:curl -X ‘POST’ \ -H ‘Content-Type: application/json; charset=utf-8’ \ -H ‘Authorization: Bearer PtnrvrtSRpWwUi3407QhgvqdUVKL’ -v ‘https://developer.api.autodesk.com/modelderivative/v2/designdata/job' \ -d ‘{ “input”: { “urn”: “dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bW9kZWxkZXJpdmF0aXZlL21vZGVsLmlmYw”, }, “output”: { “formats”: [ { “type”: “svf”, “views”: [ “3d” ], “advanced”: { “switchLoader”: true } } ] } }‘如在测试时发现认何问题,请随时通过 forge.help@autodesk.com 反馈,谢谢! ...

March 27, 2019 · 1 min · jiezi

在 Forge Viewer 里显示 Revit 格子线 (grids)

最近一些Forge客户都在询问我同一个的问题,他们希望将Revit的网格呈现在viewer中,藉此让我有机会来完成这件事,并将它记录在本文章里,就让我们开始吧!在开始之前,有件事你必须先知道:由于在Revit里格子线只能在2D视图(例如平面图、立面图、表单等等)中显示,并不会在3D视图中被看见。因此,我们也无法在ForgeViewer的3D视图中看到这些格子线,网格会在模型转文件时被忽略。据我所知,这是目前的限制。但是,这并非完全不可能。为了实现这一点,我们可以利用Rvit族群的通用线基准的模型样板创建3D格子线族,以下是他的样子:在之后,我们可以将此族组件放置在平面图中Revit格子线的位置上:为了加快速度,我写了一个Dynamo脚本来正确放置3D格子线:下图是此Dynamo脚本的运行结果:注意,此脚本将获取Revit中所有网格的位置,然后仅将3D网格放置在所选楼层中;如果您必须在多个楼层显示3D格子线,请修改此脚本或编写您自己的Revit插件以实现它。现在准备将此RVT模型上传到Forge进行转档。 只需等待转档完成,然后您就会看到格子线!此篇文章同步发表在 Forge 官方博客:https://forge.autodesk.com/bl…

March 25, 2019 · 1 min · jiezi

Android 原生app获取用户授权访问Autodesk云应用数据

oAuth机制对于网站间的授权管理是很容易实现的,设置好app回调端口,当数据服务提供方拿到其用户授权,则返回授权码发送到回调端口。上一篇文章介绍了如何授权Forge app访问Autodesk 云应用数据,即,获取三条腿的token。但移动端的原生app,授权码返回到发起请求app的某个Activity,可如何得知是哪个Activity并且跳转到此Activity?这时就要用到URL Scheme。iOS和Android都提供了这样的机制。它实现了页面内跳转协议,通过定义自己的scheme协议,非常方便跳转到app中的各个Activity。也让不同app可以唤起其它app的Activity。当然,前提app已经安装到系统。这位大咖对此话题做了专业的讲解,推荐大家先阅读:https://www.jianshu.com/p/7b0…我的同事Bryan搭建了一个框架,演示Android app 集成Forge oAuth,拿到三条腿token,并调用了用户信息(Profile)API,列出登录用户的在Forge数据管理中的基本信息。该框架很好的展示了使用URL Scheme整个过程。https://github.com/dukedhx/oa…我借此学习了有关内容。另外,基于此代码,略微改造,添加流程展示获取Autodesk云应用的Hub,Project,Folder和File,为进一步的综合应用app做一些铺垫。https://github.com/xiaodongli…oAuth相关的几个步骤:1.定义监听oAuth回传的Activity属性 <activity android:name=".MainActivity"> <intent-filter> <action android:name=“android.intent.action.MAIN”/> <category android:name=“android.intent.category.LAUNCHER”/> </intent-filter> <intent-filter> <data android:scheme="@string/FORGE_CALLBACK_SCHEME" android:host="@string/FORGE_CALLBACK_HOST"/> <action android:name=“android.intent.action.VIEW”/> <category android:name=“android.intent.category.DEFAULT”/> <category android:name=“android.intent.category.BROWSABLE”/> </intent-filter> </activity>其中,FORGE_CALLBACK_SCHEME和FORGE_CALLBACK_HOST定义为: <string name=“FORGE_CALLBACK_SCHEME”>lxdapp</string> <string name=“FORGE_CALLBACK_HOST”>mytest3legged</string>同样的, URL Scheme必须和Forge app注册的时候填写的callback URL要一致。 2.登录事件将创建一个Intent.ACTION_VIEW,用以启动网页,URL拼接了Forge app的client id,授权方式 (response_type=code),回传地址和授权的权限范围 跳转到Autodesk登录过程,等待客户授权。public void onLoginClick(View v) { Intent i = new Intent(Intent.ACTION_VIEW); Resources resources = getResources(); i.setData(Uri.parse(getResources().getString(R.string.FORGE_AUTHORIZATION_URL) + “?response_type=code&redirect_uri=” + this.getCallbackUrl() + “&scope=” + resources.getString(R.string.FORGE_SCOPE) + “&client_id=” + resources.getString(R.string.FORGE_CLIENT_ID))); startActivity(i); }3.用户授权后,将对URLScheme地址回传,也就是发起请求的Activity。Activity的OnStart拿到回传信息。由于没有设定android:mimetype, 则任何数据类型都可接收。但我们需要只是授权码,在回传请求的URL参数中。 @Override protected void onStart() { super.onStart(); Intent intent = getIntent(); Uri data = intent == null ? null : intent.getData(); //从回传请求中拿到授权码 String authorizationCode = data == null ? null : data.getQueryParameter(“code”); if (authorizationCode != null && !authorizationCode.isEmpty()) { ……4.接下来的过程就和常规的网页应用类似了,依授权码得到最终的token。5.该样例调用Forge服务采取okhttp3,推荐参考下文的详细讲解:https://www.jianshu.com/p/da4…注:Activity的URLScheme是可能同名的,而且如何保证应用之间跳转和传参的目标正确性,这方面我还在研究中。本例为方便计,将Forge Client Secret也存在了app之中。我想安全的方式是由app服务器端进行secret的管理,当原生app拿到授权码,通过它来向app服务器端发起请求,由app服务器来获取Forge token,再传回原生app。扩展的部分(Hub,Project,Folder和File)只是简单的替换ListView的内容,尚未对页面回退做处理。 ...

February 25, 2019 · 1 min · jiezi

Forge oAuth访问Autodesk云应用数据 (三条腿token)

大多数朋友刚开始接触Autodesk Forge,源于‘网页浏览和管理模型’的需求,即,使用Forge的模型转换服务和前端Forge Viewer。模型的来源通常是开发者利用Forge数据管理服务创建bucket,上传模型。而这种方式意味着其应用程序(app)是该模型(数据)拥有者。但在很多场景,模型源并不是app所有,而是存储和管理在某些其它云应用中,例如BOX,DropBox,百度盘,阿里云盘,或者Autodesk的BIM 360, Fusion 360。这些云应用的用户拥有其数据的权限。Forge的app 要对这些模型访问或操作的话,则要其它云应用的用户进行授权,拿到授权后,才能访问或操作数据(取决于授权的范围)。这就是通行的oAuth2.0。在我们平常使用支付验证的时候,也是这个机制。推荐阅读这篇文章,对oAuth做了精炼的讲解:https://www.jianshu.com/p/639…简言之,典型的流程是,app发起一个请求,让用户在第三方云应用登录和授权,给予app一定范围的数据权限(此过程全部导向第三方,app无法干预和定制),当用户完成授权过程后,第三方云应用将会给app传回一个授权码(code)。借此,app继续调用第三方的云服务API,获取到最终的token。因为有三步走 (发起,授权,获取token),这也就是为何经常把这样的token叫做三条腿token。前面提到的创建bucket,自行上传模型相关的token,叫做两条腿token。Forge全球资料有个教材演示如何一步步的搭建app,访问Autodesk云应用数据。http://learnforge.autodesk.io…本文对其中授权认证过程稍微再讲解一下:1.首先,Autodesk的云应用也提供了oAuth机制,底层是Forge的身份认证服务(大家经常用它来获取token)。例如app访问和操作BIM 360客户的数据,发起请求的过程,还要有一个回调端口监听来自Forge认证服务传回的授权码。而回调端口必须在创建app的时候设置好。如果您的app只有两条腿token的需要,此项随便给一个scheme://host 形式的字串即可。2.下载教材提供的完整代码工程 (以Node.js为例):https://github.com/Autodesk-Forge/learn.forge.viewhubmodels/tree/nodejs 打开工程,定位到config.js,填写对应的Client ID, Client Secret 和Callback URL (一般设置为环境变量)。3.此工程定义好的发起授权请求的端口在oauth.js中。它其实是拼接了一个URL,传回客户端,让客户端访问。此URL带有app的client id,授权方式 (response_type=code),回传地址和授权的权限范围。当发起请求,Forge认证服务会和app注册是对应的信息最比对,因此,必须保证client id和回传地址和app注册信息一致。router.get(’/oauth/url’, (req, res) => { const url = ‘https://developer.api.autodesk.com’ + ‘/authentication/v1/authorize?response_type=code’ + ‘&client_id=’ + config.credentials.client_id + ‘&redirect_uri=’ + config.credentials.callback_url + ‘&scope=’ + config.scopes.internal.join(’ ‘); res.end(url);});此工程定义好的回传端口在oauth.js中,router.get(’/callback/oauth’, async (req, res, next) => { //从Autodesk认证传回的授权码,用于获取最终的token const { code } = req.query; const oauth = new OAuth(req.session); try { await oauth.setCode(code); res.redirect(’/’); } catch(err) { next(err); }});客户端拿到请求地址启动访问,则会弹出Autodesk标准的登录对话框客户输入账号密码后,Autodesk授权机制会询问,某app需要如下数据权限,是否允许。客户允许后,授权码会发到回调函数接着,代码调用Forge的API,得到token。token有两个,一个是执行其他API操作的token(和两条腿的token类似,JWT),有效期也是60分钟。而另外一个是refresh token(用户授权令牌),用于保留用户授权,有效期14天。所以,当用户授权一次后,并不用每次都有授权过程,在refresh token有效期间,可以用此再调用Forge API获取新的访问token。 注:三条腿token,同样要注意高权限和低权限的token,样例代码中有演示。目前,Forge的app只允许设置一个回调函数端口。而有些其它云应用例如Azure的app,允许多个。Forge可以授予app代表某用户,使用两条腿token的方式进行数据访问,在以后文章中再介绍。BIM 360的数据访问,还需要一个特殊步骤,详情见 BIM 360 开发账户授权。oAuth2.0的机制适用于其它云存储,参考这篇博客 ...

February 24, 2019 · 1 min · jiezi

Forge云服务的本地化经验总结与优化实战

Autodesk Forge API服务的数据中心是基于AWS的海外服务搭建的,因而,由于众所周知的原因,国内部分地区(依ISP而异)访问Forge云端口的速度会受到一定程度的影响。特别是Forge Viewer浏览大型模型,以及对反馈时间比较铭感且涉及关键业务的工作流等诸多场景,对于服务端的存取效率有者较高要求。所以,如何处理好云端数据的访问与协同工作流,包括离线加载、云端缓存等方案的最佳实践,是大家一直关注的问题。今天,我们就总结一下几种常见的实现方式,以期实现流程与性能的优化。离线模型加载往期有这两篇文章可供参考:离线模型的下载和部署和Viewer模型加载本地离线缓存实战,该文介绍了使用新近浏览器原生的Service-Worker和Cache API缓存模型的方案。但是在Viewer模型加载本地离线缓存实战的实例代码中,针对Viewer库和线上模型资源本身的缓存是通过静态路径实现的://https://github.com/petrbroz/forge-disconnected/blob/master/public/service-worker.jsconst STATIC_URLS = [ ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./style.css’, ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./viewer3D.js’, ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./lmvworker.js’, ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./res/locales/en/allstrings.json’, ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./res/environments/SharpHighlights_irr.logluv.dds’, ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./res/environments/SharpHighlights_mipdrop.logluv.dds’,…该实现存在几点问题:待缓存的静态链接是根据展示用的模型所需配置的,模型更换后静态链接也需要手动更新一次性缓存了所有实例模型所需的资源,超配且不必要,影响加载性能Viewer库版本一旦更新,需手动更新缓存的静态资源链接因此,在我们后续的实战Forge Viewer渐进应用一文中,采用先注册完成缓存任务的Service Worker再加载Viewer库的流程。如此一来,Viewer库依赖与模型资源的加载请求也会自动得到缓存,无需手动干预缓存过程,大幅增进代码的可维护性:navigator.serviceWorker.register(’/service-worker.js’).then((registration) => { let script = document.createElement(‘script’); script.onload = function () { const viewer = new Autodesk.Viewing.Private.GuiViewer3D(myViewerDiv); Autodesk.Viewing.Initializer(options, () => { … //按需以在线或离线模式初始化Viewer并加载模型 viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => console.log(event); navigator.serviceWorker.controller.postMessage({ operation: ‘EXECUTE_CACHE’ }, [channel.port2]); // 模型加载完成,该模型所需资源已作记录,遂向ServiceWorker发送消息,开始缓存所需资源 }) }); }; script.src = “https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/viewer3D.min.js”; document.head.appendChild(script) //待Worker注册完毕开始作动后再载入Viewer,实现Viewer库及其依赖的自动缓存})在线模型加载建议参考下文的数据服务的优化,为Viewer的加载提供代理,增进国内访问的速度。在Viewer上实现代理的方法主要通过Autodesk.Viewing.endpoint.setEndpointAndApi来重定向获取模型数据的URL:Autodesk.Viewing.Initializer(options, function(){ Autodesk.Viewing.endpoint.setEndpointAndApi(‘https://yourhostname/your/proxy/service/path’)…随后在你的服务端将Viewer发来的请求代理至Forge API云服务https://developer.api.autodesk.com即可,注意保留Viewer原生请求的路径,这里还可以根据Forge服务上的模型所在的数据中心按需为转发目的地作进一步设置,详见:BIM 360 Docs API在操作欧洲数据中心内容的一些调整。亦可设置Viewer发送的请求头,以AOP的设计模式实现自己的访问控制等逻辑:Autodesk.Viewing.Initializer(options, function(){ Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS = {‘X-My-Custom-Header’:‘233’, …}…当然,我们还需在自己的服务端设置预检请求(Pre-flight)的返回中Access-Control-Allow-Headers的请求头,允许之前为Viewer设置的请求头不被浏览器的跨域安全机制拦截。当然,大家熟悉的Viewer原生的getAccessToken等都是依旧有效的。Android如本地的模型,由于Viewer不支持基于File协议的模型加载,需要通过重写WebView并拦截http请求,返回静态资源。webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith(“http://my/path/to/svf”)) { // 返回静态模型资源 return true; } }});Viewer加载模型的URL维持http协议即可iOS同理,iOS的实现思路是: func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) { if navigationAction.navigationType == .linkActivated { if webView.url!.absoluteString == “http://path/to/my/static/svf” { //返回静态模型资源 return } } decisionHandler(.allow)}亦可参考这里的教程:https://github.com/BKRApps/KR…跨平台针对跨平台的框架,不建议使用搭设本地静态服务器的方式克服Viewer不支持File协议的问题,因为很多Android ROM处于安全考量已经屏蔽了该特性。React Native框架: 推荐使用react-native-webview,再通过onShouldStartLoadWithRequest拦截并返回静态模型:onShouldStartLoadWithRequest = (event) => { var url = event.url; if (url && url.length) { if (url.indexOf(‘http://my/path/to/svf’) == 0) { //返回静态模型资源 } } return true; };Ionic框架推荐使用cordova-plugin-ionic-webview,再通过onShouldStartLoadWithRequest拦截并返回静态模型再结合Cordova File Plugin访问静态资源cdvfile://path/to/svf数据服务的优化建议自建云服务访问Forge API或部署代理,以便实现诸多优化事项,如缓存(可以使用http-cache等库),如集中统筹Access Token,避免为每个客户端服务请求逐一作Forge认证,提升效率的同时增强Forge App密钥和Token的安全性,亦便于实现高度自定的工作流。建议搭建位于海外机房的节点作代理,地理位置推荐香港或接入电信CN2线路的北美机房,访问速度会有不小提升,详情可以自行搜索了解。亦可搭建自己的云存储,便于实现高度自定义的工作流与高规格的安全机制,可以通过等Ceph和minio等技术实现。活用Forge Webhook API,实现基于事件回调的异步工作流,节省资源的同时提高可靠性,详情可以参考这些样例。Q&AQ:能否避免将模型上传到Forge数据平台,直接进行转换?A:Forge服务只能转换存储在Forge数据平台的模型,转换功能无法作本地部署,如模型涉密,可以再转换完成后立即从Forge数据平台删除,Forge对用户数据不作任何备案。Q:可否实现Viewer的完全本地化?即将Viewer库和模型渲染资源下载到本地?A:Forge Viewer使用许可禁止将Viewer库下载至本地加载,亦禁止对代码进行修改Q: 那如何修改Viewer源代码?我有扩展原生逻辑的需求。A: 可以使用Piggyback的方式,即在自己的逻辑中动态的替换、扩展Viewer自身的函数Q: 针对离线加载模型的场景,如何有效的提取Viewer可读的模型文件(SVF)到本地?A: 可以通过我们搭建的Extractor应用提取:https://extract.autodesk.io/,亦可自行部署该应用:https://github.com/cyrillef/e… ...

February 16, 2019 · 1 min · jiezi

实战Forge Viewer渐进应用 - 当Xamarin遇上WebAssembly

Xamarin作为移动端的跨平台原生开发框架的老牌劲旅,一直被视作Mono Project寄予厚望的当家花旦之一。近年来,虽然React Native/Ionic等后起之秀夺去大半江山,但随着Xamarin入驻微软并宣告免费,加之.NET/C#生态的日益完善与精进,Xamarin已然重焕青春!那么,如果我们将它与上期技术分享介绍的业界新贵WebAssembly双剑合璧,又会迸发出怎样的化学反应呢?今天我们就将Autodesk Forge Viewer离线方案整合到Xamarin,并发布为基于WebAssembly的Progressive Web App(简称PWA,渐进式应用) - 即实现浏览器URL访问直接安装的神奇效果!引言彩蛋首先,让我们First things first:????Autodesk ADN(开发者社区团队)祝您在新的一年大吉大利!!????事顺利!!????为什么要WebAssembly和PWA?在原生和H5应用如火纯青的今天,WebAssembly和PWA的相对意义与优势在于:如上期技术分享介绍,WebAssembly赋能我们在浏览器中以原生的性能运行CC#、Java、Rust等及基于它们的框架和技术编译后的执行包(wasm)为二进制,精简高效的同时保护源代码,各种安全机制防范恶意代码的篡改与攻击,增强应用的安全性PWA给予用户非原生应用胜似原生应用的浏览器客户端体验,通过浏览器访问URL即可完成PWA的安装,无需发布至应用商店Xamarin的优势相比Cordova/PhoneGap、Ionic、Appgyver等H5混合框架,以及React Native、Titanium等原生框架,我们为什么要关注Xamarin呢?相较Cordova/PhoneGap、Ionic、Appgyver等混合框架具有原生优势相较基于JavaScript的React Native、Titanium,.NET/C#在语言某些特性上具备相对优势,也便于熟悉.NET/C#桌面与后台开发的朋友迅速上手.NET/C#生态的相对优势,如可以使用我们的Forge .NET Client SDK相较基于状态的Functional Approach具有更底层原生UI的优势实战开始!今天实战的环节为:Forge Viewer渐进应用 > Xamarin应用 > 发布为WebAssembly的PWA > 移动端测试Forge Viewer PWA往期我们有介绍过利用ServiceWorker和Cache等API实现Forge Viewer离线方案,但是悉心的朋友或许已经发现该方案仍有不少瑕疵(将在下期着重阐述),现在我们更一进步:将整个加载过程离线缓存与客户端! 其成果是独立的Forge Viewer PWA,满足移动和桌面端的离线使用。首先定义Viewer渐进应用的ServiceWorker,有关该API的详细介绍可以参考往期。不同于往期中介绍的方案,这次我们将要缓存所有Viewer脚本、样式和加载模型的请求,且在预设的缓存列表中只有CSS,其余脚本依赖与模型数据全部在应用首次加载阶段缓存,省去手动适配不同Viewer版本与模型资源的麻烦。首先我们来监听请求事件,收集所有需要缓存的请求,并记录从后台获取的Access Token,以供Forge API认证所需。出于性能考虑,待模型加载完成后再统一缓存所有资源:const urlsToCache = [ ‘viewer.html’, //Viewer页面路径 ‘viewer-serviceworker.js’, //本ServiceWorker路径 ‘https://developer.api.autodesk.com/modelderivative/v2/viewers/6./style.min.css’ //Viewer.js样式];…self.addEventListener(‘fetch’, event => { event.respondWith( caches.match(event.request) .then( async response => { if (response) return response; if (event.request.url.endsWith(’/api/token’)) { \判断请求指向获取Access Token的后台服务 const response = await fetch(event.request); fetchOptions.headers = { ‘Authorization’: ‘Bearer ’ + response.access_token } \设定访问Forge API请求的Access Token return response; } else fetches.push(event.request.url); return fetch(event.request) }) )});在ServiceWorker中定义缓存操作self.addEventListener(‘message’, async event => { switch (event.data.operation) { case ‘EXECUTE_CACHE’: await caches.open(CACHE_NAME).then(async cache => await Promise.all(fetches.map(url=>fetch(url, fetchOptions).then(resp => cache.put(url, resp))))); event.ports[0].postMessage({ status: ‘ok’, fetches }); break; }});待Viewer触发GEOMETRY_LOADED_EVENT,即模型加载完毕,一切所需资源已记录在案,再统一触发缓存 navigator.serviceWorker.register(’/service-worker.js’).then((registration) => { alert(‘Service worker registered’, registration.scope); let script = document.createElement(‘script’); script.onload = function () { const viewer = new Autodesk.Viewing.Private.GuiViewer3D(myViewerDiv); Autodesk.Viewing.Initializer(options, () => { … //按需以在线或离线模式初始化Viewer并加载模型 viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => console.log(event); navigator.serviceWorker.controller.postMessage({ operation: ‘EXECUTE_CACHE’ }, [channel.port2]); // 模型加载完成,该模型所需资源已作记录,遂向ServiceWorker发送消息,开始缓存所需资源 }) }); }; script.src = “https://developer.api.autodesk.com/modelderivative/v2/viewers/6./viewer3D.min.js”; document.head.appendChild(script) });最后定义后台服务,获取Access Token,.NET、Node、PHP的教程可以参看这里整合Viewer PWA至Xamarin应用在Visual Studio中创建Xamarin项目,注意引用Xamarin.Forms v2.5而非v3.x!由于Viewer采用WebGL实现,其PWA的整合亦采用WebView,创建基于Xamarin XAML的Top Banner+WebView的UI界面,其中WebView指向我们之前定义的Viewer PWA页面 <Grid> <Grid.RowDefinitions> <RowDefinition Height=“Auto” /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackLayout BackgroundColor=“DimGray” VerticalOptions=“FillAndExpand” HorizontalOptions=“Fill”> <StackLayout Orientation=“Horizontal” HorizontalOptions=“Center” VerticalOptions=“Center”> <ContentView Padding=“0,10,0,10” VerticalOptions=“FillAndExpand”> <Image Source="{Binding logo}" VerticalOptions=“Center” HeightRequest=“24” /> </ContentView> </StackLayout> </StackLayout> <ScrollView Grid.Row=“1”> <StackLayout Orientation=“Vertical” > <ContentView VerticalOptions=“FillAndExpand” > <WebView Source=“URL/TO/YOUR/VIEWER/PWA.html”></WebView> </ContentView> </StackLayout> </ScrollView> </Grid>可用所见即所得的设计器(如GorillaPlayer)设计XAML,和Visual Studio 2017自带的XAML Previewer预览UI效果(如箭头所示于XAML编辑器右上角按钮进入)编译运行并检查在UI和WebView加载刚才完成的Forge Viewer PWA的效果发布为基于WebAssembly的PWA!在Visual Studio中创建基于.NET Core 2.x的Console(控制台应用)项目,并引用方才创建的Xamarin项目通过NuGet安装以下依赖,其中Ooui系列用于发布WebAssemblyOoui: https://github.com/praeclarum...Ooui WASM: 发布WebAssemblyOoui Forms: Xamarin.Forms的Ooui实现Xamarin.Forms 2.5 //重要!一定要使用2.5版本以兼容Ooui在Program.cs中定义发布过程using OurXamarinAppstatic void Main(string[] args){ Forms.Init(); var mainPage = new MainPage(); //以MainPage为应用入口为例 UI.Publish("/", mainPage.GetOouiElement());}运行项目,将在项目的\bin\Debug\netcoreapp2.1\dist路径下生成WebAssembly和配套的前端页面与脚本:定义缓存WebAssembly的ServiceWorker:const urlsToCache = [ ‘index.html’, “https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css”, //Ooui依赖 “https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css”, //Ooui依赖 ‘service-worker.js’ //本ServiceWorker路径];…self.addEventListener(‘fetch’, function (event) { event.respondWith( caches.match(event.request) .then(function (response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); } ) );});在发布生成的index.html页面中注册该ServiceWorkerwindow.addEventListener(’load’, function () { navigator.serviceWorker.register(‘service-worker.js’)})定义应用清单 (manifest.json),让浏览器识别我们的PWA并定义主题颜色、应用图标等元数据{ “name”: “Forge Viewer PWA”, “short_name”: “FVPWA”, “icons”: [ { “src”: “icons/icon-128x128.png”, “sizes”: “128x128”, “type”: “image/png” }, { “src”: “icons/icon-144x144.png”, “sizes”: “144x144”, “type”: “image/png” }, { “src”: “icons/icon-152x152.png”, “sizes”: “152x152”, “type”: “image/png” }, { “src”: “icons/icon-192x192.png”, “sizes”: “192x192”, “type”: “image/png” }, { “src”: “icons/icon-512x512.png”, “sizes”: “512x512”, “type”: “image/png” } ], “start_url”: “index.html”, “display”: “standalone”, “background_color”: “#3498DB”, “theme_color”: “#3498DB”}在index.html页面中引用清单<header>… <link rel=“manifest” href="/manifest.json"></header>移动端测试用各大Web服务器直接静态托管应用所在目录即可,并以支持ServiceWorker的浏览器访问,支持情况可参考:https://caniuse.com/#search=s…在非HTTPS/SSL测试环境下,如遇浏览器安全限制:“不安全上下文”错误或无法注册ServiceWorker (navigator.serviceWorker为null),则需给服务器启用HTTPS/SSL,或使用ngrok等云管道服务为本地环境作代理用访问应用URL时浏览器会提示保存快捷方式到桌面,图标由我们的应用清单定义:保存后进入飞行模式打开应用,测试离线状态的加载:大功告成!延伸阅读Xamarin开发资源汇总:https://blog.csdn.net/qq_4147…WebAssembly遇上React Native: https://github.com/vincentrie...PWA和小程序:https://www.cnblogs.com/softi… ...

February 4, 2019 · 2 min · jiezi

WebAssembly + Forge实战 - 整合Forge AR/VR ToolKit + Unity场景至前端框架

在浏览器环境下,解释运行JavaScript脚本实现高阶功能早已是家常便饭,然而,Web前端日新月异的需求已逐渐无法完全依赖JavaScript实现。幸运的是,打破瓶颈的新技术已逐渐成熟,它就是WebAssembly。什么是WebAssemblyWebAssembly是一项神奇的技术,简而言之就是一种底层的类汇编语言,其编译后的二进制模块wasm可在浏览器中运行以接近原生的性能运行CC++、C#、Java、GO、PHP、Rust等等语言的代码!自2015年颁布、2017年初正式发布最小功能版本以来,WebAssembly迅速开始盛行,并已得到主流浏览器的广泛支持,详细支持情况可以参见下图或MDN:图片描述图片描述(数据采于2019-01-25)需要强调的是:WebAssembly并不旨在取代JavaScript或任何现有的H5/ES6技术,而是与他们共存 - 我们耳熟能详的WebGL、Web Audio等组件都是WebAssembly模块在浏览器端的运行时,在浏览器端实现所需功能图片描述优势与异同那么问题来了 - WebAssembly究竟和asm.js、Dart等类似技术有何不同?我们早已可以通过Emscripten编译asm.js在浏览器中跑c/c++了,为什么还需要WebAssembly呢?相比之下,WebAssembly主要具备以下优势:WebAssembly模块不论在加载速度和性能上都有明显优势 - 它以二进制码的形式在浏览器中原生运行,无需像asm.js那样将原始语言编译成JavaScript,远超JavaScript引擎解释脚本的运行速度,即便首屈一指的Chrome V8有JIT加持也无济于事。WebAssembly并不是基于现有组建的扩展,而是一个Web开发新特性/标准,它有独立的路线图,不断有新特性加入进来。不受asm.js等技术在AOT等层面的限制,特性拓展潜力极大,应用场景广泛,详见底部延伸阅读部分的介绍。编译与运行那么如此神奇的技术究竟如何编译运行?当下最主流编译器可谓就是Emscripten了,广泛应用于原始语言->LLVM中间码->JavaScipt(asm.js)的编译。当然在WebAssembly全面企稳的今天,直接将原始语言编译成WebAssembly(wasm)也不在话下。较新版本的Emscripten支持跳过LLVM中间码->asm.js->wasm的过度,直接编译wasm,以c语言为例可通过如下命令直接编译:# WASM=1:仅生成wasm模块(默认为LLVM中间码),SIDE_MODULE=1:仅编译用户代码,而不包含printf、memalloc等函数./emcc hello-world.c -O3 -s WASM=1 -s SIDE_MODULE=1 -o hello-world.html编译生成的结果包括:hello-world.wasm: wasm模块二进制码hello-world.html: 展示页面hello-world.js: 读取wasm模块的JavaScript其中编译生成的hello-world.js是帮助我们在页面中调用加载wasm模块的脚本,我们也可结合Fetch API在自己的代码进行加载: fetch(‘path/to/wasm’) .then(response => response.arrayBuffer()) \将wasm文件响应转为二进制数组 .then(bits => WebAssembly.compile(bits)) \编译模块 .then(module => { return new WebAssembly.Instance(module) }); \生成模块实例可通过自带的emrun工具在指定浏览器中运行编译结果,或直接托管在Web服务器上:emrun –browser /path/to/browser/executable hello-world.html实战Unity+WebAssembly接下来我们就进入今天的实战:将经由Autodesk Forge Model Derivative服务轻量化的模型,通过Forge AR/VR Toolkit导入Unity场景,结合C#/JSLIB脚本与Unity插件,编译为WebAssembly,并集成至我们的前端框架中!图片描述参考该教程,为Unity3D项目配置好Forge AR/VR Toolkit将轻量化后的模型导入至Unity场景,如图所示填入模型的URN和从Forge服务端获取的Access Token图片描述需注意将.NET运行时版本调整为4.5以便支持TLS 1.2图片描述完成场景的建模与开发,本例结合Cinemachine的Freelook Camera插件与简单的c#脚本实现由键鼠操控的场景漫游。Cinemachine是一套强大的Unity相机管理工具,可利用其路径(Path)路点(Waypoint)等特性(并结合Timeline)轻松制作强大的预制路线漫游等效果。通过较新Unity3D(2017/5.6+)直接将场景编译为WebAssembly,设定发布目标平台为WebGL,并在发布设定中将连接器目标设为WebAssembly,开始Build编译:图片描述图片描述编译结果包括:html:展示页面Build目录:<项目名>.json(包括运行所需的参数与设置)、UnityLoad.js(浏览器加载wasm所需的脚本)、<项目名>..unityweb(发布设定中指定格式的压缩包,包含wasm模块与场景资源等)Template:展示页面依赖与前端框架整合浏览展示页面确认实际效果无误后,将Build目录导入前端项目的静态资源路径(如./src/assets)接下来将分别介绍针对Vue、React、Angular框架与无框架的整合React推荐使用react-unity-webgl加载Unity WebAssembly:npm install vue-unity-webgl在React组建中调用import Unity, { UnityContent } from “react-unity-webgl”;class App extends Component { unityContent:UnityContent = new UnityContent( “Build/forge_sample.json”, \引用编译结果,将所有编译结果置于相同路径下 “Build/UnityLoader.js” \并确保浏览器会话可以http协议访问 ); \… render() { \… <Unity unityContent={this.unityContent} /> }}与Unity中的对象通讯 this.unityContent.send( “Unity对象名称”, “C#或JSLIB脚本函数名称”, 1 \参数值 );在Unity脚本中与JavaScript通讯: [DllImport("__Internal")] private static extern void EventName (int arg); public void CallAnEvent (int arg) { EventName(arg); }在Unity中创建JSLIB脚本(如Assets/Plugins/WebGL/forge-sample.jslib)注入事件:mergeInto(LibraryManager.library, { EventName: function(arg) { ReactUnityWebGL.EventName(arg); }});在前端监听该事件 this.unityContent.on(“EventName”, arg => { \…- 同理我们可以在前端监听Unity的生命周期事件:public class NewBehaviourScript : MonoBehaviour {... [DllImport("__Internal")] private static extern void EventName (); void OnSceneLoaded (Scene scene, LoadSceneMode mode) { EventName();}... }##Vue- 推荐使用vue-unity-webgl组件加载Unity WebAssembly:npm install vue-unity-webgl- 在Vue组件中调用<template> <div>/…/<unity src=“Build/forge_sample.json” unityLoader=“Build/UnityLoader.js”></unity> \引用编译结果,将所有编译结果置于相同路径下,并确保浏览器会话可以http协议访问/…*/</div></template><script>import Unity from ‘vue-unity-webgl’...export default { components: { Unity } ...}<script>##Angular与无框架- 对于适用于Angular的Unity组件库,我们只找到了ng-unity,但在实测中出现报错,似乎是由于其内置的UnityLoader.js与我们的模块并不兼容(该库不能引用外置Loader),因此我们结合了无框架的引用方式来做示范- 在页面中引用编译生成的UnityLoad.js <script language=“JavaScript” src=“assets/Build/UnityLoader.js”></script>- 组件页面中加入容器元素 / app.component.html/ <div id=‘unityContainer’></div>- 在组件中载入模块\app.component.tsdeclare var UnityLoader: any; \声明UnityLoader为任意类export class AppComponent implements AfterViewInit{ private unityInstance: any; ... ngAfterViewInit(){(<any>window).UnityLoader = UnityLoader; \将UnityLoader对象暴露为窗体具柄this.unityInstance = UnityLoader.instantiate(‘unityContainer’, ‘./assets/Build/forge_sample.json’); \引用编译结果,将所有编译结果置于相同路径下,并确保浏览器会话可以http协议访问}sendMessage(objectName: string, functionName: any, argumentValue: any) {this.unityInstance.SendMessage(objectName, functionName, argumentValue); \与Unity对象通讯} ...}- 运行结果如下![图片描述][9]#调试与优化编译后的wasm是二进制的,可以通过编译工具(如WABT、Binaryen等)生成或转换为WebAssembly Text (wat) Format - 人类可读的类汇编代码:(module (func $i (import “imports” “imported_func”) (param i32)) (func (export “exported_func”)i32.const 42call $i))在浏览器中也可以查看wat,并断点调试![图片描述][10]##优化考量- 将wasm等依赖在服务器端压缩,加速网络传输,以Node后台为例: https://blog.csdn.net/github_38140984/article/details/83011150- 编译时的优化(如使用WebGL2.0,.NET4.5等),参考: https://docs.unity3d.com/Manual/class-PlayerSettingsWebGL.html#Optimization- 使用WebAssembly JIT:https://webassembly.org/docs/jit-library/#延伸阅读- Forge AR/VR介绍:https://segmentfault.com/a/1190000013672044- Unity Cinemachine插件学习笔记: https://blog.csdn.net/l773575310/article/details/78070808- Emscripten-WebAssembly专栏:https://segmentfault.com/blog/yunhuangbeiqing- WebAssembly入门: https://www.jianshu.com/p/bff8aa23fe4d- WebAssembly入门到入门:https://blog.csdn.net/m549393829/article/details/81839822- WebAssembly应用案例: https://blog.csdn.net/frf0lw4/article/details/79267457 [1]: /img/bVbnQqb [2]: /img/bVbnQqh [3]: /img/bVbnQqi [4]: /img/bVbnQqp [5]: /img/bVbnQqw [6]: /img/bVbnQqA [7]: /img/bVbnQqs [8]: /img/bVbnQqt [9]: /img/bVbnQqu ...

January 27, 2019 · 2 min · jiezi

Viewer模型加载本地缓存实战

由于Autodesk Forge是完全基于RESTful API框架的云平台,且暂时没有本地部署方案,特别是Viewer.js暂不支持本地搭建,必须外部引用位于服务器端的脚本,如何满足离线应用的需求一直是广大开发者关注的问题。文本将介绍来自Forge顾问团队的国际同事Petr原创的Viewer缓存范例,利用HTML5广泛用于PWA(Progessive Web App)开发的典型接口实现。时至今日,要把来自网络应用或服务的数据缓存至本地设备,我们有几种不同的技术与方式可选,本文将示范使用Service Worker,Cache和Channel Messaging等API实现,它们都是开发Progrssive Web App的常客。虽然这些API相对较为新锐,但已获得新版浏览器的广发支持,详细支持情况可以参考:当我们在JavaScript中注册了Service Worker之后,该Worker会拦截浏览器所有页面对于指定网络源或域的请求,返回缓存中的内容,Service Worker亦可以调用IndexDB、Channel Messaging、Push等API。Service Worker在Worker上下文中执行,无法直接对DOM进行操作,可以独立于页面的形式控制页面的加载。一个Service Worker可以控制多个页面,每当指定范围内的页面加载时,Service Worker便于会对其进行安装并操作,所以请小心使用全局变量,每个页面并没有自身独立的Worker。Service Worker的生命周期如下:在JavaScript中注册Service Worker浏览器下载并执行Worker脚本Worker收到“install”(安装)事件,一次性配置所需资源等待其他正在执行的Service Worker结束Worker收到“activate”(激活)事件,清除Worker的旧cacheWorker开始接受“fetch”(拦截网络请求并返回缓存中的资源)和“message”(与前端代码通讯)事件Cache是一个存储API,与LocalStorage类似IndexDB,每个网络源或域都有自己对应的存储空间,其中包括不重名的cache对象,用于存储HTTP请求与应答内容。Channel Messaging是脚本之间通讯API,支持包括主页面、iframe、Web Worker、Service Worker之间的双向通讯。缓存策略缓存诸如静态资源和API端口返回的数据并不复杂,可在Service Worker安装时缓存即可。然后,当页面向API端口发送请求时,Service Worker会当即返回缓存的内容,且可按需在后台拉取资源并更新缓存内容。缓存模型就稍许繁琐,一个模型通常会转换生成数个资源,生成的资源也时常引用其他素材,所以需要找出所有这些依赖并按需将其缓存。在本文的代码示例中,我们在后台写有接口,可根据模型的URN查询并返回所需资源的URL列表。因而在缓存模型时,Service Worker可以调用该接口缓存所有相关的URL,无需用到Viewer。代码示例我们制作了让用户选择模型作离线缓存的例子,查看代码请访问:https://github.com/petrbroz/f…,在线演示请访问:https://forge-offline.herokua…。接下来我们讲解一些具体的代码片段。例子的后台基于Express,public目录的内容作静态托管,其它服务端口位于以下三个路径:GET /api/token - 返回验证TokenGET /api/models - 返回可浏览的模型列表GET /api/models/:urn/files - 根据模型的URN查询并返回所需资源的URL列表客户端包括两个核心脚本:public/javascript/main.js和public/service-worker.js,其中public/javascript/main.js主要用于配置Viewer和UI逻辑,有两个重要的函数在脚本底部:initServiceWorker和submitWorkerTask,前者触发Service Worker的注册,后者向其发送消息:async function initServiceWorker() { try { const registration = await navigator.serviceWorker.register(’/service-worker.js’); console.log(‘Service worker registered’, registration.scope); } catch (err) { console.error(‘Could not register service worker’, err); }}On the activate event, we claim control of all instances of our web application potentially running in different browser tabs.async function activateAsync() { const clients = await self.clients.matchAll({ includeUncontrolled: true }); console.log(‘Claiming clients’, clients.map(client => client.url).join(’,’)); await self.clients.claim();}When intercepting requests via the fetch event, we reply with a cached response if there is one. One exception is the GET /api/token endpoint. Since our access token has an expiration time, we try to get a fresh token first, and only fall back to the cached one if we don’t succeed.async function fetchAsync(event) { // When requesting an access token, try getting a fresh one first if (event.request.url.endsWith(’/api/token’)) { try { const response = await fetch(event.request); return response; } catch(err) { console.log(‘Could not fetch new token, falling back to cache.’, err); } } // If there’s a cache match, return it const match = await caches.match(event.request.url, { ignoreSearch: true }); if (match) { // If this is a static asset or known API, try updating the cache as well if (STATIC_URLS.includes(event.request.url) || API_URLS.includes(event.request.url)) { caches.open(CACHE_NAME) .then((cache) => cache.add(event.request)) .catch((err) => console.log(‘Cache not updated, but that's ok…’, err)); } return match; } return fetch(event.request);}Finally, using the message event we execute “tasks” from the client.async function messageAsync(event) { switch (event.data.operation) { case ‘CACHE_URN’: try { const urls = await cacheUrn(event.data.urn, event.data.access_token); event.ports[0].postMessage({ status: ‘ok’, urls }); } catch(err) { event.ports[0].postMessage({ error: err.toString() }); } break; case ‘CLEAR_URN’: try { const urls = await clearUrn(event.data.urn); event.ports[0].postMessage({ status: ‘ok’, urls }); } catch(err) { event.ports[0].postMessage({ error: err.toString() }); } break; case ‘LIST_CACHES’: try { const urls = await listCached(); event.ports[0].postMessage({ status: ‘ok’, urls }); } catch(err) { event.ports[0].postMessage({ error: err.toString() }); } break; }}async function cacheUrn(urn, access_token) { console.log(‘Caching’, urn); // First, ask our server for all derivatives in this URN, and their file URLs const baseUrl = ‘https://developer.api.autodesk.com/derivativeservice/v2'; const res = await fetch(/api/models/${urn}/files); const derivatives = await res.json(); // Prepare fetch requests to cache all the URLs const cache = await caches.open(CACHE_NAME); const options = { headers: { ‘Authorization’: ‘Bearer ’ + access_token } }; const fetches = []; const manifestUrl = ${baseUrl}/manifest/${urn}; fetches.push(fetch(manifestUrl, options).then(resp => cache.put(manifestUrl, resp)).then(() => manifestUrl)); for (const derivative of derivatives) { const derivUrl = baseUrl + ‘/derivatives/’ + encodeURIComponent(derivative.urn); fetches.push(fetch(derivUrl, options).then(resp => cache.put(derivUrl, resp)).then(() => derivUrl)); for (const file of derivative.files) { const fileUrl = baseUrl + ‘/derivatives/’ + encodeURIComponent(derivative.basePath + file); fetches.push(fetch(fileUrl, options).then(resp => cache.put(fileUrl, resp)).then(() => fileUrl)); } } // Fetch and cache all URLs in parallel const urls = await Promise.all(fetches); return urls;}async function clearUrn(urn) { console.log(‘Clearing cache’, urn); const cache = await caches.open(CACHE_NAME); const requests = (await cache.keys()).filter(req => req.url.includes(urn)); await Promise.all(requests.map(req => cache.delete(req))); return requests.map(req => req.url);}async function listCached() { console.log(‘Listing caches’); const cache = await caches.open(CACHE_NAME); const requests = await cache.keys(); return requests.map(req => req.url);}And that’s pretty much it. If you want to see this code in action, head over to https://forge-offline.herokua… with your favorite (modern) browser, open the dev. tools, and try caching on of the listed models using the ☆ symbol next to their title. ...

January 18, 2019 · 3 min · jiezi

同步浏览多个视图

有些时候,我们需要分屏同时查看同一个模型,或者同时查看不同的模型,这里有两种方式参考。方法一. 使用Forge Viewer 6.2 开始 提供的Autodesk.SplitScreen扩展。该扩展可以在同一个Viewer实例中创建2-4个视口 (ViewerPort),减少Viewer的实例和内存消耗,而且提供了漫游同步等功能。使用很简单,如果是分屏同时查看同一个模型,先加载模型,然后启动该扩展:Viewer.loadExtension(‘Autodesk.SplitScreen’).then(function(res){ //如果想拿到扩展的句柄 splitExt = res})默认分成两个屏 如果是3个或4个视口,在启动此扩展的时候,传入如下选项:var options = { viewports: [ function(id) { return id === 1; }, function(id) { return id === 1; }, function(id) { return id === 1; }, function(id) { return id === 1; } ] };viewer.loadExtension(‘Autodesk.SplitScreen’, options);其中1是该模型在viewer中的model序号如果不同视口查看不同的模型,则需要先加载多个模型(参考关于模型聚合的资料),然后再传入参数中指定哪个视口用哪个模型:var options = { viewports: [ function(id) { return id === 1; }, function(id) { return id === 2; }, function(id) { return id === 3; }, function(id) { return id === 4; } ] };viewer.loadExtension(‘Autodesk.SplitScreen’, options);虽然分屏的扩展比较方便,但从测试发现,当前选择对象的时候有些问题,另外,也未看到能禁止同步浏览,让不同的视口有不同的视角。要达到这个目的,可以考虑方法2.方法二: 在多个Viewer实例中,分别加载模型,由于是独立的,控制不同的视角就很方便了。而假设需要viewer进行同步,则可以监控多个Viewer相机事件,当某一个发生变化,根据其参数操作其它Viewer的相机。至于选择对象,也类似,监控双方的选择集事件,相互操作。 由于Viewer的State具备了相机以及其它信息,用来操作更为简便。有一点要注意:假设Viewer1的相机事件发生,驱动Viewer2的相机事件,但这时Viewer2的相机事件又触发了,又会驱动Viewer2的状态,这就会产生死循环。我没看到Viewer中提供方法判断哪个是当前激活窗口或哪个在进行视图操作,但起码可以对Viewer各自的DIV判断鼠标的聚焦和移除,因此加上一些标志能解决。或许还有更好的办法.以下代码示意假设两个Viewer都加载模型完毕,并在适当的时候委托了相机事件。 var viewer1; var viewer2; //鼠标在Viewer1 var isNavigatingViewer1 = false; //鼠标在Viewer2 var isNavigatingViewer2 = false; $(document).ready(function () { $( “#MyViewerDiv1” ) .mouseenter(function() { isNavigatingViewer1 = true; }) .mouseleave(function() { isNavigatingViewer1 = false; }) $( “#MyViewerDiv2” ) .mouseenter(function() { isNavigatingViewer2 = true; }) .mouseleave(function() { isNavigatingViewer2 = false; }) }) //….. { // 适当的函数,确保两个Viewer都加载模型完毕 viewer1.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function() { if(isNavigatingViewer1) viewer2.restoreState(viewer1.getState()) //或只用相机 //viewer2.applyCamera(viewer1.getCamera()) }); viewer2.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function() { if(isNavigatingViewer2) viewer1.restoreState(viewer2.getState()) //或只用相机 //viewer1.applyCamera(viewer2.getCamera()) }); } ...

December 25, 2018 · 1 min · jiezi