import arcpy
import os
from collections import defaultdict
from arcpy import AddFieldDelimiters, Describe
import time
start = time.time()
# === Setup ===
gdb_path = r"C:\Users\Administrator\Desktop\Downloaded\QC02002-ESA-MOD-BIM-ARC-06-V06-00001 (1).gdb"
arcpy.env.workspace = gdb_path
arcpy.env.overwriteOutput = True
columns = os.path.join(gdb_path, "Spaces_BIMFileToGeodatab")
footprint_fc = "Columns_Footprint"
footprint_path = os.path.join(gdb_path, footprint_fc)
count_over = os.path.join(gdb_path, "count_over")
count_over_tab = os.path.join(gdb_path, "columns_tab")
singlePart = os.path.join(gdb_path, "singlePart")
arcpy.management.MultipartToSinglepart(columns, singlePart)
# === Geometry repair and footprint ===
arcpy.management.RepairGeometry(singlePart)
arcpy.ddd.MultiPatchFootprint(singlePart, footprint_path)
arcpy.analysis.CountOverlappingFeatures(footprint_path, count_over, "", count_over_tab)
# === Prepare ===
union_all = []
union_one = []
if arcpy.Exists(count_over_tab):
fields = [f.name for f in arcpy.ListFields(count_over_tab) if f.type != "OID"]
if len(fields) < 2:
print(" Table does not have at least two fields.")
else:
key_field = fields[0]
value_field = fields[1]
print(f"Using: key = {key_field}, value = {value_field}")
result_dict = defaultdict(list)
with arcpy.da.SearchCursor(count_over_tab, [key_field, value_field]) as cursor:
for key, value in cursor:
result_dict[key].append(value)
# === Get OID field and prepare layer ===
oid_field = Describe(singlePart).OIDFieldName
field_name = AddFieldDelimiters(singlePart, oid_field)
arcpy.MakeFeatureLayer_management(singlePart, "columns_layer")
# === Export all single-feature groups at once ===
single_oids = [str(oid_list[0]) for oid_list in result_dict.values() if len(oid_list) == 1]
if single_oids:
where_clause = f"{field_name} IN ({','.join(single_oids)})"
single_fc = os.path.join(gdb_path, "single_all")
arcpy.conversion.FeatureClassToFeatureClass(
in_features=singlePart,
out_path=gdb_path,
out_name="single_all",
where_clause=where_clause
)
print(f" Exported {len(single_oids)} single-feature groups.")
union_one.append(single_fc)
# === Process multi-feature groups using selections ===
for group_id, oid_list in result_dict.items():
if len(oid_list) > 1:
oid_values = ", ".join(str(oid) for oid in oid_list)
where_clause = f"{field_name} IN ({oid_values})"
arcpy.SelectLayerByAttribute_management("columns_layer", "NEW_SELECTION", where_clause)
group_fc = os.path.join(gdb_path, f"group_{group_id}")
arcpy.CopyFeatures_management("columns_layer", group_fc)
count_group = int(arcpy.management.GetCount(group_fc)[0])
print(f" Exported group {group_id} with {count_group} features.")
if count_group < 2:
print(f" Skipping group {group_id} — not enough features.")
continue
enclosed_fc = os.path.join(gdb_path, f"enclosed_{group_id}")
try:
arcpy.ddd.EncloseMultiPatch(group_fc, enclosed_fc)
print(f" Enclosed: {enclosed_fc}")
except Exception as e:
print(f" Enclose failed for group {group_id}: {e}")
continue
union_fc = os.path.join(gdb_path, f"union_{group_id}")
try:
arcpy.ddd.Union3D(enclosed_fc, union_fc, '', 'DISABLE', 'ENABLE')
print(f" Union3D completed: {union_fc}")
count = int(arcpy.management.GetCount(union_fc)[0])
print(f" Union result contains {count} feature(s)")
if count > 0:
union_all.append(union_fc)
except Exception as e:
print(f" Union3D failed for group {group_id}: {e}")
try:
# === Final Union: union_all + union_one ===
union_full = []
# Union All
if union_all:
merge_all = os.path.join(gdb_path, "merge_all")
enclosed_all = os.path.join(gdb_path, "enclosed_all")
union_all_fc = os.path.join(gdb_path, "union_all")
arcpy.management.Merge(union_all, merge_all)
arcpy.ddd.EncloseMultiPatch(merge_all, enclosed_all)
arcpy.ddd.Union3D(enclosed_all, union_all_fc, '', 'DISABLE', 'ENABLE')
union_full.append(union_all_fc)
count_all = int(arcpy.management.GetCount(union_all_fc)[0])
print(f" Union ALL result contains {count_all} feature(s)")
# Union One
if union_one:
merge_one = os.path.join(gdb_path, "merge_one")
enclosed_one = os.path.join(gdb_path, "enclosed_one")
union_one_fc = os.path.join(gdb_path, "union_one")
arcpy.management.Merge(union_one, merge_one)
arcpy.ddd.EncloseMultiPatch(merge_one, enclosed_one)
arcpy.ddd.Union3D(enclosed_one, union_one_fc, '', 'DISABLE', 'ENABLE')
union_full.append(union_one_fc)
count_one = int(arcpy.management.GetCount(union_one_fc)[0])
print(f" Union ONE result contains {count_one} feature(s)")
# Final Full Union
if union_full:
merge_full = os.path.join(gdb_path, "merge_full")
enclosed_full = os.path.join(gdb_path, "enclosed_full")
union_full_fc = os.path.join(gdb_path, "union_full")
arcpy.management.Merge(union_full, merge_full)
arcpy.ddd.EncloseMultiPatch(merge_full, enclosed_full)
arcpy.ddd.Union3D(enclosed_full, union_full_fc, '', 'DISABLE', 'ENABLE')
count_full = int(arcpy.management.GetCount(union_full_fc)[0])
print(f" FINAL Union FULL result contains {count_full} feature(s)")
except Exception as e:
print(f" Final Union3D process failed: {e}")
# Elapsed time in minutes
elapsed_minutes = (end - start) / 60
print("Elapsed time:", round(elapsed_minutes, 2), "minutes")
Hello,
i would like to create union from feature class which have more of 2000 features.My iterations is:
Apear error:
warning stating that the resulting feature is not simple
How to solved problem?
to make the code readable
Code formatting ... the Community Version - Esri Community
Union 3D (3D Analyst)—ArcGIS Pro | Documentation
A warning stating that the resulting feature is not simple and could not be created is raised if two or more multipatch features share only an edge or a vertex. This message indicates that the features were not merged because they did not share a volume of space.
DISABLE output all may be needed if features aren't overlapping.
Before you try scripting, have you tried the process manually to see at which step it is failing?
( PS code formatting is still missing indentation, so sometimes, it is best to apply the code formatting to a new copy of the code)
Dear Dan,
i will send new version of code.I try:
Problem is with not overlopping features.It can not to Union as single output.This multipatch features is or not Closed or not Simple.
How to repair Not Ovrlopping features to be for creating Union with single output?
Best Regard
for i, fc in enumerate(merge_enclose):
union_over_fc = os.path.join(gdb_path, f"unionOver_{i}")
union_over_final_fc = os.path.join(gdb_path, f"unionOver_final_{i}")
enclose_over_final = os.path.join(gdb_path, f"encloseOver_final_{i}")
table_union_over = os.path.join(gdb_path, f"table_union_Over_{i}")
try:
# --- Step 1: Make union directly on input
arcpy.ddd.Union3D(fc, union_over_fc, '', 'ENABLE', 'DISABLE', table_union_over)
# --- Step 2: Enclose and union again
grid_size = calculate_grid_size(fc)
print(grid_size)
arcpy.ddd.EncloseMultiPatch(union_over_fc, enclose_over_final)
arcpy.ddd.Union3D(enclose_over_final, union_over_final_fc, '', 'DISABLE', 'ENABLE')
# --- Step 3: Collect IDs from table_union_over
ids = [row[0] for row in arcpy.da.SearchCursor(table_union_over, ["Input_ID"])]
print(f"Found {len(ids)} overlapping IDs")
# --- Step 4: Find the OID field of fc
oid_field = arcpy.Describe(fc).OIDFieldName
print(f"OID field in {fc}: {oid_field}")
# --- Step 5: Apply filter + export
if not ids:
print("No overlaps found, copying all features.")
out_fc = os.path.join(gdb_path, f"_NotOver_{i}")
arcpy.management.CopyFeatures(fc, out_fc)
else:
values_sql = ",".join(map(str, ids))
expression = f"NOT {oid_field} IN ({values_sql})"
print(f"Using SQL: {expression}")
out_fc = os.path.join(gdb_path, f"_NotOver_{i}")
arcpy.conversion.FeatureClassToFeatureClass(fc, gdb_path, f"space_NotOver_{i}", expression)
print(f"Created {out_fc}")
except Exception as e:
print(f"❌ Union3D failed for {fc}: {e}")
Skip the script for now.
Can you run the whole process manually using the standard tools in Arctoolbox?
If you get the same error message, then there is no solution to the problem. It may be something in one of the iterations of the input data that causes the failure.