I'm currently working on some Revit API code which is running in the Autodesk Forge Design Automation cloud solution. Basically, I'm trying to create a material and attach a texture to it via the following code:
private void AddTexturePath(AssetProperty asset, string texturePath) {
Asset connectedAsset = null;
if (asset.NumberOfConnectedProperties == 0)
asset.AddConnectedAsset("UnifiedBitmapSchema");
connectedAsset = (Asset) asset.GetConnectedProperty(0);
AssetPropertyString path = (AssetPropertyString) connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap);
if (!path.IsValidValue(texturePath)) {
File.Create("texture.png");
texturePath = Path.GetFullPath("texture.png");
}
path.Value = texturePath;
}
This is actually working well, as the value for the texture path:
path.Value = texturePath;
Needs to be a reference to an existing file. I do not have this file on the cloud instance of Forge, because the path to the texture name is specified by the user when he sends the request for the Workitem.
The problem is that this sets the texture path for the material as something like this:
T:\Aces\Jobs\<workitem_id>\texture.png
Which is basically the working folder for the Workitem instance. This path is useless, because a material with texture path like this needs to be manually re-linked in Revit.
The perfect outcome for me would be if I could somehow map the material texture path to some user-friendly directory like "C:\Textures\texture.png" and it seems that the Forge instance has a "C:\" drive present (being probably a Windows instance of some sorts), but my code runs on low privileges, so it cannot create any kind of directories/files outside the working directory.
Does somebody has any idea how this could be resolved? Any help would be greatly appreciated!
After a whole day of research I pretty much arrived at a satisfying solution. Just for clarity - I am going to reference to Autodesk Forge Design Automation API for Revit, simply as "Forge".
Basically the code provided above is correct. I did not find any possible way to create a file on Forge instance, in a directory different than the Workitem working directory which is:
T:\Aces\Jobs\<workitem_id>\texture.png
Interestingly, there is a C:\ drive on the Forge instance, which contains Windows, Revit and .NET Framework installations (as Forge instance is basically some sort of Windows instance with Revit installed). It is possible to enumerate a lot of these directories, but none of the ones I've tried (and I've tried a lot - mostly the most obvious, public access Windows directories like C:\Users\Public, C:\Program Files, etc.) allow for creation of directories or files. This corresponds to what is stated in "Restrictions" area of the Forge documentation:
Your application is run with low privileges, and will not be able to freely interact with the Windows OS :
Write access is typically restricted to the job’s working folder.
Registry access is mostly restricted, writing to the registry should be avoided.
Any sub-process will also be executed with low privileges.
So after trying to save the "dummy" texture file somewhere on the Forge C:\ drive, I've found another solution - the texture path for your texture actually does not matter.
This is because Revit offers an alternative for re-linking your textures. If you fire up Revit, you can go to File -> Options -> Rendering, and under "Additional render appearance paths" field, you can specify the directories on your local machine, that Revit can use to look for missing textures. With these, you can do the following operations in order to have full control on creating materials on Forge:
Send Workitem to Forge, create the materials.
Create a dummy texture in working directory, with the correct file name.
Attach the dummy texture file to the material.
Output the resulting file (.rvt or .rfa, depending on what you're creating on Forge).
Place all textures into one folder (or multiple, this doesn't matter that much).
Add the directories with the textures to the Additional render apperance paths.
Revit will successfully re-link all the textures to new paths.
I hope someone will find this useful!
Additionally, as per Jeremy request, I post a code sample for creating material with texture and modifying different Appearance properties in Revit by using Revit API (in C#):
private void SetAppearanceParameters(Document project, Material mat, MaterialData data) {
using(Transaction setParameters = new Transaction(project, "Set material parameters")) {
setParameters.Start();
AppearanceAssetElement genericAsset = new FilteredElementCollector(project)
.OfClass(typeof(AppearanceAssetElement))
.ToElements()
.Cast < AppearanceAssetElement > ().Where(i = >i.Name.Contains("Generic"))
.FirstOrDefault();
AppearanceAssetElement newAsset = genericAsset.Duplicate(data.Name);
mat.AppearanceAssetId = newAsset.Id;
using(AppearanceAssetEditScope editAsset = new AppearanceAssetEditScope(project)) {
Asset editableAsset = editAsset.Start(newAsset.Id);
AssetProperty assetProperty = editableAsset["generic_diffuse"];
SetColor(editableAsset, data.MaterialAppearance.Color);
SetGlossiness(editableAsset, data.MaterialAppearance.Gloss);
SetReflectivity(editableAsset, data.MaterialAppearance.Reflectivity);
SetTransparency(editableAsset, data.MaterialAppearance.Transparency);
if (data.MaterialAppearance.Texture != null && data.MaterialAppearance.Texture.Length != 0)
AddTexturePath(assetProperty, $#"C:\{data.MaterialIdentity.Manufacturer}\textures\{data.MaterialAppearance.Texture}");
editAsset.Commit(true);
}
setParameters.Commit();
}
}
private void SetTransparency(Asset editableAsset, int transparency) {
AssetPropertyDouble genericTransparency = editableAsset["generic_transparency"] as AssetPropertyDouble;
genericTransparency.Value = Convert.ToDouble(transparency);
}
private void SetReflectivity(Asset editableAsset, int reflectivity) {
AssetPropertyDouble genericReflectivityZero = (AssetPropertyDouble) editableAsset["generic_reflectivity_at_0deg"];
genericReflectivityZero.Value = Convert.ToDouble(reflectivity) / 100;
AssetPropertyDouble genericReflectivityAngle = (AssetPropertyDouble) editableAsset["generic_reflectivity_at_90deg"];
genericReflectivityAngle.Value = Convert.ToDouble(reflectivity) / 100;
}
private void SetGlossiness(Asset editableAsset, int gloss) {
AssetPropertyDouble glossProperty = (AssetPropertyDouble) editableAsset["generic_glossiness"];
glossProperty.Value = Convert.ToDouble(gloss) / 100;
}
private void SetColor(Asset editableAsset, int[] color) {
AssetPropertyDoubleArray4d genericDiffuseColor = (AssetPropertyDoubleArray4d) editableAsset["generic_diffuse"];
Color newColor = new Color((byte) color[0], (byte) color[1], (byte) color[2]);
genericDiffuseColor.SetValueAsColor(newColor);
}
private void AddTexturePath(AssetProperty asset, string texturePath) {
Asset connectedAsset = null;
if (asset.NumberOfConnectedProperties == 0) asset.AddConnectedAsset("UnifiedBitmapSchema");
connectedAsset = (Asset) asset.GetConnectedProperty(0);
AssetProperty prop = connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap);
AssetPropertyString path = (AssetPropertyString) connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap);
string fileName = Path.GetFileName(texturePath);
File.Create(fileName);
texturePath = Path.GetFullPath(fileName);
path.Value = texturePath;
}
Related
I want to get the path of an existing folder SeleniumTestData inside the solution.
Why? My selenium tests should create at start of the test, temporary folder which are being ignored in Git, so each of my colleagues has their own TestData folders for their own TestExecutions on their machine (Like Save/Load Cookies) and dont pull TestData from other colleagues.
The folder where i want to create other folder by code is named SeleniumTestData folder and is inside:
..\source\repos\CoffeeTalk\src\Tests
I cant hardcore the path, as i'm facing here 2 problems:
Tests are being ran in Windows and Docker (Linux)
Co-Workers are saving the solution in different windows directories
Now i need a general solution which will work in any of these cases.
I already tried: var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
which returned: D:\source\repos\CoffeeTalk\src\Tests\Web\CoffeeTalk.Client.Selenium.Tests\bin\Debug\net6.0
and then tried to navigate back by executing the codeline currentDirectory?.Parent about 5-6 times. But its then again different in Linux.
Now im looking for a clean way. I suppose the first way i did it was not wrong by getting the CurrentDirectory and navigate back.
I already searched for solutions using stackoverflow, google. Either the solutions are outdated or im not getting the result im expecting.
Here i have the method which creates the folder, but im struggling with the GetFolderPath method.
public static void CreateFolder(string folderName, string newFolderName)
{
var folderPath = GetFolderPath(folderName);
var pathCombined = Path.Combine(folderPath, newFolderName);
var folderExists = Directory.Exists(pathCombined);
if (folderExists) return;
Directory.CreateDirectory(pathCombined);
}
Directory.GetCurrentDirectory isn't the directory with your executable file. It's something else (I don't know) that by the way depends on the OS. You should use this instead:
using System.Reflection;
// ...
string exeDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
And then go up the folders hierarchy as you want:
string neededFolder = new DirectoryInfo(exeDirectory).Parent.Parent.Parent.ToString(); // Or more "Parent" calls
As far as I know, it works on different OSs.
I am developing a Chess Game in Unity3D. I want to develop it for the Android platform. For AI I am using the Stockfish Chess Engine. I downloaded the Stockfish binary for Android named "Stockfish-9-armv7". I placed this binary file in my StreamingAssets folder so that it correctly goes into the targeted platform during build step. Everything works fine till here i.e. when I build my Unity project the file is placed in the correct location and I can very well see it.
Now in order for my AI to work I have to communicate with this binary file using UCI protocols. And so I wrote a C# script in my Unity project that creates a process to run the binary file and communicate with it. But this is not working.
However when I do the exact same thing for Windows i.e. using the windows binary version of Stockfish named "stockfish_9_x64.exe" and building it as a standalone application, things work perfectly and I am able to communicate with the engine via my C# code.
I researched it online but unable to find much resources and guidance. I found a similar post and reading through it led me conclude that maybe it has something to do with file permissions. The guy who asked this question actually solved the issue by writing these two lines of code:
string[] cmd = { "chmod", "744", Path.Combine(strToFolder, fileName) };
Java.Lang.Runtime.GetRuntime().Exec(cmd);
However he was using Xamarin and had access to Java Runtime library. I am using Unity and C# and I really don't know how to change the execute/run permission of this binary file and run it. In fact I don't even know whether this is the problem or not.
I just want to integrate stockfish into my Unity project with Android as the targeted platform. If anyone has any ideas, suggestions or if anyone has done this before, please guide me. Even if I am wrong from the start and my approach is buggy let me know that as well along with the corrected approach.
Given below is my code:
public class CommunicateWithEngine {
public static Process mProcess;
public static void Communicate()
{
// since the apk file is archived this code retreives the stockfish binary data and
// creates a copy of it in the persistantdatapath location.
string filepath = Application.persistentDataPath + "/" + "Stockfish-9-armv7";
if (!File.Exists(filepath))
{
WWW executable = new WWW("jar:file://" + Application.dataPath + "!/assets/" + "Stockfish-9-armv7");
while (!executable.isDone)
{
}
File.WriteAllBytes(filepath, executable.bytes);
}
// creating the process and communicating with the engine
mProcess = new Process();
ProcessStartInfo si = new ProcessStartInfo()
{
FileName = System.IO.Path.Combine(Application.persistentDataPath, "Stockfish-9-armv7"),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
};
mProcess.StartInfo = si;
mProcess.OutputDataReceived += new DataReceivedEventHandler(MProcess_OutputDataReceived);
mProcess.Start();
mProcess.BeginErrorReadLine();
mProcess.BeginOutputReadLine();
SendLine("uci");
SendLine("isready");
}
private static void SendLine(string command) {
mProcess.StandardInput.WriteLine(command);
mProcess.StandardInput.Flush();
}
private static void MProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
string text = e.Data;
Test.PrintStringToTheConsole(text);
}
}
For anyone struggling with the same problem as OP and me. After 12 hours searching and googling here is a solution. You can use executable stockfish binary, but you have to change extension for binary to .so and import it in unity in folder /Assets/Plugins/Android. After build you can find it on device in folder /data/data//lib. Here you can execute it and get input and output.
I'm fairly certain I'm either doing something wrong, or understanding something wrong. It's hard to give a piece of code to show my problem, so I'm going to try explaining my scenario, with the outcome.
I'm starting up several instances of a DLL, in the same console application, but in it's own app domain. I then generated a Guid.NewGuid() that I assign to a class in the instance, and set the application's folder to a new folder. This works great so far. I can see everything works great, and my instances are seperated. However... when I started changing my app's folder to the same name as the unique GUID generated for that class I started picking up anomolies.
It works fine, when I instantiate the new instances slowly, but when I hammer new ones in, the application started picking up data in its folder, when it started up. After some investigation, I found that its because that folder already exist, due to that GUID already being instantiated. On further investigation, I can see that the machine takes a bit of a pause, and then continues to generated the new instances, all with the same GUID.
I understand that the GUID generating algorithm uses the MAC as part of it, but I was under the impression that even if the same machine, at the same exact moment generates two GUIDs, it would still be unique.
Am I correct in that statement? Where am I wrong?
Code :
Guid guid = Guid.NewGuid();
string myFolder = Path.Combine(baseFolder, guid.ToString());
AppDomain ad = AppDomain.CurrentDomain;
Console.WriteLine($"{ad.Id} - {guid.ToString()}");
string newHiveDll = Path.Combine(myFolder, "HiveDriveLibrary.dll");
if (!Directory.Exists(myFolder))
{
Directory.CreateDirectory(myFolder);
}
if (!File.Exists(newHiveDll))
{
File.Copy(hiveDll, newHiveDll);
}
Directory.SetCurrentDirectory(myFolder);
var client = ServiceHelper.CreateServiceClient(serviceURL);
ElementConfig config = new ElementConfig();
ElementConfig fromFile = ElementConfigManager.GetElementConfig();
if (fromFile == null)
{
config.ElementGUID = guid;
config.LocalServiceURL = serviceURL;
config.RegisterURL = registerServiceURL;
}
else
{
config = fromFile;
}
Directory.SetCurrentDirectory is a thin wrapper atop the Kernel 32 function SetCurrentDirectory.
Unfortunately, the .NET documentation writers didn't choose to copy the warning from the native function:
Multithreaded applications and shared library code should not use the SetCurrentDirectory function and should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value
It's your reliance on this function that's creating the appearance that multiple threads have magically selected exactly the same GUID value.
I found this page where there is an example for extract cell information from an Insight-Explorer but... what about to write into the cell from a c# application?
C# extract Cell Information from In-Sight Explorer (Cognex)
If you want to control the cells in the current job file of an In-Sight camera using C#, here is the method that has worked for me. Please note, I'm using In-Sight Explorer v5.9.0, but I've tested this on earlier versions too and it still works.
NOTE: Without a Cognex In-Sight SDK license, you will not be able to run this application within Visual Studio. You will have to build the project and then run the executable directly.
Open Visual Studio
Create a new Console App (.NET Framework) project
I'm using .NET Framework 4.7.2
Right-click on the project in the solution explorer and add a reference to the Cognex.InSight.dll file (It is typically located here: C:\Program Files (x86)\Common Files\Cognex\In-Sight\5.x.x.x\Cognex.InSight.dll)
Set the target platform to x86
Paste the code, below, into your project
Change the username, password and ipAddress variables to match what's setup for your camera
Build
Go to the Debug folder and find the executable that was created after building the project
Double-click the executable to run it
using System;
using Cognex.InSight;
namespace ChangeInSightCellValue2
{
class Program
{
static void Main(string[] args)
{
string username = "admin";
string password = "";
string ipAddress = "127.0.0.1";
// Create camera object and connect to it
CvsInSight camera = LogIntoCamera(ipAddress, username, password, true, false);
// Define which cell you want to modify
CvsCellLocation cell = new CvsCellLocation(2, 'C');
// Modify the cell expression
camera.SetExpression(cell, "Filter($A$0,0,0,0,80,100,320,440,0,0,3,3,1,128,128,128,1,1,0)", true);
}
// Log into camera
private static CvsInSight LogIntoCamera(string sCamIP, string sCamUsername, string sCamPassword, bool forceConnect, bool connectAsynchronous)
{
// Create camera object
CvsInSight insight = new CvsInSight();
Console.WriteLine("Object created");
IAsyncResult result;
// Try logging into the camera on a different thread to prevent locking this one up
Action action = () =>
{
// Connect to camera
insight.Connect(sCamIP, sCamUsername, sCamPassword, forceConnect, connectAsynchronous);
};
result = action.BeginInvoke(null, null);
if (result.AsyncWaitHandle.WaitOne(5000))
return insight;
else
return insight;
}
}
}
NOTE: If you're connected to the camera with In-Sight Explorer when you run this application, the In-Sight Explorer will disconnect from the camera and then try to reconnect after your application has disconnected from the camera.
You could use Native Mode Commands to set the value of controls in the spreadsheet in In-Sight Explorer (as discussed in the question you linked to). Note that you won't be able to write data to any cell - you will only be able to write to cells containing EditInt(), EditFloat(), EditString(), CheckBox(), etc functions. Send the commands as text over a socket connection to the cameras port 23. You will need to send a username and password to the camera when the connection is established.
If you're using the Cognex SDK, use the following functions
CvsInSight.SetFloat(...) to set EditFloat control values
CvsInSight.SetInteger(...) to set EditInt control values
CvsInSight.SetListBoxIndex(...) to select items in list boxes
CvsInSight.SetString(...) to set EditString control values
CvsInSight.SetCheckBox(...) to change the state of CheckBox controls
I have a lot of regularly updating static content that is made available via HTTP through IIS as a Virtual Directory. I have a C# application that updates this static content. The static content represents a matching set.
This content changes regularly and is validated before being made available to clients. I am currently doing a Directory Copy using this code but it is a bit brute force.
The content has a manifest file with version information. I know I can update the manifest file last but I don't want to pull the rug from under clients that are already downloading older content and leave them with a dirty set of files.
What is the recommended way to do a folder replace so that existing clients don't get a mixed up version of the file set? This must be common but I cannot find any libraries or best practice guidance to do this.
I've looked things like rsync for Windows and other backup/restore style tools but they all seem like overkill and generally don't have an API.
I ended up using the Microsoft Sync Framework. It worked out reasonably well. There are still a few bugs. Here's a good intro to the framework.
This is the significant part of my implementation.
public static bool DirectorySynchronisation(string sourceFiles, string targetFiles)
{
Trace.Info("Beginning Directory Sync");
try
{
Trace.Info("... between source location: {0} and targeted location: {1}", sourceFiles, targetFiles);
//Exclude metadata from sync.
var fileSyncScopeFilter = new FileSyncScopeFilter();
fileSyncScopeFilter.FileNameExcludes.Add("metadata.abc");
// Create file system provider
var source = new FileSyncProvider(Guid.NewGuid(), sourceFiles, fileSyncScopeFilter, FileSyncOptions.None);
var target = new FileSyncProvider(Guid.NewGuid(), targetFiles);
// Ask providers to detect changes
source.DetectChanges();
target.DetectChanges();
// Sync changes
SyncOrchestrator agent = new SyncOrchestrator
{
LocalProvider = source,
RemoteProvider = target,
Direction = SyncDirectionOrder.Upload //One way only
};
agent.Synchronize();
Trace.Info("Completed Directory Sync");
return true;
}
catch (Exception exception)
{
Trace.Info("Exception thrown while syncing files");
Trace.Exception(exception);
return false;
}
}
Hope this helps someone.