Revit External application running outside of API context is not allowed - c#

I'm trying to start a transaction inside my code but, as you can see in the title, it generate an error. I have reading many thread on External Application but don't correctly know how it work.
This is the code I use to start my transaction :
inside my form :
private void Loading_FA_only()//Lance l'import en boucle suivant les items dans la file d'attente
{
foreach(FileInfo fi in selected_rfas_donnees)
{
Loading_Family.famille = fi;
Loading_Family lf = new Loading_Family();
lf.Execute(Command.uiapplication);
Listing_succes(Loading_Family.succes, fi);
}
selected_rfas.Clear();
selected_rfas_donnees.Clear();
Update_Compteur();
}
and that what I try to execute :
public class Loading_Family : IExternalEventHandler
{
public static FileInfo famille;
public static bool succes;
public void Execute(UIApplication uiapp)
{
UIDocument uidoc = uiapp.ActiveUIDocument;
Application app = uiapp.Application;
Document doc = uidoc.Document;
// Access current selection
Selection sel = uidoc.Selection;
// Retrieve elements from database
FilteredElementCollector col
= new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.OfCategory(BuiltInCategory.INVALID)
.OfClass(typeof(Wall));
// Filtered element collector is iterable
foreach (Element e in col)
{
Debug.Print(e.Name);
}
// Modify document within a transaction
succes = false;
string nom_famille = famille.Name.Remove(famille.Name.Length - 4, 4);
FilteredElementCollector familles_doc = new FilteredElementCollector(doc).OfClass(typeof(Family));
foreach (Element elmt in familles_doc)
{
if (elmt.Name == nom_famille)
{
var result = TaskDialog.Show("CPF - Importation", "Il semblerait que " + nom_famille + " soit déjà présent dans votre projet.\nVoullez-vous le remplacer ?", TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No);
if (result == TaskDialogResult.Yes)
{
using (Transaction tr = new Transaction(doc, "Importer la famille"))
{
tr.Start();
doc.LoadFamily(famille.FullName);
tr.Commit();
tr.Dispose();
}
}
}
else
{
using (Transaction tr = new Transaction(doc, "Importer la famille"))
{
tr.Start();
doc.LoadFamily(famille.FullName);
tr.Commit();
tr.Dispose();
}
}
}
familles_doc = new FilteredElementCollector(doc);
foreach (Element elmt in familles_doc)
{
if (elmt.Name == nom_famille) { succes = true; }
else { succes = false; }
}
using (Transaction tx = new Transaction(doc))
{
tx.Start("Transaction Name");
tx.Commit();
}
}
public string GetName()
{
return "my event";
}
}
I'm desperate with this. I absolutely don't know how theirs "ExternalEventHandler" or "ExternalApplication" work.
Thank for help :)

It is very simple. Use external events and make sure the external event is declared inside the constructor of MyForm. Personally I've never used ShowDialog because it blocks the user access to the rest of the UI.

Please work through the Revit API getting started material. That explains all the required fundamentals of the Revit API and its architecture, including how external applications and commands are implemented and used.
Please note that the Revit API cannot be used at all outside of a valid Revit API context, and such a context is provided exclusively by the callbacks issues by Revit.exe while running and loading a Revit add-in.
Therefore, the statement you make in your description is expected and desired: a Revit external application can never run outside of a valid Revit API context and is therefore indeed not allowed.
Please spell check your texts before submitting them, especially the title, since typos such as the one in your description make it harder to find an issue.

Related

Qlik Desktop - Custom Connector - Error On ExtractQuery and other questions

I'm trying to develop a simple custom connector to Qlik, where I Get the data from a source specific, load the data in the Qlik and extract the data according own query. However, I found some problems and question, that I describe bellow:
I pass the field, tableName and data for the connection like in the bellow codes, but when I click on "Load Data", the error "Object reference not defined as an instance of an object" is shown, how if the tables in the "QvAditiConnection.Init()" had not been defined. Bellow, the codes:
public QvAditiConectorConnection(QvxConnection connection)
{
try
{
_logApp = new LogApp();
_util = new ClsUtil();
_nomeTabela = " ";
_tipoConexao = " ";
_connectionString = " ";
_parameters = " ";
if (connection != null && connection.MParameters != null && connection.MParameters.Count > 0)
{
this.MParameters = connection.MParameters;
}
GetParametersFromConnection();
if (string.IsNullOrWhiteSpace(_parameters) == false)
{
_connectionStringParameters = _util.RecuperaParametrosConnectionString(_parameters);
_tipoConexao = _connectionStringParameters[0];
string connectionString = GetConnectionString();
if (TestarConexao(connectionString))
{
_connectionString = connectionString;
StartConnection();
}
else
{
_logApp.CriarLog("ERRO na conexão com o banco de dados.");
}
}
else
{
QvxLog.Log(QvxLogFacility.Audit, QvxLogSeverity.Error, "Init() Erro de conexão. Verifique os dados ou o servidor.");
}
Init();
}
catch (Exception ex)
{
_logApp = new LogApp();
if (string.IsNullOrWhiteSpace(ex.Message) == false)
{
QvxLog.Log(QvxLogFacility.Audit, QvxLogSeverity.Error, "Init() Erro: " + ex.Message);
_logApp.CriarLog("ERRO Constructor: " + ex.Message);
}
else
{
QvxLog.Log(QvxLogFacility.Application, QvxLogSeverity.Notice, "Init() Erro Desconhecido");
_logApp.CriarLog("ERRO não identificado");
}
}
}
public override void Init()
{
try
{
if (string.IsNullOrWhiteSpace(_parameters) == false)
{
QvxLog.SetLogLevels(true, true);
QvxLog.Log(QvxLogFacility.Application, QvxLogSeverity.Notice, "Init()");
List<QvxTable> tabelas = new List<QvxTable>();
DataTable schemaTables = _connectionPostGreSqlStaging.GetSchema("Tables");
foreach (DataRow row in schemaTables.Rows)
{
_nomeTabela = (string)row[2];
_dataTableStagingArea = new DataTable(_nomeTabela);
GetDataReader();
QvxTable dadosTable = new QvxTable();
dadosTable.TableName = _nomeTabela;
dadosTable.Fields = _qvxFields;
dadosTable.GetRows = GetDataRowsConnector;
tabelas.Add(dadosTable);
}
this.MTables = tabelas;
}
}
How can you see below, the connection is correct in this case, because the application gets and shows the tables and fields in the correct form:
Select Dialog
But, after I click in "Insert Script" and "Load Data", The error below is shown, how if the tables in the application has not been defined:
Error on click in load data button
The "ExtractQuery" is exact how bellow, and when I debug the code and use the "Add Watch" in parameter "qvxTables", I see that he's null and not contains Tables:
public IEnumerable<QvxDataRow> GetDataRowsConnector()
{
DataTable dadosTabela = _dataTableStagingArea;
foreach (var item in dadosTabela.Rows)
{
yield return MakeEntry(item as DataRow, FindTable(_nomeTabela, MTables));
}
}
public QvxDataRow MakeEntry(DataRow item, QvxTable table)
{
try
{
var row = new QvxDataRow();
for (int i = 0; i < _dataTableStagingArea.Columns.Count; i++)
{
var field = table.Fields.Where(a => a.FieldName == _dataTableStagingArea.Columns[i].ColumnName)
.Select(b => b).FirstOrDefault();
row[field] = item[field.FieldName].ToString();
}
return row;
}
catch (Exception ex)
{
throw new Exception(ex + "MakeEntry()");
}
}
In this case, when the Method "ExtractQuery" is called, the tables some times came, but the data don't came in the communication, similar to error 1. And in this case 2, when I debug step by step and arrive in the line "
dadosTable.GetRows = GetDataRowsConnector;" the debug don't enter in the"GetDataRowsConnector" or in the "MakeEntry", even when I insert breakpoints in the methods or press "F11" to use "Step Into".
How I show the preview data in select dialog(I use the QvxSdk in this case)? Even using the getPreview, I don't get the data and don't see example in the documentation. If do you have suggestions or examples for me, I appreciate if you share. And How I edit and select the fields to show in this window(How show or not Metadada, Selection Summary etc)? And how I insert a logo in the connector, like the connectors like "Oracle", "PostGreSql" etc?
select dialog with highlighted data area preview
When I click in the "Insert Script", I want to send a blank script to the editor, ao invés de send the script "Load [FIELD] SQL SELECT * FROM ...", and ao mesmo tempo get this script that the Qlik genereta automactilly in my backend, It's possible do this?
Wrong Script Editor Area
Correct Script Editor Area
Remember that I use the QvxSdk for this solution, based on the "Simple Example" provided for Qlik, but I'm open for suggestion for others APIs if is the case for answer my questions. I use too the .Net Framework 4.5 to develop the connector
Thank you in advance for your attention and help.
Thanks!

C# revit api to Create panels in Revit

I am new to revit api . I want to create a panel in revit empty project. I can create walls . I want to know how can I add panel data (studs , panel name , openings etc ) in the wall . Also I am new to construction terminologies.
here is my code:
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Document doc = uiapp.ActiveUIDocument.Document;
WallType w = getWallType(doc);
RetrievingLevels(doc);
Level newLevel = CreateLevels(doc);
if(newLevel != null)
{
IList<Curve> curves = new List<Curve>();
XYZ first = new XYZ(0, 0, 0);
XYZ second = new XYZ(20, 0, 0);
XYZ third = new XYZ(20, 0, 15);
XYZ fourth = new XYZ(0, 0, 15);
curves.Add(Line.CreateBound(first, second));
curves.Add(Line.CreateBound(second, third));
curves.Add(Line.CreateBound(third, fourth));
curves.Add(Line.CreateBound(fourth, first));
//Line l = Line.CreateBound(a1, b1);
Transaction trans = new Transaction(doc);
try
{
trans.Start("create walls");
Wall.Create(doc, curves, w.Id,newLevel.Id, true);
trans.Commit();
return Result.Succeeded;
}
catch (Exception ex)
{
trans.Dispose();
return Result.Failed;
}
}
return Result.Failed;
//IList<Curve> curves = new List<Curve>();
}
private WallType getWallType(Document doc)
{
FilteredElementCollector collector = new FilteredElementCollector(doc).OfClass(typeof(WallType));
IList<Element> WallTypes = collector.ToElements();
return WallTypes.First() as WallType;
}
private Level CreateLevels(Document document)
{
double elevation = 33.0;
Transaction t = new Transaction(document);
// Begin to create a level
t.Start("create Level");
try
{
Level level = Level.Create(document, elevation);
if (null == level)
{
throw new Exception("Create a new level failed.");
}
// Change the level name
level.Name = "New level";
t.Commit();
return level;
}
catch (Exception e)
{
}
return null;
}
Also please suggest me from where can I learn about revit APIS .
Well, it will help a lot to understand Revit and BIM from a user point of view before you try to start programming it, cf. before getting started. Then, you should work through the rest of the Revit API getting started material.
Next, you should learn how to research to find a Revit API solution for yourself.
The first step is always definitely to install the RevitLookup interactive Revit BIM database exploration tool to view and navigate element properties and relationships.
With that in place, you can start by manually creating the model that you wish to generate programmatically in the user interface first. Explore that using RevitLookup and other tools such as BipChecker and the element lister to discover what kind of elements have been generated by the manual process and their properties and relationships.
Once you understand all that, you will be all set to generate the same model programatically using the Revit API.
Good luck and have fun!

Is it possible to edit block attributes in AutoCAD using Autodesk.AutoCAD.Interop?

I have developed an external WPF application to generate drawings in c#. I have been able to draw, dimension, add blocks and every thing else required by the application using Autodesk.AutoCAD.Interop, however I can't seem to populate The title block, or generate a parts list.
All the examples I've seen are based on the mechanism that requires the application to run as a plugin inside AutoCAD. The truth is, inserting a line used is one or two lines of code using ModelSpace.InsertLine, now, it's at least 8 lines of code!
Is there a way to achieve this functionality using the Autodesk.AutoCAD.Interop? Or is there a way to combine using the interop with a plugin that can be called from the external exe?
Any pointers on this will be appreciated.
Thanks.
EDIT
To illustrate:
// before - Draw Line with Autodesk.AutoCAD.Interop
private static AcadLine DrawLine(double[] startPoint, double[] endPoint)
{
AcadLine line = ThisDrawing.ModelSpace.AddLine(startPoint, endPoint);
return line;
}
// Now - Draw line with Autodesk.AutoCAD.Runtime
[CommandMethod("DrawLine")]
public static Line DrawLine(Coordinate start, Coordinate end)
{
// Get the current document and database
// Get the current document and database
Document acDoc = Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
// Start a transaction
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
{
// Open the Block table for read
BlockTable acBlkTbl;
acBlkTbl = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead) as BlockTable;
// Open the Block table record Model space for write
BlockTableRecord acBlkTblRec;
acBlkTblRec = acTrans.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
// Create a line that starts at 5,5 and ends at 12,3
Line acLine = new Line(start.Point3d, end.Point3d);
acLine.SetDatabaseDefaults();
// Add the new object to the block table record and the transaction
acBlkTblRec.AppendEntity(acLine);
acTrans.AddNewlyCreatedDBObject(acLine, true);
// Save the new object to the database
acTrans.Commit();
return acLine;
}
}
Yes, you can absolutely combine the two approaches.
Write an in-process DLL that does the work in AutoCAD. Make the commands you wish to call available to the command line by flagging your public methods with [CommandMethod("MethodName")].
Get AutoCAD started or connected via interop calls.
Using the interop AcadApplication, netload your DLL, and then call your work functions from the command line.
*Bonus * You can pass interop parameters to internal commands much easier this way too.
Here's an example of how you could build a commandmethod in-process and then call it via COM:
[CommandMethod("EditBlockAtt")]
public void EditBlockAtt()
{
var acDb = HostApplicationServices.WorkingDatabase;
var acEd = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
var blockNamePrompt = acEd.GetString(Environment.NewLine + "Enter block name: ");
if (blockNamePrompt.Status != PromptStatus.OK) return;
var blockName = blockNamePrompt.StringResult;
var attNamePrompt = acEd.GetString(Environment.NewLine + "Enter attribute name: ");
if (attNamePrompt.Status != PromptStatus.OK) return;
var attName = attNamePrompt.StringResult;
var acPo = new PromptStringOptions(Environment.NewLine + "Enter new attribute value: "){ AllowSpaces = true };
var newValuePrompt = acEd.GetString(acPo);
if (newValuePrompt.Status != PromptStatus.OK) return;
var newValue = newValuePrompt.StringResult;
using (var acTrans = acDb.TransactionManager.StartTransaction())
{
var acBlockTable = acTrans.GetObject(acDb.BlockTableId, OpenMode.ForRead) as BlockTable;
if (acBlockTable == null) return;
var acBlockTableRecord = acTrans.GetObject(acBlockTable[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
if (acBlockTableRecord == null) return;
foreach (var blkId in acBlockTableRecord)
{
var acBlock = acTrans.GetObject(blkId, OpenMode.ForRead) as BlockReference;
if (acBlock == null) continue;
if (!acBlock.Name.Equals(blockName, StringComparison.CurrentCultureIgnoreCase)) continue;
foreach (ObjectId attId in acBlock.AttributeCollection)
{
var acAtt = acTrans.GetObject(attId, OpenMode.ForRead) as AttributeReference;
if (acAtt == null) continue;
if (!acAtt.Tag.Equals(attName, StringComparison.CurrentCultureIgnoreCase)) continue;
acAtt.UpgradeOpen();
acAtt.TextString = newValue;
}
}
acTrans.Commit();
}
}
Then from an interop AcadApplication, netload the dll and call the method from the commandline in this format:
(Command "EditBlockAtt" "BlockName" "AttributeName" "NewValue")
However if you want to go pure Interop, this may get you what you need given you have an AcadDocument object at runtime:
foreach (AcadEntity ent in acadDoc.ModelSpace)
{
var block = ent as AcadBlockReference;
if (block == null) continue;
{
if (!block.Name.Equals("BlockName", StringComparison.CurrentCultureIgnoreCase)) continue;
var atts = block.GetAttributes() as object[];
if (atts == null) continue;
foreach (var attribute in atts.OfType<AcadAttributeReference>()
.Where(attribute => attribute.TagString.Equals("AttributeName",
StringComparison.CurrentCultureIgnoreCase)))
{
attribute.TextString = "New Value";
}
}
}
Also note this is using the AutoCAD 2012 Interop libraries. YMMV.

How to get Only one acadobject by selection set

I have some trouble to select the targeted acadObject. I get the input via selectionset.SelectonScreen method.
Here i can get more number of object from modelspace based on my filter condition.But i need only one object from the user.
Here i mentioned my code below:
AcadSelectionSet selset= null;
selset=currDoc.selectionset.add("Selset");
short[] ftype=new short[1];
object[] fdata=new object[1];
ftype[0]=2;//for select the blockreference
fdata[0]=blkname;
selset.selectOnScreen ftype,fdata; // Here i can select any no. of blocks according to filter value but i need only one block reference.
Please help me to solve this problem.
That's possible using other Autocad .NET libraries (instead of Interop library). But fortunately, one does not exclude the other.
You will need to reference the libraries containing the following namespaces:
using Autodesk.Autocad.ApplicationServices
using Autodesk.Autocad.EditorInput
using Autodesk.Autocad.DatabaseServices
(you get those downloading the Object Arx libraries for free from Autodesk):
You will need to access the Editor from an autocad Document.
By the code you've shown, you're probably working with AcadDocument documents.
So, to transform an AcadDocument into a Document, do this:
//These are extension methods and must be in a static class
//Will only work if Doc is saved at least once (has full name) - if document is new, the name will be
public static Document GetAsAppServicesDoc(this IAcadDocument Doc)
{
return Application.DocumentManager.OfType<Document>().First(D => D.Name == Doc.FullOrNewName());
}
public static string FullOrNewName(this IAcadDocument Doc)
{
if (Doc.FullName == "")
return Doc.Name;
else
return Doc.FullName;
}
Once you've got a Document, get the Editor, and the GetSelection(Options, Filter)
The Options contains a property SingleOnly and a SinglePickInSpace. Setting that to true does what you want. (Try both to see wich works better)
//Seleciton options, with single selection
PromptSelectionOptions Options = new PromptSelectionOptions();
Options.SingleOnly = true;
Options.SinglePickInSpace = true;
//This is the filter for blockreferences
SelectionFilter Filter = new SelectionFilter(new TypedValue[] { new TypedValue(0, "INSERT") });
//calls the user selection
PromptSelectionResult Selection = Document.Editor.GetSelection(Options, Filter);
if (Selection.Status == PromptStatus.OK)
{
using (Transaction Trans = Document.Database.TransactionManager.StartTransaction())
{
//This line returns the selected items
AcadBlockReference SelectedRef = (AcadBlockReference)(Trans.GetObject(Selection.Value.OfType<SelectedObject>().First().ObjectId, OpenMode.ForRead).AcadObject);
}
}
this is a direct quote from autocad developer help
http://docs.autodesk.com/ACD/2013/ENU/index.html?url=files/GUID-CBECEDCF-3B4E-4DF3-99A0-47103D10DADD.htm,topicNumber=d30e724932
There is tons of documentation the AutoCAD .NET API.
you will need to have
[assembly: CommandClass(typeof(namespace.class))]
above your namespace if you want to be able to invoke this command from the command line after you NetLoad the .dll, if it is a classLibrary.
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
[CommandMethod("SelectObjectsOnscreen")]
public static void SelectObjectsOnscreen()
{
// Get the current document and database
Document acDoc = Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
// Start a transaction
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
{
// Request for objects to be selected in the drawing area
PromptSelectionResult acSSPrompt = acDoc.Editor.GetSelection();
// If the prompt status is OK, objects were selected
if (acSSPrompt.Status == PromptStatus.OK)
{
SelectionSet acSSet = acSSPrompt.Value;
// Step through the objects in the selection set
foreach (SelectedObject acSSObj in acSSet)
{
// Check to make sure a valid SelectedObject object was returned
if (acSSObj != null)
{
// Open the selected object for write
Entity acEnt = acTrans.GetObject(acSSObj.ObjectId,
OpenMode.ForWrite) as Entity;
if (acEnt != null)
{
// Change the object's color to Green
acEnt.ColorIndex = 3;
}
}
}
// Save the new object to the database
acTrans.Commit();
}
// Dispose of the transaction
}
}

C# Inserting a new Requirement in HP Quality Center - AccessViolationException

Trying to create a prototype application that will post a new Requirement to HPQC 11.
I've managed to get a solid connection but when I attempt to add the blank requirement I get an AccessViolationException.
TDConnectionClass td = HPQC_Connect(); //Open a connection
ReqFactory myReqFactory = (ReqFactory)td.ReqFactory; //Start up the Requirments Factory.
Req myReq = (Req)myReqFactory.AddItem(DBNull.Value); //Create a new blank requirement (AccessViolationException)
myReq.Name = "New Requirement"; //Populate Name
myReq.TypeId = "1"; // Populate Type: 0=Business, 1=Folder, 2=Functional, 3=Group, 4=Testing
myReq.ParentId = 0; // Populate Parent ID
myReq.Post(); // Submit
Any ideas? I'm fairly new to C# and coding in general, so it's probably best to assume I know nothing.
After some significant working through the isse the following code works correctly:
private void HPQC_Req_Create_Click()
{
TDConnection td = null;
try
{
td = new TDConnection();
td.InitConnectionEx("server");
td.Login(HPQCUIDTextbox.Text.ToString(), HPQCPassTextbox.Text.ToString());
Console.WriteLine(HPQCPassTextbox.Text.ToString());
td.Connect("DEFAULT", "Test_Automation_Playground");
bool check = td.LoggedIn;
if (check == true)
{
Console.WriteLine("Connected.");
HPQCStatus.Text = "Connected.";
}
ReqFactory myReqFactory = (ReqFactory)td.ReqFactory;
Req myReq = (Req)myReqFactory.AddItem(-1); //Error Here
myReq.Name = "New Requirement 1";
myReq.TypeId = "1"; // 0=Business, 1=Folder, 2=Functional, 3=group, 4=testing
myReq.ParentId = 0;
myReq.Post();
Console.WriteLine("Requirement Created.");
HPQCStatus.Text = "Requirement Created.";
try
{
td.Logout();
td.Disconnect();
td = null;
}
catch
{ }
}
catch (Exception ex)
{
Console.WriteLine("[Error] " + ex);
try
{
td.Logout();
td.Disconnect();
td = null;
}
catch
{ }
}
This code requires that the Server be patched to QC 11 Patch 9 (Build 11.0.0.7274) in order to work. Previous versions cause errors, most notably the error in the question.
Requirements in ALM are hierarchical, when creating requirement you need to create it under some existing requirement.
What you want to do is get a hold of the root requirement, it's Id should be either 0 or 1, you can check it in ALM UI.
And then get an instance of ReqFactory from a property on that Root requirement.
And then add your requirement to that factory.
Also, make sure you are working on STA and not MTA thread.

Categories